chocola 2.0.0-next.2 → 2.0.0-next.4
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 +32 -45
- package/compiler/component-processor.js +271 -207
- package/compiler/config.js +1 -54
- package/compiler/dom-processor.js +0 -51
- package/compiler/fs.js +0 -11
- package/compiler/index.js +12 -32
- package/compiler/pipeline.js +7 -188
- package/compiler/runtime-generator.js +0 -6
- package/compiler/utils.js +0 -94
- package/package.json +1 -1
- package/utils.js +0 -5
package/compiler/config.js
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import chalk from "chalk";
|
|
3
2
|
import { getChocolaConfig } from "../utils.js";
|
|
4
3
|
|
|
5
|
-
/**
|
|
6
|
-
* Loads and merges Chocola configuration with defaults
|
|
7
|
-
* @param {import("fs").PathLike} rootDir
|
|
8
|
-
* @returns {Promise<{
|
|
9
|
-
* srcDir: string,
|
|
10
|
-
* outDir: string,
|
|
11
|
-
* libDir: string,
|
|
12
|
-
* emptyOutDir: boolean
|
|
13
|
-
* }>}
|
|
14
|
-
*/
|
|
15
4
|
export async function loadConfig(rootDir) {
|
|
16
5
|
const config = await getChocolaConfig(rootDir);
|
|
17
6
|
const bundleConfig = config.bundle || {};
|
|
@@ -20,52 +9,10 @@ export async function loadConfig(rootDir) {
|
|
|
20
9
|
const outDir = bundleConfig.outDir || "dist";
|
|
21
10
|
const libDir = bundleConfig.libDir || "lib";
|
|
22
11
|
const emptyOutDir = bundleConfig.emptyOutDir !== false;
|
|
23
|
-
const assetImport = bundleConfig.assetImport || "legacy";
|
|
24
12
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return { srcDir, outDir, libDir, emptyOutDir, assetImport };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function logConfigWarnings(bundleConfig, emptyOutDir, assetImport) {
|
|
31
|
-
if (!bundleConfig.srcDir) {
|
|
32
|
-
console.warn(
|
|
33
|
-
chalk.bold.yellow("WARNING!"),
|
|
34
|
-
'srcDir not defined in chocola.config.json file: using default "src" directory.'
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!bundleConfig.outDir) {
|
|
39
|
-
console.warn(
|
|
40
|
-
chalk.bold.yellow("WARNING!"),
|
|
41
|
-
'outDir not defined in chocola.config.json file: using default "dist" directory.'
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!bundleConfig.libDir) {
|
|
46
|
-
console.warn(
|
|
47
|
-
chalk.bold.yellow("WARNING!"),
|
|
48
|
-
'libDir not defined in chocola.config.json file: using default "lib" directory.'
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
console.log(`> using emptyOutDir = ${emptyOutDir}`);
|
|
53
|
-
|
|
54
|
-
if (assetImport !== "static") {
|
|
55
|
-
console.warn(
|
|
56
|
-
chalk.bold.yellow("DEPRECATION WARNING!"),
|
|
57
|
-
'The legacy asset import system ("bundle.assetImport") will be removed in Chocola 2. ' +
|
|
58
|
-
'Set "bundle.assetImport": "static" in chocola.config.json and migrate your assets to src/static/.'
|
|
59
|
-
);
|
|
60
|
-
}
|
|
13
|
+
return { srcDir, outDir, libDir, emptyOutDir };
|
|
61
14
|
}
|
|
62
15
|
|
|
63
|
-
/**
|
|
64
|
-
* Resolves all path directories based on configuration
|
|
65
|
-
* @param {import("fs").PathLike} rootDir
|
|
66
|
-
* @param {object} config
|
|
67
|
-
* @returns {object}
|
|
68
|
-
*/
|
|
69
16
|
export function resolvePaths(rootDir, config) {
|
|
70
17
|
return {
|
|
71
18
|
outDir: path.join(rootDir, config.outDir),
|
|
@@ -4,20 +4,10 @@ import path from "path";
|
|
|
4
4
|
import { throwError, protectCurlyBraces, restoreCurlyBraces, compileExpression } from "./utils.js";
|
|
5
5
|
import { readMyFile } from "./fs.js";
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Creates a JSDOM instance from source index file
|
|
9
|
-
* @param {string} srcIndexContent
|
|
10
|
-
* @returns {JSDOM}
|
|
11
|
-
*/
|
|
12
7
|
export function createDOM(srcIndexContent) {
|
|
13
8
|
return new JSDOM(protectCurlyBraces(srcIndexContent));
|
|
14
9
|
}
|
|
15
10
|
|
|
16
|
-
/**
|
|
17
|
-
* Validates that the index file has an <app> root element
|
|
18
|
-
* @param {Document} doc
|
|
19
|
-
* @throws {Error} if <app> element not found
|
|
20
|
-
*/
|
|
21
11
|
export function validateAppContainer(doc) {
|
|
22
12
|
const appContainer = doc.querySelector("app");
|
|
23
13
|
if (!appContainer) {
|
|
@@ -26,20 +16,10 @@ export function validateAppContainer(doc) {
|
|
|
26
16
|
return appContainer;
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
/**
|
|
30
|
-
* Extracts all child elements from app container
|
|
31
|
-
* @param {Element} appContainer
|
|
32
|
-
* @returns {Element[]}
|
|
33
|
-
*/
|
|
34
19
|
export function getAppElements(appContainer) {
|
|
35
20
|
return Array.from(appContainer.querySelectorAll("*"));
|
|
36
21
|
}
|
|
37
22
|
|
|
38
|
-
/**
|
|
39
|
-
* Extracts context attributes from element (ctx.* attributes)
|
|
40
|
-
* @param {Element} element
|
|
41
|
-
* @returns {object}
|
|
42
|
-
*/
|
|
43
23
|
export function extractContextFromElement(element) {
|
|
44
24
|
const ctx = {};
|
|
45
25
|
for (const attr of element.attributes) {
|
|
@@ -58,31 +38,16 @@ export function extractContextFromElement(element) {
|
|
|
58
38
|
return ctx;
|
|
59
39
|
}
|
|
60
40
|
|
|
61
|
-
/**
|
|
62
|
-
* Serializes and formats DOM to pretty HTML
|
|
63
|
-
* @param {JSDOM} dom
|
|
64
|
-
* @returns {string}
|
|
65
|
-
*/
|
|
66
41
|
export async function serializeDOM(dom) {
|
|
67
42
|
const beautify = (await import("js-beautify")).default;
|
|
68
43
|
const finalHtml = restoreCurlyBraces(dom.serialize());
|
|
69
44
|
return beautify.html(finalHtml, { indent_size: 2 });
|
|
70
45
|
}
|
|
71
46
|
|
|
72
|
-
/**
|
|
73
|
-
* Writes the final HTML to output directory
|
|
74
|
-
* @param {string} html
|
|
75
|
-
* @param {import("fs").PathLike} outDirPath
|
|
76
|
-
*/
|
|
77
47
|
export async function writeHTMLOutput(html, outDirPath) {
|
|
78
48
|
await fs.writeFile(path.join(outDirPath, "index.html"), html);
|
|
79
49
|
}
|
|
80
50
|
|
|
81
|
-
/**
|
|
82
|
-
* Gets all stylesheet and icon links from document
|
|
83
|
-
* @param {Document} doc
|
|
84
|
-
* @returns {{stylesheets: HTMLLinkElement[], icons: HTMLLinkElement[]}}
|
|
85
|
-
*/
|
|
86
51
|
export function getAssetLinks(doc) {
|
|
87
52
|
const docLinks = Array.from(doc.querySelectorAll("link"));
|
|
88
53
|
const stylesheets = docLinks.filter(link => link.rel === "stylesheet");
|
|
@@ -90,21 +55,10 @@ export function getAssetLinks(doc) {
|
|
|
90
55
|
return { stylesheets, icons };
|
|
91
56
|
}
|
|
92
57
|
|
|
93
|
-
/**
|
|
94
|
-
* Writes CSS to output directory
|
|
95
|
-
* @param {string} css
|
|
96
|
-
* @param {import("fs").PathLike} outDirPath
|
|
97
|
-
* @param {string} filename
|
|
98
|
-
*/
|
|
99
58
|
export async function writeCSSOutput(css, outDirPath, filename = "scopes.css") {
|
|
100
59
|
await fs.writeFile(path.join(outDirPath, filename), css);
|
|
101
60
|
}
|
|
102
61
|
|
|
103
|
-
/**
|
|
104
|
-
* Appends a stylesheet link element to document head
|
|
105
|
-
* @param {Document} doc
|
|
106
|
-
* @param {string} filename
|
|
107
|
-
*/
|
|
108
62
|
export function appendStylesheetLink(doc, filename) {
|
|
109
63
|
const linkEl = doc.createElement("link");
|
|
110
64
|
linkEl.rel = "stylesheet";
|
|
@@ -112,11 +66,6 @@ export function appendStylesheetLink(doc, filename) {
|
|
|
112
66
|
doc.head.appendChild(linkEl);
|
|
113
67
|
}
|
|
114
68
|
|
|
115
|
-
/**
|
|
116
|
-
* Appends a script element to document body
|
|
117
|
-
* @param {Document} doc
|
|
118
|
-
* @param {string} filename
|
|
119
|
-
*/
|
|
120
69
|
export function appendRuntimeScript(doc, filename) {
|
|
121
70
|
const runtimeScriptEl = doc.createElement("script");
|
|
122
71
|
runtimeScriptEl.type = "module";
|
package/compiler/fs.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import { throwError } from "./utils.js";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Reads a file and returns its contents as a UTF-8 string
|
|
6
|
-
* @param {import("fs").PathLike} filePath - Path to the file
|
|
7
|
-
* @returns {Promise<string>} - File contents
|
|
8
|
-
* @throws {Error} - If file cannot be read
|
|
9
|
-
*/
|
|
10
4
|
export async function readMyFile(filePath) {
|
|
11
5
|
try {
|
|
12
6
|
const data = await fs.readFile(filePath, "utf-8");
|
|
@@ -16,11 +10,6 @@ export async function readMyFile(filePath) {
|
|
|
16
10
|
}
|
|
17
11
|
}
|
|
18
12
|
|
|
19
|
-
/**
|
|
20
|
-
* Checks if a file exists
|
|
21
|
-
* @param {import("fs").PathLike} filePath - Path to check
|
|
22
|
-
* @returns {Promise<boolean>} - True if file exists, false otherwise
|
|
23
|
-
*/
|
|
24
13
|
export async function checkFile(filePath) {
|
|
25
14
|
try {
|
|
26
15
|
await fs.access(filePath);
|
package/compiler/index.js
CHANGED
|
@@ -16,7 +16,6 @@ import { processAllComponents } from "./component-processor.js";
|
|
|
16
16
|
import { generateRuntimeScript } from "./runtime-generator.js";
|
|
17
17
|
import { genRandomId, throwError, compileExpression } from "./utils.js";
|
|
18
18
|
import {
|
|
19
|
-
copyResources,
|
|
20
19
|
copyStaticDir,
|
|
21
20
|
getComponents,
|
|
22
21
|
getSrcIndex,
|
|
@@ -76,15 +75,15 @@ async function setupOutputDirectory(outDirPath, emptyOutDir) {
|
|
|
76
75
|
|
|
77
76
|
async function loadAndDisplayComponents(srcComponentsPath) {
|
|
78
77
|
const foundComponents = await getComponents(srcComponentsPath);
|
|
79
|
-
const { loadedComponents, notDefComps, componentsLib } = foundComponents;
|
|
78
|
+
const { loadedComponents, notDefComps: emptyComps, componentsLib } = foundComponents;
|
|
80
79
|
|
|
81
80
|
console.log(` LOADING COMPONENTS`);
|
|
82
81
|
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
|
|
83
82
|
console.log(" ", componentsLib, "\n");
|
|
84
83
|
|
|
85
|
-
if (
|
|
86
|
-
console.warn(chalk.bold.yellow("WARNING!"), "The following
|
|
87
|
-
console.log(" ",
|
|
84
|
+
if (emptyComps?.length > 0) {
|
|
85
|
+
console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
|
|
86
|
+
console.log(" ", emptyComps);
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
return loadedComponents;
|
|
@@ -106,15 +105,7 @@ async function processAssets(doc, rootDir, srcDir, outDirPath) {
|
|
|
106
105
|
return cssContents
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
* Compiles a static build of your Chocola project from the directory provided.
|
|
111
|
-
* @param {import("fs").PathLike} rootDir
|
|
112
|
-
* @param {Object<{
|
|
113
|
-
* isHotReload?: boolean
|
|
114
|
-
* }
|
|
115
|
-
* >} config
|
|
116
|
-
*/
|
|
117
|
-
export default async function runtime(rootDir, buildConfig) {
|
|
108
|
+
export default async function compile(rootDir, buildConfig) {
|
|
118
109
|
const isHotReload = buildConfig?.isHotReload || null;
|
|
119
110
|
!isHotReload && logBanner();
|
|
120
111
|
|
|
@@ -138,20 +129,13 @@ export default async function runtime(rootDir, buildConfig) {
|
|
|
138
129
|
const doc = dom.window.document;
|
|
139
130
|
const appContainer = validateAppContainer(doc);
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (el.hasAttribute("del-if") && !el.hasAttribute("del:if") && !warnedDeprecated.has(sourceLoc)) {
|
|
144
|
-
warnedDeprecated.add(sourceLoc);
|
|
145
|
-
const name = sourceLoc ? sourceLoc.split(/[\\/]/).pop() : "unknown";
|
|
146
|
-
console.warn(chalk.yellow(`[deprecation] "del-if" is deprecated in ${chalk.cyan(name)} and will be removed in Chocola 2. Use "del:if" instead.`));
|
|
147
|
-
}
|
|
148
|
-
return el.hasAttribute("del-if") || el.hasAttribute("del:if");
|
|
132
|
+
function hasDelIfAttr(el) {
|
|
133
|
+
return el.hasAttribute("del:if");
|
|
149
134
|
}
|
|
150
135
|
function getDelIfAttr(el) {
|
|
151
|
-
return el.getAttribute("del:if")
|
|
136
|
+
return el.getAttribute("del:if");
|
|
152
137
|
}
|
|
153
138
|
function removeDelIfAttr(el) {
|
|
154
|
-
el.removeAttribute("del-if");
|
|
155
139
|
el.removeAttribute("del:if");
|
|
156
140
|
}
|
|
157
141
|
|
|
@@ -162,7 +146,7 @@ export default async function runtime(rootDir, buildConfig) {
|
|
|
162
146
|
|
|
163
147
|
for (const child of children) {
|
|
164
148
|
const hasIf = child.hasAttribute("if");
|
|
165
|
-
const hasDelIf = hasDelIfAttr(child
|
|
149
|
+
const hasDelIf = hasDelIfAttr(child);
|
|
166
150
|
const hasElif = child.hasAttribute("elif");
|
|
167
151
|
const hasElse = child.hasAttribute("else");
|
|
168
152
|
|
|
@@ -227,7 +211,7 @@ export default async function runtime(rootDir, buildConfig) {
|
|
|
227
211
|
const appElements = getAppElements(appContainer);
|
|
228
212
|
const { runtimeScript, scopesCss } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
|
|
229
213
|
const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
|
|
230
|
-
|
|
214
|
+
await processAssets(doc, rootDir, config.srcDir, paths.outDir);
|
|
231
215
|
|
|
232
216
|
if (scopesCss) {
|
|
233
217
|
const fileName = "sc-" + genRandomId(null, 6) + ".css";
|
|
@@ -240,11 +224,7 @@ export default async function runtime(rootDir, buildConfig) {
|
|
|
240
224
|
await writeHTMLOutput(html, paths.outDir);
|
|
241
225
|
|
|
242
226
|
try {
|
|
243
|
-
|
|
244
|
-
await copyStaticDir(paths.src, paths.outDir);
|
|
245
|
-
} else {
|
|
246
|
-
await copyResources(rootDir, scopesCss, globalCss, config.srcDir, paths.outDir);
|
|
247
|
-
}
|
|
227
|
+
await copyStaticDir(paths.src, paths.outDir);
|
|
248
228
|
} catch (err) {
|
|
249
229
|
throwError(err.message || err);
|
|
250
230
|
}
|
|
@@ -273,5 +253,5 @@ export const app = {
|
|
|
273
253
|
* @example
|
|
274
254
|
* @param {PathLike} __rootdir the directory where your Chocola App is
|
|
275
255
|
*/
|
|
276
|
-
async build(__rootdir) { return
|
|
256
|
+
async build(__rootdir) { return compile(__rootdir) }
|
|
277
257
|
};
|
package/compiler/pipeline.js
CHANGED
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
|
-
import {
|
|
2
|
+
import { throwError, genRandomId } 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
|
|
|
@@ -29,22 +16,15 @@ export async function getComponents(libDir) {
|
|
|
29
16
|
}
|
|
30
17
|
|
|
31
18
|
for (const comp of components) {
|
|
32
|
-
if (!comp.endsWith(".
|
|
19
|
+
if (!comp.endsWith(".html")) continue;
|
|
33
20
|
|
|
34
21
|
componentsLib.push(comp);
|
|
35
22
|
|
|
36
|
-
let module;
|
|
37
23
|
try {
|
|
38
|
-
module = await loadWithAssets(path.join(libDir, comp));
|
|
39
|
-
|
|
40
|
-
if (typeof module.default !== "function") {
|
|
41
|
-
notDefComps.push(comp);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const instance = module.default();
|
|
46
24
|
const compPath = path.join(libDir, comp);
|
|
47
|
-
instance
|
|
25
|
+
const instance = await fs.readFile(compPath, "utf-8");
|
|
26
|
+
|
|
27
|
+
if (instance === "" || instance.trim().length === 0) emptyComps.push(comp);
|
|
48
28
|
|
|
49
29
|
loadedComponents.set(comp.toLowerCase(), instance);
|
|
50
30
|
} catch (err) {
|
|
@@ -52,22 +32,12 @@ export async function getComponents(libDir) {
|
|
|
52
32
|
}
|
|
53
33
|
}
|
|
54
34
|
|
|
55
|
-
return { componentsLib, loadedComponents,
|
|
35
|
+
return { componentsLib, loadedComponents, emptyComps };
|
|
56
36
|
} catch (err) {
|
|
57
37
|
throwError(`Failed to load components from ${libDir}: ${err.message}`);
|
|
58
38
|
}
|
|
59
39
|
}
|
|
60
40
|
|
|
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
41
|
export async function getSrcIndex(srcPath) {
|
|
72
42
|
const srcHtmlPath = path.join(srcPath, "index.html");
|
|
73
43
|
|
|
@@ -85,15 +55,6 @@ export async function getSrcIndex(srcPath) {
|
|
|
85
55
|
}
|
|
86
56
|
}
|
|
87
57
|
|
|
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
58
|
export async function processStylesheet(link, rootDir, srcDir, outDirPath, fileIds) {
|
|
98
59
|
try {
|
|
99
60
|
const href = link.href;
|
|
@@ -111,14 +72,6 @@ export async function processStylesheet(link, rootDir, srcDir, outDirPath, fileI
|
|
|
111
72
|
}
|
|
112
73
|
}
|
|
113
74
|
|
|
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
75
|
export async function processIcons(link, rootDir, srcDir, outDirPath) {
|
|
123
76
|
try {
|
|
124
77
|
const href = link.href;
|
|
@@ -130,139 +83,6 @@ export async function processIcons(link, rootDir, srcDir, outDirPath) {
|
|
|
130
83
|
}
|
|
131
84
|
}
|
|
132
85
|
|
|
133
|
-
export function getCssAssets(css) {
|
|
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) {
|
|
187
|
-
try {
|
|
188
|
-
const newIndex = await fs.readFile(path.join(outDirPath, "index.html"), "utf8");
|
|
189
|
-
const newDoc = new JSDOM(newIndex);
|
|
190
|
-
const newElements = Array.from(newDoc.window.document.querySelectorAll("*"));
|
|
191
|
-
const inlinePathsRegex = /url\((.*?)\)/gi;
|
|
192
|
-
|
|
193
|
-
for (const el of newElements) {
|
|
194
|
-
if (el.tagName === "LINK" || el.tagName === "SCRIPT") continue;
|
|
195
|
-
|
|
196
|
-
const attrName = el.getAttribute("src") ? "src" : "href";
|
|
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
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const cssAssets = [...getCssAssets(scopesCss || ""), ...getCssAssets(globalCss || "")];
|
|
241
|
-
for (const assetPath of cssAssets) {
|
|
242
|
-
try {
|
|
243
|
-
if (assetPath.startsWith('http:') || assetPath.startsWith('https:')) continue;
|
|
244
|
-
const srcPath = path.join(rootDir, srcDir, assetPath);
|
|
245
|
-
const destPath = path.join(outDirPath, assetPath);
|
|
246
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
247
|
-
await fs.copyFile(srcPath, destPath);
|
|
248
|
-
} catch (err) {
|
|
249
|
-
throw new Error(
|
|
250
|
-
`Failed to copy CSS asset "${assetPath}" referenced from stylesheets: ${err.message || err}`
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
} catch (err) {
|
|
255
|
-
throw err;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
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
86
|
export async function copyStaticDir(srcPath, outDirPath) {
|
|
267
87
|
const staticSrc = path.join(srcPath, "static");
|
|
268
88
|
const staticDest = path.join(outDirPath, "static");
|
|
@@ -270,6 +90,5 @@ export async function copyStaticDir(srcPath, outDirPath) {
|
|
|
270
90
|
await fs.access(staticSrc);
|
|
271
91
|
await fs.cp(staticSrc, staticDest, { recursive: true, force: true });
|
|
272
92
|
} catch {
|
|
273
|
-
// static/ directory doesn't exist — nothing to copy
|
|
274
93
|
}
|
|
275
94
|
}
|
|
@@ -2,12 +2,6 @@ import { promises as fs } from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { genRandomId } from "./utils.js";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Generates a runtime script file and returns its filename
|
|
7
|
-
* @param {string} runtimeScript - The JavaScript code for runtime
|
|
8
|
-
* @param {import("fs").PathLike} outDirPath
|
|
9
|
-
* @returns {Promise<string>} - filename of the generated runtime script
|
|
10
|
-
*/
|
|
11
5
|
export async function generateRuntimeScript(runtimeScript, outDirPath) {
|
|
12
6
|
const fileIds = [];
|
|
13
7
|
const runtimeFilename = "run-" + genRandomId(fileIds, 6) + ".js";
|