@stacksjs/stx 0.2.158 → 0.2.160

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.
@@ -18,6 +18,12 @@
18
18
  * runtime is byte-identical everywhere and becomes a single asset, while
19
19
  * per-page CSS dedupes only where pages genuinely share a stylesheet.
20
20
  *
21
+ * The rewrite itself is `externalizeHtml`, which touches no filesystem. SSR has
22
+ * the same problem in a worse form — a static build inlines the runtime once
23
+ * per page, a server inlines it once per *request* — and it cannot use a
24
+ * post-pass over a directory because there are no files. Sharing the pure
25
+ * function means the two paths cannot drift into rewriting HTML differently.
26
+ *
21
27
  * @module build-externalize
22
28
  */
23
29
  /** Where externalized assets are written, relative to the output directory. */
@@ -30,6 +36,33 @@ export interface ExternalizeResult {
30
36
  /** Bytes removed from HTML (before any compression). */
31
37
  bytesInlined: number;
32
38
  }
39
+ /** A blob lifted out of the HTML, ready to be written or served. */
40
+ export interface ExternalizedAsset {
41
+ /** Content-addressed filename, e.g. `runtime.1a2b3c4d.js`. */
42
+ filename: string;
43
+ contents: string;
44
+ /** MIME type, so a server can respond without re-deriving it from the name. */
45
+ contentType: string;
46
+ }
47
+ export interface ExternalizeHtmlResult {
48
+ html: string;
49
+ /** Distinct assets this page referenced. Deduped within the page. */
50
+ assets: ExternalizedAsset[];
51
+ /** Bytes removed from the HTML, before any compression. */
52
+ bytesInlined: number;
53
+ }
54
+ /**
55
+ * Lift the shared blobs out of one HTML document.
56
+ *
57
+ * Pure: returns the rewritten HTML and the assets it extracted, and writes
58
+ * nothing. Callers decide whether those assets become files on disk or entries
59
+ * in a server's cache.
60
+ *
61
+ * Safe to run twice — a tag that is already a `src`/`href` reference has no
62
+ * inline body to match, so a second pass extracts nothing and returns the input
63
+ * unchanged.
64
+ */
65
+ export declare function externalizeHtml(html: string, basePath?: string): ExternalizeHtmlResult;
33
66
  /**
34
67
  * Rewrite every HTML file under `outDir`, moving the signals runtime and the
35
68
  * generated stylesheet into `_stx/` and linking to them.
@@ -18,46 +18,63 @@ function listHtmlFiles(dir) {
18
18
  walk(dir);
19
19
  return found;
20
20
  }
21
- export function externalizeSharedAssets(outDir) {
22
- const result = { pages: 0, assets: 0, bytesInlined: 0 }, assetDir = path.join(outDir, EXTERNALIZED_ASSET_DIR), written = new Set, writeAsset = (filename, contents) => {
23
- if (written.has(filename))
24
- return;
25
- written.add(filename);
26
- fs.mkdirSync(assetDir, { recursive: !0 });
27
- fs.writeFileSync(path.join(assetDir, filename), contents);
28
- result.assets++;
29
- };
30
- for (const file of listHtmlFiles(outDir)) {
31
- const original = fs.readFileSync(file, "utf8");
32
- let html = original;
33
- html = html.replace(/<script\b([^>]*\bdata-stx-runtime\b[^>]*)>([\s\S]*?)<\/script>/gi, (whole, attrs, body) => {
34
- if (/\bsrc\s*=/.test(attrs) || !body.trim())
35
- return whole;
36
- const filename = `runtime.${contentHash(body)}.js`;
37
- writeAsset(filename, body);
38
- result.bytesInlined += body.length;
39
- return `<script data-stx-runtime src="/${EXTERNALIZED_ASSET_DIR}/${filename}"></script>`;
40
- });
41
- html = html.replace(/<script\b([^>]*\bdata-stx-router\b[^>]*)>([\s\S]*?)<\/script>/gi, (whole, attrs, body) => {
42
- if (/\bsrc\s*=/.test(attrs) || !body.trim())
43
- return whole;
44
- const filename = `router.${contentHash(body)}.js`;
45
- writeAsset(filename, body);
46
- result.bytesInlined += body.length;
47
- return `<script data-stx-router src="/${EXTERNALIZED_ASSET_DIR}/${filename}"></script>`;
48
- });
49
- html = html.replace(/<style\b([^>]*\bdata-crosswind=(?:"generated"|'generated')[^>]*)>([\s\S]*?)<\/style>/gi, (whole, _attrs, css) => {
50
- if (!css.trim())
21
+ const BLOB_KINDS = [
22
+ {
23
+ pattern: /<script\b([^>]*\bdata-stx-runtime\b[^>]*)>([\s\S]*?)<\/script>/gi,
24
+ prefix: "runtime",
25
+ extension: "js",
26
+ contentType: "text/javascript; charset=utf-8",
27
+ replace: (href) => `<script data-stx-runtime src="${href}"></script>`
28
+ },
29
+ {
30
+ pattern: /<script\b([^>]*\bdata-stx-router\b[^>]*)>([\s\S]*?)<\/script>/gi,
31
+ prefix: "router",
32
+ extension: "js",
33
+ contentType: "text/javascript; charset=utf-8",
34
+ replace: (href) => `<script data-stx-router src="${href}"></script>`
35
+ },
36
+ {
37
+ pattern: /<style\b([^>]*\bdata-crosswind=(?:"generated"|'generated')[^>]*)>([\s\S]*?)<\/style>/gi,
38
+ prefix: "crosswind",
39
+ extension: "css",
40
+ contentType: "text/css; charset=utf-8",
41
+ replace: (href) => `<link data-crosswind="generated" rel="stylesheet" href="${href}">`
42
+ }
43
+ ];
44
+ export function externalizeHtml(html, basePath = `/${EXTERNALIZED_ASSET_DIR}`) {
45
+ const assets = [], seen = new Set;
46
+ let bytesInlined = 0, out = html;
47
+ for (const kind of BLOB_KINDS)
48
+ out = out.replace(kind.pattern, (whole, attrs, body) => {
49
+ if (/\b(?:src|href)\s*=/.test(attrs) || !body.trim())
51
50
  return whole;
52
- const filename = `crosswind.${contentHash(css)}.css`;
53
- writeAsset(filename, css);
54
- result.bytesInlined += css.length;
55
- return `<link data-crosswind="generated" rel="stylesheet" href="/${EXTERNALIZED_ASSET_DIR}/${filename}">`;
51
+ const filename = `${kind.prefix}.${contentHash(body)}.${kind.extension}`;
52
+ if (!seen.has(filename)) {
53
+ seen.add(filename);
54
+ assets.push({ filename, contents: body, contentType: kind.contentType });
55
+ }
56
+ bytesInlined += body.length;
57
+ return kind.replace(`${basePath}/${filename}`);
56
58
  });
57
- if (html !== original) {
58
- fs.writeFileSync(file, html);
59
- result.pages++;
59
+ return { html: out, assets, bytesInlined };
60
+ }
61
+ export function externalizeSharedAssets(outDir) {
62
+ const result = { pages: 0, assets: 0, bytesInlined: 0 }, assetDir = path.join(outDir, EXTERNALIZED_ASSET_DIR), written = new Set;
63
+ for (const file of listHtmlFiles(outDir)) {
64
+ const original = fs.readFileSync(file, "utf8"), { html, assets, bytesInlined } = externalizeHtml(original);
65
+ if (html === original)
66
+ continue;
67
+ for (const asset of assets) {
68
+ if (written.has(asset.filename))
69
+ continue;
70
+ written.add(asset.filename);
71
+ fs.mkdirSync(assetDir, { recursive: !0 });
72
+ fs.writeFileSync(path.join(assetDir, asset.filename), asset.contents);
73
+ result.assets++;
60
74
  }
75
+ fs.writeFileSync(file, html);
76
+ result.bytesInlined += bytesInlined;
77
+ result.pages++;
61
78
  }
62
79
  return result;
63
80
  }