@primate/svelte 0.9.1 → 0.11.0
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 +4 -25
- package/package.json +4 -4
- package/src/errors.js +6 -0
- package/src/module.js +182 -66
package/README.md
CHANGED
|
@@ -1,27 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Svelte handler
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This handler module supports SSR and hydration and serves Svelte components
|
|
4
|
+
with the .svelte extension.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Configure
|
|
8
|
-
|
|
9
|
-
Add to `primate.config.js`
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import svelte from "@primate/svelte";
|
|
13
|
-
|
|
14
|
-
export default {
|
|
15
|
-
modules: [svelte()],
|
|
16
|
-
};
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
If you use a bundler, specify an array of entry points:
|
|
20
|
-
|
|
21
|
-
```js
|
|
22
|
-
import svelte from "@primate/svelte";
|
|
23
|
-
|
|
24
|
-
export default {
|
|
25
|
-
modules: [svelte({entryPoints: ["EntryPage.svelte"]})],
|
|
26
|
-
};
|
|
27
|
-
```
|
|
6
|
+
## [Documentation](https://primatejs.com/modules/svelte)
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Primate Svelte module",
|
|
5
5
|
"homepage": "https://primatejs.com/modules/svelte",
|
|
6
6
|
"bugs": "https://github.com/primatejs/primate/issues",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"files": [
|
|
9
|
-
"src
|
|
9
|
+
"src/**/*.js",
|
|
10
10
|
"!src/**/*.spec.js"
|
|
11
11
|
],
|
|
12
12
|
"repository": {
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
"directory": "packages/svelte"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"runtime-compat": "^0.
|
|
18
|
+
"runtime-compat": "^0.20.2",
|
|
19
19
|
"svelte": "^3.59.1"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"primate": "0.
|
|
22
|
+
"primate": "0.20"
|
|
23
23
|
},
|
|
24
24
|
"type": "module",
|
|
25
25
|
"exports": "./src/exports.js"
|
package/src/errors.js
ADDED
package/src/module.js
CHANGED
|
@@ -1,95 +1,211 @@
|
|
|
1
|
+
import {Path} from "runtime-compat/fs";
|
|
1
2
|
import * as compiler from "svelte/compiler";
|
|
2
|
-
import {
|
|
3
|
+
import {tryreturn} from "runtime-compat/flow";
|
|
4
|
+
import errors from "./errors.js";
|
|
3
5
|
|
|
4
6
|
const endings = {
|
|
5
7
|
svelte: ".svelte",
|
|
6
8
|
js: ".svelte.js",
|
|
7
9
|
};
|
|
8
10
|
|
|
11
|
+
const hash = async (string, algorithm = "sha-256") => {
|
|
12
|
+
const base = 16;
|
|
13
|
+
const target_pad_length = 2;
|
|
14
|
+
const target_slice = 12;
|
|
15
|
+
const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
|
|
16
|
+
return Array.from(new Uint8Array(bytes))
|
|
17
|
+
.map(byte => byte.toString(base).padStart(target_pad_length, "0"))
|
|
18
|
+
.join("")
|
|
19
|
+
.slice(0, target_slice);
|
|
20
|
+
};
|
|
21
|
+
const normalize = async path => `_${await hash(path)}`;
|
|
22
|
+
|
|
23
|
+
const rootname = "root";
|
|
24
|
+
const filename = `${rootname}.js`;
|
|
25
|
+
|
|
26
|
+
const options = {
|
|
27
|
+
server: {generate: "ssr", hydratable: true},
|
|
28
|
+
client: {generate: "dom", hydratable: true},
|
|
29
|
+
};
|
|
30
|
+
|
|
9
31
|
const type = "module";
|
|
10
32
|
|
|
11
|
-
const
|
|
12
|
-
async (
|
|
13
|
-
|
|
14
|
-
|
|
33
|
+
const load = async path =>
|
|
34
|
+
tryreturn(async () => (await import(`${path}.js`)).default)
|
|
35
|
+
.orelse(() => errors.MissingComponent.throw(path.name, path));
|
|
36
|
+
|
|
37
|
+
const make_component = base => async (name, props) =>
|
|
38
|
+
({name, component: await load(base.join(name)), props});
|
|
15
39
|
|
|
16
|
-
|
|
40
|
+
const encoder = new TextEncoder();
|
|
17
41
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
42
|
+
const handler = ({path}) => (name, props = {}, {status = 200, page} = {}) =>
|
|
43
|
+
async (app, {layouts = [], as_layout} = {}) => {
|
|
44
|
+
const make = make_component(path);
|
|
45
|
+
if (as_layout) {
|
|
46
|
+
return make(name, props);
|
|
47
|
+
}
|
|
48
|
+
const headers = app.headers();
|
|
49
|
+
const components = (await Promise.all(layouts.map(layout =>
|
|
50
|
+
layout(app, {as_layout: true})
|
|
51
|
+
)))
|
|
52
|
+
/* set the actual page as the last component */
|
|
53
|
+
.concat(await make(name, props));
|
|
54
|
+
|
|
55
|
+
const data = components.map(component => component.props);
|
|
56
|
+
|
|
57
|
+
const root = app.paths.server.join(filename);
|
|
58
|
+
const imported = (await import(root)).default;
|
|
59
|
+
const {html} = imported.render({
|
|
60
|
+
components: components.map(({component}) => component),
|
|
61
|
+
data,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const names = (await Promise.all(components.map(component =>
|
|
65
|
+
normalize(component.name)))).join(", ");
|
|
66
|
+
const code = `
|
|
67
|
+
import {${rootname}, ${names}} from "app";
|
|
68
|
+
new ${rootname}({
|
|
23
69
|
target: document.body,
|
|
24
70
|
hydrate: true,
|
|
25
|
-
props
|
|
71
|
+
props: {
|
|
72
|
+
components: [${names}],
|
|
73
|
+
data: JSON.parse(${JSON.stringify(JSON.stringify(data))}),
|
|
74
|
+
},
|
|
26
75
|
});
|
|
27
76
|
`;
|
|
28
|
-
|
|
29
|
-
const integrity = await app.publish({code
|
|
77
|
+
|
|
78
|
+
const integrity = await app.publish({code, type, inline: true});
|
|
30
79
|
// hacky, CSP should be a map that gets flattened in the end
|
|
31
80
|
headers["Content-Security-Policy"] = headers["Content-Security-Policy"]
|
|
32
81
|
.replace("script-src 'self' ", `script-src 'self' '${integrity}' `);
|
|
33
82
|
|
|
34
83
|
// -> spread into new Response()
|
|
35
|
-
return [await app.render({body}), {
|
|
84
|
+
return [await app.render({body: html, page}), {
|
|
36
85
|
status,
|
|
37
86
|
headers: {...headers, "Content-Type": "text/html"},
|
|
38
87
|
}];
|
|
39
88
|
};
|
|
40
89
|
|
|
90
|
+
const create_root = (length, data) => {
|
|
91
|
+
const n = length - 1;
|
|
92
|
+
const body = Array.from({length: n}, (_, i) => i - 1)
|
|
93
|
+
.reduceRight((child, _, i) => `
|
|
94
|
+
{#if components[${i + 1}]}
|
|
95
|
+
<svelte:component this={components[${i}]} ${data}={data[${i}]}>
|
|
96
|
+
${child}
|
|
97
|
+
</svelte:component>
|
|
98
|
+
{:else}
|
|
99
|
+
<svelte:component this={components[${i}]} ${data}={data[${i}]} />
|
|
100
|
+
{/if}
|
|
101
|
+
`, `<svelte:component this={components[${n}]} ${data}={data[${n}]} />`);
|
|
102
|
+
|
|
103
|
+
return `
|
|
104
|
+
<script>
|
|
105
|
+
export let components;
|
|
106
|
+
export let data;
|
|
107
|
+
</script>
|
|
108
|
+
${body}
|
|
109
|
+
`;
|
|
110
|
+
};
|
|
111
|
+
|
|
41
112
|
const {svelte, js} = endings;
|
|
42
|
-
export default ({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await Promise.all(components.map(async component => {
|
|
73
|
-
const file = await component.file.read();
|
|
74
|
-
const client = compiler.compile(file, options);
|
|
75
|
-
const css = client.css.code;
|
|
76
|
-
const compiled = client.js.code.replaceAll(svelte, js);
|
|
77
|
-
|
|
78
|
-
await app.publish({src: `${component.name}.js`, code: compiled, type});
|
|
79
|
-
if (css !== null) {
|
|
80
|
-
const name = `${component.name}.css`;
|
|
81
|
-
await app.publish({src: name, code: css, type: "style"});
|
|
82
|
-
app.bootstrap({type: "style", code: `import "./${name}";`});
|
|
113
|
+
export default ({
|
|
114
|
+
directory,
|
|
115
|
+
dynamicProps = "data",
|
|
116
|
+
} = {}) => {
|
|
117
|
+
const env = {};
|
|
118
|
+
return {
|
|
119
|
+
name: "@primate/svelte",
|
|
120
|
+
async init(app) {
|
|
121
|
+
const source = directory ?? app.paths.components;
|
|
122
|
+
env.copyJS = source !== app.paths.components;
|
|
123
|
+
env.copy = app.copy;
|
|
124
|
+
env.source = source;
|
|
125
|
+
env.components = await source.collect(/^.*.svelte$/u);
|
|
126
|
+
env.build = app.config.build.app;
|
|
127
|
+
env.paths = {
|
|
128
|
+
server: app.paths.server.join(env.build),
|
|
129
|
+
client: app.paths.client.join(env.build),
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
register(app, next) {
|
|
133
|
+
app.register("svelte", handler({path: env.paths.server}));
|
|
134
|
+
return next(app);
|
|
135
|
+
},
|
|
136
|
+
async compile(app, next) {
|
|
137
|
+
const target = env.paths.server;
|
|
138
|
+
await target.file.create();
|
|
139
|
+
// copy any JS files from an alternative components directory
|
|
140
|
+
if (env.copyJS) {
|
|
141
|
+
await app.copy(env.source, env.paths.server);
|
|
142
|
+
await app.copy(env.source, env.paths.client);
|
|
83
143
|
}
|
|
84
|
-
}));
|
|
85
144
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
145
|
+
await Promise.all(env.components.map(async component => {
|
|
146
|
+
const file = await component.file.read();
|
|
147
|
+
const server = compiler.compile(file, options.server);
|
|
148
|
+
const to = target.join(`${component.path}.js`.replace(env.source, ""));
|
|
149
|
+
await to.directory.file.create();
|
|
150
|
+
await to.file.write(server.js.code.replaceAll(svelte, js));
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
const root = create_root(app.layoutDepth, dynamicProps);
|
|
154
|
+
const server = compiler.compile(root, options.server).js.code;
|
|
155
|
+
const to = app.paths.server.join(filename);
|
|
156
|
+
await to.file.write(server);
|
|
157
|
+
|
|
158
|
+
return next(app);
|
|
159
|
+
},
|
|
160
|
+
async publish(app, next) {
|
|
161
|
+
await app.import("svelte");
|
|
162
|
+
const {source, build} = env;
|
|
163
|
+
|
|
164
|
+
const target = env.paths.client;
|
|
165
|
+
await target.file.create();
|
|
166
|
+
|
|
167
|
+
await Promise.all(env.components.map(async component => {
|
|
168
|
+
const name = component.path.replace(`${source}/`, "");
|
|
169
|
+
const file = await component.file.read();
|
|
170
|
+
const client = compiler.compile(file, options.client);
|
|
171
|
+
{
|
|
172
|
+
{
|
|
173
|
+
const code = client.js.code.replaceAll(svelte, js);
|
|
174
|
+
const src = `${component.path}.js`.replace(`${source}`, build);
|
|
175
|
+
await app.publish({src, code, type});
|
|
176
|
+
}
|
|
177
|
+
const imported = await normalize(name);
|
|
178
|
+
app.bootstrap({
|
|
179
|
+
type: "script",
|
|
180
|
+
code: `export {default as ${imported}} from "./${name}.js";\n`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (client.css.code !== null) {
|
|
184
|
+
{
|
|
185
|
+
const {code} = client.css;
|
|
186
|
+
const src = `${component.path}.css`.replace(`${source}`, build);
|
|
187
|
+
await app.publish({src, code, type: "style"});
|
|
188
|
+
}
|
|
189
|
+
{
|
|
190
|
+
const src = `${component.path}.css`.replace(`${source}`, "");
|
|
191
|
+
app.bootstrap({type: "style", code: `import ".${src}";`});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
{
|
|
197
|
+
const root = create_root(app.layoutDepth, dynamicProps);
|
|
198
|
+
const client = compiler.compile(root, options.client);
|
|
199
|
+
const code = client.js.code.replaceAll(svelte, js);
|
|
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`;
|
|
90
205
|
app.bootstrap({type: "script", code});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return next(app);
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
};
|