@nasti-toolchain/nasti 1.3.10 → 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,1428 +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 * as __ns_${safe} from "/@modules/${pkg}";`);
368
- imports.push(`var __req_${safe} = "default" in __ns_${safe} ? __ns_${safe}["default"] : __ns_${safe};`);
369
- result = result.replaceAll(`__require("${pkg}")`, `__req_${safe}`);
370
- 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);
371
359
  }
372
- return imports.join("\n") + "\n" + result;
373
- }
374
- async function injectCjsNamedExports(code, entryFile) {
375
- try {
376
- const { createRequire: createRequire2 } = await import("module");
377
- const req = createRequire2(entryFile);
378
- const cjsExports = req(entryFile);
379
- if (!cjsExports || typeof cjsExports !== "object" && typeof cjsExports !== "function" || Array.isArray(cjsExports)) return code;
380
- const namedKeys = Object.keys(cjsExports).filter(
381
- (k) => k !== "__esModule" && k !== "default" && VALID_IDENT.test(k)
382
- );
383
- if (namedKeys.length === 0) return code;
384
- return code.replace(
385
- /^export default (\w+\(\));?\s*$/m,
386
- (_, call) => [
387
- `const __cjsMod = ${call};`,
388
- `export default __cjsMod;`,
389
- ...namedKeys.map((k) => `export const ${k} = __cjsMod[${JSON.stringify(k)}];`)
390
- ].join("\n")
391
- );
392
- } catch {
393
- return code;
394
- }
395
- }
396
- function rewriteImports(code, _config) {
397
- return code.replace(
398
- /\bfrom\s+(['"])([^'"./][^'"]*)\1/g,
399
- (match, quote, specifier) => {
400
- return `from ${quote}/@modules/${specifier}${quote}`;
401
- }
402
- ).replace(
403
- // 处理纯副作用导入: import 'bare-specifier'
404
- /\bimport\s+(['"])([^'"./][^'"]*)\1/g,
405
- (match, quote, specifier) => {
406
- return `import ${quote}/@modules/${specifier}${quote}`;
407
- }
408
- ).replace(
409
- // 处理动态导入: import('bare-specifier')
410
- /\bimport\s*\(\s*(['"])([^'"./][^'"]*)\1\s*\)/g,
411
- (match, quote, specifier) => {
412
- return `import(${quote}/@modules/${specifier}${quote})`;
413
- }
414
- );
415
- }
416
- function resolveNodeModule(root, moduleName) {
417
- let pkgName;
418
- let subpath;
419
- if (moduleName.startsWith("@")) {
420
- const parts = moduleName.split("/");
421
- pkgName = parts.slice(0, 2).join("/");
422
- subpath = parts.slice(2).join("/");
423
- } else {
424
- const slash = moduleName.indexOf("/");
425
- pkgName = slash === -1 ? moduleName : moduleName.slice(0, slash);
426
- subpath = slash === -1 ? "" : moduleName.slice(slash + 1);
427
- }
428
- let pkgDir = null;
429
- let dir = root;
430
- for (; ; ) {
431
- const candidate = import_node_path8.default.join(dir, "node_modules", pkgName);
432
- if (import_node_fs7.default.existsSync(candidate)) {
433
- pkgDir = candidate;
434
- break;
435
- }
436
- const parent = import_node_path8.default.dirname(dir);
437
- if (parent === dir) break;
438
- dir = parent;
439
- }
440
- if (!pkgDir) return null;
441
- const pkgJsonPath = import_node_path8.default.join(pkgDir, "package.json");
442
- if (!import_node_fs7.default.existsSync(pkgJsonPath)) return null;
443
- let pkg;
444
- try {
445
- pkg = JSON.parse(import_node_fs7.default.readFileSync(pkgJsonPath, "utf-8"));
446
- } catch {
447
- return null;
448
- }
449
- if (pkg.exports) {
450
- const exportKey = subpath ? `./${subpath}` : ".";
451
- const resolved = resolvePackageExports(pkg.exports, exportKey, pkgDir);
452
- if (resolved) return resolved;
453
- }
454
- if (subpath) {
455
- const subDirs = [""];
456
- for (const field of ["module", "main"]) {
457
- if (typeof pkg[field] === "string") {
458
- const dir2 = import_node_path8.default.dirname(pkg[field]);
459
- 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;
460
369
  }
461
- }
462
- for (const dir2 of subDirs) {
463
- const direct = import_node_path8.default.join(pkgDir, dir2, subpath);
464
- if (import_node_fs7.default.existsSync(direct) && import_node_fs7.default.statSync(direct).isFile()) return direct;
465
- for (const ext of RESOLVE_EXTENSIONS) {
466
- 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
+ }
467
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;
468
395
  }
469
- return null;
470
- }
471
- for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
472
- if (typeof pkg[field] === "string") {
473
- const entry = import_node_path8.default.join(pkgDir, pkg[field]);
474
- if (import_node_fs7.default.existsSync(entry)) return entry;
475
- }
476
- }
477
- const indexFallback = import_node_path8.default.join(pkgDir, "index.js");
478
- if (import_node_fs7.default.existsSync(indexFallback)) return indexFallback;
479
- return null;
480
- }
481
- function resolvePackageExports(exports2, key, pkgDir) {
482
- if (typeof exports2 === "string") {
483
- return key === "." ? import_node_path8.default.join(pkgDir, exports2) : null;
484
- }
485
- const entry = exports2[key];
486
- if (entry === void 0) return null;
487
- return resolveExportValue(entry, pkgDir);
396
+ };
488
397
  }
489
- function resolveExportValue(value, pkgDir) {
490
- if (typeof value === "string") return import_node_path8.default.join(pkgDir, value);
491
- if (Array.isArray(value)) {
492
- for (const item of value) {
493
- const r = resolveExportValue(item, pkgDir);
494
- if (r) return r;
495
- }
496
- 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
+ ]);
497
429
  }
498
- if (value && typeof value === "object") {
499
- for (const cond of ESM_CONDITIONS) {
500
- if (cond in value) {
501
- const r = resolveExportValue(value[cond], pkgDir);
502
- 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
+ });
503
445
  }
446
+ return { html, tags };
504
447
  }
505
- }
506
- return null;
448
+ };
507
449
  }
508
- function resolveUrlToFile(url, root) {
509
- const cleanUrl = url.split("?")[0];
510
- if (cleanUrl.startsWith("/@modules/")) {
511
- const moduleName = cleanUrl.slice("/@modules/".length);
512
- 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)}`);
513
458
  }
