@symbiote-native/solid 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/babel-preset.cjs +41 -0
- package/metro-css-parser.cjs +8 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @symbiote-native/solid
|
|
2
|
+
|
|
3
|
+
SolidJS adapter for SymbioteNative. Compiled Solid JSX drives real native iOS/Android views through
|
|
4
|
+
the same `@symbiote-native/engine` every other adapter uses — React's renderer is never in the path.
|
|
5
|
+
|
|
6
|
+
**Status: L4 in progress (parity phase).** The renderer seam, `mount`/`unmount`, the primitives, the
|
|
7
|
+
stateful touch components, the Touchable family, Button / ImageBackground / InputAccessoryView,
|
|
8
|
+
StatusBar and the list family all ship at full parity with the React reference — off
|
|
9
|
+
`@symbiote-native/components`, via `descriptorToSolid` where the component has a shared render fn.
|
|
10
|
+
The engine-owned runtime modules are re-exported from the barrel. Still absent: `createPortal` and
|
|
11
|
+
`createTunnel`, both of which need a same-surface replacement for `solid-js/web`'s DOM-bound
|
|
12
|
+
`Portal`/`Dynamic`. See `symbiote-new-adapter` §7 for what each layer means and why the adapter is
|
|
13
|
+
built in that order.
|
|
14
|
+
|
|
15
|
+
Solid's own control-flow `Switch`/`Match` are NOT re-exported from this package: `Switch` collides
|
|
16
|
+
with RN's Switch component, which every adapter must export under that name. Import the control-flow
|
|
17
|
+
pair from `solid-js` directly.
|
|
18
|
+
|
|
19
|
+
Publishing is gated by `"private": true` in `package.json`, which is what
|
|
20
|
+
`scripts/lib/publishable-packages.mjs` filters on — so `changeset publish` and the pkg.pr.new canary
|
|
21
|
+
both skip this package until that flag comes off. Without it `0.0.0` (a version absent from npm) would
|
|
22
|
+
be published on the next release run, shipping a reduced surface and violating the repo's
|
|
23
|
+
`<adapters_reach_full_feature_parity>` invariant. Remove the flag in the commit that finishes parity.
|
|
24
|
+
|
|
25
|
+
## Why Solid needs no DOM shim and no Metro transformer
|
|
26
|
+
|
|
27
|
+
Solid ships an official custom-renderer API, `createRenderer` from `solid-js/universal`. That is
|
|
28
|
+
exactly the framework-agnostic seam this project looks for in every framework, so the adapter is
|
|
29
|
+
`RendererOptions`' ten methods mapped onto the engine's mutation API — no shimming of private
|
|
30
|
+
internals (the Svelte adapter's approach, forced by an unmerged upstream PR), no compiler running
|
|
31
|
+
beside Metro (Angular's AOT), and no custom `babelTransformerPath` at all.
|
|
32
|
+
|
|
33
|
+
Targets **Solid 1.9**, deliberately not 2.0 (RC as of 2026-08): 2.0 moved the package to
|
|
34
|
+
`@solidjs/universal`, changed `RendererOptions` (added `createSentinel`/`cleanupNodes`, gave
|
|
35
|
+
`createElement` a staticProps argument) and changed the compiled-output shape (`use()` →
|
|
36
|
+
`applyRef`/`ref`, `effect(fn, init)` → `effect(fn, effectFn)`). Moving to it is a rewrite of
|
|
37
|
+
`src/renderer.ts`, not a range bump.
|
|
38
|
+
|
|
39
|
+
## Wiring an app
|
|
40
|
+
|
|
41
|
+
`babel.config.js` — the preset ships preconfigured, so the app can't get `moduleName` or `generate`
|
|
42
|
+
wrong:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
module.exports = {
|
|
46
|
+
presets: [
|
|
47
|
+
'module:@react-native/babel-preset',
|
|
48
|
+
// LAST on purpose: Babel applies presets in reverse order, so this runs FIRST and claims the
|
|
49
|
+
// JSX before the React Native preset's own React-JSX transform can.
|
|
50
|
+
'@symbiote-native/solid/babel-preset',
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`metro.config.js` needs `unstable_conditionNames: ['browser']` — `solid-js`'s export map has a `node`
|
|
56
|
+
branch pointing at its SSR build, and resolving that into a native bundle is the same failure mode
|
|
57
|
+
the Svelte example hit (`lifecycle_function_unavailable` on the first mount).
|
|
58
|
+
|
|
59
|
+
Solid compiles a component to `function App(props)` — a function with an uppercase name, which is
|
|
60
|
+
exactly what react-refresh's `isLikelyComponentType` heuristic looks for. It then tries to patch a
|
|
61
|
+
React Fiber tree that does not exist in this path, and the update is swallowed with no error. Force a
|
|
62
|
+
full reload instead: `unstable_forceFullRefreshPatterns: [/\.tsx$/]`.
|
|
63
|
+
|
|
64
|
+
## `./renderer` is a compiler target, not a convenience export
|
|
65
|
+
|
|
66
|
+
`babel-preset-solid` with `generate: 'universal'` rewrites JSX into direct calls imported from the
|
|
67
|
+
`moduleName` it was given. So `src/renderer.ts` exports eleven specific names because generated code
|
|
68
|
+
imports them; dropping one breaks bundling with a module-not-found on an import nobody wrote. The
|
|
69
|
+
list (`createElement`, `createTextNode`, `insertNode`, `insert`, `setProp`, `use`, `effect`, `memo`,
|
|
70
|
+
`createComponent`, `spread`, `mergeProps`) was verified by compiling representative JSX, not read off
|
|
71
|
+
the docs.
|
|
72
|
+
|
|
73
|
+
## Three things the universal runtime does that the seam has to answer correctly
|
|
74
|
+
|
|
75
|
+
Each is a one-line decision in `src/renderer.ts` with a real failure behind it — the comments there
|
|
76
|
+
cite the runtime line that forces them.
|
|
77
|
+
|
|
78
|
+
1. **`createTextNode('')` is a placeholder, not content.** The runtime parks an empty text node where
|
|
79
|
+
a dynamic expression will go. An empty `RCTRawText` genuinely paints in Fabric, so an empty string
|
|
80
|
+
maps to an engine anchor instead (skipped by the commit walk).
|
|
81
|
+
2. **`isTextNode` must answer "can I write a string into this", not "did `createTextNode` make it".**
|
|
82
|
+
The runtime asks it about that placeholder anchor; answering `true` sends it to write text into a
|
|
83
|
+
node that never reaches Fabric.
|
|
84
|
+
3. **Anchors stay visible to `getFirstChild`/`getNextSibling`.** The runtime re-derives positions
|
|
85
|
+
through those lookups, so hiding a node it inserted itself desyncs its bookkeeping from the tree.
|
|
86
|
+
Anchors are invisible to Fabric, not to traversal.
|
|
87
|
+
|
|
88
|
+
## Typing — the app points `jsxImportSource` here, and that is the whole setup
|
|
89
|
+
|
|
90
|
+
```jsonc
|
|
91
|
+
// tsconfig.json
|
|
92
|
+
"jsx": "preserve",
|
|
93
|
+
"jsxImportSource": "@symbiote-native/solid"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That is all an app needs. No `/// <reference types>` line, no `solid-env.d.ts`, no per-file pragma:
|
|
97
|
+
TypeScript resolves the entire JSX namespace from this package's `./jsx-runtime` entry, which is
|
|
98
|
+
type-only (the emitted JS is `export {};` and nothing ever imports it — JSX becomes calls on
|
|
99
|
+
`./renderer` through babel-preset-solid's `moduleName`, a separate mechanism).
|
|
100
|
+
|
|
101
|
+
Two things follow that the earlier `declare module 'solid-js'` shim could not give:
|
|
102
|
+
|
|
103
|
+
- **A web tag is a compile error.** `<div>` reports `TS2339: Property 'div' does not exist on type
|
|
104
|
+
'JSX.IntrinsicElements'` instead of building fine and failing on device. Augmenting solid-js's
|
|
105
|
+
namespace merges into its HTML/SVG list, so `<div>` stayed legal there. The Svelte adapter needed
|
|
106
|
+
a whole preprocessor for the same guarantee.
|
|
107
|
+
- **JSX children are type-checked at all.** solid-js's `JSX.Element` names the DOM's `Node`, which
|
|
108
|
+
does not resolve without a DOM lib — and `skipLibCheck` swallows that error rather than reporting
|
|
109
|
+
it, leaving `Element` as `any` and every JSX child position unchecked. The visible symptom was
|
|
110
|
+
small (a render-prop parameter needed an explicit annotation); the cause was not.
|
|
111
|
+
|
|
112
|
+
The risk recorded when this was deferred — that our `Element` must line up with what solid-js's own
|
|
113
|
+
`<Show>`/`<For>` return — resolves for the same reason: solid's is `any` in the DOM-less programs we
|
|
114
|
+
target, so it is assignable to anything of ours. `src/jsx-runtime.test.tsx` pins the runtime half;
|
|
115
|
+
the type half rides on `examples/solid`'s typecheck. Full rationale, the measurements behind each
|
|
116
|
+
claim, and the one config that would break it: `.claude/rules/solid-jsx-namespace.md`.
|
|
117
|
+
|
|
118
|
+
## Single root per process
|
|
119
|
+
|
|
120
|
+
Compiled JSX calls module-level functions with no surface argument, so there is nowhere to thread a
|
|
121
|
+
per-surface renderer through — the active surface is module state, set by `mount`. The Svelte adapter
|
|
122
|
+
reached the same single-root conclusion for its own reasons.
|
|
123
|
+
|
|
124
|
+
## Reference
|
|
125
|
+
|
|
126
|
+
- The canary app: `examples/solid` (its README covers the tarball loop and what is verified).
|
|
127
|
+
- The engine API this targets: the `symbiote-engine-core` skill, `core/engine/src/node.ts`.
|
|
128
|
+
- Building/porting an adapter: the `symbiote-new-adapter` skill.
|
|
129
|
+
- The nodeOps table this mirrors: `adapters/vue/src/renderer/index.ts`.
|
|
130
|
+
- The same seam on an ANSI target: `wolf-tui/packages/solid/src/renderer/node-ops.ts`.
|
package/babel-preset.cjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// The Babel preset a consuming app puts in its babel.config.js:
|
|
2
|
+
//
|
|
3
|
+
// presets: ['module:@react-native/babel-preset', '@symbiote-native/solid/babel-preset']
|
|
4
|
+
//
|
|
5
|
+
// It is babel-preset-solid with the two options that MUST be right pre-filled, so an app can
|
|
6
|
+
// never get them wrong:
|
|
7
|
+
//
|
|
8
|
+
// generate: 'universal' — emit calls into a custom renderer instead of DOM operations.
|
|
9
|
+
// moduleName — where those calls are imported FROM. Compiled JSX turns into
|
|
10
|
+
// `import { createElement } from '<moduleName>'`, so this string has
|
|
11
|
+
// to name a real, resolvable module exporting the 11 functions
|
|
12
|
+
// createRenderer() returns (src/renderer.ts, published as the
|
|
13
|
+
// ./renderer subpath). A typo here fails at bundle time with a
|
|
14
|
+
// module-not-found on a path nobody wrote by hand.
|
|
15
|
+
//
|
|
16
|
+
// Same reasoning as ./metro-css-parser.cjs: shipping the wiring from the adapter means the app
|
|
17
|
+
// declares no extra devDependency (babel-preset-solid is OUR dependency, and require() resolves
|
|
18
|
+
// relative to this file, not via the app's node_modules).
|
|
19
|
+
//
|
|
20
|
+
// .cjs, not .js: this package is "type": "module", and Babel require()s a preset.
|
|
21
|
+
//
|
|
22
|
+
// Preset ORDER in the app matters and is not arbitrary. Babel applies presets in REVERSE array
|
|
23
|
+
// order, so listing this one LAST runs it FIRST — before @react-native/babel-preset's own
|
|
24
|
+
// React-JSX transform gets a chance to claim the same JSX nodes.
|
|
25
|
+
module.exports = function symbioteSolidBabelPreset(_api, options = {}) {
|
|
26
|
+
return {
|
|
27
|
+
presets: [
|
|
28
|
+
[
|
|
29
|
+
require('babel-preset-solid'),
|
|
30
|
+
{
|
|
31
|
+
...options,
|
|
32
|
+
// Last, deliberately: an app may pass other babel-preset-solid options through, but
|
|
33
|
+
// not these two — overriding either one silently produces output this adapter cannot
|
|
34
|
+
// satisfy (DOM operations, or imports from a module that exports nothing we provide).
|
|
35
|
+
generate: 'universal',
|
|
36
|
+
moduleName: '@symbiote-native/solid/renderer',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Re-exported from THIS file's own location so a consuming app's
|
|
2
|
+
// require('@symbiote-native/solid/metro-css-parser') resolves css-parser as a dependency of THIS
|
|
3
|
+
// package, not the app's node_modules (Node resolves require() relative to the requiring file,
|
|
4
|
+
// not via pnpm hoisting) — the app never needs to declare @symbiote-native/css-parser itself.
|
|
5
|
+
// .cjs, not .js: this package is "type": "module", but Metro's babelTransformerPath loading needs
|
|
6
|
+
// a require()-able file. createCssMetroTransformer() is a factory — Metro needs the actual
|
|
7
|
+
// {transform, getCacheKey} object it returns, not the css-parser package barrel.
|
|
8
|
+
module.exports = require('@symbiote-native/css-parser').createCssMetroTransformer();
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@symbiote-native/solid",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "SymbioteNative's Solid adapter — a solid-js/universal createRenderer driving real native iOS/Android views through the same engine as every other adapter.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
9
|
+
"directory": "adapters/solid"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/adapters/solid#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./build/index.d.ts",
|
|
23
|
+
"default": "./build/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./bootstrap": {
|
|
26
|
+
"types": "./build/bootstrap.d.ts",
|
|
27
|
+
"default": "./build/bootstrap.js"
|
|
28
|
+
},
|
|
29
|
+
"./renderer": {
|
|
30
|
+
"types": "./build/renderer.d.ts",
|
|
31
|
+
"default": "./build/renderer.js"
|
|
32
|
+
},
|
|
33
|
+
"./jsx-runtime": {
|
|
34
|
+
"types": "./build/jsx-runtime.d.ts",
|
|
35
|
+
"default": "./build/jsx-runtime.js"
|
|
36
|
+
},
|
|
37
|
+
"./jsx-dev-runtime": {
|
|
38
|
+
"types": "./build/jsx-runtime.d.ts",
|
|
39
|
+
"default": "./build/jsx-runtime.js"
|
|
40
|
+
},
|
|
41
|
+
"./babel-preset": "./babel-preset.cjs",
|
|
42
|
+
"./metro-css-parser": "./metro-css-parser.cjs"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"build",
|
|
46
|
+
"babel-preset.cjs",
|
|
47
|
+
"metro-css-parser.cjs"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"babel-preset-solid": "^1.9.12",
|
|
54
|
+
"typescript": "~6.0.0",
|
|
55
|
+
"@symbiote-native/css-parser": "0.3.0",
|
|
56
|
+
"@symbiote-native/components": "0.4.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@symbiote-native/engine": ">=0.1.7",
|
|
60
|
+
"solid-js": ">=1.9.0",
|
|
61
|
+
"react-native": ">=0.86"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"solid-js": "^1.9.14",
|
|
65
|
+
"@symbiote-native/engine": "0.2.0",
|
|
66
|
+
"@symbiote-native/test-utils": "0.1.6"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"typecheck": "tsc --build",
|
|
70
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
71
|
+
}
|
|
72
|
+
}
|