Giter Club home page Giter Club logo

dom-expressions's Introduction

DOM Expressions

Build Status Coverage Status NPM Version Gitter

DOM Expressions is a Rendering Runtime for reactive libraries that do fine grained change detection. These libraries rely on concepts like Observables and Signals rather than Lifecycle functions and the Virtual DOM. Standard JSX transformers are not helpful to these libraries as they need to evaluate their expressions in isolation to avoid re-rendering unnecessary parts of the DOM.

This package wraps libraries like KnockoutJS or MobX and use them independent of their current render systems using a small library to render pure DOM expressions. This approach has been proven to be incredibly fast, dominating the highest rankings in the JS Framework Benchmark.

It is designed to be used with a companion render API. Currently there is a JSX Babel Plugin, and Tagged Template Literals, and HyperScript runtime APIs. Most developers will not use this package directly. It is intended to help author your own Reactive Libraries and not to be used directly in projects.

Example Implementations

  • Solid: A declarative JavaScript library for building user interfaces.
  • mobx-jsx: Ever wondered how much more performant MobX is without React? A lot.
  • vuerx-jsx: Ever wondered how much more performant Vue is without Vue? ...renderer built on @vue/reactivity
  • ko-jsx: Knockout JS with JSX rendering.
  • s-jsx: Testbed for trying new techniques in the fine grained space.

Runtime Generator

Dom Expressions is designed to allow you to create a runtime to be tree shakeable. It does that by using "babel-plugin-transform-rename-import" to rename the import to your reactive core file. Setup the babel plugin and then export * from "dom-expressions/src/runtime"from your runtime. Be sure to not exclude the dom-expressions node_module.

{
  plugins: [
    [
      "babel-plugin-transform-rename-import",
      {
        original: "rxcore",
        replacement: "../src/core"
      }
    ]
  ];
}

What is the reactive core file. It exports an object with the methods required by the runtime. Example:

import S, { root, value, sample } from "s-js";

const currentContext = null;

function memo(fn, equal) {
  if (typeof fn !== "function") return fn;
  if (!equal) return S(fn);
  const s = value(sample(fn));
  S(() => s(fn()));
  return s;
}

function createComponent(Comp, props) {
  return sample(() => Comp(props));
}

export { root, S as effect, memo, createComponent, currentContext };

Runtime Renderers

Once you have generated a runtime it can be used with companion render APIs:

JSX

Babel Plugin JSX DOM Expressions is by far the best way to use this library. Pre-compilation lends to the best performance since the whole template can be analyzed and optimal compiled into the most performant JavaScript. This allows for not only the most performant code, but the cleanest and the smallest.

Tagged Template

If precompilation is not an option Tagged Template Literals are the next best thing. Lit DOM Expressions provides a similar experience to the JSX, compiling templates at runtime into similar code on first run. This option is the largest in size and memory usage but it keeps most of the performance and syntax from the JSX version.

HyperScript

While not as performant as the other options this library provides a mechanism to expose a HyperScript version. Hyper DOM Expressions offers the greatest flexibility working with existing tooling for HyperScript and enables pure JS DSLs.

Work in Progress

This is still a work in progress. My goal here is to better understand and generalize this approach to provide non Virtual DOM alternatives to developing web applications.

dom-expressions's People

Contributors

amoutonbrady avatar atk avatar bigmistqke avatar danieltroger avatar dependabot[bot] avatar edemaine avatar elite174 avatar fernandolguevara avatar fictitious avatar frenzzy avatar gehbt avatar goodbyenjn avatar hugojosefson avatar intrnl avatar jpdutoit avatar kidonng avatar lxsmnsyc avatar mdynnl avatar nicketson avatar novacrazy avatar otonashixav avatar ryansolid avatar samjberry avatar sirenkovladd avatar titobouzout avatar trusktr avatar ulivz avatar yhdgms1 avatar yuens1002 avatar yume-chan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dom-expressions's Issues

spread syntax did not consider `delegateEvents` option from `babel-plugin-jsx-dom-expressions`