514
- const filePath = import_node_path8.default.resolve(root, cleanUrl.replace(/^\//, ""));
515
- if (import_node_fs7.default.existsSync(filePath) && import_node_fs7.default.statSync(filePath).isFile()) {
516
- return filePath;
459
+ if (head.length) {
460
+ html = html.replace(/<\/head>/i, `${serializeTags(head)}
461
+ </head>`);
517
462
  }
518
- for (const ext of RESOLVE_EXTENSIONS) {
519
- const withExt = filePath + ext;
520
- if (import_node_fs7.default.existsSync(withExt)) return withExt;
463
+ if (bodyPrepend.length) {
464
+ html = html.replace(/<body([^>]*)>/i, `<body$1>
465
+ ${serializeTags(bodyPrepend)}`);
521
466
  }
522
- for (const ext of RESOLVE_EXTENSIONS) {
523
- const indexFile = import_node_path8.default.join(filePath, "index" + ext);
524
- if (import_node_fs7.default.existsSync(indexFile)) return indexFile;
467
+ if (body.length) {
468
+ html = html.replace(/<\/body>/i, `${serializeTags(body)}
469
+ </body>`);
525
470
  }
526
- return null;
471
+ return html;
527
472
  }
528
- function isModuleRequest(url) {
529
- const cleanUrl = url.split("?")[0];
530
- if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
531
- if (cleanUrl.startsWith("/@modules/")) return true;
532
- if (!import_node_path8.default.extname(cleanUrl)) return true;
533
- return false;
473
+ function serializeTags(tags) {
474
+ return tags.map(serializeTag).join("\n");
534
475
  }
535
- function getHmrClientCode() {
536
- return `
537
- // Nasti HMR Client
538
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
539
- const hotModulesMap = new Map();
540
-
541
- socket.addEventListener('message', ({ data }) => {
542
- const payload = JSON.parse(data);
543
- switch (payload.type) {
544
- case 'connected':
545
- console.log('[nasti] connected.');
546
- break;
547
- case 'update':
548
- payload.updates.forEach((update) => {
549
- if (update.type === 'js-update') {
550
- fetchUpdate(update);
551
- } else if (update.type === 'css-update') {
552
- updateCss(update.path);
553
- }
554
- });
555
- break;
556
- case 'full-reload':
557
- console.log('[nasti] full reload');
558
- location.reload();
559
- break;
560
- case 'error':
561
- console.error('[nasti] error:', payload.err.message);
562
- showErrorOverlay(payload.err);
563
- break;
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} />`;
482
+ }
483
+ return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
484
+ }
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");
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);
564
496
  }
565
497
  });
566
498
 
567
- async function fetchUpdate(update) {
568
- const mod = hotModulesMap.get(update.path);
569
- if (mod) {
570
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
571
- mod.callbacks.forEach((cb) => cb(newMod));
572
- } else {
573
- // \u6CA1\u6709\u6CE8\u518C hot \u56DE\u8C03\uFF0C\u5C1D\u8BD5\u91CD\u65B0 import
574
- await import(update.path + '?t=' + update.timestamp);
575
- }
499
+ // src/core/transformer.ts
500
+ function shouldTransform(id) {
501
+ return TS_EXTENSIONS.test(id) || JSX_EXTENSIONS.test(id) || JS_EXTENSIONS.test(id) && false;
576
502
  }
577
-
578
- function updateCss(path) {
579
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
580
- if (el) {
581
- fetch(path + '?t=' + Date.now())
582
- .then(r => r.text())
583
- .then(css => { el.textContent = css; });
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}`);
584
519
  }
520
+ return {
521
+ code: result.code,
522
+ map: result.map ? JSON.stringify(result.map) : null
523
+ };
585
524
  }
586
-
587
- function showErrorOverlay(err) {
588
- const overlay = document.createElement('div');
589
- overlay.id = 'nasti-error-overlay';
590
- 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;';
591
- const title = document.createElement('h2');
592
- title.style.color = '#ff5555';
593
- title.textContent = 'Build Error';
594
- const pre = document.createElement('pre');
595
- pre.textContent = err.message + '\\n' + (err.stack || '');
596
- const btn = document.createElement('button');
597
- btn.style.cssText = 'margin-top:1rem;padding:0.5rem 1rem;cursor:pointer';
598
- btn.textContent = 'Close';
599
- btn.onclick = () => overlay.remove();
600
- overlay.appendChild(title);
601
- overlay.appendChild(pre);
602
- overlay.appendChild(btn);
603
- document.body.appendChild(overlay);
604
- }
605
-
606
- // import.meta.hot API
607
- const createHotContext = (ownerPath) => ({
608
- accept(deps, callback) {
609
- if (typeof deps === 'function' || !deps) {
610
- // self-accepting
611
- const callbacks = hotModulesMap.get(ownerPath)?.callbacks || [];
612
- callbacks.push(deps || (() => {}));
613
- hotModulesMap.set(ownerPath, { callbacks });
614
- }
615
- },
616
- prune(callback) {
617
- // \u6A21\u5757\u88AB\u79FB\u9664\u65F6\u6267\u884C
618
- },
619
- dispose(callback) {
620
- // \u6A21\u5757\u66F4\u65B0\u524D\u6267\u884C\u6E05\u7406
621
- },
622
- invalidate() {
623
- location.reload();
624
- },
625
- data: {},
626
- });
627
-
628
- // \u66B4\u9732\u7ED9\u6A21\u5757\u4F7F\u7528
629
- if (!window.__nasti_hot_map) window.__nasti_hot_map = new Map();
630
- window.__NASTI_HMR__ = { createHotContext };
631
- `;
632
- }
633
- var import_node_path8, import_node_fs7, REACT_REFRESH_RUNTIME, REACT_REFRESH_PREAMBLE, REACT_REFRESH_FOOTER, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
634
- var init_middleware = __esm({
635
- "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"() {
636
528
  "use strict";
637
- import_node_path8 = __toESM(require("path"), 1);
638
- import_node_fs7 = __toESM(require("fs"), 1);
639
- init_transformer();
640
- init_html();
641
- init_env();
642
- REACT_REFRESH_RUNTIME = `
643
- export function createSignatureFunctionForTransform() {
644
- return function(type, key, forceReset, getCustomHooks) { return type; };
645
- }
646
- export function register(type, id) {}
647
- export default { createSignatureFunctionForTransform, register };
648
- `;
649
- REACT_REFRESH_PREAMBLE = `
650
- import RefreshRuntime from '/@react-refresh';
651
- if (!window.$RefreshReg$) {
652
- window.$RefreshReg$ = (type, id) => RefreshRuntime.register(type, import.meta.url + ' ' + id);
653
- window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
654
- }
655
- `;
656
- REACT_REFRESH_FOOTER = `
657
- if (import.meta.hot) {
658
- import.meta.hot.accept();
659
- }
660
- `;
661
- esmBundleCache = /* @__PURE__ */ new Map();
662
- VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
663
- RESOLVE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".json", ".vue"];
664
- 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)$/;
665
533
  }
666
534
  });
