@primate/html 0.10.2 → 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/lib/private/Default.d.ts +7 -0
- package/lib/private/Default.js +11 -0
- package/lib/private/Runtime.d.ts +10 -0
- package/lib/private/Runtime.js +10 -0
- package/lib/private/render.d.ts +9 -0
- package/lib/private/render.js +81 -0
- package/lib/private/response.d.ts +4 -0
- package/lib/private/response.js +33 -0
- package/lib/public/default.d.ts +4 -0
- package/lib/public/default.js +3 -0
- package/lib/public/render.d.ts +2 -0
- package/lib/public/render.js +2 -0
- package/lib/public/response.d.ts +2 -0
- package/lib/public/response.js +2 -0
- package/lib/public/runtime.d.ts +4 -0
- package/lib/public/runtime.js +3 -0
- package/package.json +25 -22
- package/src/private/build/index.js +0 -18
- package/src/private/build/server.js +0 -8
- package/src/private/extension.js +0 -1
- package/src/private/handler.js +0 -29
- package/src/private/name.js +0 -1
- package/src/private/pkgname.js +0 -1
- package/src/private/serve/index.js +0 -7
- package/src/public/default.js +0 -10
- package/src/public/escape.js +0 -1
- package/src/public/handler.js +0 -1
- package/src/public/runtime.js +0 -8
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Runtime from "#Runtime";
|
|
2
|
+
import dedent from "@rcompat/string/dedent";
|
|
3
|
+
export default class Default extends Runtime {
|
|
4
|
+
compile = {
|
|
5
|
+
server: (text) => dedent `
|
|
6
|
+
import render from "@primate/html/render";
|
|
7
|
+
|
|
8
|
+
export default props => render(${JSON.stringify(text)}, props);`,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=Default.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import FrontendModule from "@primate/core/frontend/Module";
|
|
2
|
+
import type ViewResponse from "@primate/core/frontend/ViewResponse";
|
|
3
|
+
export default class Runtime extends FrontendModule {
|
|
4
|
+
name: string;
|
|
5
|
+
defaultExtensions: string[];
|
|
6
|
+
layouts: boolean;
|
|
7
|
+
client: boolean;
|
|
8
|
+
respond: ViewResponse;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=Runtime.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import response from "#response";
|
|
2
|
+
import FrontendModule from "@primate/core/frontend/Module";
|
|
3
|
+
export default class Runtime extends FrontendModule {
|
|
4
|
+
name = "html";
|
|
5
|
+
defaultExtensions = [".html"];
|
|
6
|
+
layouts = false;
|
|
7
|
+
client = false;
|
|
8
|
+
respond = response(this);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=Runtime.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type Dict from "@rcompat/type/Dict";
|
|
2
|
+
/**
|
|
3
|
+
* Render an HTML template with ${...} interpolation outside shielded tags.
|
|
4
|
+
* - Shield <script>/<style>/<template>/<textarea> so their contents are kept
|
|
5
|
+
* - Deep-escape all string values in props before interpolation
|
|
6
|
+
* - Authors can write \${...} (outside shields) to emit a literal ${...}
|
|
7
|
+
*/
|
|
8
|
+
export default function render(template: string, props: Dict): string;
|
|
9
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import escape from "@rcompat/html/escape";
|
|
2
|
+
const TAGS = ["script", "style", "template", "textarea"];
|
|
3
|
+
const token = (i) => `__SLOT_${i}__`;
|
|
4
|
+
function shield(html) {
|
|
5
|
+
const slots = [];
|
|
6
|
+
const re = new RegExp(`<(?:${TAGS.join("|")})\\b[^>]*>[\\s\\S]*?<\\/(?:${TAGS.join("|")})>`, "gi");
|
|
7
|
+
const code = html.replace(re, m => (slots.push(m), token(slots.length - 1)));
|
|
8
|
+
return {
|
|
9
|
+
code,
|
|
10
|
+
restore: (s) => s.replace(/__SLOT_(\d+)__/g, (_, n) => slots[+n]),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function interpolate(template, props) {
|
|
14
|
+
let out = "", i = 0;
|
|
15
|
+
while (i < template.length) {
|
|
16
|
+
const j = template.indexOf("${", i);
|
|
17
|
+
if (j === -1) {
|
|
18
|
+
out += template.slice(i);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
out += template.slice(i, j);
|
|
22
|
+
// brace/quote-aware scan
|
|
23
|
+
let k = j + 2, depth = 1, quote = null, esc = false;
|
|
24
|
+
while (k < template.length && depth > 0) {
|
|
25
|
+
const ch = template[k++];
|
|
26
|
+
if (quote) {
|
|
27
|
+
if (esc)
|
|
28
|
+
esc = false;
|
|
29
|
+
else if (ch === "\\")
|
|
30
|
+
esc = true;
|
|
31
|
+
else if (ch === quote)
|
|
32
|
+
quote = null;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (ch === "'" || ch === "\"" || ch === "`")
|
|
36
|
+
quote = ch;
|
|
37
|
+
else if (ch === "{")
|
|
38
|
+
depth++;
|
|
39
|
+
else if (ch === "}")
|
|
40
|
+
depth--;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (depth)
|
|
44
|
+
throw new Error("Unbalanced ${...} expression in template");
|
|
45
|
+
const expr = template.slice(j + 2, k - 1);
|
|
46
|
+
// evaluate with props in scope (non-strict by default)
|
|
47
|
+
const value = Function("p", `with (p) { return (${expr}); }`)(props);
|
|
48
|
+
out += String(value);
|
|
49
|
+
i = k;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Render an HTML template with ${...} interpolation outside shielded tags.
|
|
55
|
+
* - Shield <script>/<style>/<template>/<textarea> so their contents are kept
|
|
56
|
+
* - Deep-escape all string values in props before interpolation
|
|
57
|
+
* - Authors can write \${...} (outside shields) to emit a literal ${...}
|
|
58
|
+
*/
|
|
59
|
+
export default function render(template, props) {
|
|
60
|
+
const XXX = `__LBRACE_${Math.random().toString(36).slice(2)}__`;
|
|
61
|
+
// shield first so we don't touch \${ inside scripts/styles/etc.
|
|
62
|
+
const { code, restore } = shield(template);
|
|
63
|
+
// replace \${ with a placeholder in non-shielded content
|
|
64
|
+
const outside = code.replace(/\\\$\{/g, XXX);
|
|
65
|
+
// deep-escape all strings in props (JSON-serializable only)
|
|
66
|
+
let safe;
|
|
67
|
+
try {
|
|
68
|
+
safe = JSON.parse(escape(JSON.stringify(props)));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error("render(): props must be JSON-serializable for escaping");
|
|
72
|
+
}
|
|
73
|
+
// null-prototype view to avoid prototype lookups in `with`
|
|
74
|
+
const p = Object.assign(Object.create(null), safe);
|
|
75
|
+
// interpolate & restore
|
|
76
|
+
const interpolated = interpolate(outside, p);
|
|
77
|
+
const restored = restore(interpolated);
|
|
78
|
+
// put back literal \${ sequences
|
|
79
|
+
return restored.split(XXX).join("${");
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import inline from "@primate/core/inline";
|
|
2
|
+
const SCRIPT = /(?<=<script)>(?<code>.*?)(?=<\/script>)/gus;
|
|
3
|
+
const STYLE = /(?<=<style)>(?<code>.*?)(?=<\/style>)/gus;
|
|
4
|
+
const REMOVE = /<(?<tag>script|style)>.*?<\/\k<tag>>/gus;
|
|
5
|
+
export default function response(m) {
|
|
6
|
+
return (name, props = {}, options = {}) => async (app) => {
|
|
7
|
+
const component = app.component(name);
|
|
8
|
+
const rendered = await m.render(component, props);
|
|
9
|
+
const { csp = {}, headers, ...rest } = options;
|
|
10
|
+
const { script_src: xScriptSrc = [], style_src: xStyleSrc = [] } = csp;
|
|
11
|
+
const scripts = await Promise.all([...rendered.body.matchAll(SCRIPT)]
|
|
12
|
+
.flatMap(({ groups }) => groups?.code !== undefined
|
|
13
|
+
? inline(groups.code, "module")
|
|
14
|
+
: []));
|
|
15
|
+
const styles = await Promise.all([...rendered.body.matchAll(STYLE)]
|
|
16
|
+
.flatMap(({ groups }) => groups?.code !== undefined
|
|
17
|
+
? inline(groups.code, "style")
|
|
18
|
+
: []));
|
|
19
|
+
const styleSrc = styles.map(asset => asset.integrity).concat(xStyleSrc);
|
|
20
|
+
const scriptSrc = scripts.map(asset => asset.integrity).concat(xScriptSrc);
|
|
21
|
+
const head = [...scripts, ...styles].map(asset => asset.head).join("\n");
|
|
22
|
+
return app.view({
|
|
23
|
+
body: rendered.body.replaceAll(REMOVE, () => ""),
|
|
24
|
+
head: head,
|
|
25
|
+
headers: {
|
|
26
|
+
...app.headers({ "script-src": scriptSrc, "style-src": styleSrc }),
|
|
27
|
+
...headers,
|
|
28
|
+
},
|
|
29
|
+
...rest,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=response.js.map
|
package/package.json
CHANGED
|
@@ -1,46 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primate/html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Primate HTML frontend",
|
|
5
|
-
"homepage": "https://
|
|
6
|
-
"bugs": "https://github.com/
|
|
5
|
+
"homepage": "https://primate.run/docs/frontend/html",
|
|
6
|
+
"bugs": "https://github.com/primate-run/primate/issues",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"files": [
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"/lib/**/*.js",
|
|
10
|
+
"/lib/**/*.d.ts",
|
|
11
|
+
"!/**/*.spec.*"
|
|
11
12
|
],
|
|
12
13
|
"repository": {
|
|
13
14
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/
|
|
15
|
+
"url": "https://github.com/primate-run/primate",
|
|
15
16
|
"directory": "packages/html"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
|
-
"@rcompat/
|
|
19
|
-
"@
|
|
19
|
+
"@rcompat/assert": "^0.3.1",
|
|
20
|
+
"@rcompat/html": "^0.6.0",
|
|
21
|
+
"@rcompat/string": "^0.10.0",
|
|
22
|
+
"@primate/core": "^0.2.0"
|
|
20
23
|
},
|
|
21
24
|
"peerDependencies": {
|
|
22
|
-
"primate": "^0.
|
|
25
|
+
"primate": "^0.33.0"
|
|
23
26
|
},
|
|
24
27
|
"type": "module",
|
|
25
28
|
"imports": {
|
|
26
29
|
"#*": {
|
|
27
|
-
"
|
|
28
|
-
"default": "./
|
|
29
|
-
},
|
|
30
|
-
"#build": {
|
|
31
|
-
"@primate/lt": "./src/private/build/index.js",
|
|
32
|
-
"default": "./src/private/build/index.js"
|
|
33
|
-
},
|
|
34
|
-
"#serve": {
|
|
35
|
-
"@primate/lt": "./src/private/serve/index.js",
|
|
36
|
-
"default": "./src/private/serve/index.js"
|
|
30
|
+
"apekit": "./src/private/*.ts",
|
|
31
|
+
"default": "./lib/private/*.js"
|
|
37
32
|
}
|
|
38
33
|
},
|
|
39
34
|
"exports": {
|
|
40
35
|
".": {
|
|
41
|
-
"runtime": "./
|
|
42
|
-
"default": "./
|
|
36
|
+
"runtime": "./lib/public/runtime.js",
|
|
37
|
+
"default": "./lib/public/default.js"
|
|
43
38
|
},
|
|
44
|
-
"./*":
|
|
39
|
+
"./*": {
|
|
40
|
+
"apekit": "./src/public/*.ts",
|
|
41
|
+
"default": "./lib/public/*.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "npm run clean && tsc",
|
|
46
|
+
"clean": "rm -rf ./lib",
|
|
47
|
+
"lint": "eslint ."
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import name from "#name";
|
|
2
|
-
import compile from "@primate/frontend/core/compile";
|
|
3
|
-
import server from "./server.js";
|
|
4
|
-
|
|
5
|
-
export default extension => async (app, next) => {
|
|
6
|
-
app.register(extension, {
|
|
7
|
-
...await compile({
|
|
8
|
-
app,
|
|
9
|
-
extension,
|
|
10
|
-
name,
|
|
11
|
-
compile: { server },
|
|
12
|
-
}),
|
|
13
|
-
// no support for hydration
|
|
14
|
-
client: _ => _,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
return next(app);
|
|
18
|
-
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export default text => `import HTML from "@primate/html/escape";
|
|
2
|
-
export default (props = {}, options) => {
|
|
3
|
-
const encoded = JSON.parse(escape(JSON.stringify(props)));
|
|
4
|
-
const keys = Object.keys(encoded);
|
|
5
|
-
const values = Object.values(encoded);
|
|
6
|
-
const text = ${JSON.stringify(text)};
|
|
7
|
-
return new Function(...keys, \`return \\\`\${text}\\\`;\`)(...values);
|
|
8
|
-
}`;
|
package/src/private/extension.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default ".html";
|
package/src/private/handler.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import render from "@primate/frontend/core/render";
|
|
2
|
-
|
|
3
|
-
const script_re = /(?<=<script)>(?<code>.*?)(?=<\/script>)/gus;
|
|
4
|
-
const style_re = /(?<=<style)>(?<code>.*?)(?=<\/style>)/gus;
|
|
5
|
-
const remove = /<(?<tag>script|style)>.*?<\/\k<tag>>/gus;
|
|
6
|
-
|
|
7
|
-
export default (name, props = {}, options = {}) => async app => {
|
|
8
|
-
const component = await app.get_component(name);
|
|
9
|
-
const rendered = render(component, props);
|
|
10
|
-
const { head: xhead = [], csp = {}, headers, ...rest } = options;
|
|
11
|
-
const { script_src: xscript_src = [], style_src: xstyle_src = [] } = csp;
|
|
12
|
-
const scripts = await Promise.all([...rendered.matchAll(script_re)]
|
|
13
|
-
.map(({ groups: { code } }) => app.inline(code, "module")));
|
|
14
|
-
const styles = await Promise.all([...rendered.matchAll(style_re)]
|
|
15
|
-
.map(({ groups: { code } }) => app.inline(code, "style")));
|
|
16
|
-
const style_src = styles.map(asset => asset.integrity).concat(xstyle_src);
|
|
17
|
-
const script_src = scripts.map(asset => asset.integrity).concat(xscript_src);
|
|
18
|
-
const head = [...scripts, ...styles].map(asset => asset.head);
|
|
19
|
-
|
|
20
|
-
return app.view({
|
|
21
|
-
body: rendered.replaceAll(remove, _ => ""),
|
|
22
|
-
head: [...head, ...xhead].join("\n"),
|
|
23
|
-
headers: {
|
|
24
|
-
...app.headers({ "style-src": style_src, "script-src": script_src }),
|
|
25
|
-
...headers,
|
|
26
|
-
},
|
|
27
|
-
...rest,
|
|
28
|
-
});
|
|
29
|
-
};
|
package/src/private/name.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default "html";
|
package/src/private/pkgname.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default "@primate/html";
|
package/src/public/default.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import build from "#build";
|
|
2
|
-
import default_extension from "#extension";
|
|
3
|
-
import pkgname from "#pkgname";
|
|
4
|
-
import serve from "#serve";
|
|
5
|
-
|
|
6
|
-
export default ({ extension = default_extension } = {}) => ({
|
|
7
|
-
name: pkgname,
|
|
8
|
-
build: build(extension),
|
|
9
|
-
serve: serve(extension),
|
|
10
|
-
});
|
package/src/public/escape.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from "@rcompat/string/HTML";
|
package/src/public/handler.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from "#handler";
|