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