667
535
 
668
- // src/index.ts
669
- var src_exports = {};
670
- __export(src_exports, {
671
- build: () => build,
672
- createServer: () => createServer,
673
- defineConfig: () => defineConfig,
674
- resolveConfig: () => resolveConfig
675
- });
676
- module.exports = __toCommonJS(src_exports);
677
-
678
- // src/config/index.ts
679
- var import_node_url = require("url");
680
- var import_node_path = __toESM(require("path"), 1);
681
- var import_node_fs = __toESM(require("fs"), 1);
682
-
683
- // src/config/defaults.ts
684
- var defaultResolve = {
685
- alias: {},
686
- extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".vue"],
687
- conditions: ["import", "module", "browser", "default"],
688
- mainFields: ["module", "jsnext:main", "jsnext", "main"]
689
- };
690
- var defaultServer = {
691
- port: 3e3,
692
- host: "localhost",
693
- https: false,
694
- open: false,
695
- proxy: {},
696
- cors: true,
697
- hmr: true
698
- };
699
- var defaultBuild = {
700
- outDir: "dist",
701
- assetsDir: "assets",
702
- minify: true,
703
- sourcemap: false,
704
- target: "es2022",
705
- rolldownOptions: {},
706
- emptyOutDir: true
707
- };
708
- var defaults = {
709
- root: ".",
710
- base: "/",
711
- mode: "development",
712
- framework: "auto",
713
- resolve: defaultResolve,
714
- server: defaultServer,
715
- build: defaultBuild,
716
- plugins: [],
717
- envPrefix: ["NASTI_", "VITE_"],
718
- logLevel: "info"
719
- };
720
-
721
- // src/config/index.ts
722
- function loadTsconfigPaths(root) {
723
- const tsconfigPath = import_node_path.default.resolve(root, "tsconfig.json");
724
- if (!import_node_fs.default.existsSync(tsconfigPath)) return {};
725
- try {
726
- const content = import_node_fs.default.readFileSync(tsconfigPath, "utf-8");
727
- const stripped = content.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
728
- const tsconfig = JSON.parse(stripped);
729
- const paths = tsconfig?.compilerOptions?.paths ?? {};
730
- const baseUrl = tsconfig?.compilerOptions?.baseUrl ?? ".";
731
- const alias = {};
732
- for (const [pattern, targets] of Object.entries(paths)) {
733
- if (!targets.length) continue;
734
- const cleanKey = pattern.replace(/\/\*$/, "");
735
- const cleanTarget = targets[0].replace(/\/\*$/, "");
736
- 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;
737
560
  }
738
- return alias;
739
- } catch {
740
- return {};
741
561
  }
742
- }
743
- function defineConfig(config) {
744
- return config;
745
- }
746
- var CONFIG_FILES = [
747
- "nasti.config.ts",
748
- "nasti.config.js",
749
- "nasti.config.mjs",
750
- "nasti.config.mts"
751
- ];
752
- async function loadConfigFromFile(root) {
753
- for (const file of CONFIG_FILES) {
754
- const filePath = import_node_path.default.resolve(root, file);
755
- if (!import_node_fs.default.existsSync(filePath)) continue;
756
- if (file.endsWith(".ts") || file.endsWith(".mts")) {
757
- 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;
758
566
  }
759
- const mod = await import((0, import_node_url.pathToFileURL)(filePath).href);
760
- return mod.default ?? mod;
761
567
  }
762
- return {};
568
+ return filtered;
763
569
  }
764
- async function loadTsConfig(filePath) {
765
- const { transformSync: transformSync2 } = await import("oxc-transform");
766
- const code = import_node_fs.default.readFileSync(filePath, "utf-8");
767
- const result = transformSync2(filePath, code, {
768
- typescript: {}
769
- });
770
- const tmpFile = filePath + ".timestamp-" + Date.now() + ".mjs";
771
- try {
772
- import_node_fs.default.writeFileSync(tmpFile, result.code);
773
- const mod = await import((0, import_node_url.pathToFileURL)(tmpFile).href);
774
- return mod.default ?? mod;
775
- } finally {
776
- 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);
777
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;
778
580
  }
