@primate/svelte 0.14.0 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primate/svelte",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "Primate Svelte module",
5
5
  "homepage": "https://primatejs.com/modules/svelte",
6
6
  "bugs": "https://github.com/primatejs/primate/issues",
@@ -16,11 +16,11 @@
16
16
  "directory": "packages/svelte"
17
17
  },
18
18
  "dependencies": {
19
- "runtime-compat": "^0.21.0",
20
- "svelte": "^4.0.0"
19
+ "runtime-compat": "^0.24.1",
20
+ "svelte": "^4.1.1"
21
21
  },
22
22
  "peerDependencies": {
23
- "primate": "0.20"
23
+ "primate": "0.21"
24
24
  },
25
25
  "type": "module",
26
26
  "exports": "./src/exports.js"
@@ -0,0 +1,18 @@
1
+ import rootname from "./rootname.js";
2
+ import liveview from "./liveview.js";
3
+
4
+ export default ({names, data}, options) => `
5
+ import * as components from "app";
6
+ const getComponents = names =>
7
+ Object.entries(components).filter(([name, component]) =>
8
+ names.includes(name))
9
+ .map(([, component]) => component);
10
+ let root = new components.${rootname}({
11
+ target: document.body,
12
+ hydrate: true,
13
+ props: {
14
+ components: getComponents([${names.map(name => `"${name}"`)}]),
15
+ data: JSON.parse(${JSON.stringify(JSON.stringify(data))}),
16
+ },
17
+ });
18
+ ${options.liveview ? liveview : ""}`;
@@ -0,0 +1,25 @@
1
+ export default (length, data) => {
2
+ const n = length - 1;
3
+ const body = Array.from({length: n}, (_, i) => i - 1)
4
+ .reduceRight((child, _, i) => `
5
+ {#if components[${i + 1}]}
6
+ <svelte:component this={components[${i}]} ${data}={data[${i}]}>
7
+ ${child}
8
+ </svelte:component>
9
+ {:else}
10
+ <svelte:component this={components[${i}]} ${data}={data[${i}]} />
11
+ {/if}
12
+ `, `<svelte:component this={components[${n}]} ${data}={data[${n}]} />`);
13
+
14
+ return `
15
+ <script>
16
+ import {afterUpdate} from "svelte";
17
+ export let components;
18
+ export let data;
19
+ export let update = () => undefined;
20
+
21
+ afterUpdate(update);
22
+ </script>
23
+ ${body}
24
+ `;
25
+ };
@@ -0,0 +1,3 @@
1
+ export {default as client} from "./client.js";
2
+ export {default as create_root} from "./create-root.js";
3
+ export {default as rootname} from "./rootname.js";
@@ -0,0 +1,16 @@
1
+ import rootname from "./rootname.js";
2
+
3
+ export default `
4
+ const {liveview} = components;
5
+ window.addEventListener("DOMContentLoaded", _ => liveview((props, update) => {
6
+ root.$destroy();
7
+ root = new components.${rootname}({
8
+ target: document.body,
9
+ hydrate: true,
10
+ props: {
11
+ components: getComponents(props.names),
12
+ data: props.data,
13
+ update,
14
+ },
15
+ });
16
+ }));`;
@@ -0,0 +1 @@
1
+ export default "root";
package/src/module.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import crypto from "runtime-compat/crypto";
2
2
  import {Path} from "runtime-compat/fs";
3
+ import {Response, Status, MediaType} from "runtime-compat/http";
3
4
  import * as compiler from "svelte/compiler";
4
5
  import {tryreturn} from "runtime-compat/async";
5
6
  import errors from "./errors.js";
7
+ import {client, create_root, rootname} from "./client/exports.js";
6
8
 
