@nasti-toolchain/nasti 1.3.9 → 1.4.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/dist/index.cjs CHANGED
@@ -30,1427 +30,1479 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
- // src/plugins/html.ts
34
- function htmlPlugin(config) {
35
- return {
36
- name: "nasti:html",
37
- enforce: "post",
38
- transformIndexHtml(html) {
39
- const tags = [];
40
- if (config.command === "serve") {
41
- tags.push({
42
- tag: "script",
43
- attrs: { type: "module", src: "/@nasti/client" },
44
- injectTo: "head-prepend"
45
- });
46
- }
47
- return { html, tags };
48
- }
49
- };
50
- }
51
- function processHtml(html, tags) {
52
- const headPrepend = tags.filter((t) => t.injectTo === "head-prepend");
53
- const head = tags.filter((t) => t.injectTo === "head" || !t.injectTo);
54
- const bodyPrepend = tags.filter((t) => t.injectTo === "body-prepend");
55
- const body = tags.filter((t) => t.injectTo === "body");
56
- if (headPrepend.length) {
57
- html = html.replace(/<head([^>]*)>/i, `<head$1>
58
- ${serializeTags(headPrepend)}`);
59
- }
60
- if (head.length) {
61
- html = html.replace(/<\/head>/i, `${serializeTags(head)}
62
- </head>`);
63
- }
64
- if (bodyPrepend.length) {
65
- html = html.replace(/<body([^>]*)>/i, `<body$1>
66
- ${serializeTags(bodyPrepend)}`);
67
- }
68
- if (body.length) {
69
- html = html.replace(/<\/body>/i, `${serializeTags(body)}
70
- </body>`);
33
+ // src/config/defaults.ts
34
+ var defaultResolve, defaultServer, defaultBuild, defaultElectron, defaults;
35
+ var init_defaults = __esm({
36
+ "src/config/defaults.ts"() {
37
+ "use strict";
38
+ defaultResolve = {
39
+ alias: {},
40
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".vue"],
41
+ conditions: ["import", "module", "browser", "default"],
42
+ mainFields: ["module", "jsnext:main", "jsnext", "main"]
43
+ };
44
+ defaultServer = {
45
+ port: 3e3,
46
+ host: "localhost",
47
+ https: false,
48
+ open: false,
49
+ proxy: {},
50
+ cors: true,
51
+ hmr: true
52
+ };
53
+ defaultBuild = {
54
+ outDir: "dist",
55
+ assetsDir: "assets",
56
+ minify: true,
57
+ sourcemap: false,
58
+ target: "es2022",
59
+ rolldownOptions: {},
60
+ emptyOutDir: true
61
+ };
62
+ defaultElectron = {
63
+ main: "src/electron/main.ts",
64
+ preload: "src/electron/preload.ts",
65
+ renderer: "index.html",
66
+ nodeTarget: "node22",
67
+ mainFormat: "cjs",
68
+ preloadFormat: "cjs",
69
+ electronPath: "",
70
+ electronArgs: [],
71
+ autoRestart: true,
72
+ minVersion: 41,
73
+ external: ["electron"]
74
+ };
75
+ defaults = {
76
+ root: ".",
77
+ base: "/",
78
+ mode: "development",
79
+ target: "web",
80
+ framework: "auto",
81
+ resolve: defaultResolve,
82
+ server: defaultServer,
83
+ build: defaultBuild,
84
+ electron: defaultElectron,
85
+ plugins: [],
86
+ envPrefix: ["NASTI_", "VITE_"],
87
+ logLevel: "info"
88
+ };
71
89
  }
72
- return html;
73
- }
74
- function serializeTags(tags) {
75
- return tags.map(serializeTag).join("\n");
76
- }
77
- function serializeTag(tag) {
78
- const attrs = tag.attrs ? " " + Object.entries(tag.attrs).map(([k, v]) => v === true ? k : `${k}="${v}"`).join(" ") : "";
79
- const children = typeof tag.children === "string" ? tag.children : tag.children ? serializeTags(tag.children) : "";
80
- const selfClosing = ["link", "meta", "br", "hr", "img", "input"].includes(tag.tag);
81
- if (selfClosing && !children) {
82
- return ` <${tag.tag}${attrs} />`;
90
+ });
91
+
92
+ // src/config/index.ts
93
+ function loadTsconfigPaths(root) {
94
+ const tsconfigPath = import_node_path.default.resolve(root, "tsconfig.json");
95
+ if (!import_node_fs.default.existsSync(tsconfigPath)) return {};
96
+ try {
97
+ const content = import_node_fs.default.readFileSync(tsconfigPath, "utf-8");
98
+ const stripped = content.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
99
+ const tsconfig = JSON.parse(stripped);
100
+ const paths = tsconfig?.compilerOptions?.paths ?? {};
101
+ const baseUrl = tsconfig?.compilerOptions?.baseUrl ?? ".";
102
+ const alias = {};
103
+ for (const [pattern, targets] of Object.entries(paths)) {
104
+ if (!targets.length) continue;
105
+ const cleanKey = pattern.replace(/\/\*$/, "");
106
+ const cleanTarget = targets[0].replace(/\/\*$/, "");
107
+ alias[cleanKey] = import_node_path.default.resolve(root, baseUrl, cleanTarget);
108
+ }
109
+ return alias;
110
+ } catch {
111
+ return {};
83
112
  }
84
- return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
85
113
  }
86
- async function readHtmlFile(root) {
87
- const htmlPath = import_node_path5.default.resolve(root, "index.html");
88
- if (!import_node_fs4.default.existsSync(htmlPath)) return null;
89
- return import_node_fs4.default.readFileSync(htmlPath, "utf-8");
114
+ function defineConfig(config) {
115
+ return config;
90
116
  }
91
- var import_node_path5, import_node_fs4;
92
- var init_html = __esm({
93
- "src/plugins/html.ts"() {
94
- "use strict";
95
- import_node_path5 = __toESM(require("path"), 1);
96
- import_node_fs4 = __toESM(require("fs"), 1);
117
+ async function loadConfigFromFile(root) {
118
+ for (const file of CONFIG_FILES) {
119
+ const filePath = import_node_path.default.resolve(root, file);
120
+ if (!import_node_fs.default.existsSync(filePath)) continue;
121
+ if (file.endsWith(".ts") || file.endsWith(".mts")) {
122
+ return await loadTsConfig(filePath);
123
+ }
124
+ const mod = await import((0, import_node_url.pathToFileURL)(filePath).href);
125
+ return mod.default ?? mod;
97
126
  }
98
- });
99
-
100
- // src/core/transformer.ts
101
- function shouldTransform(id) {
102
- return TS_EXTENSIONS.test(id) || JSX_EXTENSIONS.test(id) || JS_EXTENSIONS.test(id) && false;
127
+ return {};
103
128
  }
104
- function transformCode(filename, code, options = {}) {
105
- const isTS = TS_EXTENSIONS.test(filename) || /\.tsx$/.test(filename);
106
- const isJSX = JSX_EXTENSIONS.test(filename);
107
- const result = (0, import_oxc_transform.transformSync)(filename, code, {
108
- typescript: isTS ? {} : void 0,
109
- jsx: isJSX || /\.tsx$/.test(filename) ? {
110
- runtime: options.jsxRuntime ?? "automatic",
111
- importSource: options.jsxImportSource ?? "react",
112
- refresh: options.reactRefresh ?? false
113
- } : void 0,
114
- sourcemap: options.sourcemap ?? true
129
+ async function loadTsConfig(filePath) {
130
+ const { transformSync: transformSync2 } = await import("oxc-transform");
131
+ const code = import_node_fs.default.readFileSync(filePath, "utf-8");
132
+ const result = transformSync2(filePath, code, {
133
+ typescript: {}
115
134
  });
116
- if (result.errors && result.errors.length > 0) {
117
- const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
118
- throw new Error(`OXC transform failed for ${filename}:
119
- ${msg}`);
135
+ const tmpFile = filePath + ".timestamp-" + Date.now() + ".mjs";
136
+ try {
137
+ import_node_fs.default.writeFileSync(tmpFile, result.code);
138
+ const mod = await import((0, import_node_url.pathToFileURL)(tmpFile).href);
139
+ return mod.default ?? mod;
140
+ } finally {
141
+ import_node_fs.default.unlinkSync(tmpFile);
120
142
  }
121
- return {
122
- code: result.code,
123
- map: result.map ? JSON.stringify(result.map) : null
124
- };
125
143
  }
126
- var import_oxc_transform, JS_EXTENSIONS, TS_EXTENSIONS, JSX_EXTENSIONS;
127
- var init_transformer = __esm({
128
- "src/core/transformer.ts"() {
129
- "use strict";
130
- import_oxc_transform = require("oxc-transform");
131
- JS_EXTENSIONS = /\.(js|mjs|cjs)$/;
132
- TS_EXTENSIONS = /\.(ts|mts|cts)$/;
133
- JSX_EXTENSIONS = /\.(jsx|tsx)$/;
134
- }
135
- });
136
-
137
- // src/core/env.ts
138
- function loadEnv(mode, root, prefixes) {
139
- const envFiles = [
140
- ".env",
141
- `.env.${mode}`,
142
- ".env.local",
143
- `.env.${mode}.local`
144
+ async function resolveConfig(inlineConfig = {}, command) {
145
+ const root = import_node_path.default.resolve(inlineConfig.root ?? defaults.root);
146
+ const fileConfig = await loadConfigFromFile(root);
147
+ const merged = deepMerge(deepMerge({}, fileConfig), inlineConfig);
148
+ const rawPlugins = [
149
+ ...fileConfig.plugins ?? [],
150
+ ...inlineConfig.plugins ?? []
144
151
  ];
145
- const raw = {};
146
- for (const file of envFiles) {
147
- const filePath = import_node_path6.default.resolve(root, file);
148
- if (!import_node_fs5.default.existsSync(filePath)) continue;
149
- const content = import_node_fs5.default.readFileSync(filePath, "utf-8");
150
- for (const line of content.split("\n")) {
151
- const trimmed = line.trim();
152
- if (!trimmed || trimmed.startsWith("#")) continue;
153
- const eqIdx = trimmed.indexOf("=");
154
- if (eqIdx === -1) continue;
155
- const key = trimmed.slice(0, eqIdx).trim();
156
- let value = trimmed.slice(eqIdx + 1).trim();
157
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
158
- value = value.slice(1, -1);
159
- }
160
- raw[key] = value;
152
+ const env = { mode: merged.mode ?? defaults.mode, command };
153
+ for (const plugin of rawPlugins) {
154
+ if (plugin.config) {
155
+ const result = await plugin.config(merged, env);
156
+ if (result) Object.assign(merged, result);
161
157
  }
162
158
  }
163
- const filtered = {};
164
- for (const [key, value] of Object.entries(raw)) {
165
- if (prefixes.some((prefix) => key.startsWith(prefix))) {
166
- filtered[key] = value;
159
+ const resolved = {
160
+ root,
161
+ base: merged.base ?? defaults.base,
162
+ mode: command === "build" ? "production" : "development",
163
+ target: merged.target ?? defaults.target,
164
+ framework: merged.framework ?? defaults.framework,
165
+ command,
166
+ resolve: {
167
+ // tsconfig paths 优先级最低:tsconfig < defaults < user config
168
+ alias: { ...loadTsconfigPaths(root), ...defaults.resolve.alias, ...merged.resolve?.alias },
169
+ extensions: merged.resolve?.extensions ?? defaults.resolve.extensions,
170
+ conditions: merged.resolve?.conditions ?? defaults.resolve.conditions,
171
+ mainFields: merged.resolve?.mainFields ?? defaults.resolve.mainFields
172
+ },
173
+ plugins: [],
174
+ server: { ...defaults.server, ...merged.server },
175
+ build: { ...defaults.build, ...merged.build },
176
+ electron: { ...defaults.electron, ...merged.electron },
177
+ envPrefix: Array.isArray(merged.envPrefix) ? merged.envPrefix : merged.envPrefix ? [merged.envPrefix] : [...defaults.envPrefix],
178
+ logLevel: merged.logLevel ?? defaults.logLevel
179
+ };
180
+ const filteredPlugins = rawPlugins.filter((p) => {
181
+ if (!p.apply) return true;
182
+ if (typeof p.apply === "function") return p.apply(resolved, env);
183
+ return p.apply === command;
184
+ });
185
+ resolved.plugins = filteredPlugins;
186
+ for (const plugin of resolved.plugins) {
187
+ if (plugin.configResolved) {
188
+ await plugin.configResolved(resolved);
167
189
  }
168
190
  }
169
- return filtered;
170
- }
171
- function buildEnvDefine(env, mode) {
172
- const define = {};
173
- for (const [key, value] of Object.entries(env)) {
174
- define[`import.meta.env.${key}`] = JSON.stringify(value);
175
- }
176
- define["import.meta.env.MODE"] = JSON.stringify(mode);
177
- define["import.meta.env.DEV"] = mode !== "production" ? "true" : "false";
178
- define["import.meta.env.PROD"] = mode === "production" ? "true" : "false";
179
- define["import.meta.env.SSR"] = "false";
180
- return define;
191
+ return resolved;
181
192
  }
182
- function replaceEnvInCode(code, define) {
183
- let result = code;
184
- for (const [key, value] of Object.entries(define)) {
185
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
186
- result = result.replace(new RegExp(escaped, "g"), value);
193
+ function deepMerge(target, source) {
194
+ const result = { ...target };
195
+ for (const key of Object.keys(source)) {
196
+ const val = source[key];
197
+ if (val && typeof val === "object" && !Array.isArray(val)) {
198
+ result[key] = deepMerge(
199
+ result[key] ?? {},
200
+ val
201
+ );
202
+ } else if (val !== void 0) {
203
+ result[key] = val;
204
+ }
187
205
  }
188
206
  return result;
189
207
  }
190
- var import_node_path6, import_node_fs5;
191
- var init_env = __esm({
192
- "src/core/env.ts"() {
208
+ var import_node_url, import_node_path, import_node_fs, CONFIG_FILES;
209
+ var init_config = __esm({
210
+ "src/config/index.ts"() {
193
211
  "use strict";
194
- import_node_path6 = __toESM(require("path"), 1);
195
- import_node_fs5 = __toESM(require("fs"), 1);
212
+ import_node_url = require("url");
213
+ import_node_path = __toESM(require("path"), 1);
214
+ import_node_fs = __toESM(require("fs"), 1);
215
+ init_defaults();
216
+ CONFIG_FILES = [
217
+ "nasti.config.ts",
218
+ "nasti.config.js",
219
+ "nasti.config.mjs",
220
+ "nasti.config.mts"
221
+ ];
196
222
  }
197
223
  });
198
224
 
199
- // src/server/middleware.ts
200
- var middleware_exports = {};
201
- __export(middleware_exports, {
202
- transformMiddleware: () => transformMiddleware,
203
- transformRequest: () => transformRequest
204
- });
205
- function transformMiddleware(ctx) {
206
- ctx.envDefine = buildEnvDefine(
207
- loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
208
- ctx.config.mode
209
- );
210
- return async (req, res, next) => {
211
- const url = req.url ?? "/";
212
- if (ctx.config.server.cors) {
213
- const origin = req.headers.origin ?? "*";
214
- res.setHeader("Access-Control-Allow-Origin", origin);
215
- res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
216
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
217
- if (req.method === "OPTIONS") {
218
- res.statusCode = 204;
219
- res.end();
220
- return;
221
- }
222
- }
223
- if (req.method !== "GET") return next();
224
- if (url === "/@nasti/client") {
225
- res.setHeader("Content-Type", "application/javascript");
226
- res.end(getHmrClientCode());
227
- return;
228
- }
229
- if (url === "/" || url.endsWith(".html")) {
230
- const html = await readHtmlFile(ctx.config.root);
231
- if (html) {
232
- let processedHtml = html;
233
- for (const plugin of ctx.config.plugins) {
234
- if (plugin.transformIndexHtml) {
235
- const result = await plugin.transformIndexHtml(processedHtml);
236
- if (typeof result === "string") {
237
- processedHtml = result;
238
- } else if (result && "html" in result) {
239
- processedHtml = processHtml(result.html, result.tags);
240
- } else if (Array.isArray(result)) {
241
- processedHtml = processHtml(processedHtml, result);
242
- }
225
+ // src/plugins/resolve.ts
226
+ function resolvePlugin(config) {
227
+ const { alias, extensions } = config.resolve;
228
+ const require2 = (0, import_node_module.createRequire)(import_node_path2.default.resolve(config.root, "package.json"));
229
+ return {
230
+ name: "nasti:resolve",
231
+ enforce: "pre",
232
+ resolveId(source, importer) {
233
+ for (const [key, value] of Object.entries(alias)) {
234
+ if (source === key || source.startsWith(key + "/")) {
235
+ source = source.replace(key, value);
236
+ if (!import_node_path2.default.isAbsolute(source)) {
237
+ source = import_node_path2.default.resolve(config.root, source);
243
238
  }
239
+ break;
244
240
  }
245
- res.setHeader("Content-Type", "text/html");
246
- res.end(processedHtml);
247
- return;
248
241
  }
249
- }
250
- if (isModuleRequest(url)) {
251
- try {
252
- const result = await transformRequest(url, ctx);
253
- if (result) {
254
- const contentType = url.endsWith(".css") ? "application/javascript" : "application/javascript";
255
- res.setHeader("Content-Type", contentType);
256
- res.setHeader("Cache-Control", "no-cache");
257
- res.end(typeof result === "string" ? result : result.code);
258
- return;
242
+ if (import_node_path2.default.isAbsolute(source)) {
243
+ const resolved = tryResolveFile(source, extensions);
244
+ if (resolved) return resolved;
245
+ }
246
+ if (source.startsWith(".")) {
247
+ const dir = importer ? import_node_path2.default.dirname(importer) : config.root;
248
+ const absolute = import_node_path2.default.resolve(dir, source);
249
+ const resolved = tryResolveFile(absolute, extensions);
250
+ if (resolved) return resolved;
251
+ }
252
+ if (!source.startsWith("/") && !source.startsWith(".")) {
253
+ try {
254
+ const resolved = require2.resolve(source, {
255
+ paths: [importer ? import_node_path2.default.dirname(importer) : config.root]
256
+ });
257
+ return resolved;
258
+ } catch {
259
+ return null;
259
260
  }
260
- } catch (err) {
261
- console.error(`[nasti] Transform error: ${url}`, err.message);
262
- res.statusCode = 500;
263
- res.end(`Transform error: ${err.message}`);
264
- return;
265
261
  }
262
+ return null;
263
+ },
264
+ load(id) {
265
+ if (!import_node_fs2.default.existsSync(id)) return null;
266
+ if (id.endsWith(".json")) {
267
+ const content = import_node_fs2.default.readFileSync(id, "utf-8");
268
+ return `export default ${content}`;
269
+ }
270
+ return import_node_fs2.default.readFileSync(id, "utf-8");
266
271
  }
267
- next();
268
272
  };
269
273
  }
270
- async function transformRequest(url, ctx) {
271
- const { config, pluginContainer, moduleGraph } = ctx;
272
- const cleanReqUrl = url.split("?")[0];
273
- const cached = moduleGraph.getModuleByUrl(url);
274
- if (cached?.transformResult) {
275
- return cached.transformResult;
276
- }
277
- if (cleanReqUrl === "/@react-refresh") {
278
- return { code: REACT_REFRESH_RUNTIME };
279
- }
280
- const filePath = resolveUrlToFile(url, config.root);
281
- if (!filePath || !import_node_fs7.default.existsSync(filePath)) return null;
282
- const mod = await moduleGraph.ensureEntryFromUrl(url);
283
- moduleGraph.registerModule(mod, filePath);
284
- if (cleanReqUrl.startsWith("/@modules/")) {
285
- const code2 = await bundlePackageAsEsm(filePath);
286
- const transformResult2 = { code: code2 };
287
- mod.transformResult = transformResult2;
288
- return transformResult2;
274
+ function tryResolveFile(file, extensions) {
275
+ if (import_node_fs2.default.existsSync(file) && import_node_fs2.default.statSync(file).isFile()) {
276
+ return file;
289
277
  }
290
- let code = import_node_fs7.default.readFileSync(filePath, "utf-8");
291
- const pluginResult = await pluginContainer.transform(code, filePath);
292
- if (pluginResult) {
293
- code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
278
+ for (const ext of extensions) {
279
+ const withExt = file + ext;
280
+ if (import_node_fs2.default.existsSync(withExt) && import_node_fs2.default.statSync(withExt).isFile()) {
281
+ return withExt;
282
+ }
294
283
  }
295
- if (shouldTransform(filePath)) {
296
- const isJsx = /\.[jt]sx$/.test(filePath);
297
- const useRefresh = isJsx && config.framework !== "vue";
298
- const result = transformCode(filePath, code, {
299
- sourcemap: true,
300
- jsxRuntime: "automatic",
301
- jsxImportSource: config.framework === "vue" ? "vue" : "react",
302
- reactRefresh: useRefresh
303
- });
304
- code = result.code;
305
- if (useRefresh) {
306
- code = REACT_REFRESH_PREAMBLE + code + REACT_REFRESH_FOOTER;
284
+ if (import_node_fs2.default.existsSync(file) && import_node_fs2.default.statSync(file).isDirectory()) {
285
+ for (const ext of extensions) {
286
+ const indexFile = import_node_path2.default.join(file, "index" + ext);
287
+ if (import_node_fs2.default.existsSync(indexFile)) {
288
+ return indexFile;
289
+ }
307
290
  }
308
291
  }
309
- const envDefine = ctx.envDefine ?? buildEnvDefine(
310
- loadEnv(config.mode, config.root, config.envPrefix),
311
- config.mode
312
- );
313
- code = replaceEnvInCode(code, envDefine);
314
- code = rewriteImports(code, config);
315
- const transformResult = { code };
316
- mod.transformResult = transformResult;
317
- return transformResult;
292
+ return null;
318
293
  }
319
- async function bundlePackageAsEsm(entryFile) {
320
- if (!esmBundleCache.has(entryFile)) {
321
- esmBundleCache.set(entryFile, doBundlePackage(entryFile));
294
+ var import_node_path2, import_node_fs2, import_node_module;
295
+ var init_resolve = __esm({
296
+ "src/plugins/resolve.ts"() {
297
+ "use strict";
298
+ import_node_path2 = __toESM(require("path"), 1);
299
+ import_node_fs2 = __toESM(require("fs"), 1);
300
+ import_node_module = require("module");
322
301
  }
323
- return esmBundleCache.get(entryFile);
302
+ });
303
+
304
+ // src/plugins/css.ts
305
+ function cssPlugin(config) {
306
+ return {
307
+ name: "nasti:css",
308
+ resolveId(source) {
309
+ if (source.endsWith(".css")) return null;
310
+ return null;
311
+ },
312
+ transform(code, id) {
313
+ if (!id.endsWith(".css")) return null;
314
+ const rewritten = rewriteCssUrls(code, id, config.root);
315
+ if (config.command === "serve") {
316
+ const escaped = JSON.stringify(rewritten);
317
+ return {
318
+ code: `
319
+ const css = ${escaped};
320
+ const __nasti_css_id__ = ${JSON.stringify(id)};
321
+ const __nasti_existing__ = document.querySelector('style[data-nasti-css=' + JSON.stringify(__nasti_css_id__) + ']');
322
+ if (__nasti_existing__) __nasti_existing__.remove();
323
+ const style = document.createElement('style');
324
+ style.setAttribute('data-nasti-css', __nasti_css_id__);
325
+ style.textContent = css;
326
+ document.head.appendChild(style);
327
+
328
+ // HMR
329
+ if (import.meta.hot) {
330
+ import.meta.hot.accept();
331
+ import.meta.hot.prune(() => {
332
+ style.remove();
333
+ });
324
334
  }
325
- async function doBundlePackage(entryFile) {
326
- const { rolldown: rolldown2 } = await import("rolldown");
327
- const bundle = await rolldown2({
328
- input: entryFile,
329
- // 仅将其他 npm 包外部化;相对路径(包内部文件)全部内联打包
330
- external: (id) => {
331
- if (id.startsWith(".") || id.startsWith("/") || /^[A-Za-z]:\\/.test(id)) return false;
332
- return true;
335
+
336
+ export default css;
337
+ `
338
+ };
339
+ }
340
+ return rewritten !== code ? { code: rewritten } : null;
341
+ }
342
+ };
343
+ }
344
+ function rewriteCssUrls(css, from, root) {
345
+ return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
346
+ if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
347
+ return match;
333
348
  }
349
+ const resolved = import_node_path3.default.resolve(import_node_path3.default.dirname(from), url);
350
+ const relative = "/" + import_node_path3.default.relative(root, resolved);
351
+ return `url(${relative})`;
334
352
  });
335
- const result = await bundle.generate({ format: "esm", exports: "named" });
336
- await bundle.close();
337
- let code = result.output[0].code;
338
- code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
339
- code = code.replace(
340
- /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
341
- (_, prefix, q, spec) => `${prefix}${q}/@modules/${spec}${q}`
342
- ).replace(
343
- /^(export\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
344
- (_, prefix, q, spec) => `${prefix}${q}/@modules/${spec}${q}`
345
- ).replace(
346
- /^(import\s+)(['"])([^'"./][^'"]*)(\2)/gm,
347
- (_, prefix, q, spec) => `${prefix}${q}/@modules/${spec}${q}`
348
- );
349
- code = rewriteExternalRequires(code);
350
- if (code.includes("__commonJSMin")) {
351
- code = await injectCjsNamedExports(code, entryFile);
352
- }
353
- return code;
354
353
  }
355
- function rewriteExternalRequires(code) {
356
- const pkgs = /* @__PURE__ */ new Set();
357
- const re = /__require\(["']([^"']+)["']\)/g;
358
- let m;
359
- while ((m = re.exec(code)) !== null) {
360
- pkgs.add(m[1]);
361
- }
362
- if (pkgs.size === 0) return code;
363
- let result = code;
364
- const imports = [];
365
- for (const pkg of pkgs) {
366
- const safe = pkg.replace(/[^a-zA-Z0-9_$]/g, "_");
367
- imports.push(`import __req_${safe} from "/@modules/${pkg}";`);
368
- result = result.replaceAll(`__require("${pkg}")`, `__req_${safe}`);
369
- result = result.replaceAll(`__require('${pkg}')`, `__req_${safe}`);
354
+ var import_node_path3;
355
+ var init_css = __esm({
356
+ "src/plugins/css.ts"() {
357
+ "use strict";
358
+ import_node_path3 = __toESM(require("path"), 1);
370
359
  }
371
- return imports.join("\n") + "\n" + result;
372
- }
373
- async function injectCjsNamedExports(code, entryFile) {
374
- try {
375
- const { createRequire: createRequire2 } = await import("module");
376
- const req = createRequire2(entryFile);
377
- const cjsExports = req(entryFile);
378
- if (!cjsExports || typeof cjsExports !== "object" && typeof cjsExports !== "function" || Array.isArray(cjsExports)) return code;
379
- const namedKeys = Object.keys(cjsExports).filter(
380
- (k) => k !== "__esModule" && k !== "default" && VALID_IDENT.test(k)
381
- );
382
- if (namedKeys.length === 0) return code;
383
- return code.replace(
384
- /^export default (\w+\(\));?\s*$/m,
385
- (_, call) => [
386
- `const __cjsMod = ${call};`,
387
- `export default __cjsMod;`,
388
- ...namedKeys.map((k) => `export const ${k} = __cjsMod[${JSON.stringify(k)}];`)
389
- ].join("\n")
390
- );
391
- } catch {
392
- return code;
393
- }
394
- }
395
- function rewriteImports(code, _config) {
396
- return code.replace(
397
- /\bfrom\s+(['"])([^'"./][^'"]*)\1/g,
398
- (match, quote, specifier) => {
399
- return `from ${quote}/@modules/${specifier}${quote}`;
400
- }
401
- ).replace(
402
- // 处理纯副作用导入: import 'bare-specifier'
403
- /\bimport\s+(['"])([^'"./][^'"]*)\1/g,
404
- (match, quote, specifier) => {
405
- return `import ${quote}/@modules/${specifier}${quote}`;
406
- }
407
- ).replace(
408
- // 处理动态导入: import('bare-specifier')
409
- /\bimport\s*\(\s*(['"])([^'"./][^'"]*)\1\s*\)/g,
410
- (match, quote, specifier) => {
411
- return `import(${quote}/@modules/${specifier}${quote})`;
412
- }
413
- );
414
- }
415
- function resolveNodeModule(root, moduleName) {
416
- let pkgName;
417
- let subpath;
418
- if (moduleName.startsWith("@")) {
419
- const parts = moduleName.split("/");
420
- pkgName = parts.slice(0, 2).join("/");
421
- subpath = parts.slice(2).join("/");
422
- } else {
423
- const slash = moduleName.indexOf("/");
424
- pkgName = slash === -1 ? moduleName : moduleName.slice(0, slash);
425
- subpath = slash === -1 ? "" : moduleName.slice(slash + 1);
426
- }
427
- let pkgDir = null;
428
- let dir = root;
429
- for (; ; ) {
430
- const candidate = import_node_path8.default.join(dir, "node_modules", pkgName);
431
- if (import_node_fs7.default.existsSync(candidate)) {
432
- pkgDir = candidate;
433
- break;
434
- }
435
- const parent = import_node_path8.default.dirname(dir);
436
- if (parent === dir) break;
437
- dir = parent;
438
- }
439
- if (!pkgDir) return null;
440
- const pkgJsonPath = import_node_path8.default.join(pkgDir, "package.json");
441
- if (!import_node_fs7.default.existsSync(pkgJsonPath)) return null;
442
- let pkg;
443
- try {
444
- pkg = JSON.parse(import_node_fs7.default.readFileSync(pkgJsonPath, "utf-8"));
445
- } catch {
446
- return null;
447
- }
448
- if (pkg.exports) {
449
- const exportKey = subpath ? `./${subpath}` : ".";
450
- const resolved = resolvePackageExports(pkg.exports, exportKey, pkgDir);
451
- if (resolved) return resolved;
452
- }
453
- if (subpath) {
454
- const subDirs = [""];
455
- for (const field of ["module", "main"]) {
456
- if (typeof pkg[field] === "string") {
457
- const dir2 = import_node_path8.default.dirname(pkg[field]);
458
- if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
360
+ });
361
+
362
+ // src/plugins/assets.ts
363
+ function assetsPlugin(config) {
364
+ return {
365
+ name: "nasti:assets",
366
+ resolveId(source) {
367
+ if (source.endsWith("?url") || source.endsWith("?raw")) {
368
+ return source;
459
369
  }
460
- }
461
- for (const dir2 of subDirs) {
462
- const direct = import_node_path8.default.join(pkgDir, dir2, subpath);
463
- if (import_node_fs7.default.existsSync(direct) && import_node_fs7.default.statSync(direct).isFile()) return direct;
464
- for (const ext of RESOLVE_EXTENSIONS) {
465
- if (import_node_fs7.default.existsSync(direct + ext)) return direct + ext;
370
+ return null;
371
+ },
372
+ load(id) {
373
+ const ext = import_node_path4.default.extname(id.replace(/\?.*$/, ""));
374
+ if (id.endsWith("?raw")) {
375
+ const file = id.slice(0, -4);
376
+ if (import_node_fs3.default.existsSync(file)) {
377
+ const content = import_node_fs3.default.readFileSync(file, "utf-8");
378
+ return `export default ${JSON.stringify(content)}`;
379
+ }
466
380
  }
381
+ if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
382
+ const file = id.replace(/\?.*$/, "");
383
+ if (!import_node_fs3.default.existsSync(file)) return null;
384
+ if (config.command === "serve") {
385
+ const url = "/" + import_node_path4.default.relative(config.root, file);
386
+ return `export default ${JSON.stringify(url)}`;
387
+ }
388
+ const content = import_node_fs3.default.readFileSync(file);
389
+ const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
390
+ const basename = import_node_path4.default.basename(file, ext);
391
+ const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
392
+ return `export default ${JSON.stringify(config.base + hashedName)}`;
393
+ }
394
+ return null;
467
395
  }
468
- return null;
469
- }
470
- for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
471
- if (typeof pkg[field] === "string") {
472
- const entry = import_node_path8.default.join(pkgDir, pkg[field]);
473
- if (import_node_fs7.default.existsSync(entry)) return entry;
474
- }
475
- }
476
- const indexFallback = import_node_path8.default.join(pkgDir, "index.js");
477
- if (import_node_fs7.default.existsSync(indexFallback)) return indexFallback;
478
- return null;
479
- }
480
- function resolvePackageExports(exports2, key, pkgDir) {
481
- if (typeof exports2 === "string") {
482
- return key === "." ? import_node_path8.default.join(pkgDir, exports2) : null;
483
- }
484
- const entry = exports2[key];
485
- if (entry === void 0) return null;
486
- return resolveExportValue(entry, pkgDir);
396
+ };
487
397
  }
488
- function resolveExportValue(value, pkgDir) {
489
- if (typeof value === "string") return import_node_path8.default.join(pkgDir, value);
490
- if (Array.isArray(value)) {
491
- for (const item of value) {
492
- const r = resolveExportValue(item, pkgDir);
493
- if (r) return r;
494
- }
495
- return null;
398
+ var import_node_path4, import_node_fs3, import_node_crypto, ASSET_EXTENSIONS;
399
+ var init_assets = __esm({
400
+ "src/plugins/assets.ts"() {
401
+ "use strict";
402
+ import_node_path4 = __toESM(require("path"), 1);
403
+ import_node_fs3 = __toESM(require("fs"), 1);
404
+ import_node_crypto = __toESM(require("crypto"), 1);
405
+ ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
406
+ ".png",
407
+ ".jpg",
408
+ ".jpeg",
409
+ ".gif",
410
+ ".svg",
411
+ ".ico",
412
+ ".webp",
413
+ ".avif",
414
+ ".mp4",
415
+ ".webm",
416
+ ".ogg",
417
+ ".mp3",
418
+ ".wav",
419
+ ".flac",
420
+ ".aac",
421
+ ".woff",
422
+ ".woff2",
423
+ ".eot",
424
+ ".ttf",
425
+ ".otf",
426
+ ".pdf",
427
+ ".txt"
428
+ ]);
496
429
  }
497
- if (value && typeof value === "object") {
498
- for (const cond of ESM_CONDITIONS) {
499
- if (cond in value) {
500
- const r = resolveExportValue(value[cond], pkgDir);
501
- if (r) return r;
430
+ });
431
+
432
+ // src/plugins/html.ts
433
+ function htmlPlugin(config) {
434
+ return {
435
+ name: "nasti:html",
436
+ enforce: "post",
437
+ transformIndexHtml(html) {
438
+ const tags = [];
439
+ if (config.command === "serve") {
440
+ tags.push({
441
+ tag: "script",
442
+ attrs: { type: "module", src: "/@nasti/client" },
443
+ injectTo: "head-prepend"
444
+ });
502
445
  }
446
+ return { html, tags };
503
447
  }
504
- }
505
- return null;
448
+ };
506
449
  }
507
- function resolveUrlToFile(url, root) {
508
- const cleanUrl = url.split("?")[0];
509
- if (cleanUrl.startsWith("/@modules/")) {
510
- const moduleName = cleanUrl.slice("/@modules/".length);
511
- return resolveNodeModule(root, moduleName);
450
+ function processHtml(html, tags) {
451
+ const headPrepend = tags.filter((t) => t.injectTo === "head-prepend");
452
+ const head = tags.filter((t) => t.injectTo === "head" || !t.injectTo);
453
+ const bodyPrepend = tags.filter((t) => t.injectTo === "body-prepend");
454
+ const body = tags.filter((t) => t.injectTo === "body");
455
+ if (headPrepend.length) {
456
+ html = html.replace(/<head([^>]*)>/i, `<head$1>
457
+ ${serializeTags(headPrepend)}`);
512
458
  }
513
- const filePath = import_node_path8.default.resolve(root, cleanUrl.replace(/^\//, ""));
514
- if (import_node_fs7.default.existsSync(filePath) && import_node_fs7.default.statSync(filePath).isFile()) {
515
- return filePath;
459
+ if (head.length) {
460
+ html = html.replace(/<\/head>/i, `${serializeTags(head)}
461
+ </head>`);
516
462
  }
517
- for (const ext of RESOLVE_EXTENSIONS) {
518
- const withExt = filePath + ext;
519
- if (import_node_fs7.default.existsSync(withExt)) return withExt;
463
+ if (bodyPrepend.length) {
464
+ html = html.replace(/<body([^>]*)>/i, `<body$1>
465
+ ${serializeTags(bodyPrepend)}`);
520
466
  }
521
- for (const ext of RESOLVE_EXTENSIONS) {
522
- const indexFile = import_node_path8.default.join(filePath, "index" + ext);
523
- if (import_node_fs7.default.existsSync(indexFile)) return indexFile;
467
+ if (body.length) {
468
+ html = html.replace(/<\/body>/i, `${serializeTags(body)}
469
+ </body>`);
524
470
  }
525
- return null;
471
+ return html;
526
472
  }
527
- function isModuleRequest(url) {
528
- const cleanUrl = url.split("?")[0];
529
- if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
530
- if (cleanUrl.startsWith("/@modules/")) return true;
531
- if (!import_node_path8.default.extname(cleanUrl)) return true;
532
- return false;
473
+ function serializeTags(tags) {
474
+ return tags.map(serializeTag).join("\n");
533
475
  }
534
- function getHmrClientCode() {
535
- return `
536
- // Nasti HMR Client
537
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
538
- const hotModulesMap = new Map();
539
-
540
- socket.addEventListener('message', ({ data }) => {
541
- const payload = JSON.parse(data);
542
- switch (payload.type) {
543
- case 'connected':
544
- console.log('[nasti] connected.');
545
- break;
546
- case 'update':
547
- payload.updates.forEach((update) => {
548
- if (update.type === 'js-update') {
549
- fetchUpdate(update);
550
- } else if (update.type === 'css-update') {
551
- updateCss(update.path);
552
- }
553
- });
554
- break;
555
- case 'full-reload':
556
- console.log('[nasti] full reload');
557
- location.reload();
558
- break;
559
- case 'error':
560
- console.error('[nasti] error:', payload.err.message);
561
- showErrorOverlay(payload.err);
562
- break;
563
- }
564
- });
565
-
566
- async function fetchUpdate(update) {
567
- const mod = hotModulesMap.get(update.path);
568
- if (mod) {
569
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
570
- mod.callbacks.forEach((cb) => cb(newMod));
571
- } else {
572
- // \u6CA1\u6709\u6CE8\u518C hot \u56DE\u8C03\uFF0C\u5C1D\u8BD5\u91CD\u65B0 import
573
- await import(update.path + '?t=' + update.timestamp);
476
+ function serializeTag(tag) {
477
+ const attrs = tag.attrs ? " " + Object.entries(tag.attrs).map(([k, v]) => v === true ? k : `${k}="${v}"`).join(" ") : "";
478
+ const children = typeof tag.children === "string" ? tag.children : tag.children ? serializeTags(tag.children) : "";
479
+ const selfClosing = ["link", "meta", "br", "hr", "img", "input"].includes(tag.tag);
480
+ if (selfClosing && !children) {
481
+ return ` <${tag.tag}${attrs} />`;
574
482
  }
483
+ return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
575
484
  }
576
-
577
- function updateCss(path) {
578
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
579
- if (el) {
580
- fetch(path + '?t=' + Date.now())
581
- .then(r => r.text())
582
- .then(css => { el.textContent = css; });
583
- }
485
+ async function readHtmlFile(root) {
486
+ const htmlPath = import_node_path5.default.resolve(root, "index.html");
487
+ if (!import_node_fs4.default.existsSync(htmlPath)) return null;
488
+ return import_node_fs4.default.readFileSync(htmlPath, "utf-8");
584
489
  }
490
+ var import_node_path5, import_node_fs4;
491
+ var init_html = __esm({
492
+ "src/plugins/html.ts"() {
493
+ "use strict";
494
+ import_node_path5 = __toESM(require("path"), 1);
495
+ import_node_fs4 = __toESM(require("fs"), 1);
496
+ }
497
+ });
585
498
 
586
- function showErrorOverlay(err) {
587
- const overlay = document.createElement('div');
588
- overlay.id = 'nasti-error-overlay';
589
- 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;';
590
- const title = document.createElement('h2');
591
- title.style.color = '#ff5555';
592
- title.textContent = 'Build Error';
593
- const pre = document.createElement('pre');
594
- pre.textContent = err.message + '\\n' + (err.stack || '');
595
- const btn = document.createElement('button');
596
- btn.style.cssText = 'margin-top:1rem;padding:0.5rem 1rem;cursor:pointer';
597
- btn.textContent = 'Close';
598
- btn.onclick = () => overlay.remove();
599
- overlay.appendChild(title);
600
- overlay.appendChild(pre);
601
- overlay.appendChild(btn);
602
- document.body.appendChild(overlay);
499
+ // src/core/transformer.ts
500
+ function shouldTransform(id) {
501
+ return TS_EXTENSIONS.test(id) || JSX_EXTENSIONS.test(id) || JS_EXTENSIONS.test(id) && false;
603
502
  }
604
-
605
- // import.meta.hot API
606
- const createHotContext = (ownerPath) => ({
607
- accept(deps, callback) {
608
- if (typeof deps === 'function' || !deps) {
609
- // self-accepting
610
- const callbacks = hotModulesMap.get(ownerPath)?.callbacks || [];
611
- callbacks.push(deps || (() => {}));
612
- hotModulesMap.set(ownerPath, { callbacks });
613
- }
614
- },
615
- prune(callback) {
616
- // \u6A21\u5757\u88AB\u79FB\u9664\u65F6\u6267\u884C
617
- },
618
- dispose(callback) {
619
- // \u6A21\u5757\u66F4\u65B0\u524D\u6267\u884C\u6E05\u7406
620
- },
621
- invalidate() {
622
- location.reload();
623
- },
624
- data: {},
625
- });
626
-
627
- // \u66B4\u9732\u7ED9\u6A21\u5757\u4F7F\u7528
628
- if (!window.__nasti_hot_map) window.__nasti_hot_map = new Map();
629
- window.__NASTI_HMR__ = { createHotContext };
630
- `;
503
+ function transformCode(filename, code, options = {}) {
504
+ const isTS = TS_EXTENSIONS.test(filename) || /\.tsx$/.test(filename);
505
+ const isJSX = JSX_EXTENSIONS.test(filename);
506
+ const result = (0, import_oxc_transform.transformSync)(filename, code, {
507
+ typescript: isTS ? {} : void 0,
508
+ jsx: isJSX || /\.tsx$/.test(filename) ? {
509
+ runtime: options.jsxRuntime ?? "automatic",
510
+ importSource: options.jsxImportSource ?? "react",
511
+ refresh: options.reactRefresh ?? false
512
+ } : void 0,
513
+ sourcemap: options.sourcemap ?? true
514
+ });
515
+ if (result.errors && result.errors.length > 0) {
516
+ const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
517
+ throw new Error(`OXC transform failed for ${filename}:
518
+ ${msg}`);
519
+ }
520
+ return {
521
+ code: result.code,
522
+ map: result.map ? JSON.stringify(result.map) : null
523
+ };
631
524
  }
632
- var import_node_path8, import_node_fs7, REACT_REFRESH_RUNTIME, REACT_REFRESH_PREAMBLE, REACT_REFRESH_FOOTER, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
633
- var init_middleware = __esm({
634
- "src/server/middleware.ts"() {
525
+ var import_oxc_transform, JS_EXTENSIONS, TS_EXTENSIONS, JSX_EXTENSIONS;
526
+ var init_transformer = __esm({
527
+ "src/core/transformer.ts"() {
635
528
  "use strict";
636
- import_node_path8 = __toESM(require("path"), 1);
637
- import_node_fs7 = __toESM(require("fs"), 1);
638
- init_transformer();
639
- init_html();
640
- init_env();
641
- REACT_REFRESH_RUNTIME = `
642
- export function createSignatureFunctionForTransform() {
643
- return function(type, key, forceReset, getCustomHooks) { return type; };
644
- }
645
- export function register(type, id) {}
646
- export default { createSignatureFunctionForTransform, register };
647
- `;
648
- REACT_REFRESH_PREAMBLE = `
649
- import RefreshRuntime from '/@react-refresh';
650
- if (!window.$RefreshReg$) {
651
- window.$RefreshReg$ = (type, id) => RefreshRuntime.register(type, import.meta.url + ' ' + id);
652
- window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
653
- }
654
- `;
655
- REACT_REFRESH_FOOTER = `
656
- if (import.meta.hot) {
657
- import.meta.hot.accept();
658
- }
659
- `;
660
- esmBundleCache = /* @__PURE__ */ new Map();
661
- VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
662
- RESOLVE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".json", ".vue"];
663
- ESM_CONDITIONS = ["import", "browser", "module", "default"];
529
+ import_oxc_transform = require("oxc-transform");
530
+ JS_EXTENSIONS = /\.(js|mjs|cjs)$/;
531
+ TS_EXTENSIONS = /\.(ts|mts|cts)$/;
532
+ JSX_EXTENSIONS = /\.(jsx|tsx)$/;
664
533
  }
665
534
  });
666
535
 
667
- // src/index.ts
668
- var src_exports = {};
669
- __export(src_exports, {
670
- build: () => build,
671
- createServer: () => createServer,
672
- defineConfig: () => defineConfig,
673
- resolveConfig: () => resolveConfig
674
- });
675
- module.exports = __toCommonJS(src_exports);
676
-
677
- // src/config/index.ts
678
- var import_node_url = require("url");
679
- var import_node_path = __toESM(require("path"), 1);
680
- var import_node_fs = __toESM(require("fs"), 1);
681
-
682
- // src/config/defaults.ts
683
- var defaultResolve = {
684
- alias: {},
685
- extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".vue"],
686
- conditions: ["import", "module", "browser", "default"],
687
- mainFields: ["module", "jsnext:main", "jsnext", "main"]
688
- };
689
- var defaultServer = {
690
- port: 3e3,
691
- host: "localhost",
692
- https: false,
693
- open: false,
694
- proxy: {},
695
- cors: true,
696
- hmr: true
697
- };
698
- var defaultBuild = {
699
- outDir: "dist",
700
- assetsDir: "assets",
701
- minify: true,
702
- sourcemap: false,
703
- target: "es2022",
704
- rolldownOptions: {},
705
- emptyOutDir: true
706
- };
707
- var defaults = {
708
- root: ".",
709
- base: "/",
710
- mode: "development",
711
- framework: "auto",
712
- resolve: defaultResolve,
713
- server: defaultServer,
714
- build: defaultBuild,
715
- plugins: [],
716
- envPrefix: ["NASTI_", "VITE_"],
717
- logLevel: "info"
718
- };
719
-
720
- // src/config/index.ts
721
- function loadTsconfigPaths(root) {
722
- const tsconfigPath = import_node_path.default.resolve(root, "tsconfig.json");
723
- if (!import_node_fs.default.existsSync(tsconfigPath)) return {};
724
- try {
725
- const content = import_node_fs.default.readFileSync(tsconfigPath, "utf-8");
726
- const stripped = content.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
727
- const tsconfig = JSON.parse(stripped);
728
- const paths = tsconfig?.compilerOptions?.paths ?? {};
729
- const baseUrl = tsconfig?.compilerOptions?.baseUrl ?? ".";
730
- const alias = {};
731
- for (const [pattern, targets] of Object.entries(paths)) {
732
- if (!targets.length) continue;
733
- const cleanKey = pattern.replace(/\/\*$/, "");
734
- const cleanTarget = targets[0].replace(/\/\*$/, "");
735
- alias[cleanKey] = import_node_path.default.resolve(root, baseUrl, cleanTarget);
536
+ // src/core/env.ts
537
+ function loadEnv(mode, root, prefixes) {
538
+ const envFiles = [
539
+ ".env",
540
+ `.env.${mode}`,
541
+ ".env.local",
542
+ `.env.${mode}.local`
543
+ ];
544
+ const raw = {};
545
+ for (const file of envFiles) {
546
+ const filePath = import_node_path6.default.resolve(root, file);
547
+ if (!import_node_fs5.default.existsSync(filePath)) continue;
548
+ const content = import_node_fs5.default.readFileSync(filePath, "utf-8");
549
+ for (const line of content.split("\n")) {
550
+ const trimmed = line.trim();
551
+ if (!trimmed || trimmed.startsWith("#")) continue;
552
+ const eqIdx = trimmed.indexOf("=");
553
+ if (eqIdx === -1) continue;
554
+ const key = trimmed.slice(0, eqIdx).trim();
555
+ let value = trimmed.slice(eqIdx + 1).trim();
556
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
557
+ value = value.slice(1, -1);
558
+ }
559
+ raw[key] = value;
736
560
  }
737
- return alias;
738
- } catch {
739
- return {};
740
561
  }
741
- }
742
- function defineConfig(config) {
743
- return config;
744
- }
745
- var CONFIG_FILES = [
746
- "nasti.config.ts",
747
- "nasti.config.js",
748
- "nasti.config.mjs",
749
- "nasti.config.mts"
750
- ];
751
- async function loadConfigFromFile(root) {
752
- for (const file of CONFIG_FILES) {
753
- const filePath = import_node_path.default.resolve(root, file);
754
- if (!import_node_fs.default.existsSync(filePath)) continue;
755
- if (file.endsWith(".ts") || file.endsWith(".mts")) {
756
- return await loadTsConfig(filePath);
562
+ const filtered = {};
563
+ for (const [key, value] of Object.entries(raw)) {
564
+ if (prefixes.some((prefix) => key.startsWith(prefix))) {
565
+ filtered[key] = value;
757
566
  }
758
- const mod = await import((0, import_node_url.pathToFileURL)(filePath).href);
759
- return mod.default ?? mod;
760
567
  }
761
- return {};
568
+ return filtered;
762
569
  }
763
- async function loadTsConfig(filePath) {
764
- const { transformSync: transformSync2 } = await import("oxc-transform");
765
- const code = import_node_fs.default.readFileSync(filePath, "utf-8");
766
- const result = transformSync2(filePath, code, {
767
- typescript: {}
768
- });
769
- const tmpFile = filePath + ".timestamp-" + Date.now() + ".mjs";
770
- try {
771
- import_node_fs.default.writeFileSync(tmpFile, result.code);
772
- const mod = await import((0, import_node_url.pathToFileURL)(tmpFile).href);
773
- return mod.default ?? mod;
774
- } finally {
775
- import_node_fs.default.unlinkSync(tmpFile);
570
+ function buildEnvDefine(env, mode) {
571
+ const define = {};
572
+ for (const [key, value] of Object.entries(env)) {
573
+ define[`import.meta.env.${key}`] = JSON.stringify(value);
776
574
  }
575
+ define["import.meta.env.MODE"] = JSON.stringify(mode);
576
+ define["import.meta.env.DEV"] = mode !== "production" ? "true" : "false";
577
+ define["import.meta.env.PROD"] = mode === "production" ? "true" : "false";
578
+ define["import.meta.env.SSR"] = "false";
579
+ return define;
777
580
  }
778
- async function resolveConfig(inlineConfig = {}, command) {
779
- const root = import_node_path.default.resolve(inlineConfig.root ?? defaults.root);
780
- const fileConfig = await loadConfigFromFile(root);
781
- const merged = deepMerge(deepMerge({}, fileConfig), inlineConfig);
782
- const rawPlugins = [
783
- ...fileConfig.plugins ?? [],
784
- ...inlineConfig.plugins ?? []
785
- ];
786
- const env = { mode: merged.mode ?? defaults.mode, command };
787
- for (const plugin of rawPlugins) {
788
- if (plugin.config) {
789
- const result = await plugin.config(merged, env);
790
- if (result) Object.assign(merged, result);
791
- }
792
- }
793
- const resolved = {
794
- root,
795
- base: merged.base ?? defaults.base,
796
- mode: command === "build" ? "production" : "development",
797
- framework: merged.framework ?? defaults.framework,
798
- command,
799
- resolve: {
800
- // tsconfig paths 优先级最低:tsconfig < defaults < user config
801
- alias: { ...loadTsconfigPaths(root), ...defaults.resolve.alias, ...merged.resolve?.alias },
802
- extensions: merged.resolve?.extensions ?? defaults.resolve.extensions,
803
- conditions: merged.resolve?.conditions ?? defaults.resolve.conditions,
804
- mainFields: merged.resolve?.mainFields ?? defaults.resolve.mainFields
805
- },
806
- plugins: [],
807
- server: { ...defaults.server, ...merged.server },
808
- build: { ...defaults.build, ...merged.build },
809
- envPrefix: Array.isArray(merged.envPrefix) ? merged.envPrefix : merged.envPrefix ? [merged.envPrefix] : [...defaults.envPrefix],
810
- logLevel: merged.logLevel ?? defaults.logLevel
811
- };
812
- const filteredPlugins = rawPlugins.filter((p) => {
813
- if (!p.apply) return true;
814
- if (typeof p.apply === "function") return p.apply(resolved, env);
815
- return p.apply === command;
816
- });
817
- resolved.plugins = filteredPlugins;
818
- for (const plugin of resolved.plugins) {
819
- if (plugin.configResolved) {
820
- await plugin.configResolved(resolved);
821
- }
581
+ function replaceEnvInCode(code, define) {
582
+ let result = code;
583
+ for (const [key, value] of Object.entries(define)) {
584
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
585
+ result = result.replace(new RegExp(escaped, "g"), value);
822
586
  }
823
- return resolved;
587
+ return result;
824
588
  }
825
- function deepMerge(target, source) {
826
- const result = { ...target };
827
- for (const key of Object.keys(source)) {
828
- const val = source[key];
829
- if (val && typeof val === "object" && !Array.isArray(val)) {
830
- result[key] = deepMerge(
831
- result[key] ?? {},
832
- val
833
- );
834
- } else if (val !== void 0) {
835
- result[key] = val;
836
- }
589
+ var import_node_path6, import_node_fs5;
590
+ var init_env = __esm({
591
+ "src/core/env.ts"() {
592
+ "use strict";
593
+ import_node_path6 = __toESM(require("path"), 1);
594
+ import_node_fs5 = __toESM(require("fs"), 1);
837
595
  }
838
- return result;
839
- }
840
-
841
- // src/build/index.ts
842
- var import_node_path7 = __toESM(require("path"), 1);
843
- var import_node_fs6 = __toESM(require("fs"), 1);
844
- var import_rolldown = require("rolldown");
596
+ });
845
597
 
846
- // src/plugins/resolve.ts
847
- var import_node_path2 = __toESM(require("path"), 1);
848
- var import_node_fs2 = __toESM(require("fs"), 1);
849
- var import_node_module = require("module");
850
- function resolvePlugin(config) {
851
- const { alias, extensions } = config.resolve;
852
- const require2 = (0, import_node_module.createRequire)(import_node_path2.default.resolve(config.root, "package.json"));
853
- return {
854
- name: "nasti:resolve",
855
- enforce: "pre",
856
- resolveId(source, importer) {
857
- for (const [key, value] of Object.entries(alias)) {
858
- if (source === key || source.startsWith(key + "/")) {
859
- source = source.replace(key, value);
860
- if (!import_node_path2.default.isAbsolute(source)) {
861
- source = import_node_path2.default.resolve(config.root, source);
598
+ // src/core/plugin-container.ts
599
+ function sortPlugins(plugins) {
600
+ const pre = [];
601
+ const normal = [];
602
+ const post = [];
603
+ for (const plugin of plugins) {
604
+ if (plugin.enforce === "pre") pre.push(plugin);
605
+ else if (plugin.enforce === "post") post.push(plugin);
606
+ else normal.push(plugin);
607
+ }
608
+ return [...pre, ...normal, ...post];
609
+ }
610
+ var PluginContainer;
611
+ var init_plugin_container = __esm({
612
+ "src/core/plugin-container.ts"() {
613
+ "use strict";
614
+ PluginContainer = class {
615
+ plugins;
616
+ config;
617
+ ctx;
618
+ emittedFiles = /* @__PURE__ */ new Map();
619
+ constructor(config) {
620
+ this.config = config;
621
+ this.plugins = sortPlugins(config.plugins);
622
+ this.ctx = this.createContext();
623
+ }
624
+ createContext() {
625
+ const container = this;
626
+ return {
627
+ async resolve(source, importer) {
628
+ return container.resolveId(source, importer);
629
+ },
630
+ emitFile(file) {
631
+ const fileName = file.fileName ?? file.name ?? `asset-${container.emittedFiles.size}`;
632
+ const id = `emitted:${fileName}`;
633
+ container.emittedFiles.set(id, {
634
+ fileName,
635
+ source: file.source ?? ""
636
+ });
637
+ return id;
638
+ },
639
+ getModuleInfo(_id) {
640
+ return null;
641
+ }
642
+ };
643
+ }
644
+ /** 返回所有通过 emitFile() 输出的文件 */
645
+ getEmittedFiles() {
646
+ return Array.from(this.emittedFiles.values());
647
+ }
648
+ async buildStart() {
649
+ for (const plugin of this.plugins) {
650
+ if (plugin.buildStart) {
651
+ await plugin.buildStart.call(this.ctx);
862
652
  }
863
- break;
864
653
  }
865
654
  }
866
- if (import_node_path2.default.isAbsolute(source)) {
867
- const resolved = tryResolveFile(source, extensions);
868
- if (resolved) return resolved;
655
+ async buildEnd(error) {
656
+ for (const plugin of this.plugins) {
657
+ if (plugin.buildEnd) {
658
+ await plugin.buildEnd.call(this.ctx, error);
659
+ }
660
+ }
869
661
  }
870
- if (source.startsWith(".")) {
871
- const dir = importer ? import_node_path2.default.dirname(importer) : config.root;
872
- const absolute = import_node_path2.default.resolve(dir, source);
873
- const resolved = tryResolveFile(absolute, extensions);
874
- if (resolved) return resolved;
662
+ async resolveId(source, importer, options = {}) {
663
+ for (const plugin of this.plugins) {
664
+ if (!plugin.resolveId) continue;
665
+ const result = await plugin.resolveId.call(
666
+ this.ctx,
667
+ source,
668
+ importer ?? void 0,
669
+ { isEntry: options.isEntry ?? false, ssr: false }
670
+ );
671
+ if (result != null) return result;
672
+ }
673
+ return null;
875
674
  }
876
- if (!source.startsWith("/") && !source.startsWith(".")) {
877
- try {
878
- const resolved = require2.resolve(source, {
879
- paths: [importer ? import_node_path2.default.dirname(importer) : config.root]
880
- });
881
- return resolved;
882
- } catch {
883
- return null;
675
+ async load(id) {
676
+ for (const plugin of this.plugins) {
677
+ if (!plugin.load) continue;
678
+ const result = await plugin.load.call(this.ctx, id);
679
+ if (result != null) return result;
884
680
  }
681
+ return null;
885
682
  }
886
- return null;
887
- },
888
- load(id) {
889
- if (!import_node_fs2.default.existsSync(id)) return null;
890
- if (id.endsWith(".json")) {
891
- const content = import_node_fs2.default.readFileSync(id, "utf-8");
892
- return `export default ${content}`;
683
+ async transform(code, id) {
684
+ let currentCode = code;
685
+ for (const plugin of this.plugins) {
686
+ if (!plugin.transform) continue;
687
+ const result = await plugin.transform.call(this.ctx, currentCode, id);
688
+ if (result == null) continue;
689
+ if (typeof result === "string") {
690
+ currentCode = result;
691
+ } else {
692
+ currentCode = result.code;
693
+ }
694
+ }
695
+ return currentCode === code ? null : { code: currentCode };
893
696
  }
894
- return import_node_fs2.default.readFileSync(id, "utf-8");
895
- }
896
- };
897
- }
898
- function tryResolveFile(file, extensions) {
899
- if (import_node_fs2.default.existsSync(file) && import_node_fs2.default.statSync(file).isFile()) {
900
- return file;
697
+ /** 完整的模块处理管道: resolveId → load → transform */
698
+ async processModule(source, importer) {
699
+ const resolveResult = await this.resolveId(source, importer, {
700
+ isEntry: !importer
701
+ });
702
+ if (resolveResult == null) return null;
703
+ const id = typeof resolveResult === "string" ? resolveResult : resolveResult.id;
704
+ const loadResult = await this.load(id);
705
+ if (loadResult == null) return null;
706
+ const loadedCode = typeof loadResult === "string" ? loadResult : loadResult.code;
707
+ const transformResult = await this.transform(loadedCode, id);
708
+ const finalCode = transformResult == null ? loadedCode : typeof transformResult === "string" ? transformResult : transformResult.code;
709
+ return { id, code: finalCode };
710
+ }
711
+ getPlugins() {
712
+ return this.plugins;
713
+ }
714
+ };
901
715
  }
902
- for (const ext of extensions) {
903
- const withExt = file + ext;
904
- if (import_node_fs2.default.existsSync(withExt) && import_node_fs2.default.statSync(withExt).isFile()) {
905
- return withExt;
716
+ });
717
+
718
+ // src/build/index.ts
719
+ var build_exports = {};
720
+ __export(build_exports, {
721
+ build: () => build
722
+ });
723
+ async function build(inlineConfig = {}) {
724
+ const config = await resolveConfig(inlineConfig, "build");
725
+ const startTime = performance.now();
726
+ console.log(import_picocolors.default.cyan("\n\u{1F528} nasti build") + import_picocolors.default.dim(` v${"1.4.0"}`));
727
+ console.log(import_picocolors.default.dim(` root: ${config.root}`));
728
+ console.log(import_picocolors.default.dim(` mode: ${config.mode}`));
729
+ const outDir = import_node_path7.default.resolve(config.root, config.build.outDir);
730
+ if (config.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
731
+ import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
732
+ }
733
+ import_node_fs6.default.mkdirSync(outDir, { recursive: true });
734
+ const html = await readHtmlFile(config.root);
735
+ let entryPoints = [];
736
+ if (html) {
737
+ const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
738
+ for (const match of scriptMatches) {
739
+ const src = match[1];
740
+ if (src && !src.startsWith("http")) {
741
+ entryPoints.push(import_node_path7.default.resolve(config.root, src.replace(/^\//, "")));
742
+ }
906
743
  }
907
744
  }
908
- if (import_node_fs2.default.existsSync(file) && import_node_fs2.default.statSync(file).isDirectory()) {
909
- for (const ext of extensions) {
910
- const indexFile = import_node_path2.default.join(file, "index" + ext);
911
- if (import_node_fs2.default.existsSync(indexFile)) {
912
- return indexFile;
745
+ if (entryPoints.length === 0) {
746
+ const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
747
+ for (const entry of fallbackEntries) {
748
+ const fullPath = import_node_path7.default.resolve(config.root, entry);
749
+ if (import_node_fs6.default.existsSync(fullPath)) {
750
+ entryPoints.push(fullPath);
751
+ break;
913
752
  }
914
753
  }
915
754
  }
916
- return null;
917
- }
918
-
919
- // src/plugins/css.ts
920
- var import_node_path3 = __toESM(require("path"), 1);
921
- function cssPlugin(config) {
922
- return {
923
- name: "nasti:css",
924
- resolveId(source) {
925
- if (source.endsWith(".css")) return null;
926
- return null;
927
- },
755
+ if (entryPoints.length === 0) {
756
+ throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
757
+ }
758
+ const builtinPlugins = [
759
+ resolvePlugin(config),
760
+ cssPlugin(config),
761
+ assetsPlugin(config)
762
+ ];
763
+ const allPlugins = [...builtinPlugins, ...config.plugins];
764
+ const pluginContainer = new PluginContainer(config);
765
+ await pluginContainer.buildStart();
766
+ const oxcTransformPlugin = {
767
+ name: "nasti:oxc-transform",
928
768
  transform(code, id) {
929
- if (!id.endsWith(".css")) return null;
930
- const rewritten = rewriteCssUrls(code, id, config.root);
931
- if (config.command === "serve") {
932
- const escaped = JSON.stringify(rewritten);
933
- return {
934
- code: `
935
- const css = ${escaped};
936
- const __nasti_css_id__ = ${JSON.stringify(id)};
937
- const __nasti_existing__ = document.querySelector('style[data-nasti-css=' + JSON.stringify(__nasti_css_id__) + ']');
938
- if (__nasti_existing__) __nasti_existing__.remove();
939
- const style = document.createElement('style');
940
- style.setAttribute('data-nasti-css', __nasti_css_id__);
941
- style.textContent = css;
942
- document.head.appendChild(style);
943
-
944
- // HMR
945
- if (import.meta.hot) {
946
- import.meta.hot.accept();
947
- import.meta.hot.prune(() => {
948
- style.remove();
769
+ if (!shouldTransform(id)) return null;
770
+ const result = transformCode(id, code, {
771
+ sourcemap: !!config.build.sourcemap,
772
+ jsxRuntime: "automatic",
773
+ jsxImportSource: config.framework === "vue" ? "vue" : "react"
774
+ });
775
+ return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
776
+ }
777
+ };
778
+ const env = loadEnv(config.mode, config.root, config.envPrefix);
779
+ const envDefine = buildEnvDefine(env, config.mode);
780
+ const bundle = await (0, import_rolldown.rolldown)({
781
+ input: entryPoints,
782
+ define: envDefine,
783
+ plugins: [
784
+ oxcTransformPlugin,
785
+ // 转换 Nasti 插件为 Rolldown 插件格式
786
+ ...allPlugins.map((p) => ({
787
+ name: p.name,
788
+ resolveId: p.resolveId,
789
+ load: p.load,
790
+ transform: p.transform,
791
+ buildStart: p.buildStart,
792
+ buildEnd: p.buildEnd
793
+ }))
794
+ ],
795
+ ...config.build.rolldownOptions
949
796
  });
950
- }
951
-
952
- export default css;
953
- `
954
- };
797
+ const { output } = await bundle.write({
798
+ dir: outDir,
799
+ format: "esm",
800
+ sourcemap: !!config.build.sourcemap,
801
+ minify: !!config.build.minify,
802
+ entryFileNames: "assets/[name].[hash].js",
803
+ chunkFileNames: "assets/[name].[hash].js",
804
+ assetFileNames: "assets/[name].[hash][extname]"
805
+ });
806
+ await bundle.close();
807
+ await pluginContainer.buildEnd();
808
+ for (const ef of pluginContainer.getEmittedFiles()) {
809
+ const dest = import_node_path7.default.resolve(outDir, ef.fileName);
810
+ import_node_fs6.default.mkdirSync(import_node_path7.default.dirname(dest), { recursive: true });
811
+ import_node_fs6.default.writeFileSync(dest, ef.source);
812
+ }
813
+ if (html) {
814
+ let processedHtml = html;
815
+ const htmlPlugin_ = htmlPlugin(config);
816
+ if (htmlPlugin_.transformIndexHtml) {
817
+ const result = await htmlPlugin_.transformIndexHtml(processedHtml);
818
+ if (typeof result === "string") {
819
+ processedHtml = result;
820
+ } else if (result && "html" in result) {
821
+ processedHtml = processHtml(result.html, result.tags);
822
+ } else if (Array.isArray(result)) {
823
+ processedHtml = processHtml(processedHtml, result);
955
824
  }
956
- return rewritten !== code ? { code: rewritten } : null;
957
825
  }
958
- };
959
- }
960
- function rewriteCssUrls(css, from, root) {
961
- return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
962
- if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
963
- return match;
826
+ for (const chunk of output) {
827
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
828
+ const originalEntry = import_node_path7.default.relative(config.root, chunk.facadeModuleId);
829
+ processedHtml = processedHtml.replace(
830
+ new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
831
+ `$1${config.base}${chunk.fileName}$3`
832
+ );
833
+ }
964
834
  }
965
- const resolved = import_node_path3.default.resolve(import_node_path3.default.dirname(from), url);
966
- const relative = "/" + import_node_path3.default.relative(root, resolved);
967
- return `url(${relative})`;
968
- });
835
+ import_node_fs6.default.writeFileSync(import_node_path7.default.resolve(outDir, "index.html"), processedHtml);
836
+ }
837
+ const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
838
+ const totalSize = output.reduce((sum, chunk) => {
839
+ if (chunk.type === "chunk" && chunk.code) return sum + chunk.code.length;
840
+ return sum;
841
+ }, 0);
842
+ console.log(import_picocolors.default.green(`
843
+ \u2713 Built in ${elapsed}s`));
844
+ console.log(import_picocolors.default.dim(` ${output.length} files, ${formatSize(totalSize)} total`));
845
+ console.log(import_picocolors.default.dim(` output: ${config.build.outDir}/
846
+ `));
847
+ return { output };
969
848
  }
849
+ function formatSize(bytes) {
850
+ if (bytes < 1024) return `${bytes} B`;
851
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} kB`;
852
+ return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
853
+ }
854
+ function escapeRegExp(string) {
855
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
856
+ }
857
+ var import_node_path7, import_node_fs6, import_rolldown, import_picocolors;
858
+ var init_build = __esm({
859
+ "src/build/index.ts"() {
860
+ "use strict";
861
+ import_node_path7 = __toESM(require("path"), 1);
862
+ import_node_fs6 = __toESM(require("fs"), 1);
863
+ import_rolldown = require("rolldown");
864
+ init_config();
865
+ init_resolve();
866
+ init_css();
867
+ init_assets();
868
+ init_html();
869
+ init_transformer();
870
+ init_env();
871
+ init_plugin_container();
872
+ import_picocolors = __toESM(require("picocolors"), 1);
873
+ }
874
+ });
970
875
 
971
- // src/plugins/assets.ts
972
- var import_node_path4 = __toESM(require("path"), 1);
973
- var import_node_fs3 = __toESM(require("fs"), 1);
974
- var import_node_crypto = __toESM(require("crypto"), 1);
975
- var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
976
- ".png",
977
- ".jpg",
978
- ".jpeg",
979
- ".gif",
980
- ".svg",
981
- ".ico",
982
- ".webp",
983
- ".avif",
984
- ".mp4",
985
- ".webm",
986
- ".ogg",
987
- ".mp3",
988
- ".wav",
989
- ".flac",
990
- ".aac",
991
- ".woff",
992
- ".woff2",
993
- ".eot",
994
- ".ttf",
995
- ".otf",
996
- ".pdf",
997
- ".txt"
998
- ]);
999
- function assetsPlugin(config) {
1000
- return {
1001
- name: "nasti:assets",
1002
- resolveId(source) {
1003
- if (source.endsWith("?url") || source.endsWith("?raw")) {
1004
- return source;
876
+ // src/core/module-graph.ts
877
+ var ModuleGraph;
878
+ var init_module_graph = __esm({
879
+ "src/core/module-graph.ts"() {
880
+ "use strict";
881
+ ModuleGraph = class {
882
+ urlToModuleMap = /* @__PURE__ */ new Map();
883
+ idToModuleMap = /* @__PURE__ */ new Map();
884
+ fileToModulesMap = /* @__PURE__ */ new Map();
885
+ getModuleByUrl(url) {
886
+ return this.urlToModuleMap.get(url);
1005
887
  }
1006
- return null;
1007
- },
1008
- load(id) {
1009
- const ext = import_node_path4.default.extname(id.replace(/\?.*$/, ""));
1010
- if (id.endsWith("?raw")) {
1011
- const file = id.slice(0, -4);
1012
- if (import_node_fs3.default.existsSync(file)) {
1013
- const content = import_node_fs3.default.readFileSync(file, "utf-8");
1014
- return `export default ${JSON.stringify(content)}`;
888
+ getModuleById(id) {
889
+ return this.idToModuleMap.get(id);
890
+ }
891
+ getModulesByFile(file) {
892
+ return this.fileToModulesMap.get(file);
893
+ }
894
+ async ensureEntryFromUrl(url) {
895
+ let mod = this.urlToModuleMap.get(url);
896
+ if (mod) return mod;
897
+ mod = this.createModule(url);
898
+ this.urlToModuleMap.set(url, mod);
899
+ return mod;
900
+ }
901
+ createModule(url, id) {
902
+ const mod = {
903
+ id: id ?? url,
904
+ file: null,
905
+ url,
906
+ type: url.endsWith(".css") ? "css" : "js",
907
+ importers: /* @__PURE__ */ new Set(),
908
+ importedModules: /* @__PURE__ */ new Set(),
909
+ acceptedHmrDeps: /* @__PURE__ */ new Set(),
910
+ transformResult: null,
911
+ lastHMRTimestamp: 0,
912
+ isSelfAccepting: false
913
+ };
914
+ this.idToModuleMap.set(mod.id, mod);
915
+ return mod;
916
+ }
917
+ /** 注册文件路径到模块的映射 */
918
+ registerModule(mod, file) {
919
+ mod.file = file;
920
+ let mods = this.fileToModulesMap.get(file);
921
+ if (!mods) {
922
+ mods = /* @__PURE__ */ new Set();
923
+ this.fileToModulesMap.set(file, mods);
1015
924
  }
925
+ mods.add(mod);
1016
926
  }
1017
- if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
1018
- const file = id.replace(/\?.*$/, "");
1019
- if (!import_node_fs3.default.existsSync(file)) return null;
1020
- if (config.command === "serve") {
1021
- const url = "/" + import_node_path4.default.relative(config.root, file);
1022
- return `export default ${JSON.stringify(url)}`;
927
+ /** 更新模块依赖关系 */
928
+ updateModuleImports(mod, importedIds) {
929
+ for (const imported of mod.importedModules) {
930
+ imported.importers.delete(mod);
931
+ }
932
+ mod.importedModules.clear();
933
+ for (const id of importedIds) {
934
+ const importedMod = this.idToModuleMap.get(id);
935
+ if (importedMod) {
936
+ mod.importedModules.add(importedMod);
937
+ importedMod.importers.add(mod);
938
+ }
1023
939
  }
1024
- const content = import_node_fs3.default.readFileSync(file);
1025
- const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
1026
- const basename = import_node_path4.default.basename(file, ext);
1027
- const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
1028
- return `export default ${JSON.stringify(config.base + hashedName)}`;
1029
940
  }
1030
- return null;
941
+ /** 使模块的转换缓存失效 */
942
+ invalidateModule(mod) {
943
+ mod.transformResult = null;
944
+ mod.lastHMRTimestamp = Date.now();
945
+ }
946
+ /** 使所有模块缓存失效 */
947
+ invalidateAll() {
948
+ for (const mod of this.idToModuleMap.values()) {
949
+ this.invalidateModule(mod);
950
+ }
951
+ }
952
+ /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
953
+ getHmrBoundaries(mod) {
954
+ const boundaries = [];
955
+ const visited = /* @__PURE__ */ new Set();
956
+ const propagate = (node, via) => {
957
+ if (visited.has(node)) return true;
958
+ visited.add(node);
959
+ if (node.isSelfAccepting) {
960
+ boundaries.push({ boundary: node, acceptedVia: via });
961
+ return true;
962
+ }
963
+ if (node.acceptedHmrDeps.has(via)) {
964
+ boundaries.push({ boundary: node, acceptedVia: via });
965
+ return true;
966
+ }
967
+ if (node.importers.size === 0) return false;
968
+ for (const importer of node.importers) {
969
+ if (!propagate(importer, node)) return false;
970
+ }
971
+ return true;
972
+ };
973
+ if (mod.isSelfAccepting) {
974
+ boundaries.push({ boundary: mod, acceptedVia: mod });
975
+ return boundaries;
976
+ }
977
+ for (const importer of mod.importers) {
978
+ if (!propagate(importer, mod)) {
979
+ return [];
980
+ }
981
+ }
982
+ return boundaries;
983
+ }
984
+ };
985
+ }
986
+ });
987
+
988
+ // src/server/ws.ts
989
+ function createWebSocketServer(server) {
990
+ const wss = new import_ws.WebSocketServer({ noServer: true });
991
+ const clients = /* @__PURE__ */ new Set();
992
+ server.on("upgrade", (req, socket, head) => {
993
+ if (req.headers["sec-websocket-protocol"] === "nasti-hmr") {
994
+ wss.handleUpgrade(req, socket, head, (ws) => {
995
+ wss.emit("connection", ws, req);
996
+ });
997
+ }
998
+ });
999
+ wss.on("connection", (ws) => {
1000
+ clients.add(ws);
1001
+ ws.send(JSON.stringify({ type: "connected" }));
1002
+ ws.on("close", () => {
1003
+ clients.delete(ws);
1004
+ });
1005
+ ws.on("error", (err) => {
1006
+ console.error("[nasti] WebSocket error:", err);
1007
+ clients.delete(ws);
1008
+ });
1009
+ });
1010
+ return {
1011
+ send(payload) {
1012
+ const data = JSON.stringify(payload);
1013
+ for (const client of clients) {
1014
+ if (client.readyState === 1) {
1015
+ client.send(data);
1016
+ }
1017
+ }
1018
+ },
1019
+ close() {
1020
+ clients.clear();
1021
+ wss.close();
1031
1022
  }
1032
1023
  };
1033
1024
  }
1025
+ var import_ws;
1026
+ var init_ws = __esm({
1027
+ "src/server/ws.ts"() {
1028
+ "use strict";
1029
+ import_ws = require("ws");
1030
+ }
1031
+ });
1034
1032
 
1035
- // src/build/index.ts
1036
- init_html();
1037
- init_transformer();
1038
- init_env();
1039
-
1040
- // src/core/plugin-container.ts
1041
- var PluginContainer = class {
1042
- plugins;
1043
- config;
1044
- ctx;
1045
- emittedFiles = /* @__PURE__ */ new Map();
1046
- constructor(config) {
1047
- this.config = config;
1048
- this.plugins = sortPlugins(config.plugins);
1049
- this.ctx = this.createContext();
1050
- }
1051
- createContext() {
1052
- const container = this;
1053
- return {
1054
- async resolve(source, importer) {
1055
- return container.resolveId(source, importer);
1056
- },
1057
- emitFile(file) {
1058
- const fileName = file.fileName ?? file.name ?? `asset-${container.emittedFiles.size}`;
1059
- const id = `emitted:${fileName}`;
1060
- container.emittedFiles.set(id, {
1061
- fileName,
1062
- source: file.source ?? ""
1063
- });
1064
- return id;
1065
- },
1066
- getModuleInfo(_id) {
1067
- return null;
1033
+ // src/server/middleware.ts
1034
+ var middleware_exports = {};
1035
+ __export(middleware_exports, {
1036
+ transformMiddleware: () => transformMiddleware,
1037
+ transformRequest: () => transformRequest
1038
+ });
1039
+ function transformMiddleware(ctx) {
1040
+ ctx.envDefine = buildEnvDefine(
1041
+ loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
1042
+ ctx.config.mode
1043
+ );
1044
+ return async (req, res, next) => {
1045
+ const url = req.url ?? "/";
1046
+ if (ctx.config.server.cors) {
1047
+ const origin = req.headers.origin ?? "*";
1048
+ res.setHeader("Access-Control-Allow-Origin", origin);
1049
+ res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
1050
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1051
+ if (req.method === "OPTIONS") {
1052
+ res.statusCode = 204;
1053
+ res.end();
1054
+ return;
1068
1055
  }
1069
- };
1056
+ }
1057
+ if (req.method !== "GET") return next();
1058
+ if (url === "/@nasti/client") {
1059
+ res.setHeader("Content-Type", "application/javascript");
1060
+ res.end(getHmrClientCode());
1061
+ return;
1062
+ }
1063
+ if (url === "/" || url.endsWith(".html")) {
1064
+ const html = await readHtmlFile(ctx.config.root);
1065
+ if (html) {
1066
+ let processedHtml = html;
1067
+ for (const plugin of ctx.config.plugins) {
1068
+ if (plugin.transformIndexHtml) {
1069
+ const result = await plugin.transformIndexHtml(processedHtml);
1070
+ if (typeof result === "string") {
1071
+ processedHtml = result;
1072
+ } else if (result && "html" in result) {
1073
+ processedHtml = processHtml(result.html, result.tags);
1074
+ } else if (Array.isArray(result)) {
1075
+ processedHtml = processHtml(processedHtml, result);
1076
+ }
1077
+ }
1078
+ }
1079
+ res.setHeader("Content-Type", "text/html");
1080
+ res.end(processedHtml);
1081
+ return;
1082
+ }
1083
+ }
1084
+ if (isModuleRequest(url)) {
1085
+ try {
1086
+ const result = await transformRequest(url, ctx);
1087
+ if (result) {
1088
+ const contentType = url.endsWith(".css") ? "application/javascript" : "application/javascript";
1089
+ res.setHeader("Content-Type", contentType);
1090
+ res.setHeader("Cache-Control", "no-cache");
1091
+ res.end(typeof result === "string" ? result : result.code);
1092
+ return;
1093
+ }
1094
+ } catch (err) {
1095
+ console.error(`[nasti] Transform error: ${url}`, err.message);
1096
+ res.statusCode = 500;
1097
+ res.end(`Transform error: ${err.message}`);
1098
+ return;
1099
+ }
1100
+ }
1101
+ next();
1102
+ };
1103
+ }
1104
+ async function transformRequest(url, ctx) {
1105
+ const { config, pluginContainer, moduleGraph } = ctx;
1106
+ const cleanReqUrl = url.split("?")[0];
1107
+ const cached = moduleGraph.getModuleByUrl(url);
1108
+ if (cached?.transformResult) {
1109
+ return cached.transformResult;
1110
+ }
1111
+ if (cleanReqUrl === "/@react-refresh") {
1112
+ return { code: REACT_REFRESH_RUNTIME };
1113
+ }
1114
+ const filePath = resolveUrlToFile(url, config.root);
1115
+ if (!filePath || !import_node_fs8.default.existsSync(filePath)) return null;
1116
+ const mod = await moduleGraph.ensureEntryFromUrl(url);
1117
+ moduleGraph.registerModule(mod, filePath);
1118
+ if (cleanReqUrl.startsWith("/@modules/")) {
1119
+ const code2 = await bundlePackageAsEsm(filePath);
1120
+ const transformResult2 = { code: code2 };
1121
+ mod.transformResult = transformResult2;
1122
+ return transformResult2;
1123
+ }
1124
+ let code = import_node_fs8.default.readFileSync(filePath, "utf-8");
1125
+ const pluginResult = await pluginContainer.transform(code, filePath);
1126
+ if (pluginResult) {
1127
+ code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
1128
+ }
1129
+ if (shouldTransform(filePath)) {
1130
+ const isJsx = /\.[jt]sx$/.test(filePath);
1131
+ const useRefresh = isJsx && config.framework !== "vue";
1132
+ const result = transformCode(filePath, code, {
1133
+ sourcemap: true,
1134
+ jsxRuntime: "automatic",
1135
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
1136
+ reactRefresh: useRefresh
1137
+ });
1138
+ code = result.code;
1139
+ if (useRefresh) {
1140
+ code = REACT_REFRESH_PREAMBLE + code + REACT_REFRESH_FOOTER;
1141
+ }
1142
+ }
1143
+ const envDefine = ctx.envDefine ?? buildEnvDefine(
1144
+ loadEnv(config.mode, config.root, config.envPrefix),
1145
+ config.mode
1146
+ );
1147
+ code = replaceEnvInCode(code, envDefine);
1148
+ code = rewriteImports(code, config);
1149
+ const transformResult = { code };
1150
+ mod.transformResult = transformResult;
1151
+ return transformResult;
1152
+ }
1153
+ async function bundlePackageAsEsm(entryFile) {
1154
+ if (!esmBundleCache.has(entryFile)) {
1155
+ esmBundleCache.set(entryFile, doBundlePackage(entryFile));
1156
+ }
1157
+ return esmBundleCache.get(entryFile);
1158
+ }
1159
+ async function doBundlePackage(entryFile) {
1160
+ const { rolldown: rolldown4 } = await import("rolldown");
1161
+ const bundle = await rolldown4({
1162
+ input: entryFile,
1163
+ // 仅将其他 npm 包外部化;相对路径(包内部文件)全部内联打包
1164
+ external: (id) => {
1165
+ if (id.startsWith(".") || id.startsWith("/") || /^[A-Za-z]:\\/.test(id)) return false;
1166
+ return true;
1167
+ }
1168
+ });
1169
+ const result = await bundle.generate({ format: "esm", exports: "named" });
1170
+ await bundle.close();
1171
+ let code = result.output[0].code;
1172
+ code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
1173
+ code = code.replace(
1174
+ /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
1175
+ (_, prefix, q, spec) => `${prefix}${q}/@modules/${spec}${q}`
1176
+ ).replace(
1177
+ /^(export\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
1178
+ (_, prefix, q, spec) => `${prefix}${q}/@modules/${spec}${q}`
1179
+ ).replace(
1180
+ /^(import\s+)(['"])([^'"./][^'"]*)(\2)/gm,
1181
+ (_, prefix, q, spec) => `${prefix}${q}/@modules/${spec}${q}`
1182
+ );
1183
+ code = rewriteExternalRequires(code);
1184
+ if (code.includes("__commonJSMin")) {
1185
+ code = await injectCjsNamedExports(code, entryFile);
1186
+ }
1187
+ return code;
1188
+ }
1189
+ function rewriteExternalRequires(code) {
1190
+ const pkgs = /* @__PURE__ */ new Set();
1191
+ const re = /__require\(["']([^"']+)["']\)/g;
1192
+ let m;
1193
+ while ((m = re.exec(code)) !== null) {
1194
+ pkgs.add(m[1]);
1195
+ }
1196
+ if (pkgs.size === 0) return code;
1197
+ let result = code;
1198
+ const imports = [];
1199
+ for (const pkg of pkgs) {
1200
+ const safe = pkg.replace(/[^a-zA-Z0-9_$]/g, "_");
1201
+ imports.push(`import * as __ns_${safe} from "/@modules/${pkg}";`);
1202
+ imports.push(`var __req_${safe} = "default" in __ns_${safe} ? __ns_${safe}["default"] : __ns_${safe};`);
1203
+ result = result.replaceAll(`__require("${pkg}")`, `__req_${safe}`);
1204
+ result = result.replaceAll(`__require('${pkg}')`, `__req_${safe}`);
1205
+ }
1206
+ return imports.join("\n") + "\n" + result;
1207
+ }
1208
+ async function injectCjsNamedExports(code, entryFile) {
1209
+ try {
1210
+ const { createRequire: createRequire3 } = await import("module");
1211
+ const req = createRequire3(entryFile);
1212
+ const cjsExports = req(entryFile);
1213
+ if (!cjsExports || typeof cjsExports !== "object" && typeof cjsExports !== "function" || Array.isArray(cjsExports)) return code;
1214
+ const namedKeys = Object.keys(cjsExports).filter(
1215
+ (k) => k !== "__esModule" && k !== "default" && VALID_IDENT.test(k)
1216
+ );
1217
+ if (namedKeys.length === 0) return code;
1218
+ return code.replace(
1219
+ /^export default (\w+\(\));?\s*$/m,
1220
+ (_, call) => [
1221
+ `const __cjsMod = ${call};`,
1222
+ `export default __cjsMod;`,
1223
+ ...namedKeys.map((k) => `export const ${k} = __cjsMod[${JSON.stringify(k)}];`)
1224
+ ].join("\n")
1225
+ );
1226
+ } catch {
1227
+ return code;
1228
+ }
1229
+ }
1230
+ function rewriteImports(code, _config) {
1231
+ return code.replace(
1232
+ /\bfrom\s+(['"])([^'"./][^'"]*)\1/g,
1233
+ (match, quote, specifier) => {
1234
+ return `from ${quote}/@modules/${specifier}${quote}`;
1235
+ }
1236
+ ).replace(
1237
+ // 处理纯副作用导入: import 'bare-specifier'
1238
+ /\bimport\s+(['"])([^'"./][^'"]*)\1/g,
1239
+ (match, quote, specifier) => {
1240
+ return `import ${quote}/@modules/${specifier}${quote}`;
1241
+ }
1242
+ ).replace(
1243
+ // 处理动态导入: import('bare-specifier')
1244
+ /\bimport\s*\(\s*(['"])([^'"./][^'"]*)\1\s*\)/g,
1245
+ (match, quote, specifier) => {
1246
+ return `import(${quote}/@modules/${specifier}${quote})`;
1247
+ }
1248
+ );
1249
+ }
1250
+ function resolveNodeModule(root, moduleName) {
1251
+ let pkgName;
1252
+ let subpath;
1253
+ if (moduleName.startsWith("@")) {
1254
+ const parts = moduleName.split("/");
1255
+ pkgName = parts.slice(0, 2).join("/");
1256
+ subpath = parts.slice(2).join("/");
1257
+ } else {
1258
+ const slash = moduleName.indexOf("/");
1259
+ pkgName = slash === -1 ? moduleName : moduleName.slice(0, slash);
1260
+ subpath = slash === -1 ? "" : moduleName.slice(slash + 1);
1261
+ }
1262
+ let pkgDir = null;
1263
+ let dir = root;
1264
+ for (; ; ) {
1265
+ const candidate = import_node_path9.default.join(dir, "node_modules", pkgName);
1266
+ if (import_node_fs8.default.existsSync(candidate)) {
1267
+ pkgDir = candidate;
1268
+ break;
1269
+ }
1270
+ const parent = import_node_path9.default.dirname(dir);
1271
+ if (parent === dir) break;
1272
+ dir = parent;
1070
1273
  }
1071
- /** 返回所有通过 emitFile() 输出的文件 */
1072
- getEmittedFiles() {
1073
- return Array.from(this.emittedFiles.values());
1274
+ if (!pkgDir) return null;
1275
+ const pkgJsonPath = import_node_path9.default.join(pkgDir, "package.json");
1276
+ if (!import_node_fs8.default.existsSync(pkgJsonPath)) return null;
1277
+ let pkg;
1278
+ try {
1279
+ pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgJsonPath, "utf-8"));
1280
+ } catch {
1281
+ return null;
1282
+ }
1283
+ if (pkg.exports) {
1284
+ const exportKey = subpath ? `./${subpath}` : ".";
1285
+ const resolved = resolvePackageExports(pkg.exports, exportKey, pkgDir);
1286
+ if (resolved) return resolved;
1074
1287
  }
1075
- async buildStart() {
1076
- for (const plugin of this.plugins) {
1077
- if (plugin.buildStart) {
1078
- await plugin.buildStart.call(this.ctx);
1288
+ if (subpath) {
1289
+ const subDirs = [""];
1290
+ for (const field of ["module", "main"]) {
1291
+ if (typeof pkg[field] === "string") {
1292
+ const dir2 = import_node_path9.default.dirname(pkg[field]);
1293
+ if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
1079
1294
  }
1080
1295
  }
1081
- }
1082
- async buildEnd(error) {
1083
- for (const plugin of this.plugins) {
1084
- if (plugin.buildEnd) {
1085
- await plugin.buildEnd.call(this.ctx, error);
1296
+ for (const dir2 of subDirs) {
1297
+ const direct = import_node_path9.default.join(pkgDir, dir2, subpath);
1298
+ if (import_node_fs8.default.existsSync(direct) && import_node_fs8.default.statSync(direct).isFile()) return direct;
1299
+ for (const ext of RESOLVE_EXTENSIONS) {
1300
+ if (import_node_fs8.default.existsSync(direct + ext)) return direct + ext;
1086
1301
  }
1087
1302
  }
1303
+ return null;
1088
1304
  }
1089
- async resolveId(source, importer, options = {}) {
1090
- for (const plugin of this.plugins) {
1091
- if (!plugin.resolveId) continue;
1092
- const result = await plugin.resolveId.call(
1093
- this.ctx,
1094
- source,
1095
- importer ?? void 0,
1096
- { isEntry: options.isEntry ?? false, ssr: false }
1097
- );
1098
- if (result != null) return result;
1305
+ for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
1306
+ if (typeof pkg[field] === "string") {
1307
+ const entry = import_node_path9.default.join(pkgDir, pkg[field]);
1308
+ if (import_node_fs8.default.existsSync(entry)) return entry;
1099
1309
  }
1100
- return null;
1101
1310
  }
1102
- async load(id) {
1103
- for (const plugin of this.plugins) {
1104
- if (!plugin.load) continue;
1105
- const result = await plugin.load.call(this.ctx, id);
1106
- if (result != null) return result;
1311
+ const indexFallback = import_node_path9.default.join(pkgDir, "index.js");
1312
+ if (import_node_fs8.default.existsSync(indexFallback)) return indexFallback;
1313
+ return null;
1314
+ }
1315
+ function resolvePackageExports(exports2, key, pkgDir) {
1316
+ if (typeof exports2 === "string") {
1317
+ return key === "." ? import_node_path9.default.join(pkgDir, exports2) : null;
1318
+ }
1319
+ const entry = exports2[key];
1320
+ if (entry === void 0) return null;
1321
+ return resolveExportValue(entry, pkgDir);
1322
+ }
1323
+ function resolveExportValue(value, pkgDir) {
1324
+ if (typeof value === "string") return import_node_path9.default.join(pkgDir, value);
1325
+ if (Array.isArray(value)) {
1326
+ for (const item of value) {
1327
+ const r = resolveExportValue(item, pkgDir);
1328
+ if (r) return r;
1107
1329
  }
1108
1330
  return null;
1109
1331
  }
1110
- async transform(code, id) {
1111
- let currentCode = code;
1112
- for (const plugin of this.plugins) {
1113
- if (!plugin.transform) continue;
1114
- const result = await plugin.transform.call(this.ctx, currentCode, id);
1115
- if (result == null) continue;
1116
- if (typeof result === "string") {
1117
- currentCode = result;
1118
- } else {
1119
- currentCode = result.code;
1332
+ if (value && typeof value === "object") {
1333
+ for (const cond of ESM_CONDITIONS) {
1334
+ if (cond in value) {
1335
+ const r = resolveExportValue(value[cond], pkgDir);
1336
+ if (r) return r;
1120
1337
  }
1121
1338
  }
1122
- return currentCode === code ? null : { code: currentCode };
1123
- }
1124
- /** 完整的模块处理管道: resolveId → load → transform */
1125
- async processModule(source, importer) {
1126
- const resolveResult = await this.resolveId(source, importer, {
1127
- isEntry: !importer
1128
- });
1129
- if (resolveResult == null) return null;
1130
- const id = typeof resolveResult === "string" ? resolveResult : resolveResult.id;
1131
- const loadResult = await this.load(id);
1132
- if (loadResult == null) return null;
1133
- const loadedCode = typeof loadResult === "string" ? loadResult : loadResult.code;
1134
- const transformResult = await this.transform(loadedCode, id);
1135
- const finalCode = transformResult == null ? loadedCode : typeof transformResult === "string" ? transformResult : transformResult.code;
1136
- return { id, code: finalCode };
1137
- }
1138
- getPlugins() {
1139
- return this.plugins;
1140
- }
1141
- };
1142
- function sortPlugins(plugins) {
1143
- const pre = [];
1144
- const normal = [];
1145
- const post = [];
1146
- for (const plugin of plugins) {
1147
- if (plugin.enforce === "pre") pre.push(plugin);
1148
- else if (plugin.enforce === "post") post.push(plugin);
1149
- else normal.push(plugin);
1150
1339
  }
1151
- return [...pre, ...normal, ...post];
1340
+ return null;
1152
1341
  }
1153
-
1154
- // src/build/index.ts
1155
- var import_picocolors = __toESM(require("picocolors"), 1);
1156
- async function build(inlineConfig = {}) {
1157
- const config = await resolveConfig(inlineConfig, "build");
1158
- const startTime = performance.now();
1159
- console.log(import_picocolors.default.cyan("\n\u{1F528} nasti build") + import_picocolors.default.dim(` v${"1.3.9"}`));
1160
- console.log(import_picocolors.default.dim(` root: ${config.root}`));
1161
- console.log(import_picocolors.default.dim(` mode: ${config.mode}`));
1162
- const outDir = import_node_path7.default.resolve(config.root, config.build.outDir);
1163
- if (config.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
1164
- import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
1342
+ function resolveUrlToFile(url, root) {
1343
+ const cleanUrl = url.split("?")[0];
1344
+ if (cleanUrl.startsWith("/@modules/")) {
1345
+ const moduleName = cleanUrl.slice("/@modules/".length);
1346
+ return resolveNodeModule(root, moduleName);
1165
1347
  }
1166
- import_node_fs6.default.mkdirSync(outDir, { recursive: true });
1167
- const html = await readHtmlFile(config.root);
1168
- let entryPoints = [];
1169
- if (html) {
1170
- const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
1171
- for (const match of scriptMatches) {
1172
- const src = match[1];
1173
- if (src && !src.startsWith("http")) {
1174
- entryPoints.push(import_node_path7.default.resolve(config.root, src.replace(/^\//, "")));
1175
- }
1176
- }
1348
+ const filePath = import_node_path9.default.resolve(root, cleanUrl.replace(/^\//, ""));
1349
+ if (import_node_fs8.default.existsSync(filePath) && import_node_fs8.default.statSync(filePath).isFile()) {
1350
+ return filePath;
1177
1351
  }
1178
- if (entryPoints.length === 0) {
1179
- const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
1180
- for (const entry of fallbackEntries) {
1181
- const fullPath = import_node_path7.default.resolve(config.root, entry);
1182
- if (import_node_fs6.default.existsSync(fullPath)) {
1183
- entryPoints.push(fullPath);
1184
- break;
1185
- }
1186
- }
1352
+ for (const ext of RESOLVE_EXTENSIONS) {
1353
+ const withExt = filePath + ext;
1354
+ if (import_node_fs8.default.existsSync(withExt)) return withExt;
1187
1355
  }
1188
- if (entryPoints.length === 0) {
1189
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
1356
+ for (const ext of RESOLVE_EXTENSIONS) {
1357
+ const indexFile = import_node_path9.default.join(filePath, "index" + ext);
1358
+ if (import_node_fs8.default.existsSync(indexFile)) return indexFile;
1190
1359
  }
1191
- const builtinPlugins = [
1192
- resolvePlugin(config),
1193
- cssPlugin(config),
1194
- assetsPlugin(config)
1195
- ];
1196
- const allPlugins = [...builtinPlugins, ...config.plugins];
1197
- const pluginContainer = new PluginContainer(config);
1198
- await pluginContainer.buildStart();
1199
- const oxcTransformPlugin = {
1200
- name: "nasti:oxc-transform",
1201
- transform(code, id) {
1202
- if (!shouldTransform(id)) return null;
1203
- const result = transformCode(id, code, {
1204
- sourcemap: !!config.build.sourcemap,
1205
- jsxRuntime: "automatic",
1206
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
1360
+ return null;
1361
+ }
1362
+ function isModuleRequest(url) {
1363
+ const cleanUrl = url.split("?")[0];
1364
+ if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
1365
+ if (cleanUrl.startsWith("/@modules/")) return true;
1366
+ if (!import_node_path9.default.extname(cleanUrl)) return true;
1367
+ return false;
1368
+ }
1369
+ function getHmrClientCode() {
1370
+ return `
1371
+ // Nasti HMR Client
1372
+ const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
1373
+ const hotModulesMap = new Map();
1374
+
1375
+ socket.addEventListener('message', ({ data }) => {
1376
+ const payload = JSON.parse(data);
1377
+ switch (payload.type) {
1378
+ case 'connected':
1379
+ console.log('[nasti] connected.');
1380
+ break;
1381
+ case 'update':
1382
+ payload.updates.forEach((update) => {
1383
+ if (update.type === 'js-update') {
1384
+ fetchUpdate(update);
1385
+ } else if (update.type === 'css-update') {
1386
+ updateCss(update.path);
1387
+ }
1207
1388
  });
1208
- return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
1209
- }
1210
- };
1211
- const env = loadEnv(config.mode, config.root, config.envPrefix);
1212
- const envDefine = buildEnvDefine(env, config.mode);
1213
- const bundle = await (0, import_rolldown.rolldown)({
1214
- input: entryPoints,
1215
- define: envDefine,
1216
- plugins: [
1217
- oxcTransformPlugin,
1218
- // 转换 Nasti 插件为 Rolldown 插件格式
1219
- ...allPlugins.map((p) => ({
1220
- name: p.name,
1221
- resolveId: p.resolveId,
1222
- load: p.load,
1223
- transform: p.transform,
1224
- buildStart: p.buildStart,
1225
- buildEnd: p.buildEnd
1226
- }))
1227
- ],
1228
- ...config.build.rolldownOptions
1229
- });
1230
- const { output } = await bundle.write({
1231
- dir: outDir,
1232
- format: "esm",
1233
- sourcemap: !!config.build.sourcemap,
1234
- minify: !!config.build.minify,
1235
- entryFileNames: "assets/[name].[hash].js",
1236
- chunkFileNames: "assets/[name].[hash].js",
1237
- assetFileNames: "assets/[name].[hash][extname]"
1238
- });
1239
- await bundle.close();
1240
- await pluginContainer.buildEnd();
1241
- for (const ef of pluginContainer.getEmittedFiles()) {
1242
- const dest = import_node_path7.default.resolve(outDir, ef.fileName);
1243
- import_node_fs6.default.mkdirSync(import_node_path7.default.dirname(dest), { recursive: true });
1244
- import_node_fs6.default.writeFileSync(dest, ef.source);
1389
+ break;
1390
+ case 'full-reload':
1391
+ console.log('[nasti] full reload');
1392
+ location.reload();
1393
+ break;
1394
+ case 'error':
1395
+ console.error('[nasti] error:', payload.err.message);
1396
+ showErrorOverlay(payload.err);
1397
+ break;
1245
1398
  }
1246
- if (html) {
1247
- let processedHtml = html;
1248
- const htmlPlugin_ = htmlPlugin(config);
1249
- if (htmlPlugin_.transformIndexHtml) {
1250
- const result = await htmlPlugin_.transformIndexHtml(processedHtml);
1251
- if (typeof result === "string") {
1252
- processedHtml = result;
1253
- } else if (result && "html" in result) {
1254
- processedHtml = processHtml(result.html, result.tags);
1255
- } else if (Array.isArray(result)) {
1256
- processedHtml = processHtml(processedHtml, result);
1257
- }
1258
- }
1259
- for (const chunk of output) {
1260
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
1261
- const originalEntry = import_node_path7.default.relative(config.root, chunk.facadeModuleId);
1262
- processedHtml = processedHtml.replace(
1263
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
1264
- `$1${config.base}${chunk.fileName}$3`
1265
- );
1266
- }
1267
- }
1268
- import_node_fs6.default.writeFileSync(import_node_path7.default.resolve(outDir, "index.html"), processedHtml);
1399
+ });
1400
+
1401
+ async function fetchUpdate(update) {
1402
+ const mod = hotModulesMap.get(update.path);
1403
+ if (mod) {
1404
+ const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
1405
+ mod.callbacks.forEach((cb) => cb(newMod));
1406
+ } else {
1407
+ // \u6CA1\u6709\u6CE8\u518C hot \u56DE\u8C03\uFF0C\u5C1D\u8BD5\u91CD\u65B0 import
1408
+ await import(update.path + '?t=' + update.timestamp);
1269
1409
  }
1270
- const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
1271
- const totalSize = output.reduce((sum, chunk) => {
1272
- if (chunk.type === "chunk" && chunk.code) return sum + chunk.code.length;
1273
- return sum;
1274
- }, 0);
1275
- console.log(import_picocolors.default.green(`
1276
- \u2713 Built in ${elapsed}s`));
1277
- console.log(import_picocolors.default.dim(` ${output.length} files, ${formatSize(totalSize)} total`));
1278
- console.log(import_picocolors.default.dim(` output: ${config.build.outDir}/
1279
- `));
1280
- return { output };
1281
1410
  }
1282
- function formatSize(bytes) {
1283
- if (bytes < 1024) return `${bytes} B`;
1284
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} kB`;
1285
- return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
1411
+
1412
+ function updateCss(path) {
1413
+ const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
1414
+ if (el) {
1415
+ fetch(path + '?t=' + Date.now())
1416
+ .then(r => r.text())
1417
+ .then(css => { el.textContent = css; });
1418
+ }
1286
1419
  }
1287
- function escapeRegExp(string) {
1288
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1420
+
1421
+ function showErrorOverlay(err) {
1422
+ const overlay = document.createElement('div');
1423
+ overlay.id = 'nasti-error-overlay';
1424
+ 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;';
1425
+ const title = document.createElement('h2');
1426
+ title.style.color = '#ff5555';
1427
+ title.textContent = 'Build Error';
1428
+ const pre = document.createElement('pre');
1429
+ pre.textContent = err.message + '\\n' + (err.stack || '');
1430
+ const btn = document.createElement('button');
1431
+ btn.style.cssText = 'margin-top:1rem;padding:0.5rem 1rem;cursor:pointer';
1432
+ btn.textContent = 'Close';
1433
+ btn.onclick = () => overlay.remove();
1434
+ overlay.appendChild(title);
1435
+ overlay.appendChild(pre);
1436
+ overlay.appendChild(btn);
1437
+ document.body.appendChild(overlay);
1289
1438
  }
1290
1439
 
1291
- // src/server/index.ts
1292
- var import_node_http = __toESM(require("http"), 1);
1293
- var import_node_path10 = __toESM(require("path"), 1);
1294
- var import_node_os = __toESM(require("os"), 1);
1295
- var import_connect = __toESM(require("connect"), 1);
1296
- var import_sirv = __toESM(require("sirv"), 1);
1297
- var import_chokidar = require("chokidar");
1298
- var import_picocolors2 = __toESM(require("picocolors"), 1);
1299
-
1300
- // src/core/module-graph.ts
1301
- var ModuleGraph = class {
1302
- urlToModuleMap = /* @__PURE__ */ new Map();
1303
- idToModuleMap = /* @__PURE__ */ new Map();
1304
- fileToModulesMap = /* @__PURE__ */ new Map();
1305
- getModuleByUrl(url) {
1306
- return this.urlToModuleMap.get(url);
1307
- }
1308
- getModuleById(id) {
1309
- return this.idToModuleMap.get(id);
1310
- }
1311
- getModulesByFile(file) {
1312
- return this.fileToModulesMap.get(file);
1313
- }
1314
- async ensureEntryFromUrl(url) {
1315
- let mod = this.urlToModuleMap.get(url);
1316
- if (mod) return mod;
1317
- mod = this.createModule(url);
1318
- this.urlToModuleMap.set(url, mod);
1319
- return mod;
1320
- }
1321
- createModule(url, id) {
1322
- const mod = {
1323
- id: id ?? url,
1324
- file: null,
1325
- url,
1326
- type: url.endsWith(".css") ? "css" : "js",
1327
- importers: /* @__PURE__ */ new Set(),
1328
- importedModules: /* @__PURE__ */ new Set(),
1329
- acceptedHmrDeps: /* @__PURE__ */ new Set(),
1330
- transformResult: null,
1331
- lastHMRTimestamp: 0,
1332
- isSelfAccepting: false
1333
- };
1334
- this.idToModuleMap.set(mod.id, mod);
1335
- return mod;
1336
- }
1337
- /** 注册文件路径到模块的映射 */
1338
- registerModule(mod, file) {
1339
- mod.file = file;
1340
- let mods = this.fileToModulesMap.get(file);
1341
- if (!mods) {
1342
- mods = /* @__PURE__ */ new Set();
1343
- this.fileToModulesMap.set(file, mods);
1344
- }
1345
- mods.add(mod);
1346
- }
1347
- /** 更新模块依赖关系 */
1348
- updateModuleImports(mod, importedIds) {
1349
- for (const imported of mod.importedModules) {
1350
- imported.importers.delete(mod);
1351
- }
1352
- mod.importedModules.clear();
1353
- for (const id of importedIds) {
1354
- const importedMod = this.idToModuleMap.get(id);
1355
- if (importedMod) {
1356
- mod.importedModules.add(importedMod);
1357
- importedMod.importers.add(mod);
1358
- }
1359
- }
1360
- }
1361
- /** 使模块的转换缓存失效 */
1362
- invalidateModule(mod) {
1363
- mod.transformResult = null;
1364
- mod.lastHMRTimestamp = Date.now();
1365
- }
1366
- /** 使所有模块缓存失效 */
1367
- invalidateAll() {
1368
- for (const mod of this.idToModuleMap.values()) {
1369
- this.invalidateModule(mod);
1370
- }
1371
- }
1372
- /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
1373
- getHmrBoundaries(mod) {
1374
- const boundaries = [];
1375
- const visited = /* @__PURE__ */ new Set();
1376
- const propagate = (node, via) => {
1377
- if (visited.has(node)) return true;
1378
- visited.add(node);
1379
- if (node.isSelfAccepting) {
1380
- boundaries.push({ boundary: node, acceptedVia: via });
1381
- return true;
1382
- }
1383
- if (node.acceptedHmrDeps.has(via)) {
1384
- boundaries.push({ boundary: node, acceptedVia: via });
1385
- return true;
1386
- }
1387
- if (node.importers.size === 0) return false;
1388
- for (const importer of node.importers) {
1389
- if (!propagate(importer, node)) return false;
1390
- }
1391
- return true;
1392
- };
1393
- if (mod.isSelfAccepting) {
1394
- boundaries.push({ boundary: mod, acceptedVia: mod });
1395
- return boundaries;
1396
- }
1397
- for (const importer of mod.importers) {
1398
- if (!propagate(importer, mod)) {
1399
- return [];
1400
- }
1440
+ // import.meta.hot API
1441
+ const createHotContext = (ownerPath) => ({
1442
+ accept(deps, callback) {
1443
+ if (typeof deps === 'function' || !deps) {
1444
+ // self-accepting
1445
+ const callbacks = hotModulesMap.get(ownerPath)?.callbacks || [];
1446
+ callbacks.push(deps || (() => {}));
1447
+ hotModulesMap.set(ownerPath, { callbacks });
1401
1448
  }
1402
- return boundaries;
1403
- }
1404
- };
1449
+ },
1450
+ prune(callback) {
1451
+ // \u6A21\u5757\u88AB\u79FB\u9664\u65F6\u6267\u884C
1452
+ },
1453
+ dispose(callback) {
1454
+ // \u6A21\u5757\u66F4\u65B0\u524D\u6267\u884C\u6E05\u7406
1455
+ },
1456
+ invalidate() {
1457
+ location.reload();
1458
+ },
1459
+ data: {},
1460
+ });
1405
1461
 
1406
- // src/server/ws.ts
1407
- var import_ws = require("ws");
1408
- function createWebSocketServer(server) {
1409
- const wss = new import_ws.WebSocketServer({ noServer: true });
1410
- const clients = /* @__PURE__ */ new Set();
1411
- server.on("upgrade", (req, socket, head) => {
1412
- if (req.headers["sec-websocket-protocol"] === "nasti-hmr") {
1413
- wss.handleUpgrade(req, socket, head, (ws) => {
1414
- wss.emit("connection", ws, req);
1415
- });
1416
- }
1417
- });
1418
- wss.on("connection", (ws) => {
1419
- clients.add(ws);
1420
- ws.send(JSON.stringify({ type: "connected" }));
1421
- ws.on("close", () => {
1422
- clients.delete(ws);
1423
- });
1424
- ws.on("error", (err) => {
1425
- console.error("[nasti] WebSocket error:", err);
1426
- clients.delete(ws);
1427
- });
1428
- });
1429
- return {
1430
- send(payload) {
1431
- const data = JSON.stringify(payload);
1432
- for (const client of clients) {
1433
- if (client.readyState === 1) {
1434
- client.send(data);
1435
- }
1436
- }
1437
- },
1438
- close() {
1439
- clients.clear();
1440
- wss.close();
1441
- }
1442
- };
1462
+ // \u66B4\u9732\u7ED9\u6A21\u5757\u4F7F\u7528
1463
+ if (!window.__nasti_hot_map) window.__nasti_hot_map = new Map();
1464
+ window.__NASTI_HMR__ = { createHotContext };
1465
+ `;
1443
1466
  }
1444
-
1445
- // src/server/index.ts
1446
- init_middleware();
1467
+ var import_node_path9, import_node_fs8, REACT_REFRESH_RUNTIME, REACT_REFRESH_PREAMBLE, REACT_REFRESH_FOOTER, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
1468
+ var init_middleware = __esm({
1469
+ "src/server/middleware.ts"() {
1470
+ "use strict";
1471
+ import_node_path9 = __toESM(require("path"), 1);
1472
+ import_node_fs8 = __toESM(require("fs"), 1);
1473
+ init_transformer();
1474
+ init_html();
1475
+ init_env();
1476
+ REACT_REFRESH_RUNTIME = `
1477
+ export function createSignatureFunctionForTransform() {
1478
+ return function(type, key, forceReset, getCustomHooks) { return type; };
1479
+ }
1480
+ export function register(type, id) {}
1481
+ export default { createSignatureFunctionForTransform, register };
1482
+ `;
1483
+ REACT_REFRESH_PREAMBLE = `
1484
+ import RefreshRuntime from '/@react-refresh';
1485
+ if (!window.$RefreshReg$) {
1486
+ window.$RefreshReg$ = (type, id) => RefreshRuntime.register(type, import.meta.url + ' ' + id);
1487
+ window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
1488
+ }
1489
+ `;
1490
+ REACT_REFRESH_FOOTER = `
1491
+ if (import.meta.hot) {
1492
+ import.meta.hot.accept();
1493
+ }
1494
+ `;
1495
+ esmBundleCache = /* @__PURE__ */ new Map();
1496
+ VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1497
+ RESOLVE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".json", ".vue"];
1498
+ ESM_CONDITIONS = ["import", "browser", "module", "default"];
1499
+ }
1500
+ });
1447
1501
 
1448
1502
  // src/server/hmr.ts
1449
- var import_node_path9 = __toESM(require("path"), 1);
1450
- var import_node_fs8 = __toESM(require("fs"), 1);
1451
1503
  async function handleFileChange(file, server) {
1452
1504
  const { moduleGraph, ws, config } = server;
1453
- const relativePath = "/" + import_node_path9.default.relative(config.root, file);
1505
+ const relativePath = "/" + import_node_path10.default.relative(config.root, file);
1454
1506
  const mods = moduleGraph.getModulesByFile(file);
1455
1507
  if (!mods || mods.size === 0) {
1456
1508
  return;
@@ -1463,7 +1515,7 @@ async function handleFileChange(file, server) {
1463
1515
  file,
1464
1516
  timestamp,
1465
1517
  modules: [mod],
1466
- read: () => import_node_fs8.default.readFileSync(file, "utf-8"),
1518
+ read: () => import_node_fs9.default.readFileSync(file, "utf-8"),
1467
1519
  server
1468
1520
  };
1469
1521
  let affectedModules = [mod];
@@ -1495,9 +1547,20 @@ async function handleFileChange(file, server) {
1495
1547
  ws.send({ type: "update", updates });
1496
1548
  }
1497
1549
  }
1550
+ var import_node_path10, import_node_fs9;
1551
+ var init_hmr = __esm({
1552
+ "src/server/hmr.ts"() {
1553
+ "use strict";
1554
+ import_node_path10 = __toESM(require("path"), 1);
1555
+ import_node_fs9 = __toESM(require("fs"), 1);
1556
+ }
1557
+ });
1498
1558
 
1499
1559
  // src/server/index.ts
1500
- init_html();
1560
+ var server_exports = {};
1561
+ __export(server_exports, {
1562
+ createServer: () => createServer
1563
+ });
1501
1564
  async function createServer(inlineConfig = {}) {
1502
1565
  const config = await resolveConfig(inlineConfig, "serve");
1503
1566
  const allPlugins = [
@@ -1516,7 +1579,7 @@ async function createServer(inlineConfig = {}) {
1516
1579
  pluginContainer,
1517
1580
  moduleGraph
1518
1581
  }));
1519
- const publicDir = import_node_path10.default.resolve(config.root, "public");
1582
+ const publicDir = import_node_path11.default.resolve(config.root, "public");
1520
1583
  app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
1521
1584
  app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
1522
1585
  const httpServer = import_node_http.default.createServer(app);
@@ -1559,14 +1622,15 @@ async function createServer(inlineConfig = {}) {
1559
1622
  let currentPort = finalPort;
1560
1623
  const onListening = () => {
1561
1624
  const actualPort = httpServer.address()?.port ?? currentPort;
1625
+ config.server.port = actualPort;
1562
1626
  const localUrl = `http://localhost:${actualPort}`;
1563
1627
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}` : null;
1564
1628
  console.log();
1565
- console.log(import_picocolors2.default.cyan(" nasti dev server") + import_picocolors2.default.dim(` v${"1.3.9"}`));
1629
+ console.log(import_picocolors3.default.cyan(" nasti dev server") + import_picocolors3.default.dim(` v${"1.4.0"}`));
1566
1630
  console.log();
1567
- console.log(` ${import_picocolors2.default.green(">")} Local: ${import_picocolors2.default.cyan(localUrl)}`);
1631
+ console.log(` ${import_picocolors3.default.green(">")} Local: ${import_picocolors3.default.cyan(localUrl)}`);
1568
1632
  if (networkUrl) {
1569
- console.log(` ${import_picocolors2.default.green(">")} Network: ${import_picocolors2.default.cyan(networkUrl)}`);
1633
+ console.log(` ${import_picocolors3.default.green(">")} Network: ${import_picocolors3.default.cyan(networkUrl)}`);
1570
1634
  }
1571
1635
  console.log();
1572
1636
  resolve(server);
@@ -1575,7 +1639,7 @@ async function createServer(inlineConfig = {}) {
1575
1639
  httpServer.on("error", (err) => {
1576
1640
  if (err.code === "EADDRINUSE") {
1577
1641
  currentPort++;
1578
- console.log(import_picocolors2.default.yellow(`Port ${currentPort - 1} is in use, trying ${currentPort}...`));
1642
+ console.log(import_picocolors3.default.yellow(`Port ${currentPort - 1} is in use, trying ${currentPort}...`));
1579
1643
  httpServer.listen(currentPort, host);
1580
1644
  } else {
1581
1645
  reject(err);
@@ -1608,11 +1672,411 @@ function getNetworkAddress() {
1608
1672
  }
1609
1673
  return "localhost";
1610
1674
  }
1675
+ var import_node_http, import_node_path11, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors3;
1676
+ var init_server = __esm({
1677
+ "src/server/index.ts"() {
1678
+ "use strict";
1679
+ import_node_http = __toESM(require("http"), 1);
1680
+ import_node_path11 = __toESM(require("path"), 1);
1681
+ import_node_os = __toESM(require("os"), 1);
1682
+ import_connect = __toESM(require("connect"), 1);
1683
+ import_sirv = __toESM(require("sirv"), 1);
1684
+ import_chokidar = require("chokidar");
1685
+ import_picocolors3 = __toESM(require("picocolors"), 1);
1686
+ init_config();
1687
+ init_plugin_container();
1688
+ init_module_graph();
1689
+ init_ws();
1690
+ init_middleware();
1691
+ init_hmr();
1692
+ init_resolve();
1693
+ init_css();
1694
+ init_assets();
1695
+ init_html();
1696
+ }
1697
+ });
1698
+
1699
+ // src/index.ts
1700
+ var src_exports = {};
1701
+ __export(src_exports, {
1702
+ build: () => build,
1703
+ buildElectron: () => buildElectron,
1704
+ createServer: () => createServer,
1705
+ defineConfig: () => defineConfig,
1706
+ electronPlugin: () => electronPlugin,
1707
+ resolveConfig: () => resolveConfig,
1708
+ startElectronDev: () => startElectronDev
1709
+ });
1710
+ module.exports = __toCommonJS(src_exports);
1711
+ init_config();
1712
+ init_build();
1713
+
1714
+ // src/build/electron.ts
1715
+ var import_node_path8 = __toESM(require("path"), 1);
1716
+ var import_node_fs7 = __toESM(require("fs"), 1);
1717
+ var import_rolldown2 = require("rolldown");
1718
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
1719
+ init_config();
1720
+ init_resolve();
1721
+
1722
+ // src/plugins/electron.ts
1723
+ var import_node_module2 = require("module");
1724
+ var NODE_BUILTINS = /* @__PURE__ */ new Set([
1725
+ ...import_node_module2.builtinModules,
1726
+ ...import_node_module2.builtinModules.map((m) => `node:${m}`)
1727
+ ]);
1728
+ var ELECTRON_MODULES = /* @__PURE__ */ new Set([
1729
+ "electron",
1730
+ "electron/main",
1731
+ "electron/common",
1732
+ "electron/renderer"
1733
+ ]);
1734
+ function electronPlugin(config) {
1735
+ const external = /* @__PURE__ */ new Set([
1736
+ ...ELECTRON_MODULES,
1737
+ ...NODE_BUILTINS,
1738
+ ...config.electron.external ?? []
1739
+ ]);
1740
+ return {
1741
+ name: "nasti:electron",
1742
+ enforce: "pre",
1743
+ resolveId(source) {
1744
+ if (external.has(source)) {
1745
+ return { id: source, external: true };
1746
+ }
1747
+ if (source.startsWith("electron/")) {
1748
+ return { id: source, external: true };
1749
+ }
1750
+ return null;
1751
+ }
1752
+ };
1753
+ }
1754
+
1755
+ // src/build/electron.ts
1756
+ init_transformer();
1757
+ init_env();
1758
+ async function buildElectron(inlineConfig = {}) {
1759
+ const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
1760
+ const startTime = performance.now();
1761
+ assertElectronVersion(config);
1762
+ console.log(import_picocolors2.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors2.default.dim(` v${"1.4.0"}`));
1763
+ console.log(import_picocolors2.default.dim(` root: ${config.root}`));
1764
+ console.log(import_picocolors2.default.dim(` mode: ${config.mode}`));
1765
+ console.log(import_picocolors2.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
1766
+ const outDir = import_node_path8.default.resolve(config.root, config.build.outDir);
1767
+ if (config.build.emptyOutDir && import_node_fs7.default.existsSync(outDir)) {
1768
+ import_node_fs7.default.rmSync(outDir, { recursive: true, force: true });
1769
+ }
1770
+ import_node_fs7.default.mkdirSync(outDir, { recursive: true });
1771
+ const rendererOutDir = import_node_path8.default.join(outDir, "renderer");
1772
+ const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
1773
+ await build2({
1774
+ ...inlineConfig,
1775
+ target: "web",
1776
+ build: {
1777
+ ...inlineConfig.build,
1778
+ outDir: rendererOutDir,
1779
+ emptyOutDir: false
1780
+ }
1781
+ });
1782
+ const mainEntry = import_node_path8.default.resolve(config.root, config.electron.main);
1783
+ if (!import_node_fs7.default.existsSync(mainEntry)) {
1784
+ throw new Error(
1785
+ `Electron main entry not found: ${config.electron.main}
1786
+ \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
1787
+ );
1788
+ }
1789
+ const mainFile = await bundleNode(config, mainEntry, {
1790
+ outFile: outFileName(outDir, "main", config.electron.mainFormat),
1791
+ format: config.electron.mainFormat,
1792
+ label: "main"
1793
+ });
1794
+ const preloadEntries = normalizePreload(config.electron.preload, config.root);
1795
+ const preloadFiles = [];
1796
+ for (const entry of preloadEntries) {
1797
+ if (!import_node_fs7.default.existsSync(entry)) {
1798
+ console.warn(import_picocolors2.default.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
1799
+ continue;
1800
+ }
1801
+ const base = import_node_path8.default.basename(entry).replace(/\.[^.]+$/, "");
1802
+ const out = outFileName(outDir, base, config.electron.preloadFormat);
1803
+ await bundleNode(config, entry, {
1804
+ outFile: out,
1805
+ format: config.electron.preloadFormat,
1806
+ label: `preload (${base})`
1807
+ });
1808
+ preloadFiles.push(out);
1809
+ }
1810
+ const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
1811
+ console.log(import_picocolors2.default.green(`
1812
+ \u2713 Electron build complete in ${elapsed}s`));
1813
+ console.log(import_picocolors2.default.dim(` renderer: ${import_node_path8.default.relative(config.root, rendererOutDir)}/`));
1814
+ console.log(import_picocolors2.default.dim(` main: ${import_node_path8.default.relative(config.root, mainFile)}`));
1815
+ for (const pf of preloadFiles) {
1816
+ console.log(import_picocolors2.default.dim(` preload: ${import_node_path8.default.relative(config.root, pf)}`));
1817
+ }
1818
+ console.log();
1819
+ return { rendererOutDir, mainFile, preloadFiles };
1820
+ }
1821
+ async function bundleNode(config, entry, opts) {
1822
+ const env = loadEnv(config.mode, config.root, config.envPrefix);
1823
+ const envDefine = {
1824
+ ...buildEnvDefine(env, config.mode),
1825
+ __ELECTRON__: "true",
1826
+ __NASTI_TARGET__: JSON.stringify("electron")
1827
+ };
1828
+ const oxcTransformPlugin = {
1829
+ name: "nasti:oxc-transform",
1830
+ transform(code, id) {
1831
+ if (!shouldTransform(id)) return null;
1832
+ const result = transformCode(id, code, {
1833
+ sourcemap: !!config.build.sourcemap,
1834
+ jsxRuntime: "automatic",
1835
+ jsxImportSource: config.framework === "vue" ? "vue" : "react"
1836
+ });
1837
+ return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
1838
+ }
1839
+ };
1840
+ const bundle = await (0, import_rolldown2.rolldown)({
1841
+ input: entry,
1842
+ define: envDefine,
1843
+ platform: "node",
1844
+ plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)],
1845
+ ...config.build.rolldownOptions
1846
+ });
1847
+ import_node_fs7.default.mkdirSync(import_node_path8.default.dirname(opts.outFile), { recursive: true });
1848
+ await bundle.write({
1849
+ file: opts.outFile,
1850
+ format: opts.format === "cjs" ? "cjs" : "esm",
1851
+ sourcemap: !!config.build.sourcemap,
1852
+ minify: !!config.build.minify,
1853
+ inlineDynamicImports: true
1854
+ });
1855
+ await bundle.close();
1856
+ console.log(import_picocolors2.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path8.default.relative(config.root, opts.outFile)}`));
1857
+ return opts.outFile;
1858
+ }
1859
+ function outFileName(outDir, base, format) {
1860
+ const ext = format === "cjs" ? ".cjs" : ".mjs";
1861
+ return import_node_path8.default.join(outDir, base + ext);
1862
+ }
1863
+ function normalizePreload(preload, root) {
1864
+ const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
1865
+ return list.map((p) => import_node_path8.default.resolve(root, p));
1866
+ }
1867
+ function assertElectronVersion(config) {
1868
+ const min = config.electron.minVersion;
1869
+ const installed = detectInstalledElectron(config.root);
1870
+ if (installed && installed < min) {
1871
+ console.warn(
1872
+ import_picocolors2.default.yellow(
1873
+ ` \u26A0 \u68C0\u6D4B\u5230 Electron ${installed}\uFF0CNasti \u8981\u6C42 \u2265 ${min}\u3002\u65E7\u7248\u672C\u53EF\u80FD\u7F3A\u5C11 ESM \u4E3B\u8FDB\u7A0B\u652F\u6301\u3002`
1874
+ )
1875
+ );
1876
+ }
1877
+ }
1878
+ function detectInstalledElectron(root) {
1879
+ try {
1880
+ const pkgPath = import_node_path8.default.resolve(root, "node_modules/electron/package.json");
1881
+ if (!import_node_fs7.default.existsSync(pkgPath)) return null;
1882
+ const pkg = JSON.parse(import_node_fs7.default.readFileSync(pkgPath, "utf-8"));
1883
+ const major = parseInt(String(pkg.version).split(".")[0], 10);
1884
+ return Number.isFinite(major) ? major : null;
1885
+ } catch {
1886
+ return null;
1887
+ }
1888
+ }
1889
+
1890
+ // src/index.ts
1891
+ init_server();
1892
+
1893
+ // src/server/electron-dev.ts
1894
+ var import_node_path12 = __toESM(require("path"), 1);
1895
+ var import_node_fs10 = __toESM(require("fs"), 1);
1896
+ var import_node_module3 = require("module");
1897
+ var import_node_child_process = require("child_process");
1898
+ var import_chokidar2 = __toESM(require("chokidar"), 1);
1899
+ var import_picocolors4 = __toESM(require("picocolors"), 1);
1900
+ init_config();
1901
+ var import_rolldown3 = require("rolldown");
1902
+ init_resolve();
1903
+ init_transformer();
1904
+ init_env();
1905
+ async function startElectronDev(inlineConfig = {}) {
1906
+ const { noSpawn, ...rest } = inlineConfig;
1907
+ const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
1908
+ warnElectronVersion(config);
1909
+ console.log(import_picocolors4.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors4.default.dim(` v${"1.4.0"}`));
1910
+ const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
1911
+ const server = await createServer2({ ...rest, target: "electron" });
1912
+ await server.listen();
1913
+ const devUrl = `http://localhost:${server.config.server.port}/`;
1914
+ console.log(import_picocolors4.default.dim(` renderer: ${devUrl}`));
1915
+ const stageDir = import_node_path12.default.resolve(config.root, ".nasti");
1916
+ import_node_fs10.default.mkdirSync(stageDir, { recursive: true });
1917
+ const mainEntry = import_node_path12.default.resolve(config.root, config.electron.main);
1918
+ const preloadEntries = normalizePreload(config.electron.preload, config.root);
1919
+ const builtMainFile = import_node_path12.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
1920
+ const builtPreloadFiles = [];
1921
+ const compileAll = async () => {
1922
+ await compileNode(config, mainEntry, {
1923
+ outFile: builtMainFile,
1924
+ format: config.electron.mainFormat,
1925
+ devUrl
1926
+ });
1927
+ builtPreloadFiles.length = 0;
1928
+ for (const entry of preloadEntries) {
1929
+ if (!import_node_fs10.default.existsSync(entry)) continue;
1930
+ const base = import_node_path12.default.basename(entry).replace(/\.[^.]+$/, "");
1931
+ const out = import_node_path12.default.join(stageDir, base + extFor(config.electron.preloadFormat));
1932
+ await compileNode(config, entry, {
1933
+ outFile: out,
1934
+ format: config.electron.preloadFormat,
1935
+ devUrl
1936
+ });
1937
+ builtPreloadFiles.push(out);
1938
+ }
1939
+ };
1940
+ await compileAll();
1941
+ if (noSpawn) {
1942
+ console.log(import_picocolors4.default.dim(" (noSpawn) \u5DF2\u7F16\u8BD1\u4E3B/preload\uFF0C\u8DF3\u8FC7\u542F\u52A8 Electron\u3002"));
1943
+ return;
1944
+ }
1945
+ const electronBin = resolveElectronBinary(config);
1946
+ if (!electronBin) {
1947
+ console.warn(
1948
+ import_picocolors4.default.yellow(
1949
+ " \u26A0 \u672A\u627E\u5230 Electron \u53EF\u6267\u884C\u6587\u4EF6\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -D electron\n \u5DF2\u7F16\u8BD1\u4E3B/preload \u81F3 .nasti/\uFF0C\u53EF\u624B\u52A8\u8FD0\u884C\u3002"
1950
+ )
1951
+ );
1952
+ return;
1953
+ }
1954
+ let child = null;
1955
+ const spawnElectron = () => {
1956
+ const args = [builtMainFile, ...config.electron.electronArgs];
1957
+ child = (0, import_node_child_process.spawn)(electronBin, args, {
1958
+ stdio: "inherit",
1959
+ env: { ...process.env, NASTI_DEV_SERVER_URL: devUrl, NASTI_TARGET: "electron" }
1960
+ });
1961
+ child.on("exit", (code) => {
1962
+ if (code !== null && child && child.__nastiKilled !== true) {
1963
+ console.log(import_picocolors4.default.dim(` Electron exited (${code}).`));
1964
+ process.exit(code ?? 0);
1965
+ }
1966
+ });
1967
+ };
1968
+ spawnElectron();
1969
+ if (config.electron.autoRestart) {
1970
+ const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs10.default.existsSync);
1971
+ const watcher = import_chokidar2.default.watch(watchTargets, { ignoreInitial: true });
1972
+ let restarting = null;
1973
+ watcher.on("all", async () => {
1974
+ if (restarting) return;
1975
+ restarting = (async () => {
1976
+ console.log(import_picocolors4.default.cyan("\n \u267B \u4E3B/preload \u53D8\u66F4\uFF0C\u91CD\u542F Electron..."));
1977
+ try {
1978
+ if (child && !child.killed) {
1979
+ ;
1980
+ child.__nastiKilled = true;
1981
+ const dying = child;
1982
+ await new Promise((resolve) => {
1983
+ const timer = setTimeout(() => resolve(), 3e3);
1984
+ dying.once("exit", () => {
1985
+ clearTimeout(timer);
1986
+ resolve();
1987
+ });
1988
+ dying.kill();
1989
+ });
1990
+ }
1991
+ await compileAll();
1992
+ spawnElectron();
1993
+ } finally {
1994
+ restarting = null;
1995
+ }
1996
+ })();
1997
+ });
1998
+ }
1999
+ }
2000
+ function extFor(format) {
2001
+ return format === "cjs" ? ".cjs" : ".mjs";
2002
+ }
2003
+ async function compileNode(config, entry, opts) {
2004
+ const env = loadEnv(config.mode, config.root, config.envPrefix);
2005
+ const envDefine = {
2006
+ ...buildEnvDefine(env, config.mode),
2007
+ __ELECTRON__: "true",
2008
+ __NASTI_TARGET__: JSON.stringify("electron"),
2009
+ __NASTI_DEV_SERVER_URL__: JSON.stringify(opts.devUrl)
2010
+ };
2011
+ const oxcTransformPlugin = {
2012
+ name: "nasti:oxc-transform",
2013
+ transform(code, id) {
2014
+ if (!shouldTransform(id)) return null;
2015
+ const result = transformCode(id, code, {
2016
+ sourcemap: !!config.build.sourcemap,
2017
+ jsxRuntime: "automatic",
2018
+ jsxImportSource: config.framework === "vue" ? "vue" : "react"
2019
+ });
2020
+ return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
2021
+ }
2022
+ };
2023
+ const bundle = await (0, import_rolldown3.rolldown)({
2024
+ input: entry,
2025
+ transform: { define: envDefine },
2026
+ platform: "node",
2027
+ plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
2028
+ });
2029
+ import_node_fs10.default.mkdirSync(import_node_path12.default.dirname(opts.outFile), { recursive: true });
2030
+ await bundle.write({
2031
+ file: opts.outFile,
2032
+ format: opts.format === "cjs" ? "cjs" : "esm",
2033
+ sourcemap: false,
2034
+ minify: false,
2035
+ inlineDynamicImports: true
2036
+ });
2037
+ await bundle.close();
2038
+ }
2039
+ function resolveElectronBinary(config) {
2040
+ if (config.electron.electronPath && import_node_fs10.default.existsSync(config.electron.electronPath)) {
2041
+ return config.electron.electronPath;
2042
+ }
2043
+ try {
2044
+ const require2 = (0, import_node_module3.createRequire)(import_node_path12.default.resolve(config.root, "package.json"));
2045
+ const pathFile = require2.resolve("electron");
2046
+ const electronModule = require2(pathFile);
2047
+ if (typeof electronModule === "string" && import_node_fs10.default.existsSync(electronModule)) {
2048
+ return electronModule;
2049
+ }
2050
+ } catch {
2051
+ }
2052
+ return null;
2053
+ }
2054
+ function warnElectronVersion(config) {
2055
+ const installed = detectInstalledElectron(config.root);
2056
+ if (installed === null) {
2057
+ console.warn(
2058
+ import_picocolors4.default.yellow(
2059
+ ` \u26A0 \u672A\u68C0\u6D4B\u5230 Electron\uFF0C\u8BF7\u5B89\u88C5\uFF1Anpm install -D electron@^${config.electron.minVersion}`
2060
+ )
2061
+ );
2062
+ return;
2063
+ }
2064
+ if (installed < config.electron.minVersion) {
2065
+ console.warn(
2066
+ import_picocolors4.default.yellow(
2067
+ ` \u26A0 Electron ${installed} \u4F4E\u4E8E Nasti \u8981\u6C42\u7684 ${config.electron.minVersion}\uFF0C\u67D0\u4E9B\u7279\u6027\uFF08\u5982 ESM \u4E3B\u8FDB\u7A0B\uFF09\u4E0D\u53EF\u7528\u3002`
2068
+ )
2069
+ );
2070
+ }
2071
+ }
1611
2072
  // Annotate the CommonJS export names for ESM import in node:
1612
2073
  0 && (module.exports = {
1613
2074
  build,
2075
+ buildElectron,
1614
2076
  createServer,
1615
2077
  defineConfig,
1616
- resolveConfig
2078
+ electronPlugin,
2079
+ resolveConfig,
2080
+ startElectronDev
1617
2081
  });
1618
2082
  //# sourceMappingURL=index.cjs.map