779
- async function resolveConfig(inlineConfig = {}, command) {
780
- const root = import_node_path.default.resolve(inlineConfig.root ?? defaults.root);
781
- const fileConfig = await loadConfigFromFile(root);
782
- const merged = deepMerge(deepMerge({}, fileConfig), inlineConfig);
783
- const rawPlugins = [
784
- ...fileConfig.plugins ?? [],
785
- ...inlineConfig.plugins ?? []
786
- ];
787
- const env = { mode: merged.mode ?? defaults.mode, command };
788
- for (const plugin of rawPlugins) {
789
- if (plugin.config) {
790
- const result = await plugin.config(merged, env);
791
- if (result) Object.assign(merged, result);
792
- }
793
- }
794
- const resolved = {
795
- root,
796
- base: merged.base ?? defaults.base,
797
- mode: command === "build" ? "production" : "development",
798
- framework: merged.framework ?? defaults.framework,
799
- command,
800
- resolve: {
801
- // tsconfig paths 优先级最低:tsconfig < defaults < user config
802
- alias: { ...loadTsconfigPaths(root), ...defaults.resolve.alias, ...merged.resolve?.alias },
803
- extensions: merged.resolve?.extensions ?? defaults.resolve.extensions,
804
- conditions: merged.resolve?.conditions ?? defaults.resolve.conditions,
805
- mainFields: merged.resolve?.mainFields ?? defaults.resolve.mainFields
806
- },
807
- plugins: [],
808
- server: { ...defaults.server, ...merged.server },
809
- build: { ...defaults.build, ...merged.build },
810
- envPrefix: Array.isArray(merged.envPrefix) ? merged.envPrefix : merged.envPrefix ? [merged.envPrefix] : [...defaults.envPrefix],
811
- logLevel: merged.logLevel ?? defaults.logLevel
812
- };
813
- const filteredPlugins = rawPlugins.filter((p) => {
814
- if (!p.apply) return true;
815
- if (typeof p.apply === "function") return p.apply(resolved, env);
816
- return p.apply === command;
817
- });
818
- resolved.plugins = filteredPlugins;
819
- for (const plugin of resolved.plugins) {
820
- if (plugin.configResolved) {
821
- await plugin.configResolved(resolved);
822
- }
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);
823
586
  }
824
- return resolved;
587
+ return result;
825
588
  }
826
- function deepMerge(target, source) {
827
- const result = { ...target };
828
- for (const key of Object.keys(source)) {
829
- const val = source[key];
830
- if (val && typeof val === "object" && !Array.isArray(val)) {
831
- result[key] = deepMerge(
832
- result[key] ?? {},
833
- val
834
- );
835
- } else if (val !== void 0) {
836
- result[key] = val;
837
- }
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);
838
595
  }
839
- return result;
840
- }
841
-
842
- // src/build/index.ts
843
- var import_node_path7 = __toESM(require("path"), 1);
844
- var import_node_fs6 = __toESM(require("fs"), 1);
845
- var import_rolldown = require("rolldown");
596
+ });
846
597
 
847
- // src/plugins/resolve.ts
848
- var import_node_path2 = __toESM(require("path"), 1);
849
- var import_node_fs2 = __toESM(require("fs"), 1);
850
- var import_node_module = require("module");
851
- function resolvePlugin(config) {
852
- const { alias, extensions } = config.resolve;
853
- const require2 = (0, import_node_module.createRequire)(import_node_path2.default.resolve(config.root, "package.json"));
854
- return {
855
- name: "nasti:resolve",
856
- enforce: "pre",
857
- resolveId(source, importer) {
858
- for (const [key, value] of Object.entries(alias)) {
859
- if (source === key || source.startsWith(key + "/")) {
860
- source = source.replace(key, value);
861
- if (!import_node_path2.default.isAbsolute(source)) {
862
- 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);
863
652
  }
864
- break;
865
653
  }
866
654
  }
867
- if (import_node_path2.default.isAbsolute(source)) {
868
- const resolved = tryResolveFile(source, extensions);
869
- 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
+ }
870
661
  }
871
- if (source.startsWith(".")) {
872
- const dir = importer ? import_node_path2.default.dirname(importer) : config.root;
873
- const absolute = import_node_path2.default.resolve(dir, source);
874
- const resolved = tryResolveFile(absolute, extensions);
875
- 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;
876
674
  }
877
- if (!source.startsWith("/") && !source.startsWith(".")) {
878
- try {
879
- const resolved = require2.resolve(source, {
880
- paths: [importer ? import_node_path2.default.dirname(importer) : config.root]
881
- });
882
- return resolved;
883
- } catch {
884
- 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;
885
680
  }
681
+ return null;
886
682
  }
887
- return null;
888
- },
889
- load(id) {
890
- if (!import_node_fs2.default.existsSync(id)) return null;
891
- if (id.endsWith(".json")) {
892
- const content = import_node_fs2.default.readFileSync(id, "utf-8");
893
- 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 };
894
696
  }
895
- return import_node_fs2.default.readFileSync(id, "utf-8");
896
- }
897
- };
898
- }
899
- function tryResolveFile(file, extensions) {
900
- if (import_node_fs2.default.existsSync(file) && import_node_fs2.default.statSync(file).isFile()) {
901
- 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
+ };
902
715
  }
903
- for (const ext of extensions) {
904
- const withExt = file + ext;
905
- if (import_node_fs2.default.existsSync(withExt) && import_node_fs2.default.statSync(withExt).isFile()) {
906
- 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
+ }
907
743
  }
908
744
  }
909
- if (import_node_fs2.default.existsSync(file) && import_node_fs2.default.statSync(file).isDirectory()) {
910
- for (const ext of extensions) {
911
- const indexFile = import_node_path2.default.join(file, "index" + ext);
912
- if (import_node_fs2.default.existsSync(indexFile)) {
913
- 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;
914
752
  }
915
753
  }
916
754
  }
