browser-runtime-solid 0.0.2 → 0.0.3
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/README.md +47 -20
- package/package.json +2 -2
- package/src/delegation.js +30 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +33 -6
- package/src/version-family.js +79 -0
package/README.md
CHANGED
|
@@ -1,32 +1,59 @@
|
|
|
1
1
|
# browser-runtime-solid
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Solid 2 JSX compiler/renderer adapter for [`browser-module-runtime`](https://www.npmjs.com/package/browser-module-runtime). This plugin is **optional**; generic runtime imports do not load Solid unless you register the adapter.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
npm install browser-module-runtime@0.0.1 browser-runtime-solid@0.0.1 solid-tag@0.0.3
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Solid rendering also needs the Solid DOM runtime available through your host's import map or runtime module configuration.
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
5
|
+
## Pinned Solid 2 family (recommended for standalone browser modules)
|
|
14
6
|
|
|
15
7
|
```js
|
|
16
8
|
import {createRuntime} from 'browser-module-runtime';
|
|
17
|
-
import {solid} from 'browser-runtime-solid'
|
|
9
|
+
import {solid} from 'browser-module-frameworks'; // or 'browser-runtime-solid'
|
|
18
10
|
|
|
19
|
-
const runtime
|
|
20
|
-
runtime.define('/Counter.jsx'
|
|
11
|
+
const runtime=createRuntime({plugins:[solid({version:'2.0.0-rc.13',provider:'esm.sh'})]});
|
|
12
|
+
runtime.define('/Counter.jsx',`
|
|
21
13
|
import {createSignal} from 'solid-js';
|
|
22
|
-
export default function Counter()
|
|
23
|
-
const [count,setCount]
|
|
24
|
-
return <button onClick={()
|
|
14
|
+
export default function Counter(){
|
|
15
|
+
const [count,setCount]=createSignal(0);
|
|
16
|
+
return <button onClick={()=>setCount(n=>n+1)}>Count: {count()}</button>;
|
|
25
17
|
}
|
|
26
|
-
`,
|
|
18
|
+
`,{type:'solid-jsx'});
|
|
19
|
+
const mount=await runtime.mount({module:'/Counter.jsx',target:'#app'});
|
|
20
|
+
// Later: await mount.dispose(); await runtime.dispose();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
A pinned adapter controls **four** framework packages as one compatible family: `solid-js`, `@solidjs/web`, `@solidjs/html`, and `@solidjs/signals`. The pinned JSX transformer `solid-tag` has a *separate* `tagVersion` (default `0.0.3`), so `solid-tag` need not match the Solid release. Generated imports, including `solid-js/internal` and the `solid-js/web` alias to `@solidjs/web`, are linked through the same runtime-local policy used to load the renderer. The tested Solid version is `2.0.0-rc.13`. This does **not** change or depend on a browser-global Solid import map.
|
|
24
|
+
|
|
25
|
+
One standalone HTML page can import just the generic engine and aggregator using `esmesm`:
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<script src="https://raw.esm.sh/esmesm@0.0.3/bootstrap.js"
|
|
29
|
+
data-map="browser-module-runtime@0.0.3; browser-module-frameworks@0.0.2"></script>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The rest is controlled per runtime by `solid({version,...})`. See `examples/06-solid-versioning.html` in the workspace; before npm publication of 0.0.3 open `/examples/06-solid-versioning.html?local=1` through `bun run dev`.
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
## Host-managed imports (backward-compatible)
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
const runtime=createRuntime({plugins:[solid({source:'host'})]});
|
|
38
|
+
// equivalent to solid() or solid({source:'auto'}) without a version/provider
|
|
30
39
|
```
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
Use this when an application bundler or `esmesm` import map already coordinates the Solid packages and `solid-tag`. Host mode does **not** silently mix partially resolved host modules with CDN fallbacks. For pinned mode, the adapter selects only the esm.sh provider because other providers have not passed Solid-family identity checks.
|
|
42
|
+
|
|
43
|
+
## Options
|
|
44
|
+
|
|
45
|
+
| Option | Description |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `version` | Exact Solid 2 family semver; enables runtime-local pinning |
|
|
48
|
+
| `provider` | `esm.sh` for pinned mode; other providers reject for now |
|
|
49
|
+
| `source` | `auto` (default), `host`, `esm.sh`; `auto` uses pinned mode when configured, otherwise host |
|
|
50
|
+
| `fallbackProvider` | Enables a deterministic provider-managed default version when `version` is omitted |
|
|
51
|
+
| `tagVersion` | Separate exact `solid-tag` compiler release, default `0.0.3` in pinned mode |
|
|
52
|
+
| `compiler`, `render`, `web` | Existing compiler/renderer injection and optional `@solidjs/web` namespace |
|
|
53
|
+
| `transformOptions` | Forwarded to `solid-tag.transformModule` |
|
|
54
|
+
| `delegatedEvents` | Iterable of Solid delegated event names; omitted uses renderer or compatibility defaults |
|
|
55
|
+
| `modules` | Host namespaces supported in host mode; family names forbidden in pinned mode |
|
|
56
|
+
|
|
57
|
+
A pinned mode owns `solid-js`, `@solidjs/web`, `@solidjs/html`, `@solidjs/signals`, and `solid-tag`. Conflicting application pins and host namespaces are rejected before importing the CDN compiler. After mounting, Solid 2 document delegation is set up once per document/renderer; individual component disposals stay independent. Shadow DOM is not part of the currently verified delegation surface.
|
|
58
|
+
|
|
59
|
+
The plugin is not a drop-in replacement for `solid-tag-runtime@0.0.34` or its specialized HTML controller. Browser integration tests should be run before publishing new Solid provider versions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-runtime-solid",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"node": ">=20"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"browser-module-runtime": "0.0.
|
|
28
|
+
"browser-module-runtime": "0.0.3",
|
|
29
29
|
"solid-tag": "0.0.3",
|
|
30
30
|
"@solidjs/web": "*"
|
|
31
31
|
},
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// The document's delegated event listener is associated with a particular
|
|
2
|
+
// @solidjs/web namespace, not with any individual mounted component.
|
|
3
|
+
const hostsByDocument=new WeakMap();
|
|
4
|
+
const eventsByNamespace=new WeakMap();
|
|
5
|
+
const defaultEvents=Object.freeze([
|
|
6
|
+
'beforeinput','click','dblclick','contextmenu','focusin','focusout','input',
|
|
7
|
+
'keydown','keyup','mousedown','mousemove','mouseout','mouseover','mouseup',
|
|
8
|
+
'pointerdown','pointermove','pointerout','pointerover','pointerup',
|
|
9
|
+
'touchend','touchmove','touchstart'
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export function ensureSolidDelegation(web,document,requestedEvents){
|
|
13
|
+
if(!web || !document || typeof web.render!=='function')return;
|
|
14
|
+
let byRenderer=hostsByDocument.get(document);
|
|
15
|
+
if(!byRenderer){byRenderer=new WeakMap();hostsByDocument.set(document,byRenderer);}
|
|
16
|
+
if(!byRenderer.has(web)){
|
|
17
|
+
// Solid 2 requires a root rendered on document to own delegated events.
|
|
18
|
+
const dispose=web.render(()=>undefined,document);
|
|
19
|
+
byRenderer.set(web,{dispose});
|
|
20
|
+
}
|
|
21
|
+
if(typeof web.delegateEvents!=='function')return;
|
|
22
|
+
let byDocument=eventsByNamespace.get(web);
|
|
23
|
+
if(!byDocument){byDocument=new WeakMap();eventsByNamespace.set(web,byDocument);}
|
|
24
|
+
let installed=byDocument.get(document);
|
|
25
|
+
if(!installed){installed=new Set();byDocument.set(document,installed);}
|
|
26
|
+
const raw=requestedEvents??web.DelegatedEvents??defaultEvents;
|
|
27
|
+
const fresh=[...new Set(Array.from(raw||[],event=>String(event).trim().toLowerCase()))]
|
|
28
|
+
.filter(event=>event&&!installed.has(event));
|
|
29
|
+
if(fresh.length){web.delegateEvents(fresh,document);fresh.forEach(event=>installed.add(event));}
|
|
30
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -2,8 +2,24 @@ import type {RuntimePlugin} from 'browser-module-runtime';
|
|
|
2
2
|
export interface SolidAdapterOptions {
|
|
3
3
|
compiler?: {compile?(source:string,options?:Record<string,unknown>):unknown;transformModule?(source:string,options?:Record<string,unknown>):unknown};
|
|
4
4
|
render?: (component:()=>unknown,target:Element)=>void|(()=>void);
|
|
5
|
+
/** Optional @solidjs/web module namespace for injected renderer/delegation. */
|
|
6
|
+
web?: {render:(fn:()=>unknown,target:Document|Element)=>void|(()=>void);delegateEvents?:(events:string[],doc:Document)=>void;DelegatedEvents?:Iterable<string>};
|
|
5
7
|
modules?:Record<string,object>;
|
|
8
|
+
/** The actual Solid framework family version (independent of adapter version). */
|
|
6
9
|
version?:string;
|
|
10
|
+
/** auto: pinned if version/provider specified, otherwise host; host: caller import map; esm.sh: pinned CDN. */
|
|
11
|
+
source?:'auto'|'host'|'esm.sh'|'jsdelivr';
|
|
12
|
+
provider?:'esm.sh'|'jsdelivr';
|
|
13
|
+
fallbackProvider?:'esm.sh'|'jsdelivr';
|
|
14
|
+
/** Independently versioned JSX transformer, pinned only in CDN mode. */
|
|
15
|
+
tagVersion?:string;
|
|
7
16
|
transformOptions?:Record<string,unknown>;
|
|
17
|
+
delegatedEvents?:Iterable<string>;
|
|
8
18
|
}
|
|
19
|
+
export declare class SolidVersionError extends Error {code:string;}
|
|
9
20
|
export declare function solid(options?:SolidAdapterOptions):RuntimePlugin;
|
|
21
|
+
export declare const SOLID_PACKAGES:ReadonlyArray<string>;
|
|
22
|
+
export declare const TESTED_SOLID_VERSION:string;
|
|
23
|
+
export declare function selectSolidMode(options?:SolidAdapterOptions):{mode:'host'|'pinned';version?:string;provider?:string;tagVersion?:string};
|
|
24
|
+
export declare function solidPackageUrl(name:string,version:string,subpath?:string):string;
|
|
25
|
+
export declare function createSolidFamilyRules(version:string):Record<string,unknown>;
|
package/src/index.js
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {createSolidFamilyRules,selectSolidMode} from './version-family.js';
|
|
2
|
+
import {ensureSolidDelegation} from './delegation.js';
|
|
3
|
+
export {SolidVersionError,selectSolidMode,createSolidFamilyRules,solidPackageUrl,SOLID_PACKAGES,TESTED_SOLID_VERSION} from './version-family.js';
|
|
4
|
+
|
|
5
|
+
/** Optional Solid 2 adapter, including independently pinned runtime families. */
|
|
6
|
+
export function solid({compiler,render,web,modules={},version,source='auto',provider,fallbackProvider,tagVersion,
|
|
7
|
+
transformOptions={},delegatedEvents}={}) {
|
|
8
|
+
const selection=selectSolidMode({version,source,provider,fallbackProvider,tagVersion});
|
|
9
|
+
if(selection.mode==='pinned'){
|
|
10
|
+
for(const name of Object.keys(modules)) {
|
|
11
|
+
if(name==='solid-tag'||Object.hasOwn(createSolidFamilyRules(selection.version),name))
|
|
12
|
+
throw new Error(`SOLID_VERSION_CONFLICT: caller-owned ${JSON.stringify(name)} namespace cannot be verified against pinned Solid ${selection.version}. Use source:"host" for supplied namespaces.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
3
15
|
let transformer=compiler;
|
|
4
16
|
let renderFunction=render;
|
|
17
|
+
let webNamespace=web;
|
|
18
|
+
let ownerRuntime;
|
|
5
19
|
return {
|
|
6
|
-
name:'solid',version:
|
|
7
|
-
|
|
20
|
+
name:'solid',version:'0.0.3',frameworkVersion:selection.version,source:selection.mode,
|
|
21
|
+
cacheKey:JSON.stringify({solidMode:selection.mode,solidVersion:selection.version??null,tagVersion:selection.tagVersion??null,transformOptions}),
|
|
22
|
+
...(selection.mode==='pinned'?{
|
|
23
|
+
packageFamilyRules:createSolidFamilyRules(selection.version),
|
|
24
|
+
packageEntries:{'solid-tag':{provider:'esm.sh',version:selection.tagVersion}}
|
|
25
|
+
}:{}),
|
|
26
|
+
async configure({runtime}) {
|
|
27
|
+
ownerRuntime=runtime;
|
|
28
|
+
for(const [id,ns] of Object.entries(modules))runtime.defineModule(id,ns);
|
|
29
|
+
},
|
|
8
30
|
compilers:[{
|
|
9
31
|
types:['solid-jsx'],extensions:['.jsx'],
|
|
10
32
|
async compile(source,{id}) {
|
|
11
|
-
transformer ||= await import('solid-tag');
|
|
33
|
+
transformer ||= await (ownerRuntime ? ownerRuntime.import('solid-tag') : import('solid-tag'));
|
|
12
34
|
if (typeof transformer.compile==='function')return transformer.compile(source,{id});
|
|
13
35
|
if(typeof transformer.transformModule!=='function')throw new TypeError('solid-tag transformer requires transformModule()');
|
|
14
36
|
const result=await transformer.transformModule(source,{...transformOptions,injectHtmlImport:true,htmlImportSource:'@solidjs/html'});
|
|
@@ -18,7 +40,12 @@ export function solid({compiler,render,modules={},version='0.0.3',transformOptio
|
|
|
18
40
|
renderers:[{
|
|
19
41
|
name:'solid',types:['solid-jsx'],
|
|
20
42
|
async mount({component,target,props}) {
|
|
21
|
-
renderFunction
|
|
43
|
+
if(!renderFunction){
|
|
44
|
+
webNamespace ||= await (ownerRuntime ? ownerRuntime.import('@solidjs/web') : import('@solidjs/web'));
|
|
45
|
+
renderFunction=webNamespace.render;
|
|
46
|
+
}
|
|
47
|
+
if(typeof renderFunction!=='function')throw new TypeError('@solidjs/web renderer requires render().');
|
|
48
|
+
if(webNamespace)ensureSolidDelegation(webNamespace,target.ownerDocument||globalThis.document,delegatedEvents);
|
|
22
49
|
const dispose=renderFunction(()=>component(props),target);
|
|
23
50
|
return {dispose};
|
|
24
51
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solid 2's browser package family has a shared reactive core. For pinned CDN
|
|
3
|
+
* operation, ALL imports (compiler-produced modules and renderer) must use the
|
|
4
|
+
* same per-runtime package URL rules. No document import-map mutations.
|
|
5
|
+
*
|
|
6
|
+
* Based on the dependency closure used by solid-tag-runtime@0.0.34.
|
|
7
|
+
*/
|
|
8
|
+
export const SOLID_PACKAGES = Object.freeze([
|
|
9
|
+
'solid-js', '@solidjs/web', '@solidjs/html', '@solidjs/signals'
|
|
10
|
+
]);
|
|
11
|
+
export const TESTED_SOLID_VERSION = '2.0.0-rc.13';
|
|
12
|
+
export const TESTED_SOLID_TAG_VERSION = '0.0.3';
|
|
13
|
+
const exactVersion = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
14
|
+
|
|
15
|
+
export class SolidVersionError extends Error {
|
|
16
|
+
constructor(code, message, details = {}) {
|
|
17
|
+
super(`${code}: ${message}`);
|
|
18
|
+
this.name = 'SolidVersionError';
|
|
19
|
+
this.code = code;
|
|
20
|
+
Object.assign(this, details);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const closures = Object.freeze({
|
|
25
|
+
'solid-js': ['@solidjs/signals'],
|
|
26
|
+
'@solidjs/web': ['solid-js', '@solidjs/signals'],
|
|
27
|
+
'@solidjs/html': ['@solidjs/web', 'solid-js', '@solidjs/signals'],
|
|
28
|
+
'@solidjs/signals': []
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export function selectSolidMode({version, source='auto', provider, fallbackProvider, tagVersion}={}) {
|
|
32
|
+
if(!['auto','host','esm.sh','jsdelivr'].includes(source))
|
|
33
|
+
throw new SolidVersionError('SOLID_SOURCE_INVALID',`Unsupported source ${JSON.stringify(source)}.`,{source});
|
|
34
|
+
if(provider!==undefined && !['esm.sh','jsdelivr'].includes(provider))
|
|
35
|
+
throw new SolidVersionError('SOLID_PROVIDER_INVALID',`Unsupported provider ${JSON.stringify(provider)}.`,{provider});
|
|
36
|
+
if(fallbackProvider!==undefined && !['esm.sh','jsdelivr'].includes(fallbackProvider))
|
|
37
|
+
throw new SolidVersionError('SOLID_PROVIDER_INVALID',`Unsupported fallbackProvider ${JSON.stringify(fallbackProvider)}.`,{fallbackProvider});
|
|
38
|
+
if(provider && source!=='auto' && source!=='host' && provider!==source)
|
|
39
|
+
throw new SolidVersionError('SOLID_PROVIDER_CONFLICT',`source ${source} conflicts with provider ${provider}.`);
|
|
40
|
+
if(source==='host' && (version!=null || provider || fallbackProvider))
|
|
41
|
+
throw new SolidVersionError('SOLID_VERSION_CONFLICT','source:"host" cannot guarantee a pinned version or CDN provider. Use source:"auto" or source:"esm.sh".');
|
|
42
|
+
const pinned = version!=null || provider!=null || fallbackProvider!=null || source==='esm.sh' || source==='jsdelivr';
|
|
43
|
+
if(!pinned){
|
|
44
|
+
if(tagVersion!=null)throw new SolidVersionError('SOLID_TAG_VERSION_REQUIRES_PROVIDER','tagVersion requires a pinned Solid provider (or a host-owned solid-tag mapping).');
|
|
45
|
+
return Object.freeze({mode:'host',source});
|
|
46
|
+
}
|
|
47
|
+
const selectedProvider = provider || (source==='auto'?fallbackProvider:source) || 'esm.sh';
|
|
48
|
+
if(selectedProvider!=='esm.sh')throw new SolidVersionError('SOLID_PROVIDER_UNSAFE',`Pinned Solid families through ${selectedProvider} are not verified for shared reactive identity. Use esm.sh.`);
|
|
49
|
+
const familyVersion=version??TESTED_SOLID_VERSION;
|
|
50
|
+
const compilerVersion=tagVersion??TESTED_SOLID_TAG_VERSION;
|
|
51
|
+
if(typeof familyVersion!=='string'||!exactVersion.test(familyVersion))
|
|
52
|
+
throw new SolidVersionError('SOLID_VERSION_INVALID',`Expected exact Solid semver; received ${JSON.stringify(familyVersion)}.`);
|
|
53
|
+
if(typeof compilerVersion!=='string'||!exactVersion.test(compilerVersion))
|
|
54
|
+
throw new SolidVersionError('SOLID_TAG_VERSION_INVALID',`Expected exact solid-tag semver; received ${JSON.stringify(compilerVersion)}.`);
|
|
55
|
+
return Object.freeze({mode:'pinned',source,provider:selectedProvider,version:familyVersion,tagVersion:compilerVersion});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Produce an esm.sh URL with pinned sibling dependencies. */
|
|
59
|
+
export function solidPackageUrl(name, version, subpath='') {
|
|
60
|
+
// Solid 2 moved the DOM implementation into @solidjs/web. Preserve its
|
|
61
|
+
// historical `solid-js/web` import spelling for compiled JSX and plugins.
|
|
62
|
+
if(name==='solid-js' && (subpath==='/web'||subpath.startsWith('/web/')))
|
|
63
|
+
return solidPackageUrl('@solidjs/web',version,subpath.slice(4));
|
|
64
|
+
if(!SOLID_PACKAGES.includes(name))throw new SolidVersionError('SOLID_PACKAGE_INVALID',`Not a Solid-family root: ${name}`);
|
|
65
|
+
if(subpath && (!subpath.startsWith('/') || subpath.startsWith('//') || /[?#%\\]/.test(subpath)))
|
|
66
|
+
throw new SolidVersionError('SOLID_SUBPATH_INVALID',`Invalid Solid package subpath: ${subpath}`);
|
|
67
|
+
const url=new URL(`https://esm.sh/${name}@${version}${subpath}`);
|
|
68
|
+
const deps=closures[name].map(dep=>`${dep}@${version}`);
|
|
69
|
+
if(deps.length)url.searchParams.set('deps',deps.join(','));
|
|
70
|
+
return url.href;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Generic policy extension; the core does not need to know Solid package names. */
|
|
74
|
+
export function createSolidFamilyRules(version){
|
|
75
|
+
return Object.fromEntries(SOLID_PACKAGES.map(name=>[name,{
|
|
76
|
+
version, provider:'esm.sh', url:solidPackageUrl(name,version),
|
|
77
|
+
subpathUrl:subpath=>solidPackageUrl(name,version,subpath)
|
|
78
|
+
}]));
|
|
79
|
+
}
|