alex-kinokon / jsx-dom Goto Github PK
View Code? Open in Web Editor NEWUse JSX to create DOM elements.
License: BSD 3-Clause "New" or "Revised" License
Use JSX to create DOM elements.
License: BSD 3-Clause "New" or "Revised" License
Hey! Thank you so much for the library I love it, it very helpful!
I updated my jsx-dom
dependency 6.4.13
to 6.4.17
And I see my bundle up to little bit heavy. I searched to https://bundlephobia.com/[email protected] and see the size change
I'm don't use the SVG but in my build has SVG code. I use the Parcel bundler and I add the alias to package.json
"alias": {
"jsx-dom": "./node_modules/jsx-dom/lib/min.js"
}
And this way I have a bundle is smaller. Before 78.02 KB and with the alias, it is 76.09 KB
Is it possible to set up tree shaking as it was in 6.4.13 version?
I'm trying to create template
elements in JSX, but none of my template
elements have any content when I run my code.
Here is an example replicating the issue:
let template = <template><div></div></template>
console.log(template.outerHTML)
Outputs the following:
<template></template>
Expected output:
<template><div></div></template>
This PR - #17 - broke jsx-dom when using it with web components that use custom events. Those nodes will (may) not have functions attached to them for event handlers and need to use addEventListener.
Can this PR be reverted so that this library will work with web component tags?
Given:
<div ref={div => {
console.log(div.clientWidth);
console.log(div.clientHeight);
}}>
Text
</div>
Both width and height report 0
.
I understand why this is happening by looking at the code, because the ref callback is triggered after the element is created and appended to the parent, but not necessarily when the element is present in the DOM.
Any ideas how to fix or workaround this issue?
I'm looking for something like connect which would pass down props to subcomponents.
like it could pass in some sort of state property to all the children of a component.
According to the JSX documentation and the README of the project, an attribute with the false
value should be ignored.
Note that false, true, null, undefined will be ignored per React documentations, and everything else will be used.
Since the v7.0.4
, the value of the boolean is rendered even if the value is false
. The change was introduced by the following commit 10cb737.
Example:
<a href="" target={false}></a>
<a href="" target={false && '_blank'}></a>
<a href="" data-target={false}></a>
<a href="" data-target={false && '_blank'}></a>
Is rendered as:
<a href="" target="false"></a> /* incorrect */
<a href="" target="false"></a> /* incorrect */
<a href=""></a> /* correct */
<a href=""></a> /* correct */
It seems data-*
attribute are ignored but not the target
attribute.
Is it a mistake?
I need to get a web component typed. I started with the following:
declare namespace JSX {
interface IntrinsicElements {
'my-hello-world': any;
}
}
This works, but typing as any
is not great. Ideally I'd like to type it like in jsx-dom:
'my-hello-world': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>
But none of those interfaces I need are exported.
Is there any alternative?
Hi, thank you for the Code. I want to understand it better by asking this question:
What are the differences to https://github.com/developit/htm if I would use either with webpack?
For me, it looks like both support inline events and JSX-DOM looks more attractive because I can write the Elements without Template Literals (so better support with my IDE). But what about performance etc?
Hi, could you please create Git tags for versions released on npm? Without those, it is needlessly difficult to review the source code changes.
Especially when the current latest version on npm 8.0.1-beta.5 is not mentioned anywhere else.
I've exported with patch-package
a few types, which are useful when creating own JSX components: ClassName
, PropsWithChildren<T>
, ReactNode
, ReactElement
and probably more in the future. In React all these are accessible via React
namespace.
As a side note, since JSX tags are not properly recognized by typescript, I've ended up replacing following definition: export type ReactElement = HTMLElement
(removed SVG). Otherwise it'd be a mess to use jsx-dom with libs like jQuery.
Currently the function "h" is not equivalent to Stencil´s Stencil.createElm, regarding the treatment of custom events names.
setAccessor https://github.com/ionic-team/stencil/blob/4537acd062fd0946611c4dd7409f8fe2436f298b/src/runtime/vdom/vdom-render.ts#L36
createElm https://github.com/ionic-team/stencil/blob/4537acd062fd0946611c4dd7409f8fe2436f298b/src/runtime/vdom/set-accessor.ts#L83
The following web component created with stencil, has a custom event called "todoCompleted".
//my-button.tsx
import { Component, Prop, h, Event, EventEmitter } from '@stencil/core';
@Component({
tag: 'my-button',
styleUrl: 'my-button.css',
shadow: true,
})
export class MyButton {
@Event() todoCompleted: EventEmitter<any>;
@Prop() label: string;
todoCompletedHandler(event: UIEvent) {
this.todocompleted.emit(event);
}
render() {
return <button onClick={this.todoCompletedHandler.bind(this)}>{this.label}</button>;
}
}
When defining the jsx syntax of the component, the onTodoCompleted event is registered as todocompleted.
//my-button.stories.js
import { h } from 'jsx-dom';
export default {
title: 'Demo/MyButton',
};
const Template = (args) => {
const handleClick = ($event) => {
console.log("handleClick", $event)
}
return <my-button label={args.label} onTodoCompleted={ev => handleClick(ev)}/>
// JSX-DOM return the following:
// const MyButton = document.createElement('my-button');
// MyButton.label = args.label;
// MyButton.addEventListener('todoCompleted', handleClick ); // <---- this
// return MyButton;
}
export const Default = Template.bind({});
Default.args = {
label: "Example"
};
When defining the jsx syntax of the component, the onTodoCompleted event must be registered as todoCompleted.
//my-button.stories.js
import { h } from 'jsx-dom';
export default {
title: 'Demo/MyButton',
};
const Template = (args) => {
const handleClick = ($event) => {
console.log("handleClick", $event)
}
return <my-button label={args.label} onTodoCompleted={ev => handleClick(ev)}/>
// JSX-DOM must be return the following:
// const MyButton = document.createElement('my-button');
// MyButton.label = args.label;
// MyButton.addEventListener('todoCompleted', handleClick ); // <---- this
// return MyButton;
}
export const Default = Template.bind({});
Default.args = {
label: "Example"
};
Update attribute function of jsx-dom file.
FROM:
//jsx.dom.ts
function attribute(key: string, value: any, node: Element & HTMLOrSVGElement){
...
} else if (useCapture) {
node.addEventListener(attribute.substring(2, attribute.length - 7), value, true)
} else {
node.addEventListener(attribute.substring(2), value)
}
...
}
TO:
//jsx.dom.ts
function attribute(key: string, value: any, node: Element & HTMLOrSVGElement){
...
} else if (useCapture) {
node.addEventListener(attribute.substring(2, attribute.length - 7), value, true)
} else {
let eventName;
if (attribute in window) {
// standard event
// the JSX attribute could have been "onMouseOver" and the
// member name "onmouseover" is on the window's prototype
// so let's add the listener "mouseover", which is all lowercased
let standardEventName = attribute.substring(2);
eventName = standardEventName;
} else {
// custom event
// the JSX attribute could have been "onMyCustomEvent"
// so let's trim off the "on" prefix and lowercase the first character
// and add the listener "myCustomEvent"
// except for the first character, we keep the event name case
let cutomEventName = attribute[2] + key.slice(3);
eventName = cutomEventName;
}
node.addEventListener(eventName, value)
}
...
}
Greetings and thank you very much for creating and maintaining the jsx-dom library.
select.value
is typed as value?: string | readonly string[] | number
, but selection is not changed with this option.
<select value="1">
<option value="0">Zero</option>
<option value="1">One</option>
<option value="2">Two</option>
</select>
Reopening because the other issue went OT. Sorry about that
error TS2786: 'TagIcon' cannot be used as a JSX component.
Its return type 'ReactElement<any, any> | null' is not a valid JSX element.
147 <TagIcon/>
~~~~~~~
I'm using @primer/octicons-react
and I'm aliasing react
to jsx-dom
in Webpack config, but TypeScript doesn't seem to automatically accept this:
import React from 'jsx-dom';
import {TagIcon} from '@primer/octicons-react';
document.body.append(<TagIcon/>)
Unless I include this type in my global.d.ts:
declare module 'react' {
const FC = (): JSX.Element => JSX.Element;
const React = {FC};
export default React;
}
Question is: Aren't these types automatically included in jsx-dom? If not, should they?
{
"compilerOptions": {
"typeRoots": [
"./node_modules/@types",
"./types"
],
"types": [
"node"
],
"lib": [
"es2017",
"esnext",
"dom"
],
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"allowSyntheticDefaultImports": false,
"strict": true,
"noImplicitReturns": true,
"suppressImplicitAnyIndexErrors": true,
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"jsx": "react",
"jsxFactory": "createElement",
"downlevelIteration": true,
"removeComments": false,
"sourceMap": true
},
"exclude": [
"./node_modules",
"./files",
"./cache"
]
}
class Notifier {
render () {
return <div> </div>;
}
};
➜ tsc
views/components/notifier/01/index.tsx(5,11): error TS2304: Cannot find name 'createElement'
import * as React from 'jsx-dom';
works fine.
When using TypeScript with "module":"es6"
in tsconfig.json
, attempting to import … from "jsx-dom"
raises a seemingly-bogus error:
index.tsx:2:25 - error TS2307: Cannot find module 'jsx-dom'.
2 import * as jsxDom from "jsx-dom";
~~~~~~~~~
But if I switch to "module":"commonjs"
, it works fine. Strange.
Demonstration in this gist.
I have a component whose props are JSX.IntrinsicElements['div']
and I want to pass its props.class
value into a class
array prop of a <div>
element (effectively merging the props.class
value with other classes), but the ClassNames
type does not allow ClassList
in the ClassName[]
type.
Is this a runtime limitation or just an issue with the ClassName
type?
Example:
function Foo(props) {
// ↓ Type 'ClassList' is not assignable to type '{ [key: string]: boolean; }'
return <div class={["foo", props.class]} />
}
The source files: https://codesandbox.io/s/quirky-agnesi-dnn33?file=/src/index.tsx
I'm using js-dom
with babel React Automatic Runtime
, but how can i import the jsx-dom types?
I have created a jsx-env.d.ts
with content:
/// <reference types="jsx-dom" />
But it still rise the error that cannot find JSX.IntrinsicElements
The types include it https://github.com/proteriax/jsx-dom/blob/bfca019baaadc2597ecc50a2337a55aef51618a1/index.d.ts#L441
But this is what I see in the DOM
Types needs update for new strict option in TS4.4. This was already done in react types DefinitelyTyped/react
Child elements cannot be created dynamically
Example:
let App = ()=>{
return <p>
{ [...Array(2)].forEach((v,i)=><div>abc</div>) }
</p>
}
document.body.appendChild(<App/>);
Currently the JSX:
<line x1={0} y1={0} x2={20} y2={20} />
yields the DOM element:
<line x-1="0" y1="0" x-2="20" y2="20"></line>
In particular, the attribute x-1
should actually be x1
.
Hi. Thank you for making jsx-dom well. I am developing a video player using jsx-dom.
When I used it before, type inference was done properly by looking at the type of jsx-dom type definition file.
But when I use a storybook library like storyshot, I see the react type of this file in jsx-dom.
I'm sorry I can't show you the project.
It seems that intrinsic elements are not automatically correctly typed:
const container = <div/>;
container.title
// ~~~~~
// Property 'title' does not exist on type 'ReactElement'.
// Property 'title' does not exist on type 'SVGElement'.
It seems that every such use must be used as:
const container = <div/> as HTMLElement;
container.title
Using dom-chef with some custom types, this has been possible in Refined GitHub. However I think this came with some drawbacks as well.
I am trying to use Storybook to develop a UI framework with jsx-dom. I understand this is not ideal (using actual React in Storybook to develop a React-like library with jsx-dom), but I was wondering if it is possible to mix the two?
I have defined my jsx-dom Button as so
export const Button = (options: ButtonOptions): HTMLButtonElement => {
return (
<button type="button">
Hello World
</button>
) as HTMLButtonElement;
};
Currently, when I define a Storybook story template, using my jsx-dom Button
const Template = (args) => <Button {...args}></Button>;
I get the error Objects are not valid as a React child (found: [object HTMLButtonElement]). If you meant to render a collection of children, use an array instead.
Which makes sense, because jsx-dom compiles it to an HTMLElement
If I include the following Container hack
import React from 'react';
class Container extends React.Component {
render() {
return <div ref={(ref) => ref?.appendChild(this.props.child)}></div>;
}
}
Then I can properly render the jsx-dom component using
const Template = (args) => <Container child={Button(args)}></Container>;
But this is not an ideal solution for all my stories.
If I install Storybook to a separate folder, I can even get it to work with
const Template = (args) => Button(args);
Is it possible to use jsx-dom to return a ReactElement so I can have it work properly with Storybook stories, or is there any other trick to getting it to work?
Thank you!
Seems like innerHTML
and innerText
when set to undefined
are not skipped as one could expect. For example <p innerHTML={undefined} />
renders as <p>undefined</p>
.
This of course comes from browser behavior:
const e = document.createElement('p')
e.innerHTML = undefined;
console.log(e.outerHTML); // <p>undefined</p>
e.innerText = undefined;
console.log(e.outerHTML); // <p>undefined</p>
e.textContent = undefined;
console.log(e.outerHTML); // <p></p>
However by React expectations anything undefined
should be probably skipped.
I encountered some issues while compiling with TypeScript, and here is a minimal reproducible example.
just run npx tsc
This seems to be an issue with my configuration, but I don't know how to fix it, sorry :(
Hello, when I try to use the disabled property in a link element, I get a type error:
Property 'disabled' does not exist on type 'DetailedHTMLProps<LinkHTMLAttributes<HTMLLinkElement>, HTMLLinkElement>'.ts(2322)
I wonder if it can be added to the type declaration to get rid of the editor warning.
Currently ref
is called on any JSX element (tag or FC), but react uses it only on tags, for FC you have to manually handle ref
prop (https://reactjs.org/docs/forwarding-refs.html).
Consider example:
interface FancyInputProps {
ref?: Ref<HTML.Input>;
}
function FancyInput({ref} : FancyInputProps){
return <div class="fancy-input">
<input type="text" ref={ref} />
</div>;
}
let input : HTML.Input;
document.body.append(<FancyInput ref={(ref) => console.log(input = ref)} />);
// ref is called twice
// <input type="text">
// <div class="fancy-input">
console.log(input);
// returns div, but input is expected
// <div class="fancy-input">
Amazing work! I've been trying to roll my own createElement/JSX suite, but this will save me weeks!
Only two minor issues that is a bit of a pain right now.
The property class?: ClassNames
is missing on interface interface SVGAttributes<T>
in index.d.ts
It would also be great if interface SVGProps<T>
could be exported so that i can declare typed vars to keep refs to svg elements.
Thanks for a great lib!
Just curious - if that would be possible to render react components via morphdom/nanomorph with, for example, augmentor for hooks.
I was attempting to generate a list of repeating data using htm , and noticed a strange issue where some data was disappearing except for the last row. After some debugging, I discovered that it seems htm, because it using template strings, was caching the element and reusing it. So, as soon as it was attached to the DOM again, it moved from the original position to the new position.
Here is a small reproduction:
https://stackblitz.com/edit/js-gkm1bu?file=index.js
import { createElement } from "jsx-dom";
import htm from "htm";
const html = htm.bind(createElement);
const appDiv = document.getElementById("app");
let count = 1;
// <div>cached static component</div>
// is totally static, so it seems to be cached by HTM. When we call appendChild a second time
// HTM provides the original instance from the
// cache and duplicates it.
function render() {
appDiv.appendChild(
html`
<div>
<div>Render ${count++}</div>
<button onClick=${() => render()}>Render again</button>
<div>cached static component</div>
</div>
`
);
}
render();
Resolution:
I'm not sure if it is possible to fix this in code, so I would suggest just leaving some kind of documentation about this weird edge case for users of htm
.
I think that regular JSX (NOT htm) would generally not have this issue, unless you set up some special optimization plugins like this: babel-plugin-transform-react-constant-elements?
error TS2786: 'TagIcon' cannot be used as a JSX component.
Its return type 'ReactElement<any, any> | null' is not a valid JSX element.
147 <TagIcon/>
~~~~~~~
I'm using @primer/octicons-react
and I'm aliasing react
to jsx-dom
in Webpack config, but TypeScript doesn't seem to automatically accept this:
import React from 'jsx-dom';
import {TagIcon} from '@primer/octicons-react';
document.body.append(<TagIcon/>)
Unless I include this type in my global.d.ts:
declare module 'react' {
const FC = (): JSX.Element => JSX.Element;
const React = {FC};
export default React;
}
Aren't these types automatically included in jsx-dom? If not, should they?
Real-life project: refined-github/refined-github#4044
Currently it includes the extra types above and it produces that error without them.
Hey,
Nice library you wrote.
But I have one question/suggestion - for my use case I prefer to have the smallest possible library and I noticed that the list of the svg tags is probably the biggest unused (in my case :) ) chunk.
Of course I can just rip it of in my fork but may be you have a better idea how to make svg support optional in order to have leaner solution.
I would imagine other cases will benefit from it as well, as in unconstrained environment one can just go for React
.
Would it be possible to add "type": "module"
in package.json
, enabling this module to be loaded in Node.js? Currently, I get this error when I try to import it:
$ node
Welcome to Node.js v15.12.0.
Type ".help" for more information.
> jsxDom = await import("jsx-dom")
(node:35343) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
node_modules/jsx-dom/index.js:477
export default index
^^^^^^
Uncaught SyntaxError: Unexpected token 'export'
I think there's no harm in adding this node-specific property and it would at least allow node to consume the module (for example, for server-side rendering).
Hello 👋 Random issue, but I couldn't find an alternative way of contacting you.
Are you the owner of the est
package on NPM? If so: I maintain a quite similar package, which has the same tree-shaking concern, but many more functions supported, and perhaps more actively maintained.
I'm looking for a non-namespaced name for my package and "est" seems quite fitting. Would you be open to transfer the ownership of that package name to me? If so you'd need to execute npm owner add fabiospampinato est
.
If you'd like to that I'll publish my package under a new major release, so there shouldn't be any disruptions in existing packages relying on est
.
Thank you.
First I tried the following as per documentation:
import { h } from 'jsx-dom/svg';
but I get:
Cannot find module 'jsx-dom/svg'
Then I tried:
import { h } from 'jsx-dom/lib/svg';
but I get:
jsx-dom/lib/svg.d.ts is not a module
That last import should work by looking at the code, but there are no emitted types for it, so I tried to omit the error:
// @ts-ignore
import { h } from 'jsx-dom/lib/svg';
but then I get other type errors related to the JSX tags.
I'm not too familiar with how the build works, but there seems to be no .d.ts output.
Would you consider making the SVG code part of the main build instead of having two separate packages? It seems like that would make the library easier to use and emit the types while not increasing the size of the package by too much (~4KB from what I can tell).
https://www.npmjs.com/package/jsx-dom
I think this isn't the same version?
I'm trying to update from 8.0.7 to 8.1.1, but as soon as I do, I'm getting TypeScript errors on anything other than simple html tags, for example any functional component that returns a simple i
element:
If I remove this line from types/index.d.ts
, which is added recently during React types update, it seems to be working again:
type ElementType = string
I think this ElementType was added in TypeScript 5.1, and determines what type can be returned from a function component. I checked latest DefinitelyTyped React typings and they changed this to:
type ElementType = string | React.JSXElementConstructor<any>;
But no such namespace in jsx-dom typings so I'm not sure how it could be applied.
I think in current state functional components can't return anything other than a string.
I also had a few errors after removing line above related to components with children, so had to revert lines below in the same file:
(props: P, context?: any): T | null
(props: PropsWithChildren<P>, context?: any): T | null
export type PropsWithChildren<P = unknown> = P & { children?: ReactNode | undefined }
export type PropsWithChildren<P> = P & { children?: ReactNode | undefined }
These React typings are too complex, and I am not sure if issue is on my side. Are you not getting any of these errors from tsc or VS Code?
Unfortunately, at the moment default values of FC
's generic arguments don't make any sense, cause without specifying them, the function returns Element
by default, which is not valid JSX.Element
since
type Element = ReactElement
type ReactElement = HTMLElement | SVGElement
Invalid code (that should not be invalid) example:
import React, { FC } from "jsx-dom";
const Foo: FC = () => <div/>;
const element = <Foo/>;
Error message:
TS2786: 'Foo' cannot be used as a JSX component.
Its return type 'Element' is not a valid JSX element.
Type 'Element' is missing the following properties from type 'SVGElement': ownerSVGElement, viewportElement, oncopy, oncut, and 90 more.
Since returning a type from a function that is not considered a valid JSX.Element
doesn't make sense (we simply cannot construct jsx with it), we can change Element
to JSX.Element
.
P.S. - I can include this fix in #40 if @proteriax agrees to apply the change
Trying to use the slot
attribute on an svg
tag, shows the error Property 'slot' does not exist on type 'SVGProps<SVGSVGElement>'
, however SVGElement
inherits the slot
property from Element
via this inheritance chain:
flowchart RL
SVGSVGElement-->SVGGraphicsElement
SVGGraphicsElement-->SVGElement
SVGElement-->Element
// Wrong path
import * as React from 'jsx-dom/svg';
// This path is resolve
import * as React from 'jsx-dom/lib/svg';
Can you provide a bundle that can be used in browser directly? Maybe just replace the CJS bundle with a UMD one.
The use case is to create tracked DOM elements without introducing much overhead in simple scenarios. Sometimes compiling is too complex for a simple script to run in browser.
It seems that there are tests mentioning forwardRef but they don't actually use it:
Lines 508 to 512 in 2115523
Does the library support it? Can it be added?
I'm trying to run jsx-dom
in my Parcel app but I'm getting the error:
Uncaught TypeError: (0 , _jsxDevRuntime.jsxDEV) is not a function
at 41oNQ.jsx-dom/jsx-dev-runtime (app.tsx:5:27)
at newRequire (index.303574b1.js:71:24)
at index.303574b1.js:122:5
at index.303574b1.js:145:3
I believe to fix this error, jsxDEV
needs to be exported from jsx-runtime.ts
See reproducible example: https://github.com/remarkablemark/parcel-jsx-dom-example
Currently key
attribute is silently skipped, shouldn't this be set as attribute or property or at least in dataSet
?
At the moment HTMLAttributes
and SVGAttributes
definition looks like:
export interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
// Extension
class?: ClassNames
// Standard HTML Attributes
className?: string
// ...
}
So class
and className
that refer to the same property have different types. And I don't know is it intentional or just copy-paste error since this approach has its own pros and cons.
Pros:
ClassNames
is one of jsx-dom
's cool features which is not supported by React. So in case we want to port a component from jsx-dom
to React
, TypeScript will remind us that this functionality must be abandoned, since React components simply don't have a class
propertyCons:
className
and class
simultaneouslyESLint + plugin:react/recommended
complains about class
without this rule:"react/no-unknown-property": ["warn", { "ignore": ["class"] }]
i see tslib in bundle. is it required in runtime?
If it is not required in runtime, maybe I configured in correctly.
Thanks
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.