917
- return null;
918
- }
919
-
920
- // src/plugins/css.ts
921
- var import_node_path3 = __toESM(require("path"), 1);
922
- function cssPlugin(config) {
923
- return {
924
- name: "nasti:css",
925
- resolveId(source) {
926
- if (source.endsWith(".css")) return null;
927
- return null;
928
- },
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",
929
768
  transform(code, id) {
930
- if (!id.endsWith(".css")) return null;
931
- const rewritten = rewriteCssUrls(code, id, config.root);
932
- if (config.command === "serve") {
933
- const escaped = JSON.stringify(rewritten);
934
- return {
935
- code: `
936
- const css = ${escaped};
937
- const __nasti_css_id__ = ${JSON.stringify(id)};
938
- const __nasti_existing__ = document.querySelector('style[data-nasti-css=' + JSON.stringify(__nasti_css_id__) + ']');
939
- if (__nasti_existing__) __nasti_existing__.remove();
940
- const style = document.createElement('style');
941
- style.setAttribute('data-nasti-css', __nasti_css_id__);
942
- style.textContent = css;
943
- document.head.appendChild(style);
944
-
945
- // HMR
946
- if (import.meta.hot) {
947
- import.meta.hot.accept();
948
- import.meta.hot.prune(() => {
949
- 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
950
796
  });
951
- }
952
-
953
- export default css;
954
- `
955
- };
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);
956
824
  }
957
- return rewritten !== code ? { code: rewritten } : null;
958
825
  }
959
- };
960
- }
961
- function rewriteCssUrls(css, from, root) {
962
- return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
963
- if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
964
- 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
+ }
965
834
  }
966
- const resolved = import_node_path3.default.resolve(import_node_path3.default.dirname(from), url);
967
- const relative = "/" + import_node_path3.default.relative(root, resolved);
968
- return `url(${relative})`;
969
- });
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 };
970
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
+ });
971
875
 
972
- // src/plugins/assets.ts
973
- var import_node_path4 = __toESM(require("path"), 1);
974
- var import_node_fs3 = __toESM(require("fs"), 1);
975
- var import_node_crypto = __toESM(require("crypto"), 1);
976
- var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
977
- ".png",
978
- ".jpg",
979
- ".jpeg",
980
- ".gif",
981
- ".svg",
982
- ".ico",
983
- ".webp",
984
- ".avif",
985
- ".mp4",
986
- ".webm",
987
- ".ogg",
988
- ".mp3",
989
- ".wav",
990
- ".flac",
991
- ".aac",
992
- ".woff",
993
- ".woff2",
994
- ".eot",
995
- ".ttf",
996
- ".otf",
997
- ".pdf",
998
- ".txt"
999
- ]);
1000
- function assetsPlugin(config) {
1001
- return {
1002
- name: "nasti:assets",
1003
- resolveId(source) {
1004
- if (source.endsWith("?url") || source.endsWith("?raw")) {
1005
- 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);
1006
887
  }
1007
- return null;
1008
- },
1009
- load(id) {
1010
- const ext = import_node_path4.default.extname(id.replace(/\?.*$/, ""));
1011
- if (id.endsWith("?raw")) {
1012
- const file = id.slice(0, -4);
1013
- if (import_node_fs3.default.existsSync(file)) {
1014
- const content = import_node_fs3.default.readFileSync(file, "utf-8");
1015
- 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);
1016
924
  }
925
+ mods.add(mod);
1017
926
  }
1018
- if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
1019
- const file = id.replace(/\?.*$/, "");
1020
- if (!import_node_fs3.default.existsSync(file)) return null;
1021
- if (config.command === "serve") {
1022
- const url = "/" + import_node_path4.default.relative(config.root, file);
1023
- 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
+ }
1024
939
  }
1025
- const content = import_node_fs3.default.readFileSync(file);
1026
- const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
1027
- const basename = import_node_path4.default.basename(file, ext);
1028
- const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
1029
- return `export default ${JSON.stringify(config.base + hashedName)}`;
1030
940
  }
1031
- 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();
1032
1022
  }
1033
1023
  };
1034
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
+ });
1035
1032
 
1036
- // src/build/index.ts
1037
- init_html();
1038
- init_transformer();
1039
- init_env();
1040
-
1041
- // src/core/plugin-container.ts
1042
- var PluginContainer = class {
1043
- plugins;
1044
- config;
1045
- ctx;
1046
- emittedFiles = /* @__PURE__ */ new Map();
1047
- constructor(config) {
1048
- this.config = config;
1049
- this.plugins = sortPlugins(config.plugins);
1050
- this.ctx = this.createContext();
1051
- }
1052
- createContext() {
1053
- const container = this;
1054
- return {
1055
- async resolve(source, importer) {
1056
- return container.resolveId(source, importer);
1057
- },
1058
- emitFile(file) {
1059
- const fileName = file.fileName ?? file.name ?? `asset-${container.emittedFiles.size}`;
1060
- const id = `emitted:${fileName}`;
1061
- container.emittedFiles.set(id, {
1062
- fileName,
1063
- source: file.source ?? ""
1064
- });
1065
- return id;
1066
- },
1067
- getModuleInfo(_id) {
1068
- 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;
1069
1055
  }
1070
- };
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;
1071
1273
  }
1072
- /** 返回所有通过 emitFile() 输出的文件 */
1073
- getEmittedFiles() {
1074
- 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;
1075
1287
  }
1076
- async buildStart() {
1077
- for (const plugin of this.plugins) {
1078
- if (plugin.buildStart) {
1079
- 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);
1080
1294
  }
1081
1295
  }
1082
- }
1083
- async buildEnd(error) {
1084
- for (const plugin of this.plugins) {
1085
- if (plugin.buildEnd) {
1086
- 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;
1087
1301
  }
1088
1302
  }
1303
+ return null;
1089
1304
  }
1090
- async resolveId(source, importer, options = {}) {
1091
- for (const plugin of this.plugins) {
1092
- if (!plugin.resolveId) continue;
1093
- const result = await plugin.resolveId.call(
1094
- this.ctx,
1095
- source,
1096
- importer ?? void 0,
1097
- { isEntry: options.isEntry ?? false, ssr: false }
1098
- );
1099
- 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;
1100
1309
  }
1101
- return null;
1102
1310
  }
1103
- async load(id) {
1104
- for (const plugin of this.plugins) {
1105
- if (!plugin.load) continue;
1106
- const result = await plugin.load.call(this.ctx, id);
1107
- 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;
1108
1329
  }
1109
1330
  return null;
1110
1331
  }
