primate 0.13.1 → 0.13.2

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",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "description": "Expressive, minimal and extensible framework for JavaScript",
5
5
  "homepage": "https://primatejs.com",
6
6
  "bugs": "https://github.com/primatejs/primate/issues",
package/src/config.js CHANGED
@@ -1,5 +1,6 @@
1
- import {File, Path} from "runtime-compat/fs";
1
+ import crypto from "runtime-compat/crypto";
2
2
  import {is} from "runtime-compat/dyndef";
3
+ import {File, Path} from "runtime-compat/fs";
3
4
  import cache from "./cache.js";
4
5
  import extend from "./extend.js";
5
6
  import defaults from "./primate.config.js";
@@ -45,11 +46,19 @@ const index = async env => {
45
46
  }
46
47
  };
47
48
 
49
+ const hash = async (string, algorithm = "sha-384") => {
50
+ const encoder = new TextEncoder();
51
+ const bytes = await crypto.subtle.digest(algorithm, encoder.encode(string));
52
+ const algo = algorithm.replace("-", () => "");
53
+ return `${algo}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
54
+ };
55
+
48
56
  export default async (filename = "primate.config.js") => {
49
57
  is(filename).string();
50
58
  const root = await getRoot();
51
59
  const config = await getConfig(root, filename);
52
60
 
61
+ const resources = [];
53
62
  const env = {
54
63
  ...config,
55
64
  paths: qualify(root, config.paths),
@@ -61,7 +70,20 @@ export default async (filename = "primate.config.js") => {
61
70
  handlers: {...handlers},
62
71
  render: async ({body = "", head = ""} = {}) => {
63
72
  const html = await index(env);
64
- return html.replace("%body%", () => body).replace("%head%", () => head);
73
+ const heads = resources.map(({src, code, type, inline, integrity}) => {
74
+ const tag = "script";
75
+ const pre = `<${tag} type="${type}" integrity="${integrity}"`;
76
+ const post = `</${tag}>`;
77
+ return inline ? `${pre}>${code}${post}` : `${pre} src="${src}">${post}`;
78
+ }).join("\n");
79
+ return html
80
+ .replace("%body%", () => body)
81
+ .replace("%head%", () => `${head}${heads}`);
82
+ },
83
+ publish: async ({src, code, type = "", inline = false}) => {
84
+ const integrity = await hash(code);
85
+ resources.push({src, code, type, inline, integrity});
86
+ return integrity;
65
87
  },
66
88
  };
67
89
  env.log.info(`${package_json.name} \x1b[34m${package_json.version}\x1b[0m`);
@@ -71,6 +93,6 @@ export default async (filename = "primate.config.js") => {
71
93
  .filter(module => module.load !== undefined)
72
94
  .map(module => module.load()(env)));
73
95
 
74
- return cache("config", filename, () => ({...env,
96
+ return cache("config", filename, () => ({...env, resources,
75
97
  modules: modules.concat(loads)}));
76
98
  };
package/src/publish.js ADDED
@@ -0,0 +1,5 @@
1
+ const filter = (key, array) => array?.flatMap(m => m[key] ?? []) ?? [];
2
+
3
+ export default async env =>
4
+ [...filter("publish", env.modules), _ => _].reduceRight((acc, handler) =>
5
+ input => handler(input, acc))(env);
package/src/run.js CHANGED
@@ -1,14 +1,21 @@
1
1
  import config from "./config.js";
2
2
  import register from "./register.js";
3
3
  import compile from "./compile.js";
4
+ import publish from "./publish.js";
4
5
  import bundle from "./bundle.js";
5
6
  import route from "./route.js";
6
7
  import serve from "./serve.js";
7
8
 
8
9
  export default async () => {
9
10
  const env = await config();
11
+ // register handlers
10
12
  await register(env);
13
+ // compile server-side code
11
14
  await compile(env);
15
+ // publish client-side code
16
+ await publish(env);
17
+ // bundle client-side code
12
18
  await bundle(env);
19
+ // serve
13
20
  serve({router: await route(env.paths.routes, env.handlers), ...env});
14
21
  };
package/src/serve.js CHANGED
@@ -22,8 +22,19 @@ export default env => {
22
22
  const _respond = async request => {
23
23
  const csp = Object.keys(env.http.csp).reduce((policy_string, key) =>
24
24
  `${policy_string}${key} ${env.http.csp[key]};`, "");
25
+ const scripts = env.resources
26
+ .map(resource => `'${resource.integrity}'`).join(" ");
27
+ const _csp = scripts === "" ? csp : `${csp}script-src 'self' ${scripts};`;
28
+ // remove inline resources
29
+ for (let i = env.resources.length - 1; i >= 0; i--) {
30
+ const resource = env.resources[i];
31
+ if (resource.inline) {
32
+ env.resources.splice(i, 1);
33
+ }
34
+ }
35
+
25
36
  const headers = {
26
- "Content-Security-Policy": csp,
37
+ "Content-Security-Policy": _csp,
27
38
  "Referrer-Policy": "same-origin",
28
39
  };
29
40
 
@@ -53,9 +64,24 @@ export default env => {
53
64
  },
54
65
  });
55
66
 
67
+ const publishedResource = request => {
68
+ const published = env.resources.find(resource =>
69
+ `/${resource.src}` === request.pathname);
70
+ if (published !== undefined) {
71
+ return new Response(published.code, {
72
+ status: statuses.OK,
73
+ headers: {
74
+ "Content-Type": mime(published.src),
75
+ },
76
+ });
77
+ }
78
+
79
+ return route(request);
80
+ };
81
+
56
82
  const _serve = async request => {
57
83
  const path = new Path(env.paths.public, request.pathname);
58
- return await path.isFile ? resource(path.file) : route(request);
84
+ return await path.isFile ? resource(path.file) : publishedResource(request);
59
85
  };
60
86
 
61
87
  const handle = async request => {