@primate/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/README.md +6 -0
- package/package.json +27 -0
- package/src/client/client.js +20 -0
- package/src/client/create-root.js +16 -0
- package/src/client/create-root2.js +22 -0
- package/src/client/exports.js +5 -0
- package/src/client/hydrate.js +1 -0
- package/src/client/liveview.js +12 -0
- package/src/client/rootname.js +1 -0
- package/src/errors.js +6 -0
- package/src/exports.js +1 -0
- package/src/module.js +211 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@primate/solid",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Primate Solid module",
|
|
5
|
+
"homepage": "https://primatejs.com/modules/solid",
|
|
6
|
+
"bugs": "https://github.com/primatejs/primate/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"files": [
|
|
9
|
+
"src/**/*.js",
|
|
10
|
+
"!src/**/*.spec.js"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/primatejs/primate",
|
|
15
|
+
"directory": "packages/solid"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"babel-preset-solid": "^1.7.7",
|
|
19
|
+
"runtime-compat": "^0.25.1",
|
|
20
|
+
"solid-js": "^1.7.11"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"primate": "0.21"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": "./src/exports.js"
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {generateHydrationScript} from "solid-js/web";
|
|
2
|
+
import rootname from "./rootname.js";
|
|
3
|
+
import liveview from "./liveview.js";
|
|
4
|
+
|
|
5
|
+
const {groups: {code: hydration_script}} = generateHydrationScript()
|
|
6
|
+
.match(/^<script>(?<code>.*?)<\/script>/u);
|
|
7
|
+
|
|
8
|
+
export default ({names, data}, options) => `
|
|
9
|
+
import * as components from "app";
|
|
10
|
+
import {hydrate_solid} from "app";
|
|
11
|
+
|
|
12
|
+
window._$HY = { events: [], completed: new WeakSet(), r: {} };
|
|
13
|
+
|
|
14
|
+
${hydration_script}
|
|
15
|
+
|
|
16
|
+
const root = hydrate_solid(() => components.${rootname}({
|
|
17
|
+
components: [${names.map(name => `components.${name}`).join(", ")}],
|
|
18
|
+
data: JSON.parse(${JSON.stringify(JSON.stringify(data))}),
|
|
19
|
+
}), globalThis.window.document.body);
|
|
20
|
+
${options.liveview ? liveview : ""}`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default (length, data) => {
|
|
2
|
+
const n = length - 1;
|
|
3
|
+
const body = Array.from({length: n}, (_, i) => i - 1)
|
|
4
|
+
.reduceRight((child, _, i) => `components[${i + 1}] !== undefined
|
|
5
|
+
? createComponent(components[${i}], {${data}: data[${i}],
|
|
6
|
+
children: ${child}})
|
|
7
|
+
: createComponent(components[${i}], {${data}: data[${i}]})
|
|
8
|
+
`, `createComponent(components[${n}], {${data}: data[${n}]})`);
|
|
9
|
+
|
|
10
|
+
return `
|
|
11
|
+
import {createComponent} from "solid-js/web";
|
|
12
|
+
export default function Root({components, data} = {}) {
|
|
13
|
+
return ${body};
|
|
14
|
+
};
|
|
15
|
+
`;
|
|
16
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default (length, data) => {
|
|
2
|
+
const n = length - 1;
|
|
3
|
+
const body = Array.from({length: n}, (_, i) => i - 1)
|
|
4
|
+
.reduceRight((child, _, i) => `<Show
|
|
5
|
+
when={components[${i + 1}] !== undefined}
|
|
6
|
+
fallback={
|
|
7
|
+
<>
|
|
8
|
+
<Dynamic component={components[${i}]} ${data}={data[${i}]} />
|
|
9
|
+
</>
|
|
10
|
+
}
|
|
11
|
+
>
|
|
12
|
+
<Dynamic component={components[${i}]} ${data}={data[${i}]}>${child}</Dynamic>
|
|
13
|
+
</Show>
|
|
14
|
+
`, `<Dynamic component={components[${n}]} ${data}={data[${n}]} />`);
|
|
15
|
+
|
|
16
|
+
return `
|
|
17
|
+
import {Dynamic, Show} from "solid-js/web";
|
|
18
|
+
export default function Root({components, data} = {}) {
|
|
19
|
+
return ${body};
|
|
20
|
+
};
|
|
21
|
+
`;
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default "export {hydrate as hydrate_solid} from \"solid-js/web\";";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import rootname from "./rootname.js";
|
|
2
|
+
|
|
3
|
+
export default `
|
|
4
|
+
const {liveview} = components;
|
|
5
|
+
window.addEventListener("DOMContentLoaded", _ => liveview((props, update) => {
|
|
6
|
+
root.render(
|
|
7
|
+
createElement(components.${rootname}, {
|
|
8
|
+
components: props.names.map(name => components[name]),
|
|
9
|
+
data: props.data,
|
|
10
|
+
})
|
|
11
|
+
);
|
|
12
|
+
}));`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default "solid_root";
|
package/src/errors.js
ADDED
package/src/exports.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default} from "./module.js";
|
package/src/module.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import crypto from "runtime-compat/crypto";
|
|
2
|
+
import {Path} from "runtime-compat/fs";
|
|
3
|
+
import {Response, Status, MediaType} from "runtime-compat/http";
|
|
4
|
+
import {transformAsync} from "@babel/core";
|
|
5
|
+
import solid from "babel-preset-solid";
|
|
6
|
+
import {renderToString} from "solid-js/web";
|
|
7
|
+
import {tryreturn} from "runtime-compat/async";
|
|
8
|
+
import errors from "./errors.js";
|
|
9
|
+
import {client, create_root, create_root2, rootname, hydrate} from "./client/exports.js";
|
|
10
|
+
|
|
11
|
+
// do not hard-depend on node
|
|
12
|
+
const packager = import.meta.runtime?.packager ?? "package.json";
|
|
13
|
+
const library = import.meta.runtime?.library ?? "node_modules";
|
|
14
|
+
|
|
15
|
+
const encoder = new TextEncoder();
|
|
16
|
+
const hash = async (string, algorithm = "sha-256") => {
|
|
17
|
+
const base = 16;
|
|
18
|
+
const target_pad_length = 2;
|
|
19
|
+
const target_slice = 12;
|
|
20
|
+
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
|
|
21
|
+
return Array.from(new Uint8Array(bytes))
|
|
22
|
+
.map(byte => byte.toString(base).padStart(target_pad_length, "0"))
|
|
23
|
+
.join("")
|
|
24
|
+
.slice(0, target_slice);
|
|
25
|
+
};
|
|
26
|
+
const normalize = async path => `solid_${await hash(path)}`;
|
|
27
|
+
|
|
28
|
+
const import$ = async (module, app, copy_dependency) => {
|
|
29
|
+
const {build: {modules}, http: {static: {root}}} = app.config;
|
|
30
|
+
const parts = module.split("/");
|
|
31
|
+
const path = [library, ...parts];
|
|
32
|
+
const pkg = await Path.resolve().join(...path, packager).json();
|
|
33
|
+
if (copy_dependency) {
|
|
34
|
+
const dependency = Path.resolve().join(...path);
|
|
35
|
+
const to = app.build.paths.client.join(modules, ...parts);
|
|
36
|
+
await dependency.file.copy(`${to}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const entry = pkg.exports["."].browser.import;
|
|
40
|
+
app.importmaps[module] = new Path(root, modules, module, entry).path;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const filename = `${rootname}.js`;
|
|
44
|
+
const type = "module";
|
|
45
|
+
|
|
46
|
+
const load = async path =>
|
|
47
|
+
tryreturn(async () => (await import(`${path}.js`)).default)
|
|
48
|
+
.orelse(_ => errors.MissingComponent.throw(path.name, path));
|
|
49
|
+
|
|
50
|
+
const make_component = base => async (name, props) =>
|
|
51
|
+
({name, component: await load(base.join(name)), props});
|
|
52
|
+
|
|
53
|
+
const handler = (name, props = {}, {status = Status.OK} = {}) =>
|
|
54
|
+
async (app, {layouts = [], as_layout} = {}, request) => {
|
|
55
|
+
const options = {
|
|
56
|
+
liveview: app.liveview !== undefined,
|
|
57
|
+
};
|
|
58
|
+
const {headers} = request;
|
|
59
|
+
const {paths} = app.build;
|
|
60
|
+
const make = make_component(paths.server.join(app.config.build.app));
|
|
61
|
+
if (as_layout) {
|
|
62
|
+
return make(name, props);
|
|
63
|
+
}
|
|
64
|
+
const components = (await Promise.all(layouts.map(layout =>
|
|
65
|
+
layout(app, {as_layout: true}, request)
|
|
66
|
+
)))
|
|
67
|
+
/* set the actual page as the last component */
|
|
68
|
+
.concat(await make(name, props));
|
|
69
|
+
|
|
70
|
+
const data = components.map(component => component.props);
|
|
71
|
+
const names = await Promise.all(components.map(component =>
|
|
72
|
+
normalize(component.name)));
|
|
73
|
+
if (options.liveview && headers.get(app.liveview.header) !== undefined) {
|
|
74
|
+
return new Response(JSON.stringify({names, data}), {
|
|
75
|
+
status,
|
|
76
|
+
headers: {...await app.headers(),
|
|
77
|
+
"Content-Type": MediaType.APPLICATION_JSON},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const root = paths.server.join(filename);
|
|
82
|
+
const imported = (await import(root)).default;
|
|
83
|
+
const body = renderToString(() => imported({
|
|
84
|
+
components: components.map(({component}) => component),
|
|
85
|
+
data,
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const code = client({names, data}, options);
|
|
89
|
+
|
|
90
|
+
await app.publish({code, type, inline: true});
|
|
91
|
+
// needs to be called before app.render
|
|
92
|
+
const headers$ = await app.headers();
|
|
93
|
+
|
|
94
|
+
return new Response(await app.render({body}), {
|
|
95
|
+
status,
|
|
96
|
+
headers: {...headers$, "Content-Type": MediaType.TEXT_HTML},
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const options = {
|
|
101
|
+
server: [solid, {
|
|
102
|
+
generate: "ssr",
|
|
103
|
+
hydratable: true,
|
|
104
|
+
}],
|
|
105
|
+
client: [solid, {
|
|
106
|
+
generate: "dom",
|
|
107
|
+
hydratable: true,
|
|
108
|
+
}],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const endings = {
|
|
112
|
+
solid: ".solid",
|
|
113
|
+
js: ".solid.js",
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const transform = {
|
|
117
|
+
async server(file) {
|
|
118
|
+
return (await transformAsync(file, {presets: [options.server]})).code;
|
|
119
|
+
},
|
|
120
|
+
async client(file) {
|
|
121
|
+
return (await transformAsync(file, {presets: [options.client]})).code;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export default ({
|
|
126
|
+
dynamicProps = "data",
|
|
127
|
+
extension = "jsx",
|
|
128
|
+
} = {}) => {
|
|
129
|
+
const env = {};
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
name: "@primate/solid",
|
|
133
|
+
init(app, next) {
|
|
134
|
+
env.source = app.build.paths.components;
|
|
135
|
+
|
|
136
|
+
return next(app);
|
|
137
|
+
},
|
|
138
|
+
register(app, next) {
|
|
139
|
+
app.register("solid", handler);
|
|
140
|
+
return next(app);
|
|
141
|
+
},
|
|
142
|
+
async compile(app, next) {
|
|
143
|
+
// copy components to build/components
|
|
144
|
+
await app.copy(app.paths.components, env.source, /^.*.(?:solid)$/u);
|
|
145
|
+
const components = await env.source.collect(/^.*.solid$/u);
|
|
146
|
+
const target = app.build.paths.server.join(app.config.build.app);
|
|
147
|
+
await target.file.create();
|
|
148
|
+
|
|
149
|
+
await Promise.all(components.map(async component => {
|
|
150
|
+
const file = await component.file.read();
|
|
151
|
+
const code = await transform.server(file);
|
|
152
|
+
const to = target.join(`${component.path}.js`.replace(env.source, ""));
|
|
153
|
+
await to.directory.file.create();
|
|
154
|
+
await to.file.write(code.replaceAll(endings.solid, endings.js));
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
const root = await transform.server(create_root2(app.layoutDepth,
|
|
158
|
+
dynamicProps));
|
|
159
|
+
const to = app.build.paths.server.join(filename);
|
|
160
|
+
await to.file.write(root);
|
|
161
|
+
|
|
162
|
+
return next(app);
|
|
163
|
+
},
|
|
164
|
+
async publish(app, next) {
|
|
165
|
+
const target = app.build.paths.client.join(app.config.build.app);
|
|
166
|
+
await target.file.create();
|
|
167
|
+
|
|
168
|
+
// create client components
|
|
169
|
+
const components = await env.source.collect(/^.*.solid/u);
|
|
170
|
+
await Promise.all(components.map(async component => {
|
|
171
|
+
const name = component.path.replace(`${env.source}/`, "");
|
|
172
|
+
const file = await component.file.read();
|
|
173
|
+
const code = await transform.client(file);
|
|
174
|
+
const build = app.config.build.app;
|
|
175
|
+
const {path} = component;
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
{
|
|
179
|
+
const src = `${path}.js`.replace(`${env.source}`, _ => build);
|
|
180
|
+
await app.publish({src,
|
|
181
|
+
code: code.replaceAll(endings.solid, endings.js),
|
|
182
|
+
type});
|
|
183
|
+
}
|
|
184
|
+
const imported = await normalize(name);
|
|
185
|
+
app.bootstrap({
|
|
186
|
+
type: "script",
|
|
187
|
+
code: `export {default as ${imported}} from "./${name}.js";\n`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
await import$("solid-js", app, true);
|
|
193
|
+
await import$("solid-js/web", app);
|
|
194
|
+
|
|
195
|
+
app.bootstrap({type: "script", code: hydrate});
|
|
196
|
+
|
|
197
|
+
{
|
|
198
|
+
const code = await transform.client(create_root2(app.layoutDepth,
|
|
199
|
+
dynamicProps));
|
|
200
|
+
const src = new Path(app.config.http.static.root, filename);
|
|
201
|
+
await app.publish({src, code, type});
|
|
202
|
+
}
|
|
203
|
+
{
|
|
204
|
+
const code = `export {default as ${rootname}} from "../${filename}";\n`;
|
|
205
|
+
app.bootstrap({type: "script", code});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return next(app);
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
};
|