7
9
  const endings = {
8
10
  svelte: ".svelte",
@@ -21,190 +23,158 @@ const hash = async (string, algorithm = "sha-256") => {
21
23
  };
22
24
  const normalize = async path => `_${await hash(path)}`;
23
25
 
24
- const rootname = "root";
25
26
  const filename = `${rootname}.js`;
26
27
 
27
- const options = {
28
- server: {generate: "ssr", hydratable: true},
29
- client: {generate: "dom", hydratable: true},
30
- };
31
-
32
28
  const type = "module";
33
29
 
34
30
  const load = async path =>
35
31
  tryreturn(async () => (await import(`${path}.js`)).default)
36
- .orelse(() => errors.MissingComponent.throw(path.name, path));
32
+ .orelse(_ => errors.MissingComponent.throw(path.name, path));
37
33
 
38
34
  const make_component = base => async (name, props) =>
39
35
  ({name, component: await load(base.join(name)), props});
40
36
 
41
37
  const encoder = new TextEncoder();
42
38
 
43
- const handler = ({path}) => (name, props = {}, {status = 200, page} = {}) =>
44
- async (app, {layouts = [], as_layout} = {}) => {
45
- const make = make_component(path);
39
+ const handler = (name, props = {}, {status = Status.OK, page} = {}) =>
40
+ async (app, {layouts = [], as_layout} = {}, request) => {
41
+ const options = {
42
+ liveview: app.liveview !== undefined,
43
+ };
44
+ const {headers} = request;
45
+ const {paths} = app.build;
46
+ const make = make_component(paths.server.join(app.config.build.app));
46
47
  if (as_layout) {
47
48
  return make(name, props);
48
49
  }
49
50
  const components = (await Promise.all(layouts.map(layout =>
50
- layout(app, {as_layout: true})
51
+ layout(app, {as_layout: true}, request)
51
52
  )))
52
53
  /* set the actual page as the last component */
53
54
  .concat(await make(name, props));
54
55
 
55
56
  const data = components.map(component => component.props);
56
-
57
- const root = app.paths.server.join(filename);
57
+ const names = await Promise.all(components.map(component =>
58
+ normalize(component.name)));
59
+
60
+ if (options.liveview && headers.get(app.liveview.header) !== undefined) {
61
+ return new Response(JSON.stringify({names, data}), {
62
+ status,
63
+ headers: {...await app.headers(),
64
+ "Content-Type": MediaType.APPLICATION_JSON},
65
+ });
66
+ }
67
+ const root = paths.server.join(filename);
58
68
  const imported = (await import(root)).default;
59
69
  const {html} = imported.render({
60
70
  components: components.map(({component}) => component),
61
71
  data,
62
72
  });
63
73
 
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}({
69
- target: document.body,
70
- hydrate: true,
71
- props: {
72
- components: [${names}],
73
- data: JSON.parse(${JSON.stringify(JSON.stringify(data))}),
74
- },
75
- });
76
- `;
74
+ const code = client({names, data}, options);
77
75
 
78
76
  await app.publish({code, type, inline: true});
79
- // needs to be called before app.render
80
- const headers = app.headers();
81
77
 
82
- // -> spread into new Response()
83
- return [await app.render({body: html, page}), {
78
+ const headers$ = await app.headers();
79
+
80
+ return new Response(await app.render({body: html, page}), {
84
81
  status,
85
- headers: {...headers, "Content-Type": "text/html"},
86
- }];
82
+ headers: {...headers$, "Content-Type": MediaType.TEXT_HTML},
83
+ });
87
84
  };
88
85
 
89
- const create_root = (length, data) => {
90
- const n = length - 1;
91
- const body = Array.from({length: n}, (_, i) => i - 1)
92
- .reduceRight((child, _, i) => `
93
- {#if components[${i + 1}]}
94
- <svelte:component this={components[${i}]} ${data}={data[${i}]}>
95
- ${child}
96
- </svelte:component>
97
- {:else}
98
- <svelte:component this={components[${i}]} ${data}={data[${i}]} />
99
- {/if}
100
- `, `<svelte:component this={components[${n}]} ${data}={data[${n}]} />`);
101
-
102
- return `
103
- <script>
104
- export let components;
105
- export let data;
106
- </script>
107
- ${body}
108
- `;
86
+ const options = {
87
+ server: {generate: "ssr", hydratable: true},
88
+ client: {generate: "dom", hydratable: true},
109
89
  };
110
90
 
111
91
  const {svelte, js} = endings;
112
- export default ({
113
- directory,
114
- dynamicProps = "data",
115
- } = {}) => {
116
- const env = {};
117
- return {
118
- name: "@primate/svelte",
119
- async init(app) {
120
- const source = directory ?? app.paths.components;
121
- env.copyJS = source !== app.paths.components;
122
- env.copy = app.copy;
123
- env.source = source;
124
- env.components = await source.collect(/^.*.svelte$/u);
125
- env.build = app.config.build.app;
126
- env.paths = {
127
- server: app.paths.server.join(env.build),
128
- client: app.paths.client.join(env.build),
129
- };
130
- },
131
- register(app, next) {
132
- app.register("svelte", handler({path: env.paths.server}));
133
- return next(app);
134
- },
135
- async compile(app, next) {
136
- const target = env.paths.server;
137
- await target.file.create();
138
- // copy any JS files from an alternative components directory
139
- if (env.copyJS) {
140
- await app.copy(env.source, env.paths.server);
141
- await app.copy(env.source, env.paths.client);
142
- }
143
-
144
- await Promise.all(env.components.map(async component => {
145
- const file = await component.file.read();
146
- const server = compiler.compile(file, options.server);
147
- const to = target.join(`${component.path}.js`.replace(env.source, ""));
148
- await to.directory.file.create();
149
- await to.file.write(server.js.code.replaceAll(svelte, js));
150
- }));
92
+ export default ({dynamicProps = "data"} = {}) => ({
93
+ name: "@primate/svelte",
94
+ register(app, next) {
95
+ app.register("svelte", handler);
96
+ return next(app);
97
+ },
98
+ async compile(app, next) {
99
+ const source = app.build.paths.components;
100
+ // copy components to build/components
101
+ await app.copy(app.paths.components, source, /^.*.(?:js|svelte)$/u);
102
+ const components = await source.collect(/^.*.svelte$/u);
103
+ const target = app.build.paths.server.join(app.config.build.app);
104
+ await target.file.create();
105
+
106
+ await Promise.all(components.map(async component => {
107
+ const file = await component.file.read();
108
+ const server = compiler.compile(file, options.server);
109
+ const to = target.join(`${component.path}.js`.replace(source, ""));
110
+ await to.directory.file.create();
111
+ await to.file.write(server.js.code.replaceAll(svelte, js));
112
+ }));
113
+
114
+ const root = create_root(app.layoutDepth, dynamicProps);
115
+ const server = compiler.compile(root, options.server).js.code;
116
+ const to = app.build.paths.server.join(filename);
117
+ await to.file.write(server);
118
+
119
+ return next(app);
120
+ },
121
+ async publish(app, next) {
122
+ const source = app.build.paths.components;
123
+ const components = await source.collect(/^.*.svelte$/u);
124
+ await app.import("svelte");
125
+
126
+ // liveview
127
+ if (app.liveview !== undefined) {
128
+ app.bootstrap({
129
+ type: "script",
130
+ code: "export {default as liveview} from \"@primate/liveview\";\n",
131
+ });
132
+ }
133
+ await Promise.all(components.map(async component => {
134
+ const name = component.path.replace(`${source}/`, "");
135
+ const file = await component.file.read();
136
+ const client = compiler.compile(file, options.client);
137
+ const build = app.config.build.app;
138
+ const {path} = component;
151
139
 
152
- const root = create_root(app.layoutDepth, dynamicProps);
153
- const server = compiler.compile(root, options.server).js.code;
154
- const to = app.paths.server.join(filename);
155
- await to.file.write(server);
156
-
157
- return next(app);
158
- },
159
- async publish(app, next) {
160
- await app.import("svelte");
161
- const {source, build} = env;
162
-
163
- const target = env.paths.client;
164
- await target.file.create();
165
-
166
- await Promise.all(env.components.map(async component => {
167
- const name = component.path.replace(`${source}/`, "");
168
- const file = await component.file.read();
169
- const client = compiler.compile(file, options.client);
140
+ {
170
141
  {
171
- {
172
- const code = client.js.code.replaceAll(svelte, js);
173
- const src = `${component.path}.js`.replace(`${source}`, build);
174
- await app.publish({src, code, type});
175
- }
176
- const imported = await normalize(name);
177
- app.bootstrap({
178
- type: "script",
179
- code: `export {default as ${imported}} from "./${name}.js";\n`,
180
- });
181
- }
182
- if (client.css.code !== null) {
183
- {
184
- const {code} = client.css;
185
- const src = `${component.path}.css`.replace(`${source}`, build);
186
- await app.publish({src, code, type: "style"});
187
- }
188
- {
189
- const src = `${component.path}.css`.replace(`${source}`, "");
190
- app.bootstrap({type: "style", code: `import ".${src}";`});
191
- }
142
+ const code = client.js.code.replaceAll(svelte, js);
143
+ const src = `${path}.js`.replace(`${source}`, _ => build);
144
+ await app.publish({src, code, type});
192
145
  }
193
- }));
194
-
195
- {
196
- const root = create_root(app.layoutDepth, dynamicProps);
197
- const client = compiler.compile(root, options.client);
198
- const code = client.js.code.replaceAll(svelte, js);
199
- const src = new Path(app.config.http.static.root, filename);
200
- await app.publish({src, code, type});
146
+ const imported = await normalize(name);
147
+ app.bootstrap({
148
+ type: "script",
149
+ code: `export {default as ${imported}} from "./${name}.js";\n`,
150
+ });
201
151
  }
202
- {
203
- const code = `export {default as ${rootname}} from "../${filename}";\n`;
204
- app.bootstrap({type: "script", code});
152
+ if (client.css.code !== null) {
153
+ {
154
+ const {code} = client.css;
155
+ const src = `${path}.css`.replace(`${source}`, _ => build);
156
+ await app.publish({src, code, type: "style"});
157
+ }
158
+ {
159
+ // sans app.config.build.app removal, for a bundler to resolve,
160
+ // irrelevant without bundling
161
+ const src = `${path}.css`.replace(`${source}`, "");
162
+ app.bootstrap({type: "style", code: `import ".${src}";`});
163
+ }
205
164
  }
165
+ }));
206
166
 
207
- return next(app);
208
- },
209
- };
210
- };
167
+ {
168
+ const root = create_root(app.layoutDepth, dynamicProps);
169
+ const client = compiler.compile(root, options.client);
170
+ const code = client.js.code.replaceAll(svelte, js);
171
+ const src = new Path(app.config.http.static.root, filename);
172
+ await app.publish({src, code, type});
173
+ }
174
+ {
175
+ const code = `export {default as ${rootname}} from "../${filename}";\n`;
176
+ app.bootstrap({type: "script", code});
177
+ }
178
+ return next(app);
179
+ },
180
+ });