@primate/svelte 0.13.2 → 0.15.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.
Files changed (2) hide show
  1. package/package.json +4 -4
  2. package/src/module.js +130 -112
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primate/svelte",
3
- "version": "0.13.2",
3
+ "version": "0.15.0",
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"
package/src/module.js CHANGED
@@ -1,5 +1,6 @@
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";
@@ -8,6 +9,7 @@ const endings = {
8
9
  svelte: ".svelte",
9
10
  js: ".svelte.js",
10
11
  };
12
+ const rootname = "root";
11
13
 
12
14
  const hash = async (string, algorithm = "sha-256") => {
13
15
  const base = 16;
@@ -21,7 +23,6 @@ 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
28
  const options = {
@@ -33,57 +34,84 @@ const type = "module";
33
34
 
34
35
  const load = async path =>
35
36
  tryreturn(async () => (await import(`${path}.js`)).default)
36
- .orelse(() => errors.MissingComponent.throw(path.name, path));
37
+ .orelse(_ => errors.MissingComponent.throw(path.name, path));
37
38
 
38
39
  const make_component = base => async (name, props) =>
39
40
  ({name, component: await load(base.join(name)), props});
40
41
 
41
42
  const encoder = new TextEncoder();
42
43
 
43
- const handler = ({path}) => (name, props = {}, {status = 200, page} = {}) =>
44
- async (app, {layouts = [], as_layout} = {}) => {
45
- const make = make_component(path);
44
+ const handler = (name, props = {}, {status = Status.OK, page} = {}) =>
45
+ async (app, {layouts = [], as_layout} = {}, request) => {
46
+ const {paths} = app.build;
47
+ const make = make_component(paths.server.join(app.config.build.app));
46
48
  if (as_layout) {
47
49
  return make(name, props);
48
50
  }
49
51
  const components = (await Promise.all(layouts.map(layout =>
50
- layout(app, {as_layout: true})
52
+ layout(app, {as_layout: true}, request)
51
53
  )))
52
54
  /* set the actual page as the last component */
53
55
  .concat(await make(name, props));
54
56
 
55
57
  const data = components.map(component => component.props);
56
-
57
- const root = app.paths.server.join(filename);
58
+ const names = await Promise.all(components.map(component =>
59
+ normalize(component.name)));
60
+
61
+ const liveview = app.liveview !== undefined;
62
+ if (liveview && request.headers.get(app.liveview.header) !== undefined) {
63
+ return new Response(JSON.stringify({names, data}), {
64
+ status,
65
+ headers: {...await app.headers(),
66
+ "Content-Type": MediaType.APPLICATION_JSON},
67
+ });
68
+ }
69
+ const root = paths.server.join(filename);
58
70
  const imported = (await import(root)).default;
59
71
  const {html} = imported.render({
60
72
  components: components.map(({component}) => component),
61
73
  data,
62
74
  });
63
75
 
64
- const names = (await Promise.all(components.map(component =>
65
- normalize(component.name)))).join(", ");
66
76
  const code = `
67
- import {${rootname}, ${names}} from "app";
68
- new ${rootname}({
77
+ import * as components from "app";
78
+ const getComponents = names =>
79
+ Object.entries(components).filter(([name, component]) =>
80
+ names.includes(name))
81
+ .map(([, component]) => component);
82
+ let root = new components.${rootname}({
69
83
  target: document.body,
70
84
  hydrate: true,
71
85
  props: {
72
- components: [${names}],
86
+ components: getComponents([${names.map(name => `"${name}"`)}]),
73
87
  data: JSON.parse(${JSON.stringify(JSON.stringify(data))}),
74
88
  },
75
89
  });
90
+
91
+ ${liveview ? `
92
+ const {liveview} = components;
93
+ window.addEventListener("DOMContentLoaded", _ => liveview(props => {
94
+ root.$destroy();
95
+ root = new components.${rootname}({
96
+ target: document.body,
97
+ hydrate: true,
98
+ props: {
99
+ components: getComponents(props.names),
100
+ data: props.data,
101
+ },
102
+ });
103
+ }));
104
+ ` : ""}
76
105
  `;
77
106
 
78
107
  await app.publish({code, type, inline: true});
79
- // needs to be called before app.render
80
- const headers = app.headers();
81
108
 
82
- // -> spread into new Response()
83
- return [await app.render({body: html, page}), {
109
+ const headers = await app.headers();
110
+
111
+ return new Response(await app.render({body: html, page}), {
84
112
  status,
85
- headers: {...headers, "Content-Type": "text/html"},
86
- }];
113
+ headers: {...headers, "Content-Type": MediaType.TEXT_HTML},
114
+ });
87
115
  };
88
116
 
89
117
  const create_root = (length, data) => {
@@ -109,102 +137,92 @@ const create_root = (length, data) => {
109
137
  };
110
138
 
111
139
  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
- }));
140
+ export default ({dynamicProps = "data"} = {}) => ({
141
+ name: "@primate/svelte",
142
+ register(app, next) {
143
+ app.register("svelte", handler);
144
+ return next(app);
145
+ },
146
+ async compile(app, next) {
147
+ const source = app.build.paths.components;
148
+ // copy components to build/components
149
+ await app.copy(app.paths.components, source, /^.*.(?:js|svelte)$/u);
150
+ const components = await source.collect(/^.*.svelte$/u);
151
+ const target = app.build.paths.server.join(app.config.build.app);
152
+ await target.file.create();
153
+
154
+ await Promise.all(components.map(async component => {
155
+ const file = await component.file.read();
156
+ const server = compiler.compile(file, options.server);
157
+ const to = target.join(`${component.path}.js`.replace(source, ""));
158
+ await to.directory.file.create();
159
+ await to.file.write(server.js.code.replaceAll(svelte, js));
160
+ }));
161
+
162
+ const root = create_root(app.layoutDepth, dynamicProps);
163
+ const server = compiler.compile(root, options.server).js.code;
164
+ const to = app.build.paths.server.join(filename);
165
+ await to.file.write(server);
166
+
167
+ return next(app);
168
+ },
169
+ async publish(app, next) {
170
+ const source = app.build.paths.components;
171
+ const components = await source.collect(/^.*.svelte$/u);
172
+ await app.import("svelte");
173
+
174
+ // liveview
175
+ if (app.liveview !== undefined) {
176
+ app.bootstrap({
177
+ type: "script",
178
+ code: "export {default as liveview} from \"@primate/liveview\";\n",
179
+ });
180
+ }
181
+ await Promise.all(components.map(async component => {
182
+ const name = component.path.replace(`${source}/`, "");
183
+ const file = await component.file.read();
184
+ const client = compiler.compile(file, options.client);
185
+ const build = app.config.build.app;
186
+ const {path} = component;
151
187
 
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);
188
+ {
170
189
  {
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
- }
190
+ const code = client.js.code.replaceAll(svelte, js);
191
+ const src = `${path}.js`.replace(`${source}`, _ => build);
192
+ await app.publish({src, code, type});
192
193
  }
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});
194
+ const imported = await normalize(name);
195
+ app.bootstrap({
196
+ type: "script",
197
+ code: `export {default as ${imported}} from "./${name}.js";\n`,
198
+ });
201
199
  }
202
- {
203
- const code = `export {default as ${rootname}} from "../${filename}";\n`;
204
- app.bootstrap({type: "script", code});
200
+ if (client.css.code !== null) {
201
+ {
202
+ const {code} = client.css;
203
+ const src = `${path}.css`.replace(`${source}`, _ => build);
204
+ await app.publish({src, code, type: "style"});
205
+ }
206
+ {
207
+ // sans app.config.build.app removal, for a bundler to resolve,
208
+ // irrelevant without bundling
209
+ const src = `${path}.css`.replace(`${source}`, "");
210
+ app.bootstrap({type: "style", code: `import ".${src}";`});
211
+ }
205
212
  }
213
+ }));
206
214
 
207
- return next(app);
208
- },
209
- };
210
- };
215
+ {
216
+ const root = create_root(app.layoutDepth, dynamicProps);
217
+ const client = compiler.compile(root, options.client);
218
+ const code = client.js.code.replaceAll(svelte, js);
219
+ const src = new Path(app.config.http.static.root, filename);
220
+ await app.publish({src, code, type});
221
+ }
222
+ {
223
+ const code = `export {default as ${rootname}} from "../${filename}";\n`;
224
+ app.bootstrap({type: "script", code});
225
+ }
226
+ return next(app);
227
+ },
228
+ });