1111
- async transform(code, id) {
1112
- let currentCode = code;
1113
- for (const plugin of this.plugins) {
1114
- if (!plugin.transform) continue;
1115
- const result = await plugin.transform.call(this.ctx, currentCode, id);
1116
- if (result == null) continue;
1117
- if (typeof result === "string") {
1118
- currentCode = result;
1119
- } else {
1120
- 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;
1121
1337
  }
1122
1338
  }
1123
- return currentCode === code ? null : { code: currentCode };
1124
- }
1125
- /** 完整的模块处理管道: resolveId → load → transform */
1126
- async processModule(source, importer) {
1127
- const resolveResult = await this.resolveId(source, importer, {
1128
- isEntry: !importer
1129
- });
1130
- if (resolveResult == null) return null;
1131
- const id = typeof resolveResult === "string" ? resolveResult : resolveResult.id;
1132
- const loadResult = await this.load(id);
1133
- if (loadResult == null) return null;
1134
- const loadedCode = typeof loadResult === "string" ? loadResult : loadResult.code;
1135
- const transformResult = await this.transform(loadedCode, id);
1136
- const finalCode = transformResult == null ? loadedCode : typeof transformResult === "string" ? transformResult : transformResult.code;
1137
- return { id, code: finalCode };
1138
- }
1139
- getPlugins() {
1140
- return this.plugins;
1141
- }
1142
- };
1143
- function sortPlugins(plugins) {
1144
- const pre = [];
1145
- const normal = [];
1146
- const post = [];
1147
- for (const plugin of plugins) {
1148
- if (plugin.enforce === "pre") pre.push(plugin);
1149
- else if (plugin.enforce === "post") post.push(plugin);
1150
- else normal.push(plugin);
1151
1339
  }
1152
- return [...pre, ...normal, ...post];
1340
+ return null;
1153
1341
  }
1154
-
1155
- // src/build/index.ts
1156
- var import_picocolors = __toESM(require("picocolors"), 1);
1157
- async function build(inlineConfig = {}) {
1158
- const config = await resolveConfig(inlineConfig, "build");
1159
- const startTime = performance.now();
1160
- console.log(import_picocolors.default.cyan("\n\u{1F528} nasti build") + import_picocolors.default.dim(` v${"1.3.10"}`));
1161
- console.log(import_picocolors.default.dim(` root: ${config.root}`));
1162
- console.log(import_picocolors.default.dim(` mode: ${config.mode}`));
1163
- const outDir = import_node_path7.default.resolve(config.root, config.build.outDir);
1164
- if (config.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
1165
- 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);
1166
1347
  }
1167
- import_node_fs6.default.mkdirSync(outDir, { recursive: true });
1168
- const html = await readHtmlFile(config.root);
1169
- let entryPoints = [];
1170
- if (html) {
1171
- const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
1172
- for (const match of scriptMatches) {
1173
- const src = match[1];
1174
- if (src && !src.startsWith("http")) {
1175
- entryPoints.push(import_node_path7.default.resolve(config.root, src.replace(/^\//, "")));
1176
- }
1177
- }
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;
1178
1351
  }
1179
- if (entryPoints.length === 0) {
1180
- const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
1181
- for (const entry of fallbackEntries) {
1182
- const fullPath = import_node_path7.default.resolve(config.root, entry);
1183
- if (import_node_fs6.default.existsSync(fullPath)) {
1184
- entryPoints.push(fullPath);
1185
- break;
1186
- }
1187
- }
1352
+ for (const ext of RESOLVE_EXTENSIONS) {
1353
+ const withExt = filePath + ext;
1354
+ if (import_node_fs8.default.existsSync(withExt)) return withExt;
1188
1355
  }
1189
- if (entryPoints.length === 0) {
1190
- 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;
1191
1359
  }
1192
- const builtinPlugins = [
1193
- resolvePlugin(config),
1194
- cssPlugin(config),
1195
- assetsPlugin(config)
1196
- ];
1197
- const allPlugins = [...builtinPlugins, ...config.plugins];
1198
- const pluginContainer = new PluginContainer(config);
1199
- await pluginContainer.buildStart();
1200
- const oxcTransformPlugin = {
1201
- name: "nasti:oxc-transform",
1202
- transform(code, id) {
1203
- if (!shouldTransform(id)) return null;
1204
- const result = transformCode(id, code, {
1205
- sourcemap: !!config.build.sourcemap,
1206
- jsxRuntime: "automatic",
1207
- 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
+ }
1208
1388
  });
1209
- return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
1210
- }
1211
- };
1212
- const env = loadEnv(config.mode, config.root, config.envPrefix);
1213
- const envDefine = buildEnvDefine(env, config.mode);
1214
- const bundle = await (0, import_rolldown.rolldown)({
1215
- input: entryPoints,
1216
- define: envDefine,
1217
- plugins: [
1218
- oxcTransformPlugin,
1219
- // 转换 Nasti 插件为 Rolldown 插件格式
1220
- ...allPlugins.map((p) => ({
1221
- name: p.name,
1222
- resolveId: p.resolveId,
1223
- load: p.load,
1224
- transform: p.transform,
1225
- buildStart: p.buildStart,
1226
- buildEnd: p.buildEnd
1227
- }))
1228
- ],
1229
- ...config.build.rolldownOptions
1230
- });
1231
- const { output } = await bundle.write({
1232
- dir: outDir,
1233
- format: "esm",
1234
- sourcemap: !!config.build.sourcemap,
1235
- minify: !!config.build.minify,
1236
- entryFileNames: "assets/[name].[hash].js",
1237
- chunkFileNames: "assets/[name].[hash].js",
1238
- assetFileNames: "assets/[name].[hash][extname]"
1239
- });
1240
- await bundle.close();
1241
- await pluginContainer.buildEnd();
1242
- for (const ef of pluginContainer.getEmittedFiles()) {
1243
- const dest = import_node_path7.default.resolve(outDir, ef.fileName);
1244
- import_node_fs6.default.mkdirSync(import_node_path7.default.dirname(dest), { recursive: true });
1245
- 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;
1246
1398
  }
1247
- if (html) {
1248
- let processedHtml = html;
1249
- const htmlPlugin_ = htmlPlugin(config);
1250
- if (htmlPlugin_.transformIndexHtml) {
1251
- const result = await htmlPlugin_.transformIndexHtml(processedHtml);
1252
- if (typeof result === "string") {
1253
- processedHtml = result;
1254
- } else if (result && "html" in result) {
1255
- processedHtml = processHtml(result.html, result.tags);
1256
- } else if (Array.isArray(result)) {
1257
- processedHtml = processHtml(processedHtml, result);
1258
- }
1259
- }
1260
- for (const chunk of output) {
1261
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
1262
- const originalEntry = import_node_path7.default.relative(config.root, chunk.facadeModuleId);
1263
- processedHtml = processedHtml.replace(
1264
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
1265
- `$1${config.base}${chunk.fileName}$3`
1266
- );
1267
- }
1268
- }
1269
- 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);
1270
1409
  }
1271
- const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
1272
- const totalSize = output.reduce((sum, chunk) => {
1273
- if (chunk.type === "chunk" && chunk.code) return sum + chunk.code.length;
1274
- return sum;
1275
- }, 0);
1276
- console.log(import_picocolors.default.green(`
1277
- \u2713 Built in ${elapsed}s`));
1278
- console.log(import_picocolors.default.dim(` ${output.length} files, ${formatSize(totalSize)} total`));
1279
- console.log(import_picocolors.default.dim(` output: ${config.build.outDir}/
1280
- `));
1281
- return { output };
1282
1410
  }
