html-component-engine 0.1.2 → 0.1.3
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 +36 -36
- package/bin/cli.js +529 -529
- package/package.json +37 -37
- package/src/engine/compiler.js +317 -312
- package/src/engine/utils.js +74 -74
package/src/engine/compiler.js
CHANGED
|
@@ -1,312 +1,317 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { pathToFileURL } from 'url';
|
|
4
|
-
import { parseComponentTag, parseVariants, parseSelfClosingComponentTag } from './utils.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Compile HTML by resolving all components
|
|
8
|
-
* @param {string} html - The HTML content to compile
|
|
9
|
-
* @param {string} root - The root directory (pages directory)
|
|
10
|
-
* @param {string} projectRoot - The project root directory
|
|
11
|
-
* @returns {Promise<string>} - Compiled HTML
|
|
12
|
-
*/
|
|
13
|
-
export async function compileHtml(html, root, projectRoot = null) {
|
|
14
|
-
const effectiveProjectRoot = projectRoot || path.dirname(root);
|
|
15
|
-
let result = html;
|
|
16
|
-
|
|
17
|
-
// First, process components with children: <Component name="...">children</Component>
|
|
18
|
-
result = await processComponentsWithChildren(result, root, effectiveProjectRoot);
|
|
19
|
-
|
|
20
|
-
// Then, process self-closing components: <Component src="..." />
|
|
21
|
-
result = await processSelfClosingComponents(result, root, effectiveProjectRoot);
|
|
22
|
-
|
|
23
|
-
return result;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Process components with children (slot-based)
|
|
28
|
-
* Matches: <Component
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const attrsStr = match[
|
|
41
|
-
const children = match[
|
|
42
|
-
|
|
43
|
-
// Parse additional attributes
|
|
44
|
-
const attrs = parseAttributes(attrsStr);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
pathsToTry.push(path.join(root,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
import { parseComponentTag, parseVariants, parseSelfClosingComponentTag } from './utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compile HTML by resolving all components
|
|
8
|
+
* @param {string} html - The HTML content to compile
|
|
9
|
+
* @param {string} root - The root directory (pages directory)
|
|
10
|
+
* @param {string} projectRoot - The project root directory
|
|
11
|
+
* @returns {Promise<string>} - Compiled HTML
|
|
12
|
+
*/
|
|
13
|
+
export async function compileHtml(html, root, projectRoot = null) {
|
|
14
|
+
const effectiveProjectRoot = projectRoot || path.dirname(root);
|
|
15
|
+
let result = html;
|
|
16
|
+
|
|
17
|
+
// First, process components with children: <Component name="...">children</Component>
|
|
18
|
+
result = await processComponentsWithChildren(result, root, effectiveProjectRoot);
|
|
19
|
+
|
|
20
|
+
// Then, process self-closing components: <Component src="..." />
|
|
21
|
+
result = await processSelfClosingComponents(result, root, effectiveProjectRoot);
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Process components with children (slot-based)
|
|
28
|
+
* Matches: <Component src="...">...children...</Component>
|
|
29
|
+
* Also supports legacy: <Component name="...">...children...</Component>
|
|
30
|
+
*/
|
|
31
|
+
async function processComponentsWithChildren(html, root, projectRoot) {
|
|
32
|
+
// Regex to match non-self-closing <Component ...>...</Component>
|
|
33
|
+
const componentRegex = /<Component\b([^>]*)>([\s\S]*?)<\/Component>/g;
|
|
34
|
+
|
|
35
|
+
let result = html;
|
|
36
|
+
let matches = [...html.matchAll(componentRegex)];
|
|
37
|
+
|
|
38
|
+
for (const match of matches) {
|
|
39
|
+
const fullTag = match[0];
|
|
40
|
+
const attrsStr = match[1];
|
|
41
|
+
const children = match[2];
|
|
42
|
+
|
|
43
|
+
// Parse additional attributes
|
|
44
|
+
const attrs = parseAttributes(attrsStr);
|
|
45
|
+
const componentName = attrs.src || attrs.name;
|
|
46
|
+
|
|
47
|
+
if (!componentName) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Load component
|
|
52
|
+
let componentContent = await loadComponent(componentName, root, projectRoot, attrs);
|
|
53
|
+
|
|
54
|
+
if (componentContent === null) {
|
|
55
|
+
console.error(`Component "${componentName}" not found`);
|
|
56
|
+
result = result.replace(fullTag, `<!-- Component "${componentName}" not found -->`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Replace {{ children }} placeholder with actual children content
|
|
61
|
+
componentContent = componentContent.replace(/\{\{\s*children\s*\}\}/g, children);
|
|
62
|
+
|
|
63
|
+
// Replace props with {{key}} placeholders
|
|
64
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
65
|
+
componentContent = componentContent.replace(new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g'), value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Recursively compile nested components
|
|
69
|
+
const compiledComponent = await compileHtml(componentContent, root, projectRoot);
|
|
70
|
+
result = result.replace(fullTag, compiledComponent);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Process self-closing components
|
|
78
|
+
* Matches: <Component src="..." />
|
|
79
|
+
*/
|
|
80
|
+
async function processSelfClosingComponents(html, root, projectRoot) {
|
|
81
|
+
const componentRegex = /<Component[^>]+\/>/g;
|
|
82
|
+
|
|
83
|
+
let result = html;
|
|
84
|
+
const matches = [...html.matchAll(componentRegex)];
|
|
85
|
+
|
|
86
|
+
for (const match of matches) {
|
|
87
|
+
const tag = match[0];
|
|
88
|
+
const attrs = parseSelfClosingComponentTag(tag);
|
|
89
|
+
if (!attrs || !attrs.src) continue;
|
|
90
|
+
|
|
91
|
+
const name = attrs.src;
|
|
92
|
+
let componentContent = await loadComponent(name, root, projectRoot, attrs);
|
|
93
|
+
|
|
94
|
+
if (componentContent === null) {
|
|
95
|
+
console.error(`Component "${name}" not found`);
|
|
96
|
+
result = result.replace(tag, `<!-- Component "${name}" not found -->`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Parse variants (only for HTML content)
|
|
101
|
+
const variants = parseVariants(componentContent);
|
|
102
|
+
|
|
103
|
+
// Replace props with {{key}} placeholders
|
|
104
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
105
|
+
if (key === 'variant' && variants[value]) {
|
|
106
|
+
componentContent = componentContent.replace(/\{\{\s*variantClasses\s*\}\}/g, variants[value]);
|
|
107
|
+
} else if (key !== 'src' && key !== 'variant') {
|
|
108
|
+
componentContent = componentContent.replace(new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g'), value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// If no variant specified, replace {{variantClasses}} with empty
|
|
113
|
+
componentContent = componentContent.replace(/\{\{\s*variantClasses\s*\}\}/g, '');
|
|
114
|
+
|
|
115
|
+
// Recursively compile nested components
|
|
116
|
+
const compiledComponent = await compileHtml(componentContent, root, projectRoot);
|
|
117
|
+
result = result.replace(tag, compiledComponent);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Load a component by name
|
|
125
|
+
* @param {string} name - Component name (e.g., "Card" or "main/Button")
|
|
126
|
+
* @param {string} root - The pages root directory
|
|
127
|
+
* @param {string} projectRoot - The project root directory
|
|
128
|
+
* @param {object} attrs - Component attributes/props
|
|
129
|
+
* @returns {Promise<string|null>} - Component content or null if not found
|
|
130
|
+
*/
|
|
131
|
+
async function loadComponent(name, root, projectRoot, attrs = {}) {
|
|
132
|
+
// Normalize name for path construction (handle both / and \)
|
|
133
|
+
const normalizedName = name.replace(/\\/g, '/');
|
|
134
|
+
|
|
135
|
+
// root = srcRoot (e.g., example/src)
|
|
136
|
+
// projectRoot = project root (e.g., example)
|
|
137
|
+
// Components are in srcRoot/components
|
|
138
|
+
const possiblePaths = [
|
|
139
|
+
path.join(root, 'components', `${normalizedName}.html`), // srcRoot/components/
|
|
140
|
+
path.join(projectRoot, 'src', 'components', `${normalizedName}.html`), // projectRoot/src/components/
|
|
141
|
+
path.join(projectRoot, 'components', `${normalizedName}.html`), // projectRoot/components/
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
for (const componentPath of possiblePaths) {
|
|
145
|
+
try {
|
|
146
|
+
await fs.access(componentPath); // Check if file exists first
|
|
147
|
+
const content = await fs.readFile(componentPath, 'utf8');
|
|
148
|
+
return content;
|
|
149
|
+
} catch {
|
|
150
|
+
// Try .js file at the same location
|
|
151
|
+
const jsPath = componentPath.replace('.html', '.js');
|
|
152
|
+
try {
|
|
153
|
+
const componentModule = await import(pathToFileURL(jsPath));
|
|
154
|
+
const componentExport = componentModule.default || componentModule;
|
|
155
|
+
|
|
156
|
+
if (typeof componentExport === 'function') {
|
|
157
|
+
const props = { ...attrs };
|
|
158
|
+
delete props.src;
|
|
159
|
+
delete props.name;
|
|
160
|
+
return componentExport(props);
|
|
161
|
+
} else {
|
|
162
|
+
return String(componentExport);
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Parse attributes from an attribute string
|
|
175
|
+
* @param {string} attrsStr - Attribute string like ' class="foo" id="bar"'
|
|
176
|
+
* @returns {object} - Object with attribute key-value pairs
|
|
177
|
+
*/
|
|
178
|
+
function parseAttributes(attrsStr) {
|
|
179
|
+
const attrs = {};
|
|
180
|
+
const attrRegex = /([\w-]+)=["']([^"']*)["']/g;
|
|
181
|
+
let attrMatch;
|
|
182
|
+
while ((attrMatch = attrRegex.exec(attrsStr)) !== null) {
|
|
183
|
+
attrs[attrMatch[1]] = attrMatch[2];
|
|
184
|
+
}
|
|
185
|
+
return attrs;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Inline CSS from <link> tags into <style> tags
|
|
190
|
+
* @param {string} html - The HTML content
|
|
191
|
+
* @param {string} root - The src root directory
|
|
192
|
+
* @param {string} projectRoot - The project root directory
|
|
193
|
+
* @returns {Promise<string>} - HTML with inlined CSS
|
|
194
|
+
*/
|
|
195
|
+
export async function inlineCss(html, root, projectRoot) {
|
|
196
|
+
const linkRegex = /<link[^>]+rel=["']stylesheet["'][^>]*>/gi;
|
|
197
|
+
const matches = [...html.matchAll(linkRegex)];
|
|
198
|
+
|
|
199
|
+
let result = html;
|
|
200
|
+
|
|
201
|
+
for (const match of matches) {
|
|
202
|
+
const linkTag = match[0];
|
|
203
|
+
const hrefMatch = linkTag.match(/href=["']([^"']+)["']/);
|
|
204
|
+
|
|
205
|
+
if (!hrefMatch) continue;
|
|
206
|
+
|
|
207
|
+
let href = hrefMatch[1];
|
|
208
|
+
|
|
209
|
+
// Skip external URLs
|
|
210
|
+
if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Resolve the CSS file path - try multiple locations
|
|
215
|
+
let cssPath = null;
|
|
216
|
+
let cssContent = null;
|
|
217
|
+
|
|
218
|
+
// Possible paths to check
|
|
219
|
+
const pathsToTry = [];
|
|
220
|
+
|
|
221
|
+
if (href.startsWith('/')) {
|
|
222
|
+
const cleanHref = href.slice(1);
|
|
223
|
+
// /styles/styles.css -> src/assets/styles/styles.css (publicDir pattern)
|
|
224
|
+
pathsToTry.push(path.join(root, 'assets', cleanHref));
|
|
225
|
+
// /assets/styles/styles.css -> src/assets/styles/styles.css
|
|
226
|
+
pathsToTry.push(path.join(root, cleanHref));
|
|
227
|
+
// Try project root
|
|
228
|
+
pathsToTry.push(path.join(projectRoot, 'src', 'assets', cleanHref));
|
|
229
|
+
} else {
|
|
230
|
+
// Relative path
|
|
231
|
+
pathsToTry.push(path.join(root, href));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const tryPath of pathsToTry) {
|
|
235
|
+
if (await fileExists(tryPath)) {
|
|
236
|
+
cssPath = tryPath;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (cssPath) {
|
|
242
|
+
try {
|
|
243
|
+
cssContent = await fs.readFile(cssPath, 'utf8');
|
|
244
|
+
const styleTag = `<style>\n${cssContent}\n</style>`;
|
|
245
|
+
result = result.replace(linkTag, styleTag);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.warn(`Could not inline CSS from ${href}: ${error.message}`);
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
console.warn(`Could not find CSS file for ${href}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Inline JS from <script src="..."> tags into inline <script> tags
|
|
259
|
+
* @param {string} html - The HTML content
|
|
260
|
+
* @param {string} root - The pages root directory
|
|
261
|
+
* @param {string} projectRoot - The project root directory
|
|
262
|
+
* @returns {Promise<string>} - HTML with inlined JS
|
|
263
|
+
*/
|
|
264
|
+
export async function inlineJs(html, root, projectRoot) {
|
|
265
|
+
const scriptRegex = /<script[^>]+src=["']([^"']+)["'][^>]*><\/script>/gi;
|
|
266
|
+
const matches = [...html.matchAll(scriptRegex)];
|
|
267
|
+
|
|
268
|
+
let result = html;
|
|
269
|
+
|
|
270
|
+
for (const match of matches) {
|
|
271
|
+
const scriptTag = match[0];
|
|
272
|
+
let src = match[1];
|
|
273
|
+
|
|
274
|
+
// Skip external URLs and Vite client
|
|
275
|
+
if (src.startsWith('http://') || src.startsWith('https://') || src.includes('@vite')) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Resolve the JS file path
|
|
280
|
+
let jsPath;
|
|
281
|
+
if (src.startsWith('/')) {
|
|
282
|
+
jsPath = path.join(projectRoot, 'src', 'assets', src.slice(1));
|
|
283
|
+
if (!await fileExists(jsPath)) {
|
|
284
|
+
jsPath = path.join(root, 'assets', src.slice(1));
|
|
285
|
+
}
|
|
286
|
+
if (!await fileExists(jsPath)) {
|
|
287
|
+
jsPath = path.join(root, src.slice(1));
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
jsPath = path.join(root, src);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const jsContent = await fs.readFile(jsPath, 'utf8');
|
|
295
|
+
const inlineScriptTag = `<script>\n${jsContent}\n</script>`;
|
|
296
|
+
result = result.replace(scriptTag, inlineScriptTag);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.warn(`Could not inline JS from ${src}: ${error.message}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check if a file exists
|
|
307
|
+
* @param {string} filePath - Path to check
|
|
308
|
+
* @returns {Promise<boolean>}
|
|
309
|
+
*/
|
|
310
|
+
async function fileExists(filePath) {
|
|
311
|
+
try {
|
|
312
|
+
await fs.access(filePath);
|
|
313
|
+
return true;
|
|
314
|
+
} catch {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|