solidjs / solid

A declarative, efficient, and flexible JavaScript library for building user interfaces.
https://solidjs.com
MIT License
32.05k stars 914 forks source link

Does solid-js/universal support Class Component? #1073

Closed chenzx closed 2 years ago

chenzx commented 2 years ago

Describe the bug

I'm try to build a minimal demo to bridge solid-js/universal to a js native UI framework(my native UI framework is named Caf, and can run in Node.js CLI and Web/wasm env), Follow example from https://codesandbox.io/s/custom-dom-renderer-yu2pj?file=/renderer.js , upon which i made a little modification:

  createElement(elementName) {
    // return new (globalThis[elementName])();
    if(elementName==='TextView'){
        return globalThis.TextView.create({}); //TextView is a native js ui class component, which has a static create method; i use globalThis to decouple because the lib is packaged using Webpack/ncc which seems not compatible with Parcel;
    }
    throw `unsupported elementName ${elementName}!`
  },

Then app is a simple demo:

import { createSignal, createEffect } from "solid-js";
import { render } from "./renderer";

function App() {
  const [count, setCount] = createSignal(0);
  createEffect(()=>{
    setInterval(()=>{
        setCount(count()+1);
    }, 1000);
  });

  return (
    <TextView text={`${count()}`} />
  );
}

render(App, globalThis.appWindow.rootView);

However, when try to load test in chrome, DevTools reported error:

Uncaught TypeError: Class constructor g cannot be invoked without 'new'
    at solid.js:1157:24
    at $08748564c2988224$export$363ebc4b78b8f807 (solid.js:435:12)
    at $08748564c2988224$export$60e54eaca7e7fb38 (solid.js:1157:10)
    at $bfef713ee137b1cb$var$App (index.jsx:14:3)
    at universal.js:214:25
    at updateFn (solid.js:166:41)
    at $08748564c2988224$var$runUpdates (solid.js:742:17)
    at $08748564c2988224$export$882461b6382ed46c (solid.js:170:12)
    at render (universal.js:212:17)
    at Object.<anonymous> (index.jsx:19:7)

solid.js:1157 is:

function createComponent(Comp, props) {
  if (hydrationEnabled) {
    if (sharedConfig.context) {
      const c = sharedConfig.context;
      setHydrateContext(nextHydrateContext());
      const r = untrack(() => Comp(props || {}));
      setHydrateContext(c);
      return r;
    }
  }
  return untrack(() => Comp(props || {}));
}

Here, i found Comp is exactly globalThis.TextView which i exported from my js native ui lib, but i meant to use globalThis.TextView.create in solid-js/universal createElement callback!

Your Example Website or App

--

Steps to Reproduce the Bug or Issue

Info as described above.

Expected behavior

I expect to use solid-js/universal to adapt it to my js native ui framework lib, but failed.

Screenshots or Videos

No response

Platform

Additional context

No response

lxsmnsyc commented 2 years ago

Native elements in JSX cannot begin in upper-case letters, or else the compiler would think it is a component (or it refers to a function). Maybe try text-view instead of TextView?

Another thing, the error refers to this line: return globalThis.TextView.create({});. You are missing new

chenzx commented 2 years ago

@LXSMNSYC

in my js native ui lib, TextView is a view class name, i just want to map it directly, how can i mitigate this JSX limit? TextView.create is a class static factory method and it's in createElement(elementName) callback, so i thought should be able to...

chenzx commented 2 years ago

Use text-view trick is ok, but since my js native view class name is TextView style, it seems not very natural...

lxsmnsyc commented 2 years ago

I wouldn't recommend bypassing the JSX spec (the entire thing is canonical) as well as the entire Pascal-case/lower-case difference is hard-coded, neither there's a way to bypass it, which is why we recommend having the built-in components be in lower-case. React does the same thing as well. But if your intent is in pascal-case then maybe you can export a function component that renders the lower-case element:

function TextView(props) {
  return <text-view {...props} />
}
chenzx commented 2 years ago

In my js native ui framework(conceptually like React-Native, but it's targeted on embedded devices, not on desktop env), there is 2 kinds of view components: one is called View, which is the basic building blocks and cannot be atomically decomposed, to name a little: ScrollView、CompositeView、ImageView、TextView、……;The other is called Widget which is built upon Views.

I'm just using JSX for a intermediate stage, i. thought JSX is for Web env, not for native render env(which is exactly solid-js/universal built for).

Our team is developing a UI design/editor for the js native ui framework, the UI design result is a json description which combines View tree hierarchies、render/layout properties、data binding(especially the reactive system)、layout,I'm hoping to employ solidjs to build a code genrate module from the UI json desc, to let the UI design/edit result directly run in target embed env...

chenzx commented 2 years ago

I'm somewhat familiar with Solidjs's createSignal api... (the reactive engine), but i'm not familiar with the AOT codegen mechanism from JSX(or h...) to JS functions code, there is not much documentation. Its secrets seems to be in the babel-preset-solid plugin with dependencies on dom-expressions

edemaine commented 2 years ago

It sounds like you don't want to use document.createElement at all, so should be avoiding <foo> where foo is lower case. Then you should probably be defining components, maybe like this:

function TextView(props) {
  const textView = globalThis.TextView.create({});  // maybe needs a 'new'?
  createEffect(() => {
    // Reactively propagate props to TextView interface
    textView.setText(props.text);  // I'm guessing the interface here
  });
  return textView;
}

// usage
<TextView text={`${count()}`} />

But we're guessing without an actual link to the js-native framework you're using.

chenzx commented 2 years ago

The js native ui framework is not open sourced... but it's not the point here.

Basically, The js native ui framework consists of 2 parts: the JS scripting layer, which defines the whole View classes; & the C++ render engine part, depends on OpenGL ES 2.0, skia, freetype, harfbuzz, and so on, which is compiled into a nodejs addon. The later is now also compiled into wasm with embind js bindings, so now it can be run in browser with a 3d element(WebGL).

@edemaine I'm somewhat confused: Is the name TextView in TextView(props) not conflicted with that in globalThis.TextView? I guess it's the only way to avoid use <text-view> in JSX (it's not intuitional,IMO)

lxsmnsyc commented 2 years ago

Is the name TextView in TextView(props) not conflicted with that in globalThis.TextView?

it doesn't.

ryansolid commented 2 years ago

Yeah basically there are a couple ways to attack this but the capitalizing in JSX is something all tools (including TypeScript and syntax highlighting) respect. So in generally fighting that isn't worth it. This leaves you with 2 high level choices, universal renderer to treat these as native elements or wrapping them as components without using universal renderer. Both approaches actually work. The difference is with components you will manage the reactivity yourself, need to define each component, and on the usage they's need to be available in scope. Whereas the custom renderer leaves Solid to do the reactivity and you just implement the functions, and assumed as native it does not need to be in scope. But then they need to be lowercase.

chenzx commented 2 years ago

I've figured out what to do. Seems i could fork babel-plugin-jsx-dom-expressions module & let universal dom model can use TextView model without implementing function component, but this is low-priority work to do. Naming should be convenient, flexible to allow customization IMO.

now, i need find out how to build a Babel plugin to reduce my json ui view tree desc format to JSX AST form, then pass it to babel-plugin-jsx-dom-expressions...

ryansolid commented 2 years ago

Yeah I mean it isn't really up to us unless we want to opt into breaking every existing tool for JSX. They expect the convention. I opened an issue with TypeScript for this specific issue oh over 3 years ago and it hasn't budged. Not even a tiny bit: https://github.com/microsoft/TypeScript/issues/31606