1283
- function formatSize(bytes) {
1284
- if (bytes < 1024) return `${bytes} B`;
1285
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} kB`;
1286
- 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
+ }
1287
1419
  }
1288
- function escapeRegExp(string) {
1289
- 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);
1290
1438
  }
1291
1439
 
1292
- // src/server/index.ts
1293
- var import_node_http = __toESM(require("http"), 1);
1294
- var import_node_path10 = __toESM(require("path"), 1);
1295
- var import_node_os = __toESM(require("os"), 1);
1296
- var import_connect = __toESM(require("connect"), 1);
1297
- var import_sirv = __toESM(require("sirv"), 1);
1298
- var import_chokidar = require("chokidar");
1299
- var import_picocolors2 = __toESM(require("picocolors"), 1);
1300
-
1301
- // src/core/module-graph.ts
1302
- var ModuleGraph = class {
1303
- urlToModuleMap = /* @__PURE__ */ new Map();
1304
- idToModuleMap = /* @__PURE__ */ new Map();
1305
- fileToModulesMap = /* @__PURE__ */ new Map();
1306
- getModuleByUrl(url) {
1307
- return this.urlToModuleMap.get(url);
1308
- }
1309
- getModuleById(id) {
1310
- return this.idToModuleMap.get(id);
1311
- }
1312
- getModulesByFile(file) {
1313
- return this.fileToModulesMap.get(file);
1314
- }
1315
- async ensureEntryFromUrl(url) {
1316
- let mod = this.urlToModuleMap.get(url);
1317
- if (mod) return mod;
1318
- mod = this.createModule(url);
1319
- this.urlToModuleMap.set(url, mod);
1320
- return mod;
1321
- }
1322
- createModule(url, id) {
1323
- const mod = {
1324
- id: id ?? url,
1325
- file: null,
1326
- url,
1327
- type: url.endsWith(".css") ? "css" : "js",
1328
- importers: /* @__PURE__ */ new Set(),
1329
- importedModules: /* @__PURE__ */ new Set(),
1330
- acceptedHmrDeps: /* @__PURE__ */ new Set(),
1331
- transformResult: null,
1332
- lastHMRTimestamp: 0,
1333
- isSelfAccepting: false
1334
- };
1335
- this.idToModuleMap.set(mod.id, mod);
1336
- return mod;
1337
- }
1338
- /** 注册文件路径到模块的映射 */
1339
- registerModule(mod, file) {
1340
- mod.file = file;
1341
- let mods = this.fileToModulesMap.get(file);
1342
- if (!mods) {
1343
- mods = /* @__PURE__ */ new Set();
1344
- this.fileToModulesMap.set(file, mods);
1345
- }
1346
- mods.add(mod);
1347
- }
1348
- /** 更新模块依赖关系 */
1349
- updateModuleImports(mod, importedIds) {
1350
- for (const imported of mod.importedModules) {
1351
- imported.importers.delete(mod);
1352
- }
1353
- mod.importedModules.clear();
1354
- for (const id of importedIds) {
1355
- const importedMod = this.idToModuleMap.get(id);
1356
- if (importedMod) {
1357
- mod.importedModules.add(importedMod);
1358
- importedMod.importers.add(mod);
1359
- }
1360
- }
1361
- }
1362
- /** 使模块的转换缓存失效 */
1363
- invalidateModule(mod) {
1364
- mod.transformResult = null;
1365
- mod.lastHMRTimestamp = Date.now();
1366
- }
1367
- /** 使所有模块缓存失效 */
1368
- invalidateAll() {
1369
- for (const mod of this.idToModuleMap.values()) {
1370
- this.invalidateModule(mod);
1371
- }
1372
- }
1373
- /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
1374
- getHmrBoundaries(mod) {
1375
- const boundaries = [];
1376
- const visited = /* @__PURE__ */ new Set();
1377
- const propagate = (node, via) => {
1378
- if (visited.has(node)) return true;
1379
- visited.add(node);
1380
- if (node.isSelfAccepting) {
1381
- boundaries.push({ boundary: node, acceptedVia: via });
1382
- return true;
1383
- }
1384
- if (node.acceptedHmrDeps.has(via)) {
1385
- boundaries.push({ boundary: node, acceptedVia: via });
1386
- return true;
1387
- }
1388
- if (node.importers.size === 0) return false;
1389
- for (const importer of node.importers) {
1390
- if (!propagate(importer, node)) return false;
1391
- }
1392
- return true;
1393
- };
1394
- if (mod.isSelfAccepting) {
1395
- boundaries.push({ boundary: mod, acceptedVia: mod });
1396
- return boundaries;
1397
- }
1398
- for (const importer of mod.importers) {
1399
- if (!propagate(importer, mod)) {
1400
- return [];
1401
- }
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 });
1402
1448
  }
1403
- return boundaries;
1404
- }
1405
- };
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
+ });
1406
1461
 
1407
- // src/server/ws.ts
1408
- var import_ws = require("ws");
1409
- function createWebSocketServer(server) {
1410
- const wss = new import_ws.WebSocketServer({ noServer: true });
1411
- const clients = /* @__PURE__ */ new Set();
1412
- server.on("upgrade", (req, socket, head) => {
1413
- if (req.headers["sec-websocket-protocol"] === "nasti-hmr") {
1414
- wss.handleUpgrade(req, socket, head, (ws) => {
1415
- wss.emit("connection", ws, req);
1416
- });
1417
- }
1418
- });
1419
- wss.on("connection", (ws) => {
1420
- clients.add(ws);
1421
- ws.send(JSON.stringify({ type: "connected" }));
1422
- ws.on("close", () => {
1423
- clients.delete(ws);
1424
- });
1425
- ws.on("error", (err) => {
1426
- console.error("[nasti] WebSocket error:", err);
1427
- clients.delete(ws);
1428
- });
1429
- });
1430
- return {
1431
- send(payload) {
1432
- const data = JSON.stringify(payload);
1433
- for (const client of clients) {
1434
- if (client.readyState === 1) {
1435
- client.send(data);
1436
- }
1437
- }
1438
- },
1439
- close() {
1440
- clients.clear();
1441
- wss.close();
1442
- }
1443
- };
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
+ `;
1444
1466
  }
1445
-
1446
- // src/server/index.ts
1447
- 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
+ });
1448
1501
 
1449
1502
  // src/server/hmr.ts
1450
- var import_node_path9 = __toESM(require("path"), 1);
1451
- var import_node_fs8 = __toESM(require("fs"), 1);
1452
1503
  async function handleFileChange(file, server) {
1453
1504
  const { moduleGraph, ws, config } = server;
1454
- const relativePath = "/" + import_node_path9.default.relative(config.root, file);
1505
+ const relativePath = "/" + import_node_path10.default.relative(config.root, file);
1455
1506
  const mods = moduleGraph.getModulesByFile(file);
1456
1507
  if (!mods || mods.size === 0) {
1457
1508
  return;
@@ -1464,7 +1515,7 @@ async function handleFileChange(file, server) {
1464
1515
  file,
1465
1516
  timestamp,
1466
1517
  modules: [mod],
1467
- read: () => import_node_fs8.default.readFileSync(file, "utf-8"),
1518
+ read: () => import_node_fs9.default.readFileSync(file, "utf-8"),
1468
1519
  server
1469
1520
  };
1470
1521
  let affectedModules = [mod];
@@ -1496,9 +1547,20 @@ async function handleFileChange(file, server) {
1496
1547
  ws.send({ type: "update", updates });
1497
1548
  }
1498
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
+ });
1499
1558
 
1500
1559
  // src/server/index.ts
1501
- init_html();
1560
+ var server_exports = {};
1561
+ __export(server_exports, {
1562
+ createServer: () => createServer
1563
+ });
1502
1564
  async function createServer(inlineConfig = {}) {
1503
1565
  const config = await resolveConfig(inlineConfig, "serve");
1504
1566
  const allPlugins = [
@@ -1517,7 +1579,7 @@ async function createServer(inlineConfig = {}) {
1517
1579
  pluginContainer,
1518
1580
  moduleGraph
1519
1581
  }));
1520
- const publicDir = import_node_path10.default.resolve(config.root, "public");
1582
+ const publicDir = import_node_path11.default.resolve(config.root, "public");
1521
1583
  app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
1522
1584
  app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
1523
1585
  const httpServer = import_node_http.default.createServer(app);
@@ -1560,14 +1622,15 @@ async function createServer(inlineConfig = {}) {
1560
1622
  let currentPort = finalPort;
1561
1623
  const onListening = () => {
1562
1624
  const actualPort = httpServer.address()?.port ?? currentPort;
1625
+ config.server.port = actualPort;
1563
1626
  const localUrl = `http://localhost:${actualPort}`;
1564
1627
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}` : null;
1565
1628
  console.log();
1566
- console.log(import_picocolors2.default.cyan(" nasti dev server") + import_picocolors2.default.dim(` v${"1.3.10"}`));
1629
+ console.log(import_picocolors3.default.cyan(" nasti dev server") + import_picocolors3.default.dim(` v${"1.4.0"}`));
1567
1630
  console.log();
1568
- 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)}`);
1569
1632
  if (networkUrl) {
1570
- 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)}`);
1571
1634
  }
1572
1635
  console.log();
1573
1636
  resolve(server);
@@ -1576,7 +1639,7 @@ async function createServer(inlineConfig = {}) {
1576
1639
  httpServer.on("error", (err) => {
1577
1640
  if (err.code === "EADDRINUSE") {
1578
1641
  currentPort++;
1579
- 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}...`));
1580
1643
  httpServer.listen(currentPort, host);
1581
1644
  } else {
1582
1645
  reject(err);
@@ -1609,11 +1672,411 @@ function getNetworkAddress() {
1609
1672
  }
1610
1673
  return "localhost";
1611
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
+ }
1612
2072
  // Annotate the CommonJS export names for ESM import in node:
1613
2073
  0 && (module.exports = {
1614
2074
  build,
2075
+ buildElectron,
1615
2076
  createServer,
1616
2077
  defineConfig,
1617
- resolveConfig
2078
+ electronPlugin,
2079
+ resolveConfig,
2080
+ startElectronDev
1618
2081
  });
1619
2082
  //# sourceMappingURL=index.cjs.map