@primate/html 0.10.3 → 0.12.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.
@@ -0,0 +1,7 @@
1
+ import Runtime from "#Runtime";
2
+ export default class Default extends Runtime {
3
+ compile: {
4
+ server: (text: string) => string;
5
+ };
6
+ }
7
+ //# sourceMappingURL=Default.d.ts.map
@@ -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,4 @@
1
+ import type Module from "@primate/core/frontend/Module";
2
+ import type ViewResponse from "@primate/core/frontend/ViewResponse";
3
+ export default function response(m: Module): ViewResponse;
4
+ //# sourceMappingURL=response.d.ts.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 view = app.loadView(name);
8
+ const rendered = await m.render(view, 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
@@ -0,0 +1,4 @@
1
+ import Default from "#Default";
2
+ declare const _default: (options?: typeof Default.input) => Default;
3
+ export default _default;
4
+ //# sourceMappingURL=default.d.ts.map
@@ -0,0 +1,3 @@
1
+ import Default from "#Default";
2
+ export default (options) => new Default(options);
3
+ //# sourceMappingURL=default.js.map
@@ -0,0 +1,2 @@
1
+ export { default } from "#render";
2
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { default } from "#render";
2
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1,2 @@
1
+ export { default } from "#response";
2
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { default } from "#response";
2
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1,4 @@
1
+ import Runtime from "#Runtime";
2
+ declare const _default: (options?: typeof Runtime.input) => Runtime;
3
+ export default _default;
4
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1,3 @@
1
+ import Runtime from "#Runtime";
2
+ export default (options) => new Runtime(options);
3
+ //# sourceMappingURL=runtime.js.map
package/package.json CHANGED
@@ -1,46 +1,49 @@
1
1
  {
2
2
  "name": "@primate/html",
3
- "version": "0.10.3",
3
+ "version": "0.12.0",
4
4
  "description": "Primate HTML frontend",
5
- "homepage": "https://primatejs.com/modules/html",
6
- "bugs": "https://github.com/primatejs/primate/issues",
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
- "src/**/*.js",
10
- "!src/**/*.spec.js"
9
+ "/lib/**/*.js",
10
+ "/lib/**/*.d.ts",
11
+ "!/**/*.spec.*"
11
12
  ],
12
13
  "repository": {
13
14
  "type": "git",
14
- "url": "https://github.com/primatejs/primate",
15
+ "url": "https://github.com/primate-run/primate",
15
16
  "directory": "packages/html"
16
17
  },
17
18
  "dependencies": {
18
- "@rcompat/html": "^0.1.0",
19
- "@primate/frontend": "^0.16.1"
19
+ "@rcompat/assert": "^0.3.1",
20
+ "@rcompat/html": "^0.6.0",
21
+ "@rcompat/string": "^0.10.0",
22
+ "@primate/core": "^0.3.0"
20
23
  },
21
24
  "peerDependencies": {
22
- "primate": "^0.32.2"
25
+ "primate": "^0.34.0"
23
26
  },
24
27
  "type": "module",
25
28
  "imports": {
26
29
  "#*": {
27
- "@primate/lt": "./src/private/*.js",
28
- "default": "./src/private/*.js"
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": "./src/public/runtime.js",
42
- "default": "./src/public/default.js"
36
+ "runtime": "./lib/public/runtime.js",
37
+ "default": "./lib/public/default.js"
43
38
  },
44
- "./*": "./src/public/*.js"
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 escape 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
- }`;
@@ -1 +0,0 @@
1
- export default ".html";
@@ -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
- };
@@ -1 +0,0 @@
1
- export default "html";
@@ -1 +0,0 @@
1
- export default "@primate/html";
@@ -1,7 +0,0 @@
1
- import handler from "#handler";
2
-
3
- export default extension => (app, next) => {
4
- app.register(extension, handler);
5
-
6
- return next(app);
7
- };
@@ -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
- });
@@ -1 +0,0 @@
1
- export { default } from "@rcompat/html/escape";
@@ -1 +0,0 @@
1
- export { default } from "#handler";
@@ -1,8 +0,0 @@
1
- import default_extension from "#extension";
2
- import pkgname from "#pkgname";
3
- import serve from "#serve";
4
-
5
- export default ({ extension = default_extension } = {}) => ({
6
- name: pkgname,
7
- serve: serve(extension),
8
- });