see:

if (!NonComposedEvents.has(lc.slice(2))) {

I am trying to disable event delegation by setting babel preset as

    [
      "babel-preset-solid",
      {
        "delegateEvents": false
      }
    ]

It works for most case, but I found when there is props-spreading syntax in the component, the global delegated handler occurs again. By researching the codebase, I found in the babel-preset it indeed considered the deleteEvents option, but there is no peer consideration in the spread situation.

Consider whether we should combine spreads with other grouped expressions to preserve order

Right now it looks like spreads go in first which is a bit awkward. Reactivity will cause last thing to win anyways and if it's dynamic versus static, well dynamic wins regardless of attribute order but can we do better with dynamic things. I kept spreads out because of awkwardness of diffing but I think we've made improvements here lately so that they could be inserted in order.

How to use the Babel plugin?

I couldn't find any instructions on how to use the plugin in the documentation. I tried the following:

// .babelrc.json
{
  "plugins": [
    "babel-plugin-jsx-dom-expressions"
  ]
}

And I run it with @babel/cli:

npx babel index.js -o compiled.js

But I keep getting a syntax error because of the JSX:

TypeError: /home/me/library/index.js: Property left of AssignmentExpression expected node to be of a type ["LVal"] but instead got "ArrowFunctionExpression"

How do I use this with Babel?

SWC Plugin?

SWC is an alternative to Babel that boasts having much better performance, so it would be nice to see an SWC equivalent to babel-plugin-jsx-dom-expressions (and also babel-preset-solid, though that's technically not relevant here ๐Ÿ˜„) in order to speed up the development cycle. However, I haven't looked into the similarities/differences between the two plugin APIs, so I'm not sure how much of a project this would be.

Reference @babel/types as dependency / dev dependency

I'm in the process of migrating every project I have related to solid to yarn 2. One of the good thing about yarn 2 is that it's stricter with its module resolution and when using babel-preset-solid which relies on babel-plugin-jsx-dom-expression, it gets stuck because it can't find any reference to @babel/types in the package.json dependencies/dev dependencies despite being imported multiple times within the source code as seen in the below screenshot.

image

Would it be possible to add it or is there a specific reason for it to be missing? (or am I just blind and I missed it)

Here's the error, for reference.

[!] (plugin babel) Error: babel-plugin-jsx-dom-expressions tried to access @babel/types, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound.

As an alternative, I can always install it within my poject, but that's not ideal since I'm not really the one relying on it directly.

Camel Case vs Kebab Case for CSSProperties

I was expecting to use camel case for CSS property styles in Solid (probably because of React). I'm not sure if this is documented anywhere but I just wanted to note it. I'm assuming you've given this consideration already but I'd be curious to hear your rationale...

Spread can be problematic with SVG and the class attribute

When using spread with an SVGElement, spread calls spreadExpression which in turn calls assign.
In the tree of possibilities in assign if we are spreading a class we end up on that branch because class is a keyword registered in the Attributes constant and is of type property.

We then arrives to this expression: } else node[info.alias] = value;. I believe this is where things are a bit weird with SVG and the className attribute. I found this stackoverflow about it. I believe you are doing something similar which causes the following error:

Cannot assign to read only property 'className' of object '#<SVGSVGElement>'

Here's a small repro: https://codesandbox.io/s/solid-svg-spreading-class-bug-vdefz?file=/src/index.tsx

Let me know if you need more details.

TypeScript for style attributes is wrong

Ever since the setProperty changes in solidjs/solid#143, we have to use hyphenated names like style={{'flex-direction': 'column'}}, but Typescript is still set to use CSSStyleDeclaration so I get errors like:

Type '{ 'flex-direction': string; }' is not assignable to type 'string | Partial<CSSStyleDeclaration> | undefined'.
  Object literal may only specify known properties, and ''flex-direction'' does not exist in type 'Partial<CSSStyleDeclaration>'.

Maybe use CSS.PropertiesHyphen from https://github.com/frenic/csstype instead?

Potential savings in attributes transpilation

Is there any reason why empty attributes <div empty /> are getting transpiled into empty=""? As I understand by default HTML understands empty as "" value. Getting rid of ="" could reduce the build size while still persisting the same effect.

results.template += value ? `="${value.value}"` : `=""`;

- results.template += value ? `="${value.value}"` : `=""`; 
+ results.template += value ? `="${value.value}"` : ""; 

I also noticed another case that could prevent generating more templates.
In many cases the templates differs only by attributes.

    <>
      <div>
        <div class="bang">
          Hello
          <span>!</span>
        </div>
      </div>
      <div>
        <div class="h1">
          Hello
          <span>!</span>
        </div>
      </div>
    </>

Currently this syntax would generate 2 the same templates with just a different attribute value:

const _tmpl$ = template(`<div><div class="bang">Hello<span>!</span></div></div>`, 6),
      _tmpl$2 = template(`<div><div class="h1">Hello<span>!</span></div></div>`, 6);

We could optimize it by treating attributes always as dynamic:

setAttribute(_el$2, "my-attr", "value");

That why we could reduce build size drastically reducing the amount of templates.

I am aware that currently this can be achieved by wrapping each attr with template literals but this is inconvenient for DX

Feature request: Directives for components

Currently, the code <Counter use:example="Hello World" /> compiles into

createComponent(Counter, {
  "use:example": "Hello World"
})

We could probably make it work into

createComponent(Counter, {
  ref(node) {
    example(node, 'Hello World');
  },
});

since ref is already a special prop for both host and components.

What do you think?

The only design challenge would be that props.ref can be assigned anytime, anywhere which makes it lack constraint unlike host nodes, posing some memory leaks.

path is undfined in babel plugin / ssr

In one of our projects that uses babel-jsx-dom-expressions, we are getting a crash due to path not begin defined.
I looked at the code and it seems that path is used in a function but its never sent as an argument.

function transformClasslistObject(expr, values, quasis) {
  expr.properties.forEach((prop, i) => {
    const isLast = expr.properties.length - 1 === i;
    let key = prop.key;
    if (t.isIdentifier(prop.key) && !prop.computed) key = t.stringLiteral(key.name);
    else if (prop.computed) {
      registerImportMethod(path, "escape"); <-------------------- this is where path is not defined
...

The line of code is here: https://github.com/ryansolid/dom-expressions/blob/main/packages/babel-plugin-jsx-dom-expressions/src/ssr/element.js#L355

From what I can see, the function transformClasslistObject is only used twice in the file and it would be easy to pass the path argument to it, but since I'm not familiar enough with the code, I might miss something crucial. I would otherwise submit a pull request.

Cheers and thank you!

className is wrong when both `class` and `className` are used and one of them is `null` or `undefined`

repro
the class name for <div className={x1} class={x2}></div> is transformed to `${x1} ${x2}` which is "null undefined" if x1=null, x2=undefined.

I've taken a look at

if (classAttributes.length > 1) {
const first = classAttributes[0].node,
values = [],
quasis = [t.TemplateElement({ raw: "" })];
for (let i = 0; i < classAttributes.length; i++) {
const attr = classAttributes[i].node,
isLast = i === classAttributes.length - 1;
if (!t.isJSXExpressionContainer(attr.value)) {
quasis.pop();
quasis.push(
t.TemplateElement({ raw: (i ? " " : "") + `${attr.value.value}` + (isLast ? "" : " ") })
);
} else {
values.push(attr.value.expression);
quasis.push(t.TemplateElement({ raw: isLast ? "" : " " }));
}
i && attributes.splice(classAttributes[i].key);
}
first.value = t.JSXExpressionContainer(t.TemplateLiteral(quasis, values));

to see how it could be fixed, but I don't think I understand it enought to fix it.

The possible solutions I have in mind are:

  • Transform it to `${x1 ?? ""} ${x2 ?? ""}` but the class name would end up having extra spaces.
  • Transform it to a more complex inline expression: `${(c = x1) == null ? "" : c + " "}${(c = x2) == null ? "" : c}` but there can also end up being an extra space at the end
  • Use the classList runtime function or one similar to it

`classList` / `toggleClassKey` throwing an error when class key has leading or trailing spaces

I was working on a Solid.js project and stumble upon an error when classList key has leading/trailing spaces.

Error:
Uncaught DOMException: An invalid or illegal string was specified dev.js:306:65

After some research, it seems like the issue is coming from the split regex on toggleClassKey which will split the trailing/leading space as an empty string and try to toggle it.

e.g.: " red" will split up to ["", "red"] which will throw the error when trying to node.classList.toggle("", value)

Here is the specific line:

node.classList.toggle(classNames[i], value);

Stacktrace

Uncaught DOMException: An invalid or illegal string was specified dev.js:306:65
    toggleClassKey dev.js:306
    classList dev.js:151
    Loading icons.tsx:40
    Loading icons.tsx:47
    c dev.js:475
    untrack dev.js:400
    c dev.js:475
    runComputation dev.js:612
    updateComputation dev.js:600
    devComponent dev.js:481
    createComponent dev.js:1069
    get children index.tsx:64
    Switch dev.js:1225
    runComputation dev.js:612
    updateComputation dev.js:600
    createMemo dev.js:215
    Switch dev.js:1222
    c dev.js:475
    untrack dev.js:400
    c dev.js:475
    runComputation dev.js:612
    updateComputation dev.js:600
    devComponent dev.js:481
    createComponent dev.js:1069
    get children index.tsx:54
    get dev.js:1103
    get dev.js:1077
    get children index.js:68
    res dev.js:834
    runComputation dev.js:612
    updateComputation dev.js:600
    createMemo dev.js:215
    children dev.js:523
    res dev.js:834
    untrack dev.js:400
    provider dev.js:830
    runComputation dev.js:612
    updateComputation dev.js:600
    createComputed dev.js:194
    provider dev.js:830
    c dev.js:475
    untrack dev.js:400
    c dev.js:475
    runComputation dev.js:612
    updateComputation dev.js:600
    devComponent dev.js:481
    createComponent dev.js:1069
    Auth0 index.js:37
    c dev.js:475
    untrack dev.js:400
    c dev.js:475
    runComputation dev.js:612
    updateComputation dev.js:600
    devComponent dev.js:481
    createComponent dev.js:1069
     index.tsx:36
    render dev.js:90
    createRoot dev.js:169
    runUpdates dev.js:684
    createRoot dev.js:169
    render dev.js:88
     index.tsx:36
    InnerModuleEvaluation self-hosted:2384
    evaluation self-hosted:2335

Feature: `renderToJSON`/`serialize`

Add an SSR feature called renderToJSON/serialize that instead of rendering an HTML markup, renders the JSON representation of the markup.

This is a very wild idea. Obviously, the SSR formats right now only support HTML so this feature might only work if the compiler itself is compatible to the runtime. Supposedly, renderToJSONAsync and renderToJSONStream are also similar ideas that can be added.

`ArrayExpression` and possibly `ObjectExpression` is treated as static for props.

I forgot to make an issue about this last month, this can easily be reproduced.

For expressions like

<For each={[...array()]}>

it is compiled into

each: [...array()]

which is like a false-positive. The hotfix would be to recursively check each item in the ArrayExpression to check for dynamic and static expressions. I believe ObjectExpression happens to behave like this.

Alternative to use directive syntax

Hey Ryan :)

I really like the direction you are moving to with dom-expression. The latest feature use directive is very similar to modifiers known from Swift UI or Ember.js. You literally read in my mind with that as I was going to fork the repo and implement it this week.

I was wondering, would you be keen on exposing an alternative syntax? The current one is not fully JSX and types friendly. Have you considered a syntax like this?

<button $={[on('click', clickFn)]}>Click Me</button>

the $ is one of not reserved and allowed by JSX. Also passing the modifiers as an array, makes this syntax fully supported by typescript and JSX.

the example on function would generate a new function that will be used exactly like the current use:on directive. I am really curious about your opinion on that.

Support for reactive prop source

It could be useful to support this. Currently, DX treats prop sources from spread expressions to be "static" and thus, when done this way, DX cannot reapply new props when received from sources that are reactive. Consider this SolidJS example:

function Heading(props){
  return (
    <h1>
      {props.foo && `foo: ${props.foo}`}
      {props.baz && `baz: ${props.baz}`}
    </h1>
  );
}

function App() {
  const [customProps, setCustomProps] = createSignal({
    foo: 'bar',
  });
  
  function toggleToBaz() {
    setCustomProps({ baz: 'bar' });
  }
  function toggleToFoo() {
    setCustomProps({ foo: 'bar' });
  }
  
  return (
     <>
       <button type="button" onClick={toggleToBaz}>Set property to baz</button>
       <button type="button" onClick={toggleToFoo}>Set property to foo</button>
       <Heading {...customProps()} />
     </>
  );
}

If we treat this with same behavior with children and props, it is expected for Heading to behave similarly for props spreading (as is if we apply the same for libraries like React).

Currently, the only workaround is to wrap the element into an accessor:

  return (
     <>
       <button type="button" onClick={toggleToBaz}>Set property to baz</button>
       <button type="button" onClick={toggleToFoo}>Set property to foo</button>
       {() => <Heading {...customProps()} />}
     </>
  );

However this solution is unlikely to be known or discovered by many users, and causes the component to re-render as a whole and not patch.

This can be solved easily if, by default, elements with props spreading compiles to createComponent wrapped into an accessor, although this might be too naive in some cases. Another solution could be to allow mergeProps to receive accessor to objects.

Better detection of poorly formed JSX (HTML)

Not sure what to do yet but would be great if we could validate templates in a way that we ensure the browser won't mess with them when we instantiate them. Things like how browsers insert <tbody> if you omit it. Or the way certain elements aren't allowed certain children. This is critical because it breaks walks during render/hydration.

Right now I just count the tags at runtime which is super rudimentary and it only runs in dev mode since we don't want the additional code/overhead. In theory this can be done at compile time but I don't know of any existing packages. Maybe failing that we could just develop something ourselves?

Example?

The recent update to make this a generator looks great. An example would be even greater :-)

I've managed to build a runtime using s-js. It only exports a single {wrap} function.

Next I copy that runtime as and use it in a new project 's-jsx/index.js' (using rollup + the babel-jsx.. plugin).
A simple build where I import
import S from 's-js';
import { wrap } from './s-jsx';
and run
console.log('Hello World')
works fine...
When I create a "dumb" component (no data just a static message) and use S.root(()=>...)
it also works fine
As soon as I start using S to add some data and render it in my component, it screams out "Can't find variable: r"
Last week, r, root, selectEach and selectWhen would all have been exported by the runtime I built; but no longer. The question is - where's it hiding now?

classList doesn't applied Tailwind's padding and margin classes

As mentioned in Discord, the code below won't apply px, py, mx, my

const Btn: Component<Props> = (props) => {
  return (
    <button
      type="button"
      classList={{
        "px-2.5 py-1.5 text-xs": props.size === "xs",
        "px-3 py-2 text-sm": props.size === "sm",
        "px-4 py-2 text-sm": props.size === "base",
        "px-4 py-2 text-base": props.size === "lg",
        "px-6 py-3 text-base": props.size === "xl",
        [`text-${props.textColor} bg-${props.bgColor}-600 hover:bg-${props.bgColor}-700 focus:outline-none`]:
          !props.disabled,
        "text-gray-600 bg-gray-200 cursor-not-allowed focus:outline-none":
          props.disabled,
      }}
    >
      {/* content here */}
    </button>
  );
};

support rxcore API for batching updates in event handlers

I've discussed this on Discord temporarily.

Provide a way for the client to batch updates inside event handlers. React is already doing this internally with react-dom, and I believe others as well. This is useful specially if there's multiple signal updates in a single event call. Solid, compostate and other libraries out there currently provides a way to do so.

import { batch } from 'rxcore';

Delegated events do not bubble

First off, thanks for writing this library! I've been using MobX in combination with React for quite some time and always felt like there must be a more reactive and finer grained approach to updating the DOM with an observer library like MobX. I'm really happy I found your libraries as it looks like they bring it all together in a very nice way. Congrats on your work and the new ideas!

I noticed that setting event handler props that result in delegated events do not bubble, which I assume is not by design. Here is an example:

function MyComponent() {
  function onClick(e) {
    console.log(e)    
  }

  return (
    <div onClick={onClick}>
      Parent
      <div onClick={onClick}>
        Child
      </div>
    </div>
  )
}

Clicking on the child div prints to the console only once even though stopPropagation is not called. Using the onclick instead of the onClick prop works as expected and clicking on the child div results in two log entries in the console.
I would have expected the delegated events to bubble as long as stopPropagation is not called.

ERR: The browser resolved template HTML does not match JSX input

What does this if statement do?

if ("_DX_DEV_" && check && t.innerHTML.split("<").length - 1 !== check)
    throw `The browser resolved template HTML does not match JSX input:\n${t.innerHTML}\n\n${html}. Is your HTML properly formed?`;

(Line: 41): https://github.com/ryansolid/dom-expressions/blob/98e890c6b8c2afbd497b715f1d867cd90da6d66a/packages/dom-expressions/src/client.js

I just get a super annoying error when creating tables like this (in production no doubt about it)

<table>
    <tr>Do you really wanna listen to her large mouth instead of playing video games?</tr>
    <tr>Get the hell out of there, dude</tr>
</table>

When I remove these four lines of code everything works. Like, when I remove all the <tr> it works. As I said, in build version everything works just fine, I think there should be a bug, or why should this break?

document.addEventListener() is called multiple times and registered events are triggered more than once

This is re: babel-plugin-jsx-dom-expressions v0.25.4 with Solid v0.24.7

Steps:

  • I bundled a module with delegateEvents set to true
  • I imported this module into an app which is also bundled with delegateEvents set to true

The result is that document.addEventListener() is called multiple times and registered events are triggered twice (or more depending on the number of imported modules)

Would it be worth somehow scoping what handlers should be triggered within a single bundle? Or making the event handler global?

"this" references are no longer transformed correctly in some cases

The issue can be seen here

The expression this.shouldStay here

<Component prop={this.something} onClick={() => this.shouldStay}>

should be transformed into _self$.shouldStay here

this is going to refer to the object literal being passed as the second argument to _$createComponent() instead of the this in the render function, which is what would be expected.

The bug was introduced in commit f8ea5cc

bug: renderToNodeStream fn throws error if resource resolves to `undefined`

When implementing a function like the one discussed in Solid Discussions #451, I noticed that this line results in an error being thrown if the resource promise resolves to undefined (i.e. method "replace" doesn't exist on type undefined). Returning an empty string seems to be a simple work-around.

I'm using the setup provided by solid-start. The error isn't always triggered, but is generally triggered if you reload the page (I don't think it's triggered by hot module reloading).

[bug] [lit-dom-expressions] html template tag outputs duplicate DOM

I tried re-exporting html from my LUME lib, and tried this simple demo:

  const {html, variable} = LUME
  
  const count = variable(0)

  setInterval(() => count(count() + 1), 1000)

  const div = html`
    <div>${count}</div>
  `

  setTimeout(() => {
    document.body.append(div)
  }, 1000)

What happens is that this (somehow?) outputs two div elements to the page. So I see the number in count twice on the screen (it works, just there's two of them).

Reasoning for string template literals

Is there a reason that template literals are generated here instead of regular strings? Unless I am missing something there are never any expressions in the strings.

Event handler attributes with dashes in them generates invalid syntax

I'm using Shoelace together with Solid and noticed that Shoelace's custom events can't be used because it leads to invalid JavaScript syntax.

Example usage of such Shoelace component and custom event handler:

const TitleInput = () => {
  const [title, setTitle] = createSignal("");

  return (
    <sl-textarea
      value={title()}
      onSl-input={(e) => setTitle(e.target?.value)}
    />
  );
};

That yields the following syntax:

_el$4.__sl-input = e => setTitle(e.target?.value);

And error:

SyntaxError: invalid assignment left-hand side

Because of the non-quoted __sl-input property.

Unclear way of indicating attributes

As per README:

All attributes are props (so use className) and to indicate attributes wrap in 'attrs' object

I did:

h('label', { attrs: { for: 'someId' } })

but it doesn't create attributes, instead leaves attrs prop with the stringified object. Can we clarify it?

incorrect compilation when spreading inline objects with getters with computed property names

When spreading an object with computed keys, the object is wrongly assumed to be static and transformed as such.

Reproduction

Expected

I ran into this code recently: https://playground.solidjs.com/?hash=-743945147&version=1.3.9; notice due to how mergeProps and Dynamic work, the textarea is recreated whenever props.value changes. There are a few areas where compilation is broken or could be better with regards to inline objects:

Bugs:

  1. Objects with computed properties should be wrapped in a thunk as otherwise reactivity from the computed property names is lost.; Minimal Reproduction: https://playground.solidjs.com/?hash=-1317144494&version=1.3.9

Possible improvements:

  1. Static properties should be wrapped in getters to avoid unnecessary subscriptions. The thunk can be removed if all the properties have static names and are wrapped in getters.; Minimal Reproduction: https://playground.solidjs.com/?hash=650658910&version=1.3.9
  2. Computed properties should be wrapped in getters to avoid unnecessary subscriptions.; Minimal Reproduction: https://playground.solidjs.com/?hash=-1262057988&version=1.3.9

This is obviously quite specific to spreading objects declared inline, since no transformations will be done to explicit mergeProps calls or when spreading objects declared elsewhere.

style={undefined}

Is it intentional that style={undefined} in Solid clears all inline styles?

if (value == null || typeof value === "string") return (nodeStyle.cssText = value);

I expected only previously set styles to be cleared, which is how style={{}} behaves with the current code. For point of comparison, this is how React behaves: undefined just clears previously set styles.

My intuition is this: In most of Solid and React, foo={undefined} is equivalent to omitting/removing the foo attribute in the HTML, which in the reactive case means undoing whatever was done by setting it. So I found it confusing that style={undefined} did something very different (.style.cssText = undefined) in Solid.

The context is that I wanted to write style={hide() ? {display: 'none'} : undefined} (mainly because this is easy to write as style={display: 'none' if hide()} in CoffeeScript), but currently I need to write style={hide() ? {display: 'none'} : {}}. It took me a long time to debug this, as I couldn't figure out why my style attributes set manually in an effect were getting destroyed โ€” I didn't expect it to be relevant to the display setting.

For example, @mdynnl suggests a replacement function signature of

function style(node, value = {}, prev = {}) {

Discord discussion

Respect order of directives against dynamic attributes

Currently, directives are always guaranteed to run first, regardless if the dynamic attribute is declared first. We can consider this issue in the future however we can't do this right now considering that dom-expressions does not do true fine-grained attributes, as dynamic attributes are grouped into a single effect rather than their own independent effects. With ordered directives, it requires these dynamic attributes to either be split into their own independent effects or their group sequences (longest sequence of dynamic attributes before and after a directive, much like mergeProps is right now.), but of course we don't want this right now as this trades the creation/memory cost against the update cost.

incremental-dom similarities

First - Thank you very much for all that toolchain
its very impressive :)

I wondering around that field, and i came across your projects and https://github.com/jridgewell/babel-plugin-transform-incremental-dom

And i have a feeling that some of it overlaps, and maybe using incremental-dom for the actual dom manipulation can work very nicely with this model
Have you made any evaluations and comparison of the approaches?

A simple demo of jsx -> incremental dom + mobx:
https://codesandbox.io/s/jsx-incremental-dom-mobx-6ugg1

I'm still trying to wrap my head around how to make granular updates

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.