@nasti-toolchain/nasti 1.1.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/LICENSE +21 -0
- package/README.md +169 -0
- package/bin/nasti.js +2 -0
- package/client/hmr.ts +19 -0
- package/dist/cli.cjs +1369 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1346 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/hmr.cjs +19 -0
- package/dist/client/hmr.cjs.map +1 -0
- package/dist/client/hmr.d.cts +15 -0
- package/dist/client/hmr.d.ts +15 -0
- package/dist/client/hmr.js +1 -0
- package/dist/client/hmr.js.map +1 -0
- package/dist/index.cjs +1233 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +216 -0
- package/dist/index.d.ts +216 -0
- package/dist/index.js +1204 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1204 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/plugins/html.ts
|
|
18
|
+
import path5 from "path";
|
|
19
|
+
import fs4 from "fs";
|
|
20
|
+
function htmlPlugin(config) {
|
|
21
|
+
return {
|
|
22
|
+
name: "nasti:html",
|
|
23
|
+
enforce: "post",
|
|
24
|
+
transformIndexHtml(html) {
|
|
25
|
+
const tags = [];
|
|
26
|
+
if (config.command === "serve") {
|
|
27
|
+
tags.push({
|
|
28
|
+
tag: "script",
|
|
29
|
+
attrs: { type: "module", src: "/@nasti/client" },
|
|
30
|
+
injectTo: "head-prepend"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return { html, tags };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function processHtml(html, tags) {
|
|
38
|
+
const headPrepend = tags.filter((t) => t.injectTo === "head-prepend");
|
|
39
|
+
const head = tags.filter((t) => t.injectTo === "head" || !t.injectTo);
|
|
40
|
+
const bodyPrepend = tags.filter((t) => t.injectTo === "body-prepend");
|
|
41
|
+
const body = tags.filter((t) => t.injectTo === "body");
|
|
42
|
+
if (headPrepend.length) {
|
|
43
|
+
html = html.replace(/<head([^>]*)>/i, `<head$1>
|
|
44
|
+
${serializeTags(headPrepend)}`);
|
|
45
|
+
}
|
|
46
|
+
if (head.length) {
|
|
47
|
+
html = html.replace(/<\/head>/i, `${serializeTags(head)}
|
|
48
|
+
</head>`);
|
|
49
|
+
}
|
|
50
|
+
if (bodyPrepend.length) {
|
|
51
|
+
html = html.replace(/<body([^>]*)>/i, `<body$1>
|
|
52
|
+
${serializeTags(bodyPrepend)}`);
|
|
53
|
+
}
|
|
54
|
+
if (body.length) {
|
|
55
|
+
html = html.replace(/<\/body>/i, `${serializeTags(body)}
|
|
56
|
+
</body>`);
|
|
57
|
+
}
|
|
58
|
+
return html;
|
|
59
|
+
}
|
|
60
|
+
function serializeTags(tags) {
|
|
61
|
+
return tags.map(serializeTag).join("\n");
|
|
62
|
+
}
|
|
63
|
+
function serializeTag(tag) {
|
|
64
|
+
const attrs = tag.attrs ? " " + Object.entries(tag.attrs).map(([k, v]) => v === true ? k : `${k}="${v}"`).join(" ") : "";
|
|
65
|
+
const children = typeof tag.children === "string" ? tag.children : tag.children ? serializeTags(tag.children) : "";
|
|
66
|
+
const selfClosing = ["link", "meta", "br", "hr", "img", "input"].includes(tag.tag);
|
|
67
|
+
if (selfClosing && !children) {
|
|
68
|
+
return ` <${tag.tag}${attrs} />`;
|
|
69
|
+
}
|
|
70
|
+
return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
|
|
71
|
+
}
|
|
72
|
+
async function readHtmlFile(root) {
|
|
73
|
+
const htmlPath = path5.resolve(root, "index.html");
|
|
74
|
+
if (!fs4.existsSync(htmlPath)) return null;
|
|
75
|
+
return fs4.readFileSync(htmlPath, "utf-8");
|
|
76
|
+
}
|
|
77
|
+
var init_html = __esm({
|
|
78
|
+
"src/plugins/html.ts"() {
|
|
79
|
+
"use strict";
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// src/core/transformer.ts
|
|
84
|
+
import { transformSync } from "oxc-transform";
|
|
85
|
+
function shouldTransform(id) {
|
|
86
|
+
return TS_EXTENSIONS.test(id) || JSX_EXTENSIONS.test(id) || JS_EXTENSIONS.test(id) && false;
|
|
87
|
+
}
|
|
88
|
+
function transformCode(filename, code, options = {}) {
|
|
89
|
+
const isTS = TS_EXTENSIONS.test(filename) || /\.tsx$/.test(filename);
|
|
90
|
+
const isJSX = JSX_EXTENSIONS.test(filename);
|
|
91
|
+
const result = transformSync(filename, code, {
|
|
92
|
+
typescript: isTS ? {} : void 0,
|
|
93
|
+
jsx: isJSX || /\.tsx$/.test(filename) ? {
|
|
94
|
+
runtime: options.jsxRuntime ?? "automatic",
|
|
95
|
+
importSource: options.jsxImportSource ?? "react",
|
|
96
|
+
refresh: options.reactRefresh ?? false
|
|
97
|
+
} : void 0,
|
|
98
|
+
sourcemap: options.sourcemap ?? true
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
code: result.code,
|
|
102
|
+
map: result.map ? JSON.stringify(result.map) : null
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
var JS_EXTENSIONS, TS_EXTENSIONS, JSX_EXTENSIONS;
|
|
106
|
+
var init_transformer = __esm({
|
|
107
|
+
"src/core/transformer.ts"() {
|
|
108
|
+
"use strict";
|
|
109
|
+
JS_EXTENSIONS = /\.(js|mjs|cjs)$/;
|
|
110
|
+
TS_EXTENSIONS = /\.(ts|mts|cts)$/;
|
|
111
|
+
JSX_EXTENSIONS = /\.(jsx|tsx)$/;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// src/server/middleware.ts
|
|
116
|
+
var middleware_exports = {};
|
|
117
|
+
__export(middleware_exports, {
|
|
118
|
+
transformMiddleware: () => transformMiddleware,
|
|
119
|
+
transformRequest: () => transformRequest
|
|
120
|
+
});
|
|
121
|
+
import path7 from "path";
|
|
122
|
+
import fs6 from "fs";
|
|
123
|
+
function transformMiddleware(ctx) {
|
|
124
|
+
return async (req, res, next) => {
|
|
125
|
+
const url = req.url ?? "/";
|
|
126
|
+
if (req.method !== "GET") return next();
|
|
127
|
+
if (url === "/@nasti/client") {
|
|
128
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
129
|
+
res.end(getHmrClientCode());
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (url === "/" || url.endsWith(".html")) {
|
|
133
|
+
const html = await readHtmlFile(ctx.config.root);
|
|
134
|
+
if (html) {
|
|
135
|
+
let processedHtml = html;
|
|
136
|
+
for (const plugin of ctx.config.plugins) {
|
|
137
|
+
if (plugin.transformIndexHtml) {
|
|
138
|
+
const result = await plugin.transformIndexHtml(processedHtml);
|
|
139
|
+
if (typeof result === "string") {
|
|
140
|
+
processedHtml = result;
|
|
141
|
+
} else if (result && "html" in result) {
|
|
142
|
+
processedHtml = processHtml(result.html, result.tags);
|
|
143
|
+
} else if (Array.isArray(result)) {
|
|
144
|
+
processedHtml = processHtml(processedHtml, result);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
processedHtml = processedHtml.replace(
|
|
149
|
+
"<head>",
|
|
150
|
+
'<head>\n <script type="module" src="/@nasti/client"></script>'
|
|
151
|
+
);
|
|
152
|
+
res.setHeader("Content-Type", "text/html");
|
|
153
|
+
res.end(processedHtml);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (isModuleRequest(url)) {
|
|
158
|
+
try {
|
|
159
|
+
const result = await transformRequest(url, ctx);
|
|
160
|
+
if (result) {
|
|
161
|
+
const contentType = url.endsWith(".css") ? "application/javascript" : "application/javascript";
|
|
162
|
+
res.setHeader("Content-Type", contentType);
|
|
163
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
164
|
+
res.end(typeof result === "string" ? result : result.code);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error(`[nasti] Transform error: ${url}`, err.message);
|
|
169
|
+
res.statusCode = 500;
|
|
170
|
+
res.end(`Transform error: ${err.message}`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
next();
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function transformRequest(url, ctx) {
|
|
178
|
+
const { config, pluginContainer, moduleGraph } = ctx;
|
|
179
|
+
const cached = moduleGraph.getModuleByUrl(url);
|
|
180
|
+
if (cached?.transformResult) {
|
|
181
|
+
return cached.transformResult;
|
|
182
|
+
}
|
|
183
|
+
const filePath = resolveUrlToFile(url, config.root);
|
|
184
|
+
if (!filePath || !fs6.existsSync(filePath)) return null;
|
|
185
|
+
const mod = await moduleGraph.ensureEntryFromUrl(url);
|
|
186
|
+
moduleGraph.registerModule(mod, filePath);
|
|
187
|
+
let code = fs6.readFileSync(filePath, "utf-8");
|
|
188
|
+
const pluginResult = await pluginContainer.transform(code, filePath);
|
|
189
|
+
if (pluginResult) {
|
|
190
|
+
code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
|
|
191
|
+
}
|
|
192
|
+
if (shouldTransform(filePath)) {
|
|
193
|
+
const result = transformCode(filePath, code, {
|
|
194
|
+
sourcemap: true,
|
|
195
|
+
jsxRuntime: "automatic",
|
|
196
|
+
jsxImportSource: config.framework === "vue" ? "vue" : "react",
|
|
197
|
+
reactRefresh: config.framework !== "vue"
|
|
198
|
+
});
|
|
199
|
+
code = result.code;
|
|
200
|
+
}
|
|
201
|
+
code = rewriteImports(code, config);
|
|
202
|
+
const transformResult = { code };
|
|
203
|
+
mod.transformResult = transformResult;
|
|
204
|
+
return transformResult;
|
|
205
|
+
}
|
|
206
|
+
function rewriteImports(code, config) {
|
|
207
|
+
return code.replace(
|
|
208
|
+
/from\s+['"]([^'"./][^'"]*)['"]/g,
|
|
209
|
+
(match, specifier) => {
|
|
210
|
+
if (specifier.startsWith("/") || specifier.startsWith(".")) return match;
|
|
211
|
+
return `from "/@modules/${specifier}"`;
|
|
212
|
+
}
|
|
213
|
+
).replace(
|
|
214
|
+
/import\s+['"]([^'"./][^'"]*)['"]/g,
|
|
215
|
+
(match, specifier) => {
|
|
216
|
+
if (specifier.startsWith("/") || specifier.startsWith(".")) return match;
|
|
217
|
+
return `import "/@modules/${specifier}"`;
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
function resolveUrlToFile(url, root) {
|
|
222
|
+
const cleanUrl = url.split("?")[0];
|
|
223
|
+
if (cleanUrl.startsWith("/@modules/")) {
|
|
224
|
+
const moduleName = cleanUrl.slice("/@modules/".length);
|
|
225
|
+
try {
|
|
226
|
+
const { createRequire: createRequire2 } = __require("module");
|
|
227
|
+
const req = createRequire2(path7.resolve(root, "package.json"));
|
|
228
|
+
return req.resolve(moduleName);
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return path7.resolve(root, cleanUrl.replace(/^\//, ""));
|
|
234
|
+
}
|
|
235
|
+
function isModuleRequest(url) {
|
|
236
|
+
const cleanUrl = url.split("?")[0];
|
|
237
|
+
return /\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl) || cleanUrl.startsWith("/@modules/");
|
|
238
|
+
}
|
|
239
|
+
function getHmrClientCode() {
|
|
240
|
+
return `
|
|
241
|
+
// Nasti HMR Client
|
|
242
|
+
const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
|
|
243
|
+
const hotModulesMap = new Map();
|
|
244
|
+
|
|
245
|
+
socket.addEventListener('message', ({ data }) => {
|
|
246
|
+
const payload = JSON.parse(data);
|
|
247
|
+
switch (payload.type) {
|
|
248
|
+
case 'connected':
|
|
249
|
+
console.log('[nasti] connected.');
|
|
250
|
+
break;
|
|
251
|
+
case 'update':
|
|
252
|
+
payload.updates.forEach((update) => {
|
|
253
|
+
if (update.type === 'js-update') {
|
|
254
|
+
fetchUpdate(update);
|
|
255
|
+
} else if (update.type === 'css-update') {
|
|
256
|
+
updateCss(update.path);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
break;
|
|
260
|
+
case 'full-reload':
|
|
261
|
+
console.log('[nasti] full reload');
|
|
262
|
+
location.reload();
|
|
263
|
+
break;
|
|
264
|
+
case 'error':
|
|
265
|
+
console.error('[nasti] error:', payload.err.message);
|
|
266
|
+
showErrorOverlay(payload.err);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
async function fetchUpdate(update) {
|
|
272
|
+
const mod = hotModulesMap.get(update.path);
|
|
273
|
+
if (mod) {
|
|
274
|
+
const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
|
|
275
|
+
mod.callbacks.forEach((cb) => cb(newMod));
|
|
276
|
+
} else {
|
|
277
|
+
// \u6CA1\u6709\u6CE8\u518C hot \u56DE\u8C03\uFF0C\u5C1D\u8BD5\u91CD\u65B0 import
|
|
278
|
+
await import(update.path + '?t=' + update.timestamp);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function updateCss(path) {
|
|
283
|
+
const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
|
|
284
|
+
if (el) {
|
|
285
|
+
fetch(path + '?t=' + Date.now())
|
|
286
|
+
.then(r => r.text())
|
|
287
|
+
.then(css => { el.textContent = css; });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function showErrorOverlay(err) {
|
|
292
|
+
const overlay = document.createElement('div');
|
|
293
|
+
overlay.id = 'nasti-error-overlay';
|
|
294
|
+
overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.85);color:#fff;font-family:monospace;padding:2rem;overflow:auto;';
|
|
295
|
+
overlay.innerHTML = \`<h2 style="color:#ff5555">Build Error</h2><pre>\${err.message}\\n\${err.stack || ''}</pre><button onclick="this.parentElement.remove()" style="margin-top:1rem;padding:0.5rem 1rem;cursor:pointer">Close</button>\`;
|
|
296
|
+
document.body.appendChild(overlay);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// import.meta.hot API
|
|
300
|
+
const createHotContext = (ownerPath) => ({
|
|
301
|
+
accept(deps, callback) {
|
|
302
|
+
if (typeof deps === 'function' || !deps) {
|
|
303
|
+
// self-accepting
|
|
304
|
+
const callbacks = hotModulesMap.get(ownerPath)?.callbacks || [];
|
|
305
|
+
callbacks.push(deps || (() => {}));
|
|
306
|
+
hotModulesMap.set(ownerPath, { callbacks });
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
prune(callback) {
|
|
310
|
+
// \u6A21\u5757\u88AB\u79FB\u9664\u65F6\u6267\u884C
|
|
311
|
+
},
|
|
312
|
+
dispose(callback) {
|
|
313
|
+
// \u6A21\u5757\u66F4\u65B0\u524D\u6267\u884C\u6E05\u7406
|
|
314
|
+
},
|
|
315
|
+
invalidate() {
|
|
316
|
+
location.reload();
|
|
317
|
+
},
|
|
318
|
+
data: {},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// \u66B4\u9732\u7ED9\u6A21\u5757\u4F7F\u7528
|
|
322
|
+
if (!window.__nasti_hot_map) window.__nasti_hot_map = new Map();
|
|
323
|
+
window.__NASTI_HMR__ = { createHotContext };
|
|
324
|
+
`;
|
|
325
|
+
}
|
|
326
|
+
var init_middleware = __esm({
|
|
327
|
+
"src/server/middleware.ts"() {
|
|
328
|
+
"use strict";
|
|
329
|
+
init_transformer();
|
|
330
|
+
init_html();
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// src/config/index.ts
|
|
335
|
+
import { pathToFileURL } from "url";
|
|
336
|
+
import path from "path";
|
|
337
|
+
import fs from "fs";
|
|
338
|
+
|
|
339
|
+
// src/config/defaults.ts
|
|
340
|
+
var defaultResolve = {
|
|
341
|
+
alias: {},
|
|
342
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".vue"],
|
|
343
|
+
conditions: ["import", "module", "browser", "default"],
|
|
344
|
+
mainFields: ["module", "jsnext:main", "jsnext", "main"]
|
|
345
|
+
};
|
|
346
|
+
var defaultServer = {
|
|
347
|
+
port: 3e3,
|
|
348
|
+
host: "localhost",
|
|
349
|
+
https: false,
|
|
350
|
+
open: false,
|
|
351
|
+
proxy: {},
|
|
352
|
+
cors: true,
|
|
353
|
+
hmr: true
|
|
354
|
+
};
|
|
355
|
+
var defaultBuild = {
|
|
356
|
+
outDir: "dist",
|
|
357
|
+
assetsDir: "assets",
|
|
358
|
+
minify: true,
|
|
359
|
+
sourcemap: false,
|
|
360
|
+
target: "es2022",
|
|
361
|
+
rolldownOptions: {},
|
|
362
|
+
emptyOutDir: true
|
|
363
|
+
};
|
|
364
|
+
var defaults = {
|
|
365
|
+
root: ".",
|
|
366
|
+
base: "/",
|
|
367
|
+
mode: "development",
|
|
368
|
+
framework: "auto",
|
|
369
|
+
resolve: defaultResolve,
|
|
370
|
+
server: defaultServer,
|
|
371
|
+
build: defaultBuild,
|
|
372
|
+
plugins: [],
|
|
373
|
+
envPrefix: ["NASTI_", "VITE_"],
|
|
374
|
+
logLevel: "info"
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// src/config/index.ts
|
|
378
|
+
function defineConfig(config) {
|
|
379
|
+
return config;
|
|
380
|
+
}
|
|
381
|
+
var CONFIG_FILES = [
|
|
382
|
+
"nasti.config.ts",
|
|
383
|
+
"nasti.config.js",
|
|
384
|
+
"nasti.config.mjs",
|
|
385
|
+
"nasti.config.mts"
|
|
386
|
+
];
|
|
387
|
+
async function loadConfigFromFile(root) {
|
|
388
|
+
for (const file of CONFIG_FILES) {
|
|
389
|
+
const filePath = path.resolve(root, file);
|
|
390
|
+
if (!fs.existsSync(filePath)) continue;
|
|
391
|
+
if (file.endsWith(".ts") || file.endsWith(".mts")) {
|
|
392
|
+
return await loadTsConfig(filePath);
|
|
393
|
+
}
|
|
394
|
+
const mod = await import(pathToFileURL(filePath).href);
|
|
395
|
+
return mod.default ?? mod;
|
|
396
|
+
}
|
|
397
|
+
return {};
|
|
398
|
+
}
|
|
399
|
+
async function loadTsConfig(filePath) {
|
|
400
|
+
const { transformSync: transformSync2 } = await import("oxc-transform");
|
|
401
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
402
|
+
const result = transformSync2(filePath, code, {
|
|
403
|
+
typescript: {}
|
|
404
|
+
});
|
|
405
|
+
const tmpFile = filePath + ".timestamp-" + Date.now() + ".mjs";
|
|
406
|
+
try {
|
|
407
|
+
fs.writeFileSync(tmpFile, result.code);
|
|
408
|
+
const mod = await import(pathToFileURL(tmpFile).href);
|
|
409
|
+
return mod.default ?? mod;
|
|
410
|
+
} finally {
|
|
411
|
+
fs.unlinkSync(tmpFile);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async function resolveConfig(inlineConfig = {}, command) {
|
|
415
|
+
const root = path.resolve(inlineConfig.root ?? defaults.root);
|
|
416
|
+
const fileConfig = await loadConfigFromFile(root);
|
|
417
|
+
const merged = deepMerge(deepMerge({}, fileConfig), inlineConfig);
|
|
418
|
+
const rawPlugins = [
|
|
419
|
+
...fileConfig.plugins ?? [],
|
|
420
|
+
...inlineConfig.plugins ?? []
|
|
421
|
+
];
|
|
422
|
+
const env = { mode: merged.mode ?? defaults.mode, command };
|
|
423
|
+
for (const plugin of rawPlugins) {
|
|
424
|
+
if (plugin.config) {
|
|
425
|
+
const result = await plugin.config(merged, env);
|
|
426
|
+
if (result) Object.assign(merged, result);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const filteredPlugins = rawPlugins.filter((p) => {
|
|
430
|
+
if (!p.apply) return true;
|
|
431
|
+
if (typeof p.apply === "function") return p.apply(resolved, env);
|
|
432
|
+
return p.apply === command;
|
|
433
|
+
});
|
|
434
|
+
const resolved = {
|
|
435
|
+
root,
|
|
436
|
+
base: merged.base ?? defaults.base,
|
|
437
|
+
mode: command === "build" ? "production" : "development",
|
|
438
|
+
framework: merged.framework ?? defaults.framework,
|
|
439
|
+
command,
|
|
440
|
+
resolve: {
|
|
441
|
+
alias: { ...defaults.resolve.alias, ...merged.resolve?.alias },
|
|
442
|
+
extensions: merged.resolve?.extensions ?? defaults.resolve.extensions,
|
|
443
|
+
conditions: merged.resolve?.conditions ?? defaults.resolve.conditions,
|
|
444
|
+
mainFields: merged.resolve?.mainFields ?? defaults.resolve.mainFields
|
|
445
|
+
},
|
|
446
|
+
plugins: filteredPlugins,
|
|
447
|
+
server: { ...defaults.server, ...merged.server },
|
|
448
|
+
build: { ...defaults.build, ...merged.build },
|
|
449
|
+
envPrefix: Array.isArray(merged.envPrefix) ? merged.envPrefix : merged.envPrefix ? [merged.envPrefix] : [...defaults.envPrefix],
|
|
450
|
+
logLevel: merged.logLevel ?? defaults.logLevel
|
|
451
|
+
};
|
|
452
|
+
for (const plugin of resolved.plugins) {
|
|
453
|
+
if (plugin.configResolved) {
|
|
454
|
+
await plugin.configResolved(resolved);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return resolved;
|
|
458
|
+
}
|
|
459
|
+
function deepMerge(target, source) {
|
|
460
|
+
const result = { ...target };
|
|
461
|
+
for (const key of Object.keys(source)) {
|
|
462
|
+
const val = source[key];
|
|
463
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
464
|
+
result[key] = deepMerge(
|
|
465
|
+
result[key] ?? {},
|
|
466
|
+
val
|
|
467
|
+
);
|
|
468
|
+
} else if (val !== void 0) {
|
|
469
|
+
result[key] = val;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return result;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/build/index.ts
|
|
476
|
+
import path6 from "path";
|
|
477
|
+
import fs5 from "fs";
|
|
478
|
+
import { rolldown } from "rolldown";
|
|
479
|
+
|
|
480
|
+
// src/plugins/resolve.ts
|
|
481
|
+
import path2 from "path";
|
|
482
|
+
import fs2 from "fs";
|
|
483
|
+
import { createRequire } from "module";
|
|
484
|
+
function resolvePlugin(config) {
|
|
485
|
+
const { alias, extensions } = config.resolve;
|
|
486
|
+
const require2 = createRequire(path2.resolve(config.root, "package.json"));
|
|
487
|
+
return {
|
|
488
|
+
name: "nasti:resolve",
|
|
489
|
+
enforce: "pre",
|
|
490
|
+
resolveId(source, importer) {
|
|
491
|
+
for (const [key, value] of Object.entries(alias)) {
|
|
492
|
+
if (source === key || source.startsWith(key + "/")) {
|
|
493
|
+
source = source.replace(key, value);
|
|
494
|
+
if (!path2.isAbsolute(source)) {
|
|
495
|
+
source = path2.resolve(config.root, source);
|
|
496
|
+
}
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (path2.isAbsolute(source)) {
|
|
501
|
+
const resolved = tryResolveFile(source, extensions);
|
|
502
|
+
if (resolved) return resolved;
|
|
503
|
+
}
|
|
504
|
+
if (source.startsWith(".")) {
|
|
505
|
+
const dir = importer ? path2.dirname(importer) : config.root;
|
|
506
|
+
const absolute = path2.resolve(dir, source);
|
|
507
|
+
const resolved = tryResolveFile(absolute, extensions);
|
|
508
|
+
if (resolved) return resolved;
|
|
509
|
+
}
|
|
510
|
+
if (!source.startsWith("/") && !source.startsWith(".")) {
|
|
511
|
+
try {
|
|
512
|
+
const resolved = require2.resolve(source, {
|
|
513
|
+
paths: [importer ? path2.dirname(importer) : config.root]
|
|
514
|
+
});
|
|
515
|
+
return resolved;
|
|
516
|
+
} catch {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return null;
|
|
521
|
+
},
|
|
522
|
+
load(id) {
|
|
523
|
+
if (fs2.existsSync(id)) {
|
|
524
|
+
return fs2.readFileSync(id, "utf-8");
|
|
525
|
+
}
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function tryResolveFile(file, extensions) {
|
|
531
|
+
if (fs2.existsSync(file) && fs2.statSync(file).isFile()) {
|
|
532
|
+
return file;
|
|
533
|
+
}
|
|
534
|
+
for (const ext of extensions) {
|
|
535
|
+
const withExt = file + ext;
|
|
536
|
+
if (fs2.existsSync(withExt) && fs2.statSync(withExt).isFile()) {
|
|
537
|
+
return withExt;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (fs2.existsSync(file) && fs2.statSync(file).isDirectory()) {
|
|
541
|
+
for (const ext of extensions) {
|
|
542
|
+
const indexFile = path2.join(file, "index" + ext);
|
|
543
|
+
if (fs2.existsSync(indexFile)) {
|
|
544
|
+
return indexFile;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/plugins/css.ts
|
|
552
|
+
import path3 from "path";
|
|
553
|
+
function cssPlugin(config) {
|
|
554
|
+
return {
|
|
555
|
+
name: "nasti:css",
|
|
556
|
+
resolveId(source) {
|
|
557
|
+
if (source.endsWith(".css")) return null;
|
|
558
|
+
return null;
|
|
559
|
+
},
|
|
560
|
+
transform(code, id) {
|
|
561
|
+
if (!id.endsWith(".css")) return null;
|
|
562
|
+
if (config.command === "serve") {
|
|
563
|
+
const escaped = JSON.stringify(code);
|
|
564
|
+
return {
|
|
565
|
+
code: `
|
|
566
|
+
const css = ${escaped};
|
|
567
|
+
const style = document.createElement('style');
|
|
568
|
+
style.setAttribute('data-nasti-css', ${JSON.stringify(id)});
|
|
569
|
+
style.textContent = css;
|
|
570
|
+
document.head.appendChild(style);
|
|
571
|
+
|
|
572
|
+
// HMR
|
|
573
|
+
if (import.meta.hot) {
|
|
574
|
+
import.meta.hot.accept();
|
|
575
|
+
import.meta.hot.prune(() => {
|
|
576
|
+
style.remove();
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export default css;
|
|
581
|
+
`
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/plugins/assets.ts
|
|
590
|
+
import path4 from "path";
|
|
591
|
+
import fs3 from "fs";
|
|
592
|
+
import crypto from "crypto";
|
|
593
|
+
var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
594
|
+
".png",
|
|
595
|
+
".jpg",
|
|
596
|
+
".jpeg",
|
|
597
|
+
".gif",
|
|
598
|
+
".svg",
|
|
599
|
+
".ico",
|
|
600
|
+
".webp",
|
|
601
|
+
".avif",
|
|
602
|
+
".mp4",
|
|
603
|
+
".webm",
|
|
604
|
+
".ogg",
|
|
605
|
+
".mp3",
|
|
606
|
+
".wav",
|
|
607
|
+
".flac",
|
|
608
|
+
".aac",
|
|
609
|
+
".woff",
|
|
610
|
+
".woff2",
|
|
611
|
+
".eot",
|
|
612
|
+
".ttf",
|
|
613
|
+
".otf",
|
|
614
|
+
".pdf",
|
|
615
|
+
".txt"
|
|
616
|
+
]);
|
|
617
|
+
function assetsPlugin(config) {
|
|
618
|
+
return {
|
|
619
|
+
name: "nasti:assets",
|
|
620
|
+
resolveId(source) {
|
|
621
|
+
if (source.endsWith("?url") || source.endsWith("?raw")) {
|
|
622
|
+
return source;
|
|
623
|
+
}
|
|
624
|
+
return null;
|
|
625
|
+
},
|
|
626
|
+
load(id) {
|
|
627
|
+
const ext = path4.extname(id.replace(/\?.*$/, ""));
|
|
628
|
+
if (id.endsWith("?raw")) {
|
|
629
|
+
const file = id.slice(0, -4);
|
|
630
|
+
if (fs3.existsSync(file)) {
|
|
631
|
+
const content = fs3.readFileSync(file, "utf-8");
|
|
632
|
+
return `export default ${JSON.stringify(content)}`;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
|
|
636
|
+
const file = id.replace(/\?.*$/, "");
|
|
637
|
+
if (!fs3.existsSync(file)) return null;
|
|
638
|
+
if (config.command === "serve") {
|
|
639
|
+
const url = "/" + path4.relative(config.root, file);
|
|
640
|
+
return `export default ${JSON.stringify(url)}`;
|
|
641
|
+
}
|
|
642
|
+
const content = fs3.readFileSync(file);
|
|
643
|
+
const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
644
|
+
const basename = path4.basename(file, ext);
|
|
645
|
+
const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
|
|
646
|
+
return `export default ${JSON.stringify(config.base + hashedName)}`;
|
|
647
|
+
}
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/build/index.ts
|
|
654
|
+
init_html();
|
|
655
|
+
init_transformer();
|
|
656
|
+
import pc from "picocolors";
|
|
657
|
+
async function build(inlineConfig = {}) {
|
|
658
|
+
const config = await resolveConfig(inlineConfig, "build");
|
|
659
|
+
const startTime = performance.now();
|
|
660
|
+
console.log(pc.cyan("\n\u{1F528} nasti build") + pc.dim(` v${process.env.npm_package_version ?? "0.0.1"}`));
|
|
661
|
+
console.log(pc.dim(` root: ${config.root}`));
|
|
662
|
+
console.log(pc.dim(` mode: ${config.mode}`));
|
|
663
|
+
const outDir = path6.resolve(config.root, config.build.outDir);
|
|
664
|
+
if (config.build.emptyOutDir && fs5.existsSync(outDir)) {
|
|
665
|
+
fs5.rmSync(outDir, { recursive: true, force: true });
|
|
666
|
+
}
|
|
667
|
+
fs5.mkdirSync(outDir, { recursive: true });
|
|
668
|
+
const html = await readHtmlFile(config.root);
|
|
669
|
+
let entryPoints = [];
|
|
670
|
+
if (html) {
|
|
671
|
+
const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
|
|
672
|
+
for (const match of scriptMatches) {
|
|
673
|
+
const src = match[1];
|
|
674
|
+
if (src && !src.startsWith("http")) {
|
|
675
|
+
entryPoints.push(path6.resolve(config.root, src.replace(/^\//, "")));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (entryPoints.length === 0) {
|
|
680
|
+
const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
|
|
681
|
+
for (const entry of fallbackEntries) {
|
|
682
|
+
const fullPath = path6.resolve(config.root, entry);
|
|
683
|
+
if (fs5.existsSync(fullPath)) {
|
|
684
|
+
entryPoints.push(fullPath);
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (entryPoints.length === 0) {
|
|
690
|
+
throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
|
|
691
|
+
}
|
|
692
|
+
const builtinPlugins = [
|
|
693
|
+
resolvePlugin(config),
|
|
694
|
+
cssPlugin(config),
|
|
695
|
+
assetsPlugin(config)
|
|
696
|
+
];
|
|
697
|
+
const allPlugins = [...builtinPlugins, ...config.plugins];
|
|
698
|
+
const oxcTransformPlugin = {
|
|
699
|
+
name: "nasti:oxc-transform",
|
|
700
|
+
transform(code, id) {
|
|
701
|
+
if (!shouldTransform(id)) return null;
|
|
702
|
+
const result = transformCode(id, code, {
|
|
703
|
+
sourcemap: !!config.build.sourcemap,
|
|
704
|
+
jsxRuntime: "automatic",
|
|
705
|
+
jsxImportSource: config.framework === "vue" ? "vue" : "react"
|
|
706
|
+
});
|
|
707
|
+
return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
const bundle = await rolldown({
|
|
711
|
+
input: entryPoints,
|
|
712
|
+
plugins: [
|
|
713
|
+
oxcTransformPlugin,
|
|
714
|
+
// 转换 Nasti 插件为 Rolldown 插件格式
|
|
715
|
+
...allPlugins.map((p) => ({
|
|
716
|
+
name: p.name,
|
|
717
|
+
resolveId: p.resolveId,
|
|
718
|
+
load: p.load,
|
|
719
|
+
transform: p.transform,
|
|
720
|
+
buildStart: p.buildStart,
|
|
721
|
+
buildEnd: p.buildEnd
|
|
722
|
+
}))
|
|
723
|
+
],
|
|
724
|
+
...config.build.rolldownOptions
|
|
725
|
+
});
|
|
726
|
+
const { output } = await bundle.write({
|
|
727
|
+
dir: outDir,
|
|
728
|
+
format: "esm",
|
|
729
|
+
sourcemap: !!config.build.sourcemap,
|
|
730
|
+
entryFileNames: "assets/[name].[hash].js",
|
|
731
|
+
chunkFileNames: "assets/[name].[hash].js",
|
|
732
|
+
assetFileNames: "assets/[name].[hash][extname]"
|
|
733
|
+
});
|
|
734
|
+
await bundle.close();
|
|
735
|
+
if (html) {
|
|
736
|
+
let processedHtml = html;
|
|
737
|
+
const htmlPlugin_ = htmlPlugin(config);
|
|
738
|
+
if (htmlPlugin_.transformIndexHtml) {
|
|
739
|
+
const result = await htmlPlugin_.transformIndexHtml(processedHtml);
|
|
740
|
+
if (typeof result === "string") {
|
|
741
|
+
processedHtml = result;
|
|
742
|
+
} else if (result && "html" in result) {
|
|
743
|
+
processedHtml = processHtml(result.html, result.tags);
|
|
744
|
+
} else if (Array.isArray(result)) {
|
|
745
|
+
processedHtml = processHtml(processedHtml, result);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
for (const chunk of output) {
|
|
749
|
+
if (chunk.type === "chunk" && chunk.isEntry) {
|
|
750
|
+
const originalEntry = path6.relative(config.root, entryPoints[0]);
|
|
751
|
+
processedHtml = processedHtml.replace(
|
|
752
|
+
new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
|
|
753
|
+
`$1${config.base}${chunk.fileName}$3`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
fs5.writeFileSync(path6.resolve(outDir, "index.html"), processedHtml);
|
|
758
|
+
}
|
|
759
|
+
const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
|
|
760
|
+
const totalSize = output.reduce((sum, chunk) => {
|
|
761
|
+
if (chunk.type === "chunk" && chunk.code) return sum + chunk.code.length;
|
|
762
|
+
return sum;
|
|
763
|
+
}, 0);
|
|
764
|
+
console.log(pc.green(`
|
|
765
|
+
\u2713 Built in ${elapsed}s`));
|
|
766
|
+
console.log(pc.dim(` ${output.length} files, ${formatSize(totalSize)} total`));
|
|
767
|
+
console.log(pc.dim(` output: ${config.build.outDir}/
|
|
768
|
+
`));
|
|
769
|
+
return { output };
|
|
770
|
+
}
|
|
771
|
+
function formatSize(bytes) {
|
|
772
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
773
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} kB`;
|
|
774
|
+
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
775
|
+
}
|
|
776
|
+
function escapeRegExp(string) {
|
|
777
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/server/index.ts
|
|
781
|
+
import http from "http";
|
|
782
|
+
import path9 from "path";
|
|
783
|
+
import connect from "connect";
|
|
784
|
+
import sirv from "sirv";
|
|
785
|
+
import { watch } from "chokidar";
|
|
786
|
+
import pc2 from "picocolors";
|
|
787
|
+
|
|
788
|
+
// src/core/plugin-container.ts
|
|
789
|
+
var PluginContainer = class {
|
|
790
|
+
plugins;
|
|
791
|
+
config;
|
|
792
|
+
ctx;
|
|
793
|
+
constructor(config) {
|
|
794
|
+
this.config = config;
|
|
795
|
+
this.plugins = sortPlugins(config.plugins);
|
|
796
|
+
this.ctx = this.createContext();
|
|
797
|
+
}
|
|
798
|
+
createContext() {
|
|
799
|
+
const container = this;
|
|
800
|
+
return {
|
|
801
|
+
async resolve(source, importer) {
|
|
802
|
+
return container.resolveId(source, importer);
|
|
803
|
+
},
|
|
804
|
+
emitFile(_file) {
|
|
805
|
+
return "";
|
|
806
|
+
},
|
|
807
|
+
getModuleInfo(_id) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
async buildStart() {
|
|
813
|
+
for (const plugin of this.plugins) {
|
|
814
|
+
if (plugin.buildStart) {
|
|
815
|
+
await plugin.buildStart.call(this.ctx);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async buildEnd(error) {
|
|
820
|
+
for (const plugin of this.plugins) {
|
|
821
|
+
if (plugin.buildEnd) {
|
|
822
|
+
await plugin.buildEnd.call(this.ctx, error);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
async resolveId(source, importer, options = {}) {
|
|
827
|
+
for (const plugin of this.plugins) {
|
|
828
|
+
if (!plugin.resolveId) continue;
|
|
829
|
+
const result = await plugin.resolveId.call(
|
|
830
|
+
this.ctx,
|
|
831
|
+
source,
|
|
832
|
+
importer ?? void 0,
|
|
833
|
+
{ isEntry: options.isEntry ?? false, ssr: false }
|
|
834
|
+
);
|
|
835
|
+
if (result != null) return result;
|
|
836
|
+
}
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
async load(id) {
|
|
840
|
+
for (const plugin of this.plugins) {
|
|
841
|
+
if (!plugin.load) continue;
|
|
842
|
+
const result = await plugin.load.call(this.ctx, id);
|
|
843
|
+
if (result != null) return result;
|
|
844
|
+
}
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
async transform(code, id) {
|
|
848
|
+
let currentCode = code;
|
|
849
|
+
for (const plugin of this.plugins) {
|
|
850
|
+
if (!plugin.transform) continue;
|
|
851
|
+
const result = await plugin.transform.call(this.ctx, currentCode, id);
|
|
852
|
+
if (result == null) continue;
|
|
853
|
+
if (typeof result === "string") {
|
|
854
|
+
currentCode = result;
|
|
855
|
+
} else {
|
|
856
|
+
currentCode = result.code;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return currentCode === code ? null : { code: currentCode };
|
|
860
|
+
}
|
|
861
|
+
/** 完整的模块处理管道: resolveId → load → transform */
|
|
862
|
+
async processModule(source, importer) {
|
|
863
|
+
const resolveResult = await this.resolveId(source, importer, {
|
|
864
|
+
isEntry: !importer
|
|
865
|
+
});
|
|
866
|
+
if (resolveResult == null) return null;
|
|
867
|
+
const id = typeof resolveResult === "string" ? resolveResult : resolveResult.id;
|
|
868
|
+
const loadResult = await this.load(id);
|
|
869
|
+
if (loadResult == null) return null;
|
|
870
|
+
const loadedCode = typeof loadResult === "string" ? loadResult : loadResult.code;
|
|
871
|
+
const transformResult = await this.transform(loadedCode, id);
|
|
872
|
+
const finalCode = transformResult == null ? loadedCode : typeof transformResult === "string" ? transformResult : transformResult.code;
|
|
873
|
+
return { id, code: finalCode };
|
|
874
|
+
}
|
|
875
|
+
getPlugins() {
|
|
876
|
+
return this.plugins;
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
function sortPlugins(plugins) {
|
|
880
|
+
const pre = [];
|
|
881
|
+
const normal = [];
|
|
882
|
+
const post = [];
|
|
883
|
+
for (const plugin of plugins) {
|
|
884
|
+
if (plugin.enforce === "pre") pre.push(plugin);
|
|
885
|
+
else if (plugin.enforce === "post") post.push(plugin);
|
|
886
|
+
else normal.push(plugin);
|
|
887
|
+
}
|
|
888
|
+
return [...pre, ...normal, ...post];
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/core/module-graph.ts
|
|
892
|
+
var ModuleGraph = class {
|
|
893
|
+
urlToModuleMap = /* @__PURE__ */ new Map();
|
|
894
|
+
idToModuleMap = /* @__PURE__ */ new Map();
|
|
895
|
+
fileToModulesMap = /* @__PURE__ */ new Map();
|
|
896
|
+
getModuleByUrl(url) {
|
|
897
|
+
return this.urlToModuleMap.get(url);
|
|
898
|
+
}
|
|
899
|
+
getModuleById(id) {
|
|
900
|
+
return this.idToModuleMap.get(id);
|
|
901
|
+
}
|
|
902
|
+
getModulesByFile(file) {
|
|
903
|
+
return this.fileToModulesMap.get(file);
|
|
904
|
+
}
|
|
905
|
+
async ensureEntryFromUrl(url) {
|
|
906
|
+
let mod = this.urlToModuleMap.get(url);
|
|
907
|
+
if (mod) return mod;
|
|
908
|
+
mod = this.createModule(url);
|
|
909
|
+
this.urlToModuleMap.set(url, mod);
|
|
910
|
+
return mod;
|
|
911
|
+
}
|
|
912
|
+
createModule(url, id) {
|
|
913
|
+
const mod = {
|
|
914
|
+
id: id ?? url,
|
|
915
|
+
file: null,
|
|
916
|
+
url,
|
|
917
|
+
type: url.endsWith(".css") ? "css" : "js",
|
|
918
|
+
importers: /* @__PURE__ */ new Set(),
|
|
919
|
+
importedModules: /* @__PURE__ */ new Set(),
|
|
920
|
+
acceptedHmrDeps: /* @__PURE__ */ new Set(),
|
|
921
|
+
transformResult: null,
|
|
922
|
+
lastHMRTimestamp: 0,
|
|
923
|
+
isSelfAccepting: false
|
|
924
|
+
};
|
|
925
|
+
this.idToModuleMap.set(mod.id, mod);
|
|
926
|
+
return mod;
|
|
927
|
+
}
|
|
928
|
+
/** 注册文件路径到模块的映射 */
|
|
929
|
+
registerModule(mod, file) {
|
|
930
|
+
mod.file = file;
|
|
931
|
+
let mods = this.fileToModulesMap.get(file);
|
|
932
|
+
if (!mods) {
|
|
933
|
+
mods = /* @__PURE__ */ new Set();
|
|
934
|
+
this.fileToModulesMap.set(file, mods);
|
|
935
|
+
}
|
|
936
|
+
mods.add(mod);
|
|
937
|
+
}
|
|
938
|
+
/** 更新模块依赖关系 */
|
|
939
|
+
updateModuleImports(mod, importedIds) {
|
|
940
|
+
for (const imported of mod.importedModules) {
|
|
941
|
+
imported.importers.delete(mod);
|
|
942
|
+
}
|
|
943
|
+
mod.importedModules.clear();
|
|
944
|
+
for (const id of importedIds) {
|
|
945
|
+
const importedMod = this.idToModuleMap.get(id);
|
|
946
|
+
if (importedMod) {
|
|
947
|
+
mod.importedModules.add(importedMod);
|
|
948
|
+
importedMod.importers.add(mod);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
/** 使模块的转换缓存失效 */
|
|
953
|
+
invalidateModule(mod) {
|
|
954
|
+
mod.transformResult = null;
|
|
955
|
+
mod.lastHMRTimestamp = Date.now();
|
|
956
|
+
}
|
|
957
|
+
/** 使所有模块缓存失效 */
|
|
958
|
+
invalidateAll() {
|
|
959
|
+
for (const mod of this.idToModuleMap.values()) {
|
|
960
|
+
this.invalidateModule(mod);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
/** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
|
|
964
|
+
getHmrBoundaries(mod) {
|
|
965
|
+
const boundaries = [];
|
|
966
|
+
const visited = /* @__PURE__ */ new Set();
|
|
967
|
+
const propagate = (node, via) => {
|
|
968
|
+
if (visited.has(node)) return true;
|
|
969
|
+
visited.add(node);
|
|
970
|
+
if (node.isSelfAccepting) {
|
|
971
|
+
boundaries.push({ boundary: node, acceptedVia: via });
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
if (node.acceptedHmrDeps.has(via)) {
|
|
975
|
+
boundaries.push({ boundary: node, acceptedVia: via });
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
if (node.importers.size === 0) return false;
|
|
979
|
+
for (const importer of node.importers) {
|
|
980
|
+
if (!propagate(importer, node)) return false;
|
|
981
|
+
}
|
|
982
|
+
return true;
|
|
983
|
+
};
|
|
984
|
+
if (mod.isSelfAccepting) {
|
|
985
|
+
boundaries.push({ boundary: mod, acceptedVia: mod });
|
|
986
|
+
return boundaries;
|
|
987
|
+
}
|
|
988
|
+
for (const importer of mod.importers) {
|
|
989
|
+
if (!propagate(importer, mod)) {
|
|
990
|
+
return [];
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return boundaries;
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// src/server/ws.ts
|
|
998
|
+
import { WebSocketServer as WsServer } from "ws";
|
|
999
|
+
function createWebSocketServer(server) {
|
|
1000
|
+
const wss = new WsServer({ noServer: true });
|
|
1001
|
+
const clients = /* @__PURE__ */ new Set();
|
|
1002
|
+
server.on("upgrade", (req, socket, head) => {
|
|
1003
|
+
if (req.headers["sec-websocket-protocol"] === "nasti-hmr") {
|
|
1004
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
1005
|
+
wss.emit("connection", ws, req);
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
wss.on("connection", (ws) => {
|
|
1010
|
+
clients.add(ws);
|
|
1011
|
+
ws.send(JSON.stringify({ type: "connected" }));
|
|
1012
|
+
ws.on("close", () => {
|
|
1013
|
+
clients.delete(ws);
|
|
1014
|
+
});
|
|
1015
|
+
ws.on("error", (err) => {
|
|
1016
|
+
console.error("[nasti] WebSocket error:", err);
|
|
1017
|
+
clients.delete(ws);
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
return {
|
|
1021
|
+
send(payload) {
|
|
1022
|
+
const data = JSON.stringify(payload);
|
|
1023
|
+
for (const client of clients) {
|
|
1024
|
+
if (client.readyState === 1) {
|
|
1025
|
+
client.send(data);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
close() {
|
|
1030
|
+
clients.clear();
|
|
1031
|
+
wss.close();
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/server/index.ts
|
|
1037
|
+
init_middleware();
|
|
1038
|
+
|
|
1039
|
+
// src/server/hmr.ts
|
|
1040
|
+
import path8 from "path";
|
|
1041
|
+
import fs7 from "fs";
|
|
1042
|
+
async function handleFileChange(file, server) {
|
|
1043
|
+
const { moduleGraph, ws, config } = server;
|
|
1044
|
+
const relativePath = "/" + path8.relative(config.root, file);
|
|
1045
|
+
const mods = moduleGraph.getModulesByFile(file);
|
|
1046
|
+
if (!mods || mods.size === 0) {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const updates = [];
|
|
1050
|
+
const timestamp = Date.now();
|
|
1051
|
+
for (const mod of mods) {
|
|
1052
|
+
moduleGraph.invalidateModule(mod);
|
|
1053
|
+
const ctx = {
|
|
1054
|
+
file,
|
|
1055
|
+
timestamp,
|
|
1056
|
+
modules: [mod],
|
|
1057
|
+
read: () => fs7.readFileSync(file, "utf-8"),
|
|
1058
|
+
server
|
|
1059
|
+
};
|
|
1060
|
+
let affectedModules = [mod];
|
|
1061
|
+
for (const plugin of config.plugins) {
|
|
1062
|
+
if (plugin.handleHotUpdate) {
|
|
1063
|
+
const result = await plugin.handleHotUpdate(ctx);
|
|
1064
|
+
if (result) {
|
|
1065
|
+
affectedModules = result;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
for (const affected of affectedModules) {
|
|
1070
|
+
const boundaries = moduleGraph.getHmrBoundaries(affected);
|
|
1071
|
+
if (boundaries.length === 0) {
|
|
1072
|
+
ws.send({ type: "full-reload", path: relativePath });
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
for (const { boundary } of boundaries) {
|
|
1076
|
+
updates.push({
|
|
1077
|
+
type: boundary.type === "css" ? "css-update" : "js-update",
|
|
1078
|
+
path: boundary.url,
|
|
1079
|
+
acceptedPath: affected.url,
|
|
1080
|
+
timestamp
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (updates.length > 0) {
|
|
1086
|
+
ws.send({ type: "update", updates });
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/server/index.ts
|
|
1091
|
+
init_html();
|
|
1092
|
+
async function createServer(inlineConfig = {}) {
|
|
1093
|
+
const config = await resolveConfig(inlineConfig, "serve");
|
|
1094
|
+
const allPlugins = [
|
|
1095
|
+
resolvePlugin(config),
|
|
1096
|
+
cssPlugin(config),
|
|
1097
|
+
assetsPlugin(config),
|
|
1098
|
+
htmlPlugin(config),
|
|
1099
|
+
...config.plugins
|
|
1100
|
+
];
|
|
1101
|
+
const configWithPlugins = { ...config, plugins: allPlugins };
|
|
1102
|
+
const moduleGraph = new ModuleGraph();
|
|
1103
|
+
const pluginContainer = new PluginContainer(configWithPlugins);
|
|
1104
|
+
const app = connect();
|
|
1105
|
+
app.use(transformMiddleware({
|
|
1106
|
+
config: configWithPlugins,
|
|
1107
|
+
pluginContainer,
|
|
1108
|
+
moduleGraph
|
|
1109
|
+
}));
|
|
1110
|
+
const publicDir = path9.resolve(config.root, "public");
|
|
1111
|
+
app.use(sirv(publicDir, { dev: true, etag: true }));
|
|
1112
|
+
app.use(sirv(config.root, { dev: true, etag: true }));
|
|
1113
|
+
const httpServer = http.createServer(app);
|
|
1114
|
+
const ws = createWebSocketServer(httpServer);
|
|
1115
|
+
const watcher = watch(config.root, {
|
|
1116
|
+
ignored: [
|
|
1117
|
+
"**/node_modules/**",
|
|
1118
|
+
"**/.git/**",
|
|
1119
|
+
`**/${config.build.outDir}/**`
|
|
1120
|
+
],
|
|
1121
|
+
ignoreInitial: true
|
|
1122
|
+
});
|
|
1123
|
+
let server;
|
|
1124
|
+
watcher.on("change", (file) => {
|
|
1125
|
+
handleFileChange(file, server);
|
|
1126
|
+
});
|
|
1127
|
+
watcher.on("add", (file) => {
|
|
1128
|
+
handleFileChange(file, server);
|
|
1129
|
+
});
|
|
1130
|
+
const postMiddlewares = [];
|
|
1131
|
+
for (const plugin of allPlugins) {
|
|
1132
|
+
if (plugin.configureServer) {
|
|
1133
|
+
const result = await plugin.configureServer(server);
|
|
1134
|
+
if (typeof result === "function") {
|
|
1135
|
+
postMiddlewares.push(result);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
server = {
|
|
1140
|
+
config: configWithPlugins,
|
|
1141
|
+
middlewares: app,
|
|
1142
|
+
moduleGraph,
|
|
1143
|
+
watcher,
|
|
1144
|
+
ws,
|
|
1145
|
+
async listen(port) {
|
|
1146
|
+
const finalPort = port ?? config.server.port;
|
|
1147
|
+
const host = config.server.host === true ? "0.0.0.0" : config.server.host;
|
|
1148
|
+
await pluginContainer.buildStart();
|
|
1149
|
+
return new Promise((resolve, reject) => {
|
|
1150
|
+
httpServer.listen(finalPort, host, () => {
|
|
1151
|
+
const localUrl = `http://localhost:${finalPort}`;
|
|
1152
|
+
const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${finalPort}` : null;
|
|
1153
|
+
console.log();
|
|
1154
|
+
console.log(pc2.cyan(" nasti dev server") + pc2.dim(` v0.0.1`));
|
|
1155
|
+
console.log();
|
|
1156
|
+
console.log(` ${pc2.green(">")} Local: ${pc2.cyan(localUrl)}`);
|
|
1157
|
+
if (networkUrl) {
|
|
1158
|
+
console.log(` ${pc2.green(">")} Network: ${pc2.cyan(networkUrl)}`);
|
|
1159
|
+
}
|
|
1160
|
+
console.log();
|
|
1161
|
+
resolve(server);
|
|
1162
|
+
});
|
|
1163
|
+
httpServer.on("error", (err) => {
|
|
1164
|
+
if (err.code === "EADDRINUSE") {
|
|
1165
|
+
console.log(pc2.yellow(`Port ${finalPort} is in use, trying ${finalPort + 1}...`));
|
|
1166
|
+
httpServer.listen(finalPort + 1, host);
|
|
1167
|
+
} else {
|
|
1168
|
+
reject(err);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
});
|
|
1172
|
+
},
|
|
1173
|
+
async transformRequest(url) {
|
|
1174
|
+
const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
|
|
1175
|
+
return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
|
|
1176
|
+
},
|
|
1177
|
+
async close() {
|
|
1178
|
+
await pluginContainer.buildEnd();
|
|
1179
|
+
watcher.close();
|
|
1180
|
+
ws.close();
|
|
1181
|
+
httpServer.close();
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
return server;
|
|
1185
|
+
}
|
|
1186
|
+
function getNetworkAddress() {
|
|
1187
|
+
const os = __require("os");
|
|
1188
|
+
const interfaces = os.networkInterfaces();
|
|
1189
|
+
for (const name of Object.keys(interfaces)) {
|
|
1190
|
+
for (const iface of interfaces[name] ?? []) {
|
|
1191
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
1192
|
+
return iface.address;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return "localhost";
|
|
1197
|
+
}
|
|
1198
|
+
export {
|
|
1199
|
+
build,
|
|
1200
|
+
createServer,
|
|
1201
|
+
defineConfig,
|
|
1202
|
+
resolveConfig
|
|
1203
|
+
};
|
|
1204
|
+
//# sourceMappingURL=index.js.map
|