tiendu 0.4.0 → 0.5.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.
- package/README.md +54 -13
- package/bin/tiendu.mjs +1 -136
- package/lib/api.mjs +18 -50
- package/lib/archive.mjs +30 -0
- package/lib/assets.mjs +245 -0
- package/lib/build.mjs +299 -41
- package/lib/dev.mjs +239 -136
- package/lib/fs-utils.mjs +35 -0
- package/lib/local-preview.mjs +350 -0
- package/lib/postcss.mjs +166 -0
- package/lib/preview.mjs +19 -9
- package/lib/push.mjs +39 -50
- package/lib/retry.mjs +69 -0
- package/package.json +2 -2
package/lib/build.mjs
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
import { watch } from "node:fs";
|
|
2
2
|
import {
|
|
3
|
-
cp,
|
|
4
3
|
mkdir,
|
|
5
4
|
readdir,
|
|
5
|
+
readFile,
|
|
6
6
|
rm,
|
|
7
7
|
stat,
|
|
8
8
|
copyFile,
|
|
9
|
+
writeFile,
|
|
9
10
|
} from "node:fs/promises";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
import * as esbuild from "esbuild";
|
|
12
13
|
import * as p from "@clack/prompts";
|
|
13
14
|
import { readThemeConfig } from "./config.mjs";
|
|
15
|
+
import {
|
|
16
|
+
flattenAssetLogicalPath,
|
|
17
|
+
getAssetImportFilter,
|
|
18
|
+
getAssetSourceInfo,
|
|
19
|
+
getStaticAssetSourceDirs,
|
|
20
|
+
syncSingleStaticAsset,
|
|
21
|
+
syncStaticAssets,
|
|
22
|
+
} from "./assets.mjs";
|
|
23
|
+
import { listFilesRecursive } from "./fs-utils.mjs";
|
|
24
|
+
import { createCssPostCssPlugin } from "./postcss.mjs";
|
|
14
25
|
|
|
15
|
-
const
|
|
26
|
+
const THEME_SOURCE_OUTPUT_DIRS = ["layout", "templates", "snippets"];
|
|
27
|
+
const LIQUID_LIKE_EXTENSIONS = new Set([".liquid", ".html", ".htm"]);
|
|
28
|
+
const ENTRY_SOURCE_EXTENSIONS = new Set([".js", ".ts", ".css"]);
|
|
29
|
+
const NESTED_ASSET_PATH_PATTERN = /\/assets\/([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._/-]+)+)([?#][A-Za-z0-9=&._-]+)?/g;
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
32
|
* Discover JS/TS and CSS entry points from src/layout and src/templates.
|
|
@@ -51,31 +65,142 @@ const discoverEntryPoints = async (srcDir) => {
|
|
|
51
65
|
};
|
|
52
66
|
|
|
53
67
|
/**
|
|
54
|
-
*
|
|
68
|
+
* @param {string} rootDir
|
|
69
|
+
* @param {string} relativeDir
|
|
70
|
+
* @returns {Promise<boolean>}
|
|
55
71
|
*/
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
const directoryExists = async (rootDir, relativeDir) => {
|
|
73
|
+
try {
|
|
74
|
+
const info = await stat(path.join(rootDir, relativeDir));
|
|
75
|
+
return info.isDirectory();
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const getThemeSourceDirs = async (rootDir) => {
|
|
82
|
+
const resolvedDirs = [];
|
|
83
|
+
|
|
84
|
+
for (const outputRelativeDir of THEME_SOURCE_OUTPUT_DIRS) {
|
|
85
|
+
const sourceCandidates = [`src/${outputRelativeDir}`, outputRelativeDir];
|
|
86
|
+
|
|
87
|
+
for (const sourceRelativeDir of sourceCandidates) {
|
|
88
|
+
if (!(await directoryExists(rootDir, sourceRelativeDir))) continue;
|
|
89
|
+
|
|
90
|
+
resolvedDirs.push({ sourceRelativeDir, outputRelativeDir });
|
|
91
|
+
break;
|
|
64
92
|
}
|
|
65
|
-
await cp(src, dest, { recursive: true });
|
|
66
93
|
}
|
|
94
|
+
|
|
95
|
+
return resolvedDirs;
|
|
67
96
|
};
|
|
68
97
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
const rewriteDirectAssetPaths = (source, knownAssetLogicalPaths) =>
|
|
99
|
+
source.replace(NESTED_ASSET_PATH_PATTERN, (match, assetPath, suffix = "") => {
|
|
100
|
+
if (!knownAssetLogicalPaths.has(assetPath)) {
|
|
101
|
+
return match;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const flattened = flattenAssetLogicalPath(assetPath);
|
|
105
|
+
return flattened ? `/assets/${flattened}${suffix}` : match;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const shouldCopyThemeSourceFile = (sourceRelativePath) => {
|
|
109
|
+
const extension = path.extname(sourceRelativePath).toLowerCase();
|
|
110
|
+
return !ENTRY_SOURCE_EXTENSIONS.has(extension);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const copyThemeSourceFile = async (
|
|
114
|
+
rootDir,
|
|
115
|
+
distDir,
|
|
116
|
+
sourceRelativePath,
|
|
117
|
+
outputRelativePath,
|
|
118
|
+
knownAssetLogicalPaths,
|
|
119
|
+
) => {
|
|
120
|
+
const src = path.join(rootDir, sourceRelativePath);
|
|
121
|
+
const dest = path.join(distDir, outputRelativePath);
|
|
75
122
|
await mkdir(path.dirname(dest), { recursive: true });
|
|
123
|
+
|
|
124
|
+
if (LIQUID_LIKE_EXTENSIONS.has(path.extname(src).toLowerCase())) {
|
|
125
|
+
const source = await readFile(src, "utf-8");
|
|
126
|
+
await writeFile(dest, rewriteDirectAssetPaths(source, knownAssetLogicalPaths), "utf-8");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
76
130
|
await copyFile(src, dest);
|
|
77
131
|
};
|
|
78
132
|
|
|
133
|
+
const copyThemeFiles = async (rootDir, distDir, themeSourceDirs, knownAssetLogicalPaths) => {
|
|
134
|
+
for (const sourceDir of themeSourceDirs) {
|
|
135
|
+
const absoluteSourceDir = path.join(rootDir, sourceDir.sourceRelativeDir);
|
|
136
|
+
const absoluteFiles = await listFilesRecursive(absoluteSourceDir);
|
|
137
|
+
|
|
138
|
+
for (const absolutePath of absoluteFiles) {
|
|
139
|
+
const nestedRelativePath = path.relative(absoluteSourceDir, absolutePath);
|
|
140
|
+
const sourceRelativePath = path.join(sourceDir.sourceRelativeDir, nestedRelativePath);
|
|
141
|
+
const outputRelativePath = path.join(sourceDir.outputRelativeDir, nestedRelativePath);
|
|
142
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath)) continue;
|
|
143
|
+
await copyThemeSourceFile(
|
|
144
|
+
rootDir,
|
|
145
|
+
distDir,
|
|
146
|
+
sourceRelativePath,
|
|
147
|
+
outputRelativePath,
|
|
148
|
+
knownAssetLogicalPaths,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const shouldTriggerTailwindCssRebuild = (relativePath) => {
|
|
155
|
+
const normalizedPath = relativePath.split(path.sep).join("/");
|
|
156
|
+
const extension = path.extname(normalizedPath).toLowerCase();
|
|
157
|
+
|
|
158
|
+
if (!["layout/", "templates/", "snippets/"].some((prefix) => normalizedPath.startsWith(prefix))) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return [".liquid", ".html", ".htm", ".js", ".ts", ".json", ".md"].includes(extension);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const createSourceAssetImportPlugin = (rootDir, sourceDirs) => ({
|
|
166
|
+
name: "tiendu-source-assets",
|
|
167
|
+
setup(build) {
|
|
168
|
+
build.onResolve({ filter: getAssetImportFilter() }, async (args) => {
|
|
169
|
+
if (!args.path.startsWith(".") && !args.path.startsWith("/")) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const candidatePath = path.isAbsolute(args.path)
|
|
174
|
+
? args.path
|
|
175
|
+
: path.resolve(args.resolveDir, args.path);
|
|
176
|
+
const assetInfo = await getAssetSourceInfo(rootDir, candidatePath, sourceDirs);
|
|
177
|
+
if (!assetInfo) return null;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
path: assetInfo.absolutePath,
|
|
181
|
+
namespace: "tiendu-source-asset",
|
|
182
|
+
pluginData: assetInfo,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
build.onLoad({ filter: /.*/, namespace: "tiendu-source-asset" }, async (args) => {
|
|
187
|
+
const assetInfo = args.pluginData;
|
|
188
|
+
return {
|
|
189
|
+
contents: `export default ${JSON.stringify(`/${assetInfo.outputRelativePath}`)};`,
|
|
190
|
+
loader: "js",
|
|
191
|
+
watchFiles: [assetInfo.absolutePath],
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const runEntryBuilds = async (jsBuildOptions, cssBuildOptions) => {
|
|
198
|
+
const builds = [];
|
|
199
|
+
if (jsBuildOptions) builds.push(esbuild.build(jsBuildOptions));
|
|
200
|
+
if (cssBuildOptions) builds.push(esbuild.build(cssBuildOptions));
|
|
201
|
+
await Promise.all(builds);
|
|
202
|
+
};
|
|
203
|
+
|
|
79
204
|
/**
|
|
80
205
|
* Run a one-shot build or start watch mode.
|
|
81
206
|
* @param {{ watch?: boolean }} options
|
|
@@ -96,14 +221,52 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
96
221
|
await rm(distDir, { recursive: true, force: true });
|
|
97
222
|
await mkdir(distDir, { recursive: true });
|
|
98
223
|
|
|
99
|
-
|
|
100
|
-
await
|
|
224
|
+
const themeSourceDirs = await getThemeSourceDirs(rootDir);
|
|
225
|
+
const staticAssetSourceDirs = await getStaticAssetSourceDirs(rootDir, { refresh: true });
|
|
101
226
|
|
|
102
227
|
// Discover entry points (JS and CSS separately to avoid key collisions)
|
|
103
228
|
const { jsEntries, cssEntries } = await discoverEntryPoints(srcDir);
|
|
104
229
|
const jsCount = Object.keys(jsEntries).length;
|
|
105
230
|
const cssCount = Object.keys(cssEntries).length;
|
|
106
231
|
const entryCount = jsCount + cssCount;
|
|
232
|
+
const reservedOutputPaths = new Set([
|
|
233
|
+
...Object.keys(jsEntries).map((key) => `assets/${key}.js`),
|
|
234
|
+
...Object.keys(cssEntries).map((key) => `assets/${key}.css`),
|
|
235
|
+
]);
|
|
236
|
+
let staticAssetOwners = new Map();
|
|
237
|
+
const knownAssetLogicalPaths = new Set();
|
|
238
|
+
const cssPlugins = [];
|
|
239
|
+
const jsPlugins = [createSourceAssetImportPlugin(rootDir, staticAssetSourceDirs)];
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
staticAssetOwners = await syncStaticAssets(
|
|
243
|
+
rootDir,
|
|
244
|
+
distDir,
|
|
245
|
+
reservedOutputPaths,
|
|
246
|
+
staticAssetSourceDirs,
|
|
247
|
+
);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
p.log.error(`Static asset build failed: ${error.message}`);
|
|
250
|
+
return { ok: false };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const logicalPath of staticAssetOwners.values()) {
|
|
254
|
+
knownAssetLogicalPaths.add(logicalPath);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Copy theme files after asset paths are known
|
|
258
|
+
await copyThemeFiles(rootDir, distDir, themeSourceDirs, knownAssetLogicalPaths);
|
|
259
|
+
|
|
260
|
+
if (cssCount > 0) {
|
|
261
|
+
try {
|
|
262
|
+
cssPlugins.push(
|
|
263
|
+
await createCssPostCssPlugin(rootDir, { sourceDirs: staticAssetSourceDirs }),
|
|
264
|
+
);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
p.log.error(`CSS pipeline failed to initialize: ${error.message}`);
|
|
267
|
+
return { ok: false };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
107
270
|
|
|
108
271
|
if (entryCount === 0) {
|
|
109
272
|
p.log.warn("No entry points found in src/layout or src/templates.");
|
|
@@ -112,19 +275,16 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
112
275
|
|
|
113
276
|
const outdir = path.join(distDir, "assets");
|
|
114
277
|
const jsBuildOptions = jsCount > 0
|
|
115
|
-
? { entryPoints: jsEntries, bundle: true, format: "esm", target: "es2020", outdir, logLevel: "warning", write: true }
|
|
278
|
+
? { entryPoints: jsEntries, bundle: true, format: "esm", target: "es2020", outdir, logLevel: "warning", write: true, plugins: jsPlugins }
|
|
116
279
|
: null;
|
|
117
280
|
const cssBuildOptions = cssCount > 0
|
|
118
|
-
? { entryPoints: cssEntries, bundle: true, outdir, logLevel: "warning", write: true }
|
|
281
|
+
? { entryPoints: cssEntries, bundle: true, outdir, logLevel: "warning", write: true, plugins: cssPlugins }
|
|
119
282
|
: null;
|
|
120
283
|
|
|
121
284
|
if (!watchMode) {
|
|
122
285
|
// One-shot build
|
|
123
286
|
try {
|
|
124
|
-
|
|
125
|
-
if (jsBuildOptions) builds.push(esbuild.build(jsBuildOptions));
|
|
126
|
-
if (cssBuildOptions) builds.push(esbuild.build(cssBuildOptions));
|
|
127
|
-
await Promise.all(builds);
|
|
287
|
+
await runEntryBuilds(jsBuildOptions, cssBuildOptions);
|
|
128
288
|
p.log.success(
|
|
129
289
|
`Built ${entryCount} entry point${entryCount === 1 ? "" : "s"} to dist/`,
|
|
130
290
|
);
|
|
@@ -137,14 +297,17 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
137
297
|
|
|
138
298
|
// Watch mode — create contexts for both JS and CSS
|
|
139
299
|
const contexts = [];
|
|
300
|
+
let cssCtx = null;
|
|
140
301
|
try {
|
|
302
|
+
await runEntryBuilds(jsBuildOptions, cssBuildOptions);
|
|
303
|
+
|
|
141
304
|
if (jsBuildOptions) {
|
|
142
305
|
const jsCtx = await esbuild.context(jsBuildOptions);
|
|
143
306
|
await jsCtx.watch();
|
|
144
307
|
contexts.push(jsCtx);
|
|
145
308
|
}
|
|
146
309
|
if (cssBuildOptions) {
|
|
147
|
-
|
|
310
|
+
cssCtx = await esbuild.context(cssBuildOptions);
|
|
148
311
|
await cssCtx.watch();
|
|
149
312
|
contexts.push(cssCtx);
|
|
150
313
|
}
|
|
@@ -162,39 +325,133 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
162
325
|
const themeWatchers = [];
|
|
163
326
|
const debounceMap = new Map();
|
|
164
327
|
const DEBOUNCE_MS = 200;
|
|
328
|
+
let cssRebuildTimer = null;
|
|
329
|
+
let cssRebuildInFlight = false;
|
|
330
|
+
let cssRebuildQueued = false;
|
|
331
|
+
|
|
332
|
+
const runCssRebuild = async () => {
|
|
333
|
+
if (!cssCtx) return;
|
|
334
|
+
if (cssRebuildInFlight) {
|
|
335
|
+
cssRebuildQueued = true;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
cssRebuildInFlight = true;
|
|
165
340
|
|
|
166
|
-
for (const dir of THEME_DIRS) {
|
|
167
|
-
const dirPath = path.join(rootDir, dir);
|
|
168
341
|
try {
|
|
169
|
-
await
|
|
170
|
-
|
|
171
|
-
|
|
342
|
+
await cssCtx.rebuild();
|
|
343
|
+
console.log("⟳ CSS bundles");
|
|
344
|
+
} catch (error) {
|
|
345
|
+
p.log.warn(`Error rebuilding CSS: ${error.message}`);
|
|
346
|
+
} finally {
|
|
347
|
+
cssRebuildInFlight = false;
|
|
348
|
+
if (cssRebuildQueued) {
|
|
349
|
+
cssRebuildQueued = false;
|
|
350
|
+
queueCssRebuild();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const queueCssRebuild = () => {
|
|
356
|
+
if (!cssCtx) return;
|
|
357
|
+
if (cssRebuildTimer) clearTimeout(cssRebuildTimer);
|
|
358
|
+
|
|
359
|
+
cssRebuildTimer = setTimeout(() => {
|
|
360
|
+
cssRebuildTimer = null;
|
|
361
|
+
void runCssRebuild();
|
|
362
|
+
}, DEBOUNCE_MS);
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const handleStaticAssetChange = async (relativePath) => {
|
|
366
|
+
const result = await syncSingleStaticAsset(
|
|
367
|
+
rootDir,
|
|
368
|
+
distDir,
|
|
369
|
+
relativePath,
|
|
370
|
+
reservedOutputPaths,
|
|
371
|
+
staticAssetOwners,
|
|
372
|
+
staticAssetSourceDirs,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (!result) return;
|
|
376
|
+
|
|
377
|
+
if (result.type === "copy") {
|
|
378
|
+
knownAssetLogicalPaths.add(result.logicalPath);
|
|
379
|
+
} else {
|
|
380
|
+
knownAssetLogicalPaths.delete(result.logicalPath);
|
|
172
381
|
}
|
|
173
382
|
|
|
383
|
+
console.log(`${result.type === "delete" ? "✕" : "⟳"} ${result.outputRelativePath}`);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
for (const sourceDir of themeSourceDirs) {
|
|
387
|
+
const dirPath = path.join(rootDir, sourceDir.sourceRelativeDir);
|
|
388
|
+
|
|
174
389
|
const watcher = watch(dirPath, { recursive: true }, (eventType, filename) => {
|
|
175
390
|
if (!filename) return;
|
|
176
|
-
const
|
|
391
|
+
const normalizedFilename = filename.split(path.sep).join("/");
|
|
392
|
+
const sourceRelativePath = `${sourceDir.sourceRelativeDir}/${normalizedFilename}`;
|
|
393
|
+
const outputRelativePath = `${sourceDir.outputRelativeDir}/${normalizedFilename}`;
|
|
177
394
|
|
|
178
|
-
const existing = debounceMap.get(
|
|
395
|
+
const existing = debounceMap.get(sourceRelativePath);
|
|
179
396
|
if (existing) clearTimeout(existing);
|
|
180
397
|
|
|
181
398
|
const timer = setTimeout(async () => {
|
|
182
|
-
debounceMap.delete(
|
|
399
|
+
debounceMap.delete(sourceRelativePath);
|
|
183
400
|
try {
|
|
184
|
-
|
|
401
|
+
if (!shouldCopyThemeSourceFile(sourceRelativePath)) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const fileStat = await stat(path.join(rootDir, sourceRelativePath)).catch(
|
|
185
406
|
() => null,
|
|
186
407
|
);
|
|
187
408
|
if (fileStat && fileStat.isFile()) {
|
|
188
|
-
await
|
|
189
|
-
|
|
409
|
+
await copyThemeSourceFile(
|
|
410
|
+
rootDir,
|
|
411
|
+
distDir,
|
|
412
|
+
sourceRelativePath,
|
|
413
|
+
outputRelativePath,
|
|
414
|
+
knownAssetLogicalPaths,
|
|
415
|
+
);
|
|
416
|
+
console.log(`⟳ ${outputRelativePath}`);
|
|
190
417
|
} else if (!fileStat) {
|
|
191
418
|
// File deleted — remove from dist
|
|
192
|
-
const dest = path.join(distDir,
|
|
419
|
+
const dest = path.join(distDir, outputRelativePath);
|
|
193
420
|
await rm(dest, { force: true });
|
|
194
|
-
console.log(`✕ ${
|
|
421
|
+
console.log(`✕ ${outputRelativePath}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (shouldTriggerTailwindCssRebuild(outputRelativePath)) {
|
|
425
|
+
queueCssRebuild();
|
|
195
426
|
}
|
|
196
427
|
} catch (error) {
|
|
197
|
-
p.log.warn(`Error copying ${
|
|
428
|
+
p.log.warn(`Error copying ${sourceRelativePath}: ${error.message}`);
|
|
429
|
+
}
|
|
430
|
+
}, DEBOUNCE_MS);
|
|
431
|
+
|
|
432
|
+
debounceMap.set(sourceRelativePath, timer);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
themeWatchers.push(watcher);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
for (const assetDir of await getStaticAssetSourceDirs(rootDir)) {
|
|
439
|
+
const watcher = watch(assetDir.absoluteDir, { recursive: true }, (eventType, filename) => {
|
|
440
|
+
if (!filename) return;
|
|
441
|
+
const relativePath = `${assetDir.relativeDir}/${filename.split(path.sep).join("/")}`;
|
|
442
|
+
|
|
443
|
+
const existing = debounceMap.get(relativePath);
|
|
444
|
+
if (existing) clearTimeout(existing);
|
|
445
|
+
|
|
446
|
+
const timer = setTimeout(async () => {
|
|
447
|
+
debounceMap.delete(relativePath);
|
|
448
|
+
try {
|
|
449
|
+
await handleStaticAssetChange(relativePath);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
const errorLabel = error.message.includes("Asset collision")
|
|
452
|
+
? "Asset collision"
|
|
453
|
+
: "Error compiling";
|
|
454
|
+
p.log.warn(`${errorLabel} ${relativePath}: ${error.message}`);
|
|
198
455
|
}
|
|
199
456
|
}, DEBOUNCE_MS);
|
|
200
457
|
|
|
@@ -207,6 +464,7 @@ export const build = async ({ watch: watchMode = false } = {}) => {
|
|
|
207
464
|
const cleanup = async () => {
|
|
208
465
|
for (const w of themeWatchers) w.close();
|
|
209
466
|
for (const timer of debounceMap.values()) clearTimeout(timer);
|
|
467
|
+
if (cssRebuildTimer) clearTimeout(cssRebuildTimer);
|
|
210
468
|
for (const ctx of contexts) await ctx.dispose();
|
|
211
469
|
};
|
|
212
470
|
|