chocola 1.6.0 → 2.0.0-next.10
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/README.md +34 -36
- package/compiler/chalk.js +34 -0
- package/compiler/component-processor.js +526 -398
- package/compiler/config.js +3 -56
- package/compiler/dom-processor.js +12 -75
- package/compiler/fs.js +0 -11
- package/compiler/index.js +126 -254
- package/compiler/module-graph.js +210 -0
- package/compiler/pipeline.js +54 -196
- package/compiler/render.js +181 -0
- package/compiler/runtime-generator.js +20 -15
- package/compiler/utils.js +79 -126
- package/dev/index.js +28 -11
- package/index.js +0 -31
- package/package.json +11 -13
- package/parser/component.js +268 -0
- package/parser/context.js +59 -0
- package/parser/css.js +96 -0
- package/parser/index.js +5 -0
- package/parser/template.js +120 -0
- package/parser/utils.js +32 -0
- package/runtime/index.js +312 -0
- package/server/index.js +402 -0
- package/utils.js +41 -6
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import { parseHTML } from "linkedom";
|
|
4
|
+
import chalk from "./chalk.js";
|
|
5
|
+
import { loadConfig, resolvePaths } from "./config.js";
|
|
6
|
+
import { getComponents, getSrcIndex } from "./pipeline.js";
|
|
7
|
+
import { protectCurlyBraces } from "../utils.js";
|
|
8
|
+
import { deterministicHash, runtimeFunctionId } from "./utils.js";
|
|
9
|
+
import { createDOM, getAssetLinks, getScriptElements } from "./dom-processor.js";
|
|
10
|
+
import {
|
|
11
|
+
extractPropsDefaults, extractTopLevelFunctions, extractTopLevelVariables,
|
|
12
|
+
} from "../parser/index.js";
|
|
13
|
+
|
|
14
|
+
const RUNTIME_KW = "$runtime";
|
|
15
|
+
const PAGE_ID = "index.html";
|
|
16
|
+
|
|
17
|
+
export class ChocolaModule {
|
|
18
|
+
constructor({ id, kind, sourcePath, source, mtimeMs }) {
|
|
19
|
+
this.id = id;
|
|
20
|
+
this.kind = kind;
|
|
21
|
+
this.sourcePath = sourcePath;
|
|
22
|
+
this.source = source;
|
|
23
|
+
this.mtimeMs = mtimeMs;
|
|
24
|
+
this.compName = kind === "component" ? path.basename(id) : null;
|
|
25
|
+
this.deps = new Set();
|
|
26
|
+
this.parsed = false;
|
|
27
|
+
this.script = null;
|
|
28
|
+
this.template = null;
|
|
29
|
+
this.styles = null;
|
|
30
|
+
this.compProps = [];
|
|
31
|
+
this.topFuncs = [];
|
|
32
|
+
this.topVars = [];
|
|
33
|
+
this.imports = [];
|
|
34
|
+
this.cssId = null;
|
|
35
|
+
this.fnId = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ModuleGraph {
|
|
40
|
+
constructor(rootDir, config, paths) {
|
|
41
|
+
this.rootDir = rootDir;
|
|
42
|
+
this.config = config;
|
|
43
|
+
this.paths = paths;
|
|
44
|
+
this.modules = new Map();
|
|
45
|
+
this.components = new Map();
|
|
46
|
+
this.loadedComponents = new Map();
|
|
47
|
+
this.page = null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
addModule(module) {
|
|
51
|
+
this.modules.set(module.id, module);
|
|
52
|
+
if (module.kind === "component") {
|
|
53
|
+
this.components.set(module.compName, module);
|
|
54
|
+
this.loadedComponents.set(module.compName, module.source);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
component(compName) {
|
|
59
|
+
return this.components.get(compName);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
moduleById(id) {
|
|
63
|
+
return this.modules.get(id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function getFileMtime(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
return (await fs.stat(filePath)).mtimeMs;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isExternalUrl(url) {
|
|
76
|
+
return url.startsWith("http://") || url.startsWith("https://");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function compileComponentModule(module, graph) {
|
|
80
|
+
if (module.parsed) return module;
|
|
81
|
+
|
|
82
|
+
const dom = parseHTML(protectCurlyBraces(module.source));
|
|
83
|
+
const doc = dom.document;
|
|
84
|
+
module.script = doc.querySelector("script")?.innerHTML ?? null;
|
|
85
|
+
module.template = doc.querySelector("template")?.innerHTML ?? null;
|
|
86
|
+
module.styles = doc.querySelector("style")?.innerHTML ?? null;
|
|
87
|
+
module.compProps = extractPropsDefaults(module.script);
|
|
88
|
+
module.topFuncs = extractTopLevelFunctions(module.script || "", RUNTIME_KW);
|
|
89
|
+
module.topVars = extractTopLevelVariables(module.script || "");
|
|
90
|
+
module.cssId = deterministicHash(module.compName, 8);
|
|
91
|
+
module.fnId = runtimeFunctionId(module.compName);
|
|
92
|
+
|
|
93
|
+
const deps = new Set();
|
|
94
|
+
if (module.script) {
|
|
95
|
+
const importRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?\s*/g;
|
|
96
|
+
let match;
|
|
97
|
+
while ((match = importRegex.exec(module.script)) !== null) {
|
|
98
|
+
const importedCompName = path.basename(match[2]).toLowerCase();
|
|
99
|
+
const importedModule = graph.component(importedCompName);
|
|
100
|
+
if (importedModule) deps.add(importedModule.id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (module.template) {
|
|
104
|
+
const frag = parseHTML(module.template).document;
|
|
105
|
+
for (const el of frag.querySelectorAll("*")) {
|
|
106
|
+
const childModule = graph.component(el.tagName.toLowerCase() + ".html");
|
|
107
|
+
if (childModule) deps.add(childModule.id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
module.deps = deps;
|
|
111
|
+
module.parsed = true;
|
|
112
|
+
return module;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function scanComponentDeps(root, graph) {
|
|
116
|
+
const deps = new Set();
|
|
117
|
+
for (const el of root.querySelectorAll("*")) {
|
|
118
|
+
const childModule = graph.component(el.tagName.toLowerCase() + ".html");
|
|
119
|
+
if (childModule) deps.add(childModule.id);
|
|
120
|
+
}
|
|
121
|
+
return deps;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function scanAssetModules(doc, graph) {
|
|
125
|
+
const deps = new Set();
|
|
126
|
+
const { stylesheets, icons } = getAssetLinks(doc);
|
|
127
|
+
const scripts = getScriptElements(doc);
|
|
128
|
+
|
|
129
|
+
const collect = (href) => {
|
|
130
|
+
if (isExternalUrl(href)) return;
|
|
131
|
+
const id = href.split("\\").join("/");
|
|
132
|
+
const existing = graph.moduleById(id);
|
|
133
|
+
if (existing) {
|
|
134
|
+
deps.add(id);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const asset = new ChocolaModule({
|
|
138
|
+
id,
|
|
139
|
+
kind: "asset",
|
|
140
|
+
sourcePath: path.join(graph.paths.src, href),
|
|
141
|
+
source: null,
|
|
142
|
+
mtimeMs: null,
|
|
143
|
+
});
|
|
144
|
+
graph.addModule(asset);
|
|
145
|
+
deps.add(id);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
for (const link of stylesheets) collect(link.href);
|
|
149
|
+
for (const link of icons) collect(link.href);
|
|
150
|
+
for (const script of scripts) collect(script.getAttribute("src"));
|
|
151
|
+
|
|
152
|
+
return deps;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function buildModuleGraph(rootDir) {
|
|
156
|
+
const config = await loadConfig(rootDir);
|
|
157
|
+
const paths = resolvePaths(rootDir, config);
|
|
158
|
+
|
|
159
|
+
const graph = new ModuleGraph(rootDir, config, paths);
|
|
160
|
+
|
|
161
|
+
const indexFiles = await getSrcIndex(paths.src);
|
|
162
|
+
const pageModule = new ChocolaModule({
|
|
163
|
+
id: PAGE_ID,
|
|
164
|
+
kind: "page",
|
|
165
|
+
sourcePath: indexFiles.srcPath,
|
|
166
|
+
source: indexFiles.srcHtmlFile,
|
|
167
|
+
mtimeMs: await getFileMtime(indexFiles.srcPath),
|
|
168
|
+
});
|
|
169
|
+
graph.addModule(pageModule);
|
|
170
|
+
graph.page = pageModule;
|
|
171
|
+
|
|
172
|
+
const foundComponents = await getComponents(paths.components);
|
|
173
|
+
const { loadedComponents, componentsLib, emptyComps } = foundComponents;
|
|
174
|
+
|
|
175
|
+
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(paths.components) + ":");
|
|
176
|
+
console.log(" ", componentsLib, "\n\n");
|
|
177
|
+
|
|
178
|
+
if (emptyComps?.length > 0) {
|
|
179
|
+
console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
|
|
180
|
+
console.log(" ", emptyComps);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const filename of componentsLib) {
|
|
184
|
+
const compName = filename.toLowerCase();
|
|
185
|
+
const source = loadedComponents.get(compName);
|
|
186
|
+
if (source === undefined) continue;
|
|
187
|
+
const id = path.posix.join(config.libDir, compName);
|
|
188
|
+
const module = new ChocolaModule({
|
|
189
|
+
id,
|
|
190
|
+
kind: "component",
|
|
191
|
+
sourcePath: path.join(paths.components, filename),
|
|
192
|
+
source,
|
|
193
|
+
mtimeMs: await getFileMtime(path.join(paths.components, filename)),
|
|
194
|
+
});
|
|
195
|
+
graph.addModule(module);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const module of graph.modules.values()) {
|
|
199
|
+
if (module.kind === "component") compileComponentModule(module, graph);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const pageDom = createDOM(pageModule.source);
|
|
203
|
+
const pageDeps = scanComponentDeps(pageDom.document, graph);
|
|
204
|
+
for (const dep of scanAssetModules(pageDom.document, graph)) {
|
|
205
|
+
pageDeps.add(dep);
|
|
206
|
+
}
|
|
207
|
+
pageModule.deps = pageDeps;
|
|
208
|
+
|
|
209
|
+
return graph;
|
|
210
|
+
}
|
package/compiler/pipeline.js
CHANGED
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
|
-
import {
|
|
2
|
+
import { throwError, deterministicHash } from "./utils.js";
|
|
3
3
|
import { readMyFile, checkFile } from "./fs.js";
|
|
4
|
-
import { JSDOM } from "jsdom";
|
|
5
4
|
import path from "path";
|
|
6
5
|
|
|
7
|
-
/**
|
|
8
|
-
* Discovers and loads all components from a library directory.
|
|
9
|
-
* Components are JavaScript files that start with an uppercase letter.
|
|
10
|
-
* They must have a default export that is a function
|
|
11
|
-
* @param {import("node:fs").PathLike} libDir - Directory containing component files
|
|
12
|
-
* @returns {Promise<{
|
|
13
|
-
* componentsLib: string[],
|
|
14
|
-
* loadedComponents: Map<string, object>,
|
|
15
|
-
* notDefComps: string[]
|
|
16
|
-
* }>}
|
|
17
|
-
* @throws {Error} if libDir cannot be found
|
|
18
|
-
*/
|
|
19
6
|
export async function getComponents(libDir) {
|
|
20
7
|
try {
|
|
21
8
|
let componentsLib = [];
|
|
22
9
|
let loadedComponents = new Map();
|
|
23
|
-
let
|
|
10
|
+
let emptyComps = [];
|
|
24
11
|
|
|
25
12
|
const components = await fs.readdir(libDir);
|
|
26
13
|
|
|
@@ -28,46 +15,30 @@ export async function getComponents(libDir) {
|
|
|
28
15
|
throw Error(`The specified components folder ${libDir} could not be found.`);
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
18
|
+
const reads = components
|
|
19
|
+
.filter(comp => comp.endsWith(".html"))
|
|
20
|
+
.map(async (comp) => {
|
|
21
|
+
try {
|
|
22
|
+
const compPath = path.join(libDir, comp);
|
|
23
|
+
const instance = await fs.readFile(compPath, "utf-8");
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
module = await loadWithAssets(path.join(libDir, comp));
|
|
25
|
+
if (instance === "" || instance.trim().length === 0) emptyComps.push(comp);
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
loadedComponents.set(comp.toLowerCase(), instance);
|
|
28
|
+
componentsLib.push(comp);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
throwError(`Failed to load component "${comp}": ${err.message || err}`);
|
|
43
31
|
}
|
|
32
|
+
});
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
const compPath = path.join(libDir, comp);
|
|
47
|
-
instance.__sourceFile = compPath;
|
|
48
|
-
|
|
49
|
-
loadedComponents.set(comp.toLowerCase(), instance);
|
|
50
|
-
} catch (err) {
|
|
51
|
-
throwError(`Failed to load component "${comp}": ${err.message || err}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
34
|
+
await Promise.all(reads);
|
|
54
35
|
|
|
55
|
-
return { componentsLib, loadedComponents,
|
|
36
|
+
return { componentsLib, loadedComponents, emptyComps };
|
|
56
37
|
} catch (err) {
|
|
57
38
|
throwError(`Failed to load components from ${libDir}: ${err.message}`);
|
|
58
39
|
}
|
|
59
40
|
}
|
|
60
41
|
|
|
61
|
-
/**
|
|
62
|
-
* Loads the project index file (HTML or .choco)
|
|
63
|
-
* If both HTML and .choco files exist, throws an error
|
|
64
|
-
* @param {import("fs").PathLike} srcPath - Source directory
|
|
65
|
-
* @returns {Promise<{
|
|
66
|
-
* srcHtmlFile: string | null,
|
|
67
|
-
* srcChocoFile: string | null
|
|
68
|
-
* }>}
|
|
69
|
-
* @throws {Error} if both index files exist or .choco is used (not yet supported)
|
|
70
|
-
*/
|
|
71
42
|
export async function getSrcIndex(srcPath) {
|
|
72
43
|
const srcHtmlPath = path.join(srcPath, "index.html");
|
|
73
44
|
|
|
@@ -85,24 +56,21 @@ export async function getSrcIndex(srcPath) {
|
|
|
85
56
|
}
|
|
86
57
|
}
|
|
87
58
|
|
|
88
|
-
|
|
89
|
-
* Processes stylesheet links: copies CSS files to output and updates link href
|
|
90
|
-
* @param {HTMLLinkElement} link - The link element to process
|
|
91
|
-
* @param {import("fs").PathLike} rootDir - Root project directory
|
|
92
|
-
* @param {string} srcDir - Source directory name
|
|
93
|
-
* @param {import("fs").PathLike} outDirPath - Output directory path
|
|
94
|
-
* @param {Array} fileIds - Array to track used file IDs
|
|
95
|
-
* @throws {Error} if stylesheet cannot be read or written
|
|
96
|
-
*/
|
|
97
|
-
export async function processStylesheet(link, rootDir, srcDir, outDirPath, fileIds) {
|
|
59
|
+
export async function processStylesheet(link, rootDir, srcDir, out) {
|
|
98
60
|
try {
|
|
99
61
|
const href = link.href;
|
|
100
62
|
if (href.startsWith("http://") || href.startsWith("https://")) return;
|
|
101
63
|
const stylesheetPath = path.join(rootDir, srcDir, href);
|
|
102
64
|
const css = await fs.readFile(stylesheetPath, { encoding: "utf8" });
|
|
103
|
-
const
|
|
65
|
+
const base = deterministicHash(css + href, 6);
|
|
66
|
+
let cssFileName = "css-" + base + ".css";
|
|
67
|
+
let counter = 0;
|
|
68
|
+
while (out.ids.includes(cssFileName)) {
|
|
69
|
+
cssFileName = "css-" + deterministicHash(css + href + counter++, 6) + ".css";
|
|
70
|
+
}
|
|
71
|
+
out.ids.push(cssFileName);
|
|
104
72
|
|
|
105
|
-
|
|
73
|
+
out.files.push({ path: cssFileName, content: css });
|
|
106
74
|
link.setAttribute("href", "./" + cssFileName);
|
|
107
75
|
|
|
108
76
|
return css;
|
|
@@ -111,165 +79,55 @@ export async function processStylesheet(link, rootDir, srcDir, outDirPath, fileI
|
|
|
111
79
|
}
|
|
112
80
|
}
|
|
113
81
|
|
|
114
|
-
|
|
115
|
-
* Processes icon links: copies icon files to output directory
|
|
116
|
-
* @param {HTMLLinkElement} link - The link element to process
|
|
117
|
-
* @param {import("fs").PathLike} rootDir - Root project directory
|
|
118
|
-
* @param {string} srcDir - Source directory name
|
|
119
|
-
* @param {import("fs").PathLike} outDirPath - Output directory path
|
|
120
|
-
* @throws {Error} if icon cannot be copied
|
|
121
|
-
*/
|
|
122
|
-
export async function processIcons(link, rootDir, srcDir, outDirPath) {
|
|
82
|
+
export async function processIcons(link, rootDir, srcDir, out) {
|
|
123
83
|
try {
|
|
124
84
|
const href = link.href;
|
|
125
85
|
if (href.startsWith("http://") || href.startsWith("https://")) return;
|
|
126
86
|
const iconPath = path.join(rootDir, srcDir, href);
|
|
127
|
-
|
|
87
|
+
out.copies.push({ from: iconPath, to: path.join(out.outDir, href) });
|
|
128
88
|
} catch (err) {
|
|
129
89
|
throwError(`Failed to copy icon: ${err}`);
|
|
130
90
|
}
|
|
131
91
|
}
|
|
132
92
|
|
|
133
|
-
export function
|
|
134
|
-
const results = new Set();
|
|
135
|
-
const urlRegex = /url\(([^)]+)\)/gi;
|
|
136
|
-
const importStringRegex = /@import\s+["']([^"']+)["']/gi;
|
|
137
|
-
const importUrlRegex = /@import\s+url\(([^)]+)\)/gi;
|
|
138
|
-
|
|
139
|
-
const clean = (raw) => {
|
|
140
|
-
let p = raw.trim();
|
|
141
|
-
if (
|
|
142
|
-
(p.startsWith('"') && p.endsWith('"')) ||
|
|
143
|
-
(p.startsWith("'") && p.endsWith("'"))
|
|
144
|
-
) {
|
|
145
|
-
p = p.slice(1, -1);
|
|
146
|
-
}
|
|
147
|
-
if (p.startsWith("data:")) return null;
|
|
148
|
-
return p;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
let match;
|
|
152
|
-
while ((match = urlRegex.exec(css))) {
|
|
153
|
-
const p = clean(match[1]);
|
|
154
|
-
if (p) results.add(p);
|
|
155
|
-
}
|
|
156
|
-
while ((match = importStringRegex.exec(css))) {
|
|
157
|
-
const p = clean(match[1]);
|
|
158
|
-
if (p) results.add(p);
|
|
159
|
-
}
|
|
160
|
-
while ((match = importUrlRegex.exec(css))) {
|
|
161
|
-
const p = clean(match[1]);
|
|
162
|
-
if (p) results.add(p);
|
|
163
|
-
}
|
|
164
|
-
return [...results];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function getComponentSource(el) {
|
|
168
|
-
let cur = el;
|
|
169
|
-
while (cur) {
|
|
170
|
-
const src = cur.getAttribute?.("data-ch-source");
|
|
171
|
-
if (src) return src;
|
|
172
|
-
cur = cur.parentElement;
|
|
173
|
-
}
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Copies all local resources (images, fonts, etc.) to output directory.
|
|
179
|
-
* Excludes web links and script/link tags.
|
|
180
|
-
* @param {import("fs").PathLike} rootDir - Root project directory
|
|
181
|
-
* @param {string} scopesCss - Scoped CSS content
|
|
182
|
-
* @param {string} globalCss - Global CSS content
|
|
183
|
-
* @param {string} srcDir - Source directory name
|
|
184
|
-
* @param {import("fs").PathLike} outDirPath - Output directory path
|
|
185
|
-
*/
|
|
186
|
-
export async function copyResources(rootDir, scopesCss, globalCss, srcDir, outDirPath) {
|
|
93
|
+
export async function processScript(doc, script, rootDir, srcDir, out) {
|
|
187
94
|
try {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const src = el.getAttribute(attrName);
|
|
198
|
-
if (src && !isWebLink(src) && !src.startsWith("#") && !src.startsWith("/")) {
|
|
199
|
-
const srcPath = path.join(rootDir, srcDir, src);
|
|
200
|
-
const destPath = path.join(outDirPath, src);
|
|
201
|
-
try {
|
|
202
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
203
|
-
await fs.copyFile(srcPath, destPath);
|
|
204
|
-
} catch (err) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`Failed to copy resource referenced by <${el.tagName.toLowerCase()} ${attrName}="${src}">` +
|
|
207
|
-
(getComponentSource(el) ? ` in component "${getComponentSource(el)}"` : "") +
|
|
208
|
-
`: ${err.message || err}`
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const styles = el.getAttribute("style");
|
|
214
|
-
if (styles) {
|
|
215
|
-
let stylesMatch;
|
|
216
|
-
while ((stylesMatch = inlinePathsRegex.exec(styles)) !== null) {
|
|
217
|
-
let filePath = stylesMatch[1].trim();
|
|
218
|
-
if (
|
|
219
|
-
(filePath.startsWith('"') && filePath.endsWith('"')) ||
|
|
220
|
-
(filePath.startsWith("'") && filePath.endsWith("'"))
|
|
221
|
-
) {
|
|
222
|
-
filePath = filePath.slice(1, -1);
|
|
223
|
-
}
|
|
224
|
-
const srcPath = path.join(rootDir, srcDir, filePath);
|
|
225
|
-
const destPath = path.join(outDirPath, filePath);
|
|
226
|
-
try {
|
|
227
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
228
|
-
await fs.copyFile(srcPath, destPath);
|
|
229
|
-
} catch (err) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`Failed to copy resource from inline style on <${el.tagName.toLowerCase()}> url("${filePath}")` +
|
|
232
|
-
(getComponentSource(el) ? ` in component "${getComponentSource(el)}"` : "") +
|
|
233
|
-
`: ${err.message || err}`
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
95
|
+
const src = script.getAttribute("src");
|
|
96
|
+
if (src.startsWith("http://") || src.startsWith("https://")) return;
|
|
97
|
+
const scriptPath = path.join(rootDir, srcDir, src);
|
|
98
|
+
const content = await fs.readFile(scriptPath, { encoding: "utf8" });
|
|
99
|
+
const base = deterministicHash(content + src, 6);
|
|
100
|
+
let jsFileName = "js-" + base + ".js";
|
|
101
|
+
let counter = 0;
|
|
102
|
+
while (out.ids.includes(jsFileName)) {
|
|
103
|
+
jsFileName = "js-" + deterministicHash(content + src + counter++, 6) + ".js";
|
|
238
104
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
} catch (err) {
|
|
249
|
-
throw new Error(
|
|
250
|
-
`Failed to copy CSS asset "${assetPath}" referenced from stylesheets: ${err.message || err}`
|
|
251
|
-
);
|
|
105
|
+
out.ids.push(jsFileName);
|
|
106
|
+
out.files.push({ path: jsFileName, content });
|
|
107
|
+
|
|
108
|
+
const newScript = doc.createElement("script");
|
|
109
|
+
for (const attr of script.attributes) {
|
|
110
|
+
if (attr.name === "src") {
|
|
111
|
+
newScript.setAttribute("src", "./" + jsFileName);
|
|
112
|
+
} else {
|
|
113
|
+
newScript.setAttribute(attr.name, attr.value);
|
|
252
114
|
}
|
|
253
115
|
}
|
|
116
|
+
if (script.textContent.trim()) {
|
|
117
|
+
newScript.textContent = script.textContent;
|
|
118
|
+
}
|
|
119
|
+
script.parentNode.replaceChild(newScript, script);
|
|
254
120
|
} catch (err) {
|
|
255
|
-
|
|
121
|
+
throwError(`Failed to process script: ${err}`);
|
|
256
122
|
}
|
|
257
123
|
}
|
|
258
124
|
|
|
259
|
-
|
|
260
|
-
* Copies the static/ directory from source to output.
|
|
261
|
-
* The entire static/ directory tree is copied recursively.
|
|
262
|
-
* Silently skips if static/ does not exist.
|
|
263
|
-
* @param {import("fs").PathLike} srcPath - Source directory path
|
|
264
|
-
* @param {import("fs").PathLike} outDirPath - Output directory path
|
|
265
|
-
*/
|
|
266
|
-
export async function copyStaticDir(srcPath, outDirPath) {
|
|
125
|
+
export async function copyStaticDir(srcPath, out) {
|
|
267
126
|
const staticSrc = path.join(srcPath, "static");
|
|
268
|
-
const staticDest = path.join(
|
|
127
|
+
const staticDest = path.join(out.outDir, "static");
|
|
269
128
|
try {
|
|
270
129
|
await fs.access(staticSrc);
|
|
271
|
-
|
|
130
|
+
out.copies.push({ from: staticSrc, to: staticDest, recursive: true });
|
|
272
131
|
} catch {
|
|
273
|
-
// static/ directory doesn't exist — nothing to copy
|
|
274
132
|
}
|
|
275
133
|
}
|