primate 0.23.2 → 0.24.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/package.json +2 -2
- package/src/app.js +42 -32
- package/src/handlers/html.js +14 -11
- package/src/hooks/publish.js +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Expressive, minimal and extensible web framework",
|
|
5
5
|
"homepage": "https://primatejs.com",
|
|
6
6
|
"bugs": "https://github.com/primatejs/primate/issues",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"directory": "packages/primate"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"runtime-compat": "^0.
|
|
21
|
+
"runtime-compat": "^0.29.0"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=18"
|
package/src/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import {tryreturn} from "runtime-compat/async";
|
|
|
3
3
|
import {File, Path} from "runtime-compat/fs";
|
|
4
4
|
import {bold, blue} from "runtime-compat/colors";
|
|
5
5
|
import {is} from "runtime-compat/invariant";
|
|
6
|
-
import {transform, valmap
|
|
6
|
+
import {transform, valmap} from "runtime-compat/object";
|
|
7
7
|
import {globify} from "runtime-compat/string";
|
|
8
8
|
import * as runtime from "runtime-compat/meta";
|
|
9
9
|
|
|
@@ -29,6 +29,22 @@ const attribute = attributes => Object.keys(attributes).length > 0
|
|
|
29
29
|
: "";
|
|
30
30
|
const tag = ({name, attributes = {}, code = "", close = true}) =>
|
|
31
31
|
`<${name}${attribute(attributes)}${close ? `>${code}</${name}>` : "/>"}`;
|
|
32
|
+
const tags = {
|
|
33
|
+
// inline: <script type integrity>...</script>
|
|
34
|
+
// outline: <script type integrity src></script>
|
|
35
|
+
script({inline, code, type, integrity, src}) {
|
|
36
|
+
return inline
|
|
37
|
+
? tag({name: "script", attributes: {type, integrity}, code})
|
|
38
|
+
: tag({name: "script", attributes: {type, integrity, src}});
|
|
39
|
+
},
|
|
40
|
+
// inline: <style>...</style>
|
|
41
|
+
// outline: <link rel="stylesheet" href/>
|
|
42
|
+
style({inline, code, href, rel = "stylesheet"}) {
|
|
43
|
+
return inline
|
|
44
|
+
? tag({name: "style", code})
|
|
45
|
+
: tag({name: "link", attributes: {rel, href}, close: false});
|
|
46
|
+
},
|
|
47
|
+
};
|
|
32
48
|
|
|
33
49
|
const {name, version} = await new Path(import.meta.url).up(2)
|
|
34
50
|
.join(runtime.manifest).json();
|
|
@@ -96,24 +112,21 @@ export default async (log, root, config) => {
|
|
|
96
112
|
}
|
|
97
113
|
}));
|
|
98
114
|
},
|
|
99
|
-
headers() {
|
|
115
|
+
headers({script = "", style = ""} = {}) {
|
|
100
116
|
const csp = Object.keys(http.csp).reduce((policy, key) =>
|
|
101
117
|
`${policy}${key} ${http.csp[key]};`, "")
|
|
102
|
-
.replace("script-src 'self'", `script-src 'self' ${
|
|
118
|
+
.replace("script-src 'self'", `script-src 'self' ${script} ${
|
|
103
119
|
this.assets
|
|
104
120
|
.filter(({type}) => type !== "style")
|
|
105
121
|
.map(asset => `'${asset.integrity}'`).join(" ")
|
|
106
|
-
}
|
|
107
|
-
.replace("style-src 'self'", `style-src 'self' ${
|
|
122
|
+
}`)
|
|
123
|
+
.replace("style-src 'self'", `style-src 'self' ${style} ${
|
|
108
124
|
this.assets
|
|
109
125
|
.filter(({type}) => type === "style")
|
|
110
126
|
.map(asset => `'${asset.integrity}'`).join(" ")
|
|
111
|
-
}
|
|
127
|
+
}`);
|
|
112
128
|
|
|
113
|
-
return {
|
|
114
|
-
"Content-Security-Policy": csp,
|
|
115
|
-
"Referrer-Policy": "same-origin",
|
|
116
|
-
};
|
|
129
|
+
return {"Content-Security-Policy": csp, "Referrer-Policy": "same-origin"};
|
|
117
130
|
},
|
|
118
131
|
runpath(...directories) {
|
|
119
132
|
return this.path.build.join(...directories);
|
|
@@ -122,29 +135,22 @@ export default async (log, root, config) => {
|
|
|
122
135
|
const {location: {pages}} = this.config;
|
|
123
136
|
|
|
124
137
|
const html = await index(this.runpath(pages), page, config.pages.index);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const script = ({inline, code, type, integrity, src}) => inline
|
|
128
|
-
? tag({name: "script", attributes: {type, integrity}, code})
|
|
129
|
-
: tag({name: "script", attributes: {type, integrity, src}});
|
|
130
|
-
// inline: <style>...</style>
|
|
131
|
-
// outline: <link rel="stylesheet" href/>
|
|
132
|
-
const style = ({inline, code, href, rel = "stylesheet"}) => inline
|
|
133
|
-
? tag({name: "style", code})
|
|
134
|
-
: tag({name: "link", attributes: {rel, href}, close: false});
|
|
135
|
-
|
|
136
|
-
const heads = head.concat("\n", to_sorted(this.assets,
|
|
138
|
+
|
|
139
|
+
const heads = to_sorted(this.assets,
|
|
137
140
|
({type}) => -1 * (type === "importmap"))
|
|
138
141
|
.map(({src, code, type, inline, integrity}) =>
|
|
139
142
|
type === "style"
|
|
140
|
-
? style({inline, code, href: src})
|
|
141
|
-
: script({inline, code, type, integrity, src})
|
|
142
|
-
).join("\n"));
|
|
143
|
-
// remove inline assets
|
|
144
|
-
this.assets = this.assets.filter(({inline, type}) => !inline
|
|
145
|
-
|| type === "importmap");
|
|
143
|
+
? tags.style({inline, code, href: src})
|
|
144
|
+
: tags.script({inline, code, type, integrity, src})
|
|
145
|
+
).join("\n").concat("\n", head);
|
|
146
146
|
return html.replace("%body%", _ => body).replace("%head%", _ => heads);
|
|
147
147
|
},
|
|
148
|
+
async inline(code, type) {
|
|
149
|
+
const integrity = await this.hash(code);
|
|
150
|
+
const tag_name = type === "style" ? "style" : "script";
|
|
151
|
+
const head = tags[tag_name]({code, type, inline: true, integrity});
|
|
152
|
+
return {head, csp: `'${integrity}'`};
|
|
153
|
+
},
|
|
148
154
|
async publish({src, code, type = "", inline = false, copy = true}) {
|
|
149
155
|
if (!inline && copy) {
|
|
150
156
|
const base = this.runpath(this.config.location.client).join(src);
|
|
@@ -173,7 +179,7 @@ export default async (log, root, config) => {
|
|
|
173
179
|
const prefix = algorithm.replace("-", _ => "");
|
|
174
180
|
return `${prefix}-${btoa(String.fromCharCode(...new Uint8Array(bytes)))}`;
|
|
175
181
|
},
|
|
176
|
-
async import(module) {
|
|
182
|
+
async import(module, deep_import) {
|
|
177
183
|
const {http: {static: {root}}, location: {client}} = this.config;
|
|
178
184
|
|
|
179
185
|
const parts = module.split("/");
|
|
@@ -182,12 +188,16 @@ export default async (log, root, config) => {
|
|
|
182
188
|
const exports = pkg.exports === undefined
|
|
183
189
|
? {[module]: `/${module}/${pkg.main}`}
|
|
184
190
|
: transform(pkg.exports, entry => entry
|
|
185
|
-
.filter(([, export$]) =>
|
|
191
|
+
.filter(([, export$]) =>
|
|
192
|
+
export$.browser?.[deep_import] !== undefined
|
|
193
|
+
|| export$.browser?.default !== undefined
|
|
186
194
|
|| export$.import !== undefined
|
|
187
195
|
|| export$.default !== undefined)
|
|
188
196
|
.map(([key, value]) => [
|
|
189
|
-
key.replace(".",
|
|
190
|
-
|
|
197
|
+
key.replace(".", deep_import === undefined
|
|
198
|
+
? module : `${module}/${deep_import}`),
|
|
199
|
+
value.browser?.[deep_import]?.replace(".", `./${module}`)
|
|
200
|
+
?? value.browser?.default.replace(".", `./${module}`)
|
|
191
201
|
?? value.default?.replace(".", `./${module}`)
|
|
192
202
|
?? value.import?.replace(".", `./${module}`),
|
|
193
203
|
]));
|
package/src/handlers/html.js
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import {Response, Status, MediaType} from "runtime-compat/http";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const script_re = /(?<=<script)>(?<code>.*?)(?=<\/script>)/gus;
|
|
4
|
+
const style_re = /(?<=<style)>(?<code>.*?)(?=<\/style>)/gus;
|
|
5
5
|
const remove = /<(?<tag>script|style)>.*?<\/\k<tag>>/gus;
|
|
6
|
-
const inline = true;
|
|
7
6
|
|
|
8
7
|
export default (name, options = {}) => {
|
|
9
8
|
const {status = Status.OK, partial = false} = options;
|
|
10
9
|
|
|
11
10
|
return async app => {
|
|
12
11
|
const html = await app.path.components.join(name).text();
|
|
13
|
-
await Promise.all([...html.matchAll(
|
|
14
|
-
.map(({groups: {code}}) => app.
|
|
15
|
-
await Promise.all([...html.matchAll(
|
|
16
|
-
.map(({groups: {code}}) => app.
|
|
12
|
+
const scripts = await Promise.all([...html.matchAll(script_re)]
|
|
13
|
+
.map(({groups: {code}}) => app.inline(code, "module")));
|
|
14
|
+
const styles = await Promise.all([...html.matchAll(style_re)]
|
|
15
|
+
.map(({groups: {code}}) => app.inline(code, "style")));
|
|
16
|
+
const assets = [...scripts, ...styles];
|
|
17
|
+
|
|
17
18
|
const body = html.replaceAll(remove, _ => "");
|
|
18
|
-
|
|
19
|
-
const
|
|
19
|
+
const head = assets.map(asset => asset.head).join("\n");
|
|
20
|
+
const script = scripts.map(asset => asset.csp).join(" ");
|
|
21
|
+
const style = styles.map(asset => asset.csp).join(" ");
|
|
22
|
+
const headers = {script, style};
|
|
20
23
|
|
|
21
|
-
return new Response(partial ? body : await app.render({body}), {
|
|
24
|
+
return new Response(partial ? body : await app.render({body, head}), {
|
|
22
25
|
status,
|
|
23
|
-
headers: {...headers, "Content-Type": MediaType.TEXT_HTML},
|
|
26
|
+
headers: {...app.headers(headers), "Content-Type": MediaType.TEXT_HTML},
|
|
24
27
|
});
|
|
25
28
|
};
|
|
26
29
|
};
|
package/src/hooks/publish.js
CHANGED
|
@@ -25,9 +25,8 @@ const post = async app => {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const imports = {...app.importmaps, app: src.path};
|
|
28
|
-
const inline = true;
|
|
29
28
|
const type = "importmap";
|
|
30
|
-
await app.publish({inline, code: stringify({imports}), type});
|
|
29
|
+
await app.publish({inline: true, code: stringify({imports}), type});
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
if (await path.static.exists) {
|