@nasti-toolchain/nasti 1.4.1 → 1.5.1

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
@@ -164,6 +164,14 @@ async function resolveConfig(inlineConfig = {}, command) {
164
164
  return p.apply === command;
165
165
  });
166
166
  resolved.plugins = filteredPlugins;
167
+ if (resolved.target === "electron") {
168
+ const autoExternal = detectNativeDeps(root);
169
+ if (autoExternal.length > 0) {
170
+ const current = new Set(resolved.electron.external ?? []);
171
+ for (const dep of autoExternal) current.add(dep);
172
+ resolved.electron.external = [...current];
173
+ }
174
+ }
167
175
  for (const plugin of resolved.plugins) {
168
176
  if (plugin.configResolved) {
169
177
  await plugin.configResolved(resolved);
@@ -171,6 +179,73 @@ async function resolveConfig(inlineConfig = {}, command) {
171
179
  }
172
180
  return resolved;
173
181
  }
182
+ function detectNativeDeps(root) {
183
+ const result = /* @__PURE__ */ new Set();
184
+ const pkgJsonPath = path.resolve(root, "package.json");
185
+ if (!fs.existsSync(pkgJsonPath)) return [];
186
+ let pkg;
187
+ try {
188
+ pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
189
+ } catch {
190
+ return [];
191
+ }
192
+ const deps = {
193
+ ...pkg.dependencies ?? {},
194
+ ...pkg.optionalDependencies ?? {}
195
+ };
196
+ const KNOWN_NATIVE = /* @__PURE__ */ new Set([
197
+ "node-pty",
198
+ "better-sqlite3",
199
+ "sharp",
200
+ "serialport",
201
+ "@vscode/tree-sitter-wasm",
202
+ "keytar",
203
+ "nodegit",
204
+ "sqlite3",
205
+ "fsevents"
206
+ ]);
207
+ for (const dep of Object.keys(deps)) {
208
+ if (KNOWN_NATIVE.has(dep)) {
209
+ result.add(dep);
210
+ continue;
211
+ }
212
+ const depDir = path.resolve(root, "node_modules", dep);
213
+ if (!fs.existsSync(depDir)) continue;
214
+ if (fs.existsSync(path.join(depDir, "binding.gyp"))) {
215
+ result.add(dep);
216
+ continue;
217
+ }
218
+ const subPkg = path.join(depDir, "package.json");
219
+ if (fs.existsSync(subPkg)) {
220
+ try {
221
+ const sub = JSON.parse(fs.readFileSync(subPkg, "utf-8"));
222
+ if (sub.gypfile === true || sub.binary?.module_name) {
223
+ result.add(dep);
224
+ continue;
225
+ }
226
+ } catch {
227
+ }
228
+ }
229
+ if (hasDotNodeFile(depDir)) {
230
+ result.add(dep);
231
+ }
232
+ }
233
+ return [...result];
234
+ }
235
+ function hasDotNodeFile(dir, depth = 0) {
236
+ if (depth > 3) return false;
237
+ try {
238
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
239
+ for (const e of entries) {
240
+ if (e.isFile() && e.name.endsWith(".node")) return true;
241
+ if (e.isDirectory() && (e.name === "build" || e.name === "prebuilds" || e.name === "Release")) {
242
+ if (hasDotNodeFile(path.join(dir, e.name), depth + 1)) return true;
243
+ }
244
+ }
245
+ } catch {
246
+ }
247
+ return false;
248
+ }
174
249
  function deepMerge(target, source) {
175
250
  const result = { ...target };
176
251
  for (const key of Object.keys(source)) {
@@ -415,6 +490,15 @@ function htmlPlugin(config) {
415
490
  transformIndexHtml(html) {
416
491
  const tags = [];
417
492
  if (config.command === "serve") {
493
+ const isReactLike = config.framework === "react" || config.framework === "auto";
494
+ if (isReactLike) {
495
+ tags.push({
496
+ tag: "script",
497
+ attrs: { type: "module" },
498
+ children: REACT_REFRESH_HTML_PREAMBLE,
499
+ injectTo: "head-prepend"
500
+ });
501
+ }
418
502
  tags.push({
419
503
  tag: "script",
420
504
  attrs: { type: "module", src: "/@nasti/client" },
@@ -465,9 +549,17 @@ async function readHtmlFile(root) {
465
549
  if (!fs4.existsSync(htmlPath)) return null;
466
550
  return fs4.readFileSync(htmlPath, "utf-8");
467
551
  }
552
+ var REACT_REFRESH_HTML_PREAMBLE;
468
553
  var init_html = __esm({
469
554
  "src/plugins/html.ts"() {
470
555
  "use strict";
556
+ REACT_REFRESH_HTML_PREAMBLE = `
557
+ import RefreshRuntime from "/@react-refresh";
558
+ RefreshRuntime.injectIntoGlobalHook(window);
559
+ window.$RefreshReg$ = () => {};
560
+ window.$RefreshSig$ = () => (type) => type;
561
+ window.__vite_plugin_react_preamble_installed__ = true;
562
+ `.trim();
471
563
  }
472
564
  });
473
565
 
@@ -701,7 +793,7 @@ import pc from "picocolors";
701
793
  async function build(inlineConfig = {}) {
702
794
  const config = await resolveConfig(inlineConfig, "build");
703
795
  const startTime = performance.now();
704
- console.log(pc.cyan("\n\u{1F528} nasti build") + pc.dim(` v${"1.4.1"}`));
796
+ console.log(pc.cyan("\n\u{1F528} nasti build") + pc.dim(` v${"1.5.1"}`));
705
797
  console.log(pc.dim(` root: ${config.root}`));
706
798
  console.log(pc.dim(` mode: ${config.mode}`));
707
799
  const outDir = path7.resolve(config.root, config.build.outDir);
@@ -1005,11 +1097,85 @@ var init_ws = __esm({
1005
1097
  // src/server/middleware.ts
1006
1098
  var middleware_exports = {};
1007
1099
  __export(middleware_exports, {
1100
+ REACT_REFRESH_GLOBAL_PREAMBLE: () => REACT_REFRESH_GLOBAL_PREAMBLE,
1008
1101
  transformMiddleware: () => transformMiddleware,
1009
1102
  transformRequest: () => transformRequest
1010
1103
  });
1011
1104
  import path9 from "path";
1012
1105
  import fs8 from "fs";
1106
+ import { createRequire as createRequire2 } from "module";
1107
+ import { fileURLToPath } from "url";
1108
+ function getReactRefreshRuntimeEsm() {
1109
+ if (__refreshRuntimeCache) return __refreshRuntimeCache;
1110
+ let cjsPath;
1111
+ try {
1112
+ cjsPath = __require.resolve("react-refresh/cjs/react-refresh-runtime.development.js");
1113
+ } catch {
1114
+ cjsPath = path9.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
1115
+ }
1116
+ const cjsSource = fs8.readFileSync(cjsPath, "utf-8");
1117
+ __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
1118
+ const exports = {};
1119
+ const module = { exports };
1120
+ const process = { env: { NODE_ENV: 'development' } };
1121
+ ${cjsSource}
1122
+ const __rt = module.exports;
1123
+ export const injectIntoGlobalHook = __rt.injectIntoGlobalHook;
1124
+ export const register = __rt.register;
1125
+ export const createSignatureFunctionForTransform = __rt.createSignatureFunctionForTransform;
1126
+ export const performReactRefresh = __rt.performReactRefresh;
1127
+ export const isLikelyComponentType = __rt.isLikelyComponentType;
1128
+ export const hasUnrecoverableErrors = __rt.hasUnrecoverableErrors;
1129
+ export const setSignature = __rt.setSignature;
1130
+ export const getFamilyByID = __rt.getFamilyByID;
1131
+ export const getFamilyByType = __rt.getFamilyByType;
1132
+ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
1133
+ export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
1134
+ export default __rt;
1135
+ `;
1136
+ return __refreshRuntimeCache;
1137
+ }
1138
+ function buildReactRefreshWrapper(moduleUrl, transformedCode) {
1139
+ const urlLit = JSON.stringify(moduleUrl);
1140
+ const userCode = transformedCode.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
1141
+ return `import * as RefreshRuntime from "/@react-refresh";
1142
+ import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
1143
+ const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
1144
+
1145
+ if (!window.__vite_plugin_react_preamble_installed__) {
1146
+ throw new Error("[nasti] React Fast Refresh preamble missing. Make sure nasti:html is wired with framework: 'react'.");
1147
+ }
1148
+
1149
+ const prevRefreshReg = window.$RefreshReg$;
1150
+ const prevRefreshSig = window.$RefreshSig$;
1151
+ window.$RefreshReg$ = (type, id) => {
1152
+ RefreshRuntime.register(type, ${urlLit} + " " + id);
1153
+ };
1154
+ window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
1155
+
1156
+ ${userCode}
1157
+
1158
+ window.$RefreshReg$ = prevRefreshReg;
1159
+ window.$RefreshSig$ = prevRefreshSig;
1160
+
1161
+ if (__nasti_hot__) {
1162
+ __nasti_hot__.accept(() => {
1163
+ clearTimeout(window.__nasti_refresh_timer__);
1164
+ window.__nasti_refresh_timer__ = setTimeout(() => {
1165
+ RefreshRuntime.performReactRefresh();
1166
+ }, 30);
1167
+ });
1168
+ }
1169
+ `;
1170
+ }
1171
+ function injectImportMetaHot(code, moduleUrl) {
1172
+ if (!/\bimport\.meta\.hot\b/.test(code)) return code;
1173
+ const urlLit = JSON.stringify(moduleUrl);
1174
+ const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
1175
+ const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
1176
+ `;
1177
+ return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
1178
+ }
1013
1179
  function transformMiddleware(ctx) {
1014
1180
  ctx.envDefine = buildEnvDefine(
1015
1181
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
@@ -1083,7 +1249,7 @@ async function transformRequest(url, ctx) {
1083
1249
  return cached.transformResult;
1084
1250
  }
1085
1251
  if (cleanReqUrl === "/@react-refresh") {
1086
- return { code: REACT_REFRESH_RUNTIME };
1252
+ return { code: getReactRefreshRuntimeEsm() };
1087
1253
  }
1088
1254
  const filePath = resolveUrlToFile(url, config.root);
1089
1255
  if (!filePath || !fs8.existsSync(filePath)) return null;
@@ -1100,6 +1266,8 @@ async function transformRequest(url, ctx) {
1100
1266
  if (pluginResult) {
1101
1267
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
1102
1268
  }
1269
+ const stableUrl = cleanReqUrl;
1270
+ let wrappedWithRefresh = false;
1103
1271
  if (shouldTransform(filePath)) {
1104
1272
  const isJsx = /\.[jt]sx$/.test(filePath);
1105
1273
  const useRefresh = isJsx && config.framework !== "vue";
@@ -1111,9 +1279,14 @@ async function transformRequest(url, ctx) {
1111
1279
  });
1112
1280
  code = result.code;
1113
1281
  if (useRefresh) {
1114
- code = REACT_REFRESH_PREAMBLE + code + REACT_REFRESH_FOOTER;
1282
+ code = buildReactRefreshWrapper(stableUrl, code);
1283
+ wrappedWithRefresh = true;
1284
+ mod.isSelfAccepting = true;
1115
1285
  }
1116
1286
  }
1287
+ if (!wrappedWithRefresh) {
1288
+ code = injectImportMetaHot(code, stableUrl);
1289
+ }
1117
1290
  const envDefine = ctx.envDefine ?? buildEnvDefine(
1118
1291
  loadEnv(config.mode, config.root, config.envPrefix),
1119
1292
  config.mode
@@ -1181,8 +1354,8 @@ function rewriteExternalRequires(code) {
1181
1354
  }
1182
1355
  async function injectCjsNamedExports(code, entryFile) {
1183
1356
  try {
1184
- const { createRequire: createRequire3 } = await import("module");
1185
- const req = createRequire3(entryFile);
1357
+ const { createRequire: createRequire5 } = await import("module");
1358
+ const req = createRequire5(entryFile);
1186
1359
  const cjsExports = req(entryFile);
1187
1360
  if (!cjsExports || typeof cjsExports !== "object" && typeof cjsExports !== "function" || Array.isArray(cjsExports)) return code;
1188
1361
  const namedKeys = Object.keys(cjsExports).filter(
@@ -1345,12 +1518,15 @@ function getHmrClientCode() {
1345
1518
  // Nasti HMR Client
1346
1519
  const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
1347
1520
  const hotModulesMap = new Map();
1521
+ const disposeMap = new Map();
1522
+ const pruneMap = new Map();
1348
1523
 
1349
1524
  socket.addEventListener('message', ({ data }) => {
1350
1525
  const payload = JSON.parse(data);
1351
1526
  switch (payload.type) {
1352
1527
  case 'connected':
1353
1528
  console.log('[nasti] connected.');
1529
+ clearErrorOverlay();
1354
1530
  break;
1355
1531
  case 'update':
1356
1532
  payload.updates.forEach((update) => {
@@ -1360,11 +1536,18 @@ socket.addEventListener('message', ({ data }) => {
1360
1536
  updateCss(update.path);
1361
1537
  }
1362
1538
  });
1539
+ clearErrorOverlay();
1363
1540
  break;
1364
1541
  case 'full-reload':
1365
1542
  console.log('[nasti] full reload');
1366
1543
  location.reload();
1367
1544
  break;
1545
+ case 'prune':
1546
+ payload.paths.forEach((p) => {
1547
+ const cb = pruneMap.get(p);
1548
+ if (cb) cb();
1549
+ });
1550
+ break;
1368
1551
  case 'error':
1369
1552
  console.error('[nasti] error:', payload.err.message);
1370
1553
  showErrorOverlay(payload.err);
@@ -1372,14 +1555,23 @@ socket.addEventListener('message', ({ data }) => {
1372
1555
  }
1373
1556
  });
1374
1557
 
1558
+ // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
1559
+ let reconnectTimer = 0;
1560
+ socket.addEventListener('close', () => {
1561
+ clearTimeout(reconnectTimer);
1562
+ reconnectTimer = setTimeout(() => location.reload(), 1000);
1563
+ });
1564
+
1375
1565
  async function fetchUpdate(update) {
1376
1566
  const mod = hotModulesMap.get(update.path);
1567
+ // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
1568
+ const dispose = disposeMap.get(update.path);
1569
+ if (dispose) dispose();
1570
+
1571
+ const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
1377
1572
  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);
1573
+ // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
1574
+ [...mod.callbacks].forEach((cb) => cb(newMod));
1383
1575
  }
1384
1576
  }
1385
1577
 
@@ -1392,7 +1584,13 @@ function updateCss(path) {
1392
1584
  }
1393
1585
  }
1394
1586
 
1587
+ function clearErrorOverlay() {
1588
+ const el = document.getElementById('nasti-error-overlay');
1589
+ if (el) el.remove();
1590
+ }
1591
+
1395
1592
  function showErrorOverlay(err) {
1593
+ clearErrorOverlay();
1396
1594
  const overlay = document.createElement('div');
1397
1595
  overlay.id = 'nasti-error-overlay';
1398
1596
  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;';
@@ -1411,59 +1609,55 @@ function showErrorOverlay(err) {
1411
1609
  document.body.appendChild(overlay);
1412
1610
  }
1413
1611
 
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 };
1612
+ /**
1613
+ * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
1614
+ * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
1615
+ * \u6BCF\u6B21\u6A21\u5757\u91CD\u65B0 import \u90FD\u4F1A\u8C03\u7528 createHotContext\uFF0C\u65E7\u56DE\u8C03\u4F1A\u88AB fetchUpdate \u8C03\u7528\u540E\u7ACB\u5373\u88AB\u65B0 import
1616
+ * \u91CC\u7684 accept \u66FF\u6362\u3002\u4E0D\u66FF\u6362\u7684\u8BDD\u6BCF\u7F16\u8F91\u4E00\u6B21\u5C31\u591A\u4E00\u4E2A\u56DE\u8C03\uFF0C\u8D8A\u8DD1\u8D8A\u6162\u3002
1617
+ */
1618
+ export function createHotContext(ownerPath) {
1619
+ return {
1620
+ accept(deps, callback) {
1621
+ // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
1622
+ if (typeof deps === 'function' || deps === undefined) {
1623
+ hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
1624
+ return;
1625
+ }
1626
+ // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
1627
+ const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
1628
+ hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
1629
+ },
1630
+ prune(callback) {
1631
+ pruneMap.set(ownerPath, callback);
1632
+ },
1633
+ dispose(callback) {
1634
+ disposeMap.set(ownerPath, callback);
1635
+ },
1636
+ invalidate() {
1637
+ location.reload();
1638
+ },
1639
+ data: {},
1640
+ };
1641
+ }
1439
1642
  `;
1440
1643
  }
1441
- var REACT_REFRESH_RUNTIME, REACT_REFRESH_PREAMBLE, REACT_REFRESH_FOOTER, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
1644
+ var __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
1442
1645
  var init_middleware = __esm({
1443
1646
  "src/server/middleware.ts"() {
1444
1647
  "use strict";
1445
1648
  init_transformer();
1446
1649
  init_html();
1447
1650
  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
- `;
1651
+ __dirname_esm = path9.dirname(fileURLToPath(import.meta.url));
1652
+ __require = createRequire2(import.meta.url);
1653
+ __refreshRuntimeCache = null;
1654
+ REACT_REFRESH_GLOBAL_PREAMBLE = `
1655
+ import RefreshRuntime from "/@react-refresh";
1656
+ RefreshRuntime.injectIntoGlobalHook(window);
1657
+ window.$RefreshReg$ = () => {};
1658
+ window.$RefreshSig$ = () => (type) => type;
1659
+ window.__vite_plugin_react_preamble_installed__ = true;
1660
+ `.trim();
1467
1661
  esmBundleCache = /* @__PURE__ */ new Map();
1468
1662
  VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1469
1663
  RESOLVE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".json", ".vue"];
@@ -1577,15 +1771,6 @@ async function createServer(inlineConfig = {}) {
1577
1771
  watcher.on("add", (file) => {
1578
1772
  handleFileChange(file, server);
1579
1773
  });
1580
- const postMiddlewares = [];
1581
- for (const plugin of allPlugins) {
1582
- if (plugin.configureServer) {
1583
- const result = await plugin.configureServer(server);
1584
- if (typeof result === "function") {
1585
- postMiddlewares.push(result);
1586
- }
1587
- }
1588
- }
1589
1774
  server = {
1590
1775
  config: configWithPlugins,
1591
1776
  middlewares: app,
@@ -1604,7 +1789,7 @@ async function createServer(inlineConfig = {}) {
1604
1789
  const localUrl = `http://localhost:${actualPort}`;
1605
1790
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}` : null;
1606
1791
  console.log();
1607
- console.log(pc3.cyan(" nasti dev server") + pc3.dim(` v${"1.4.1"}`));
1792
+ console.log(pc3.cyan(" nasti dev server") + pc3.dim(` v${"1.5.1"}`));
1608
1793
  console.log();
1609
1794
  console.log(` ${pc3.green(">")} Local: ${pc3.cyan(localUrl)}`);
1610
1795
  if (networkUrl) {
@@ -1637,6 +1822,16 @@ async function createServer(inlineConfig = {}) {
1637
1822
  httpServer.close();
1638
1823
  }
1639
1824
  };
1825
+ const postMiddlewares = [];
1826
+ for (const plugin of allPlugins) {
1827
+ if (plugin.configureServer) {
1828
+ const result = await plugin.configureServer(server);
1829
+ if (typeof result === "function") {
1830
+ postMiddlewares.push(result);
1831
+ }
1832
+ }
1833
+ }
1834
+ for (const run of postMiddlewares) run();
1640
1835
  return server;
1641
1836
  }
1642
1837
  function getNetworkAddress() {
@@ -1718,7 +1913,7 @@ async function buildElectron(inlineConfig = {}) {
1718
1913
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
1719
1914
  const startTime = performance.now();
1720
1915
  assertElectronVersion(config);
1721
- console.log(pc2.cyan("\n\u26A1 nasti build (electron)") + pc2.dim(` v${"1.4.1"}`));
1916
+ console.log(pc2.cyan("\n\u26A1 nasti build (electron)") + pc2.dim(` v${"1.5.1"}`));
1722
1917
  console.log(pc2.dim(` root: ${config.root}`));
1723
1918
  console.log(pc2.dim(` mode: ${config.mode}`));
1724
1919
  console.log(pc2.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -1853,7 +2048,7 @@ init_server();
1853
2048
  init_config();
1854
2049
  import path12 from "path";
1855
2050
  import fs10 from "fs";
1856
- import { createRequire as createRequire2 } from "module";
2051
+ import { createRequire as createRequire3 } from "module";
1857
2052
  import { spawn } from "child_process";
1858
2053
  import chokidar from "chokidar";
1859
2054
  import pc4 from "picocolors";
@@ -1865,7 +2060,7 @@ async function startElectronDev(inlineConfig = {}) {
1865
2060
  const { noSpawn, ...rest } = inlineConfig;
1866
2061
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
1867
2062
  warnElectronVersion(config);
1868
- console.log(pc4.cyan("\n\u26A1 nasti electron dev") + pc4.dim(` v${"1.4.1"}`));
2063
+ console.log(pc4.cyan("\n\u26A1 nasti electron dev") + pc4.dim(` v${"1.5.1"}`));
1869
2064
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
1870
2065
  const server = await createServer2({ ...rest, target: "electron" });
1871
2066
  await server.listen();
@@ -1929,11 +2124,16 @@ async function startElectronDev(inlineConfig = {}) {
1929
2124
  const watchTargets = [mainEntry, ...preloadEntries].filter(fs10.existsSync);
1930
2125
  const watcher = chokidar.watch(watchTargets, { ignoreInitial: true });
1931
2126
  let restarting = null;
1932
- watcher.on("all", async () => {
1933
- if (restarting) return;
2127
+ let pending = false;
2128
+ const restart = async () => {
2129
+ if (restarting) {
2130
+ pending = true;
2131
+ return;
2132
+ }
1934
2133
  restarting = (async () => {
1935
- console.log(pc4.cyan("\n \u267B \u4E3B/preload \u53D8\u66F4\uFF0C\u91CD\u542F Electron..."));
1936
- try {
2134
+ do {
2135
+ pending = false;
2136
+ console.log(pc4.cyan("\n \u267B \u4E3B/preload \u53D8\u66F4\uFF0C\u91CD\u542F Electron..."));
1937
2137
  if (child && !child.killed) {
1938
2138
  ;
1939
2139
  child.__nastiKilled = true;
@@ -1947,12 +2147,24 @@ async function startElectronDev(inlineConfig = {}) {
1947
2147
  dying.kill();
1948
2148
  });
1949
2149
  }
1950
- await compileAll();
1951
- spawnElectron();
1952
- } finally {
1953
- restarting = null;
1954
- }
2150
+ try {
2151
+ await compileAll();
2152
+ spawnElectron();
2153
+ } catch (e) {
2154
+ console.warn(pc4.yellow(` \u26A0 \u91CD\u542F\u7F16\u8BD1\u5931\u8D25\uFF0C\u4FDD\u7559\u4E0A\u4E00\u6B21\u8FDB\u7A0B: ${e.message}`));
2155
+ }
2156
+ } while (pending);
2157
+ restarting = null;
1955
2158
  })();
2159
+ return restarting;
2160
+ };
2161
+ let debounceTimer = null;
2162
+ watcher.on("all", () => {
2163
+ if (debounceTimer) clearTimeout(debounceTimer);
2164
+ debounceTimer = setTimeout(() => {
2165
+ debounceTimer = null;
2166
+ void restart();
2167
+ }, 80);
1956
2168
  });
1957
2169
  }
1958
2170
  }
@@ -2000,7 +2212,7 @@ function resolveElectronBinary(config) {
2000
2212
  return config.electron.electronPath;
2001
2213
  }
2002
2214
  try {
2003
- const require2 = createRequire2(path12.resolve(config.root, "package.json"));
2215
+ const require2 = createRequire3(path12.resolve(config.root, "package.json"));
2004
2216
  const pathFile = require2.resolve("electron");
2005
2217
  const electronModule = require2(pathFile);
2006
2218
  if (typeof electronModule === "string" && fs10.existsSync(electronModule)) {
@@ -2028,12 +2240,222 @@ function warnElectronVersion(config) {
2028
2240
  );
2029
2241
  }
2030
2242
  }
2243
+
2244
+ // src/plugins/monaco-editor.ts
2245
+ import path13 from "path";
2246
+ import fs11 from "fs";
2247
+ import crypto2 from "crypto";
2248
+ import { createRequire as createRequire4 } from "module";
2249
+ var DEFAULT_WORKERS = {
2250
+ editorWorkerService: "monaco-editor/esm/vs/editor/editor.worker",
2251
+ css: "monaco-editor/esm/vs/language/css/css.worker",
2252
+ html: "monaco-editor/esm/vs/language/html/html.worker",
2253
+ json: "monaco-editor/esm/vs/language/json/json.worker",
2254
+ typescript: "monaco-editor/esm/vs/language/typescript/ts.worker"
2255
+ };
2256
+ var DEFAULT_PUBLIC_PATH = "monacoeditorwork";
2257
+ function isCDN(p) {
2258
+ return /^((https?:)?\/\/|file:)/.test(p);
2259
+ }
2260
+ function normalizePublicPath(p) {
2261
+ if (isCDN(p)) return p.replace(/\/+$/, "");
2262
+ const withLead = p.startsWith("/") ? p : "/" + p;
2263
+ return withLead.replace(/\/+$/, "") || "/";
2264
+ }
2265
+ function readMonacoVersion(root) {
2266
+ try {
2267
+ const require2 = createRequire4(path13.resolve(root, "package.json"));
2268
+ const pkgJsonPath = require2.resolve("monaco-editor/package.json", { paths: [root] });
2269
+ const pkg = JSON.parse(fs11.readFileSync(pkgJsonPath, "utf-8"));
2270
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
2271
+ } catch {
2272
+ return "unknown";
2273
+ }
2274
+ }
2275
+ function monacoEditorPlugin(options = {}) {
2276
+ const languageWorkers = options.languageWorkers ?? Object.keys(DEFAULT_WORKERS);
2277
+ const customWorkers = options.customWorkers ?? [];
2278
+ const publicPath = normalizePublicPath(options.publicPath ?? DEFAULT_PUBLIC_PATH);
2279
+ const globalAPI = !!options.globalAPI;
2280
+ const forceBuildCDN = !!options.forceBuildCDN;
2281
+ const workers = [
2282
+ ...languageWorkers.map((label) => ({ label, entry: DEFAULT_WORKERS[label] })),
2283
+ ...customWorkers
2284
+ ];
2285
+ let resolvedConfig;
2286
+ let cacheDir = "";
2287
+ const building = /* @__PURE__ */ new Map();
2288
+ async function buildWorker(worker) {
2289
+ const cacheFile = path13.join(cacheDir, `${worker.label}.worker.js`);
2290
+ if (fs11.existsSync(cacheFile)) return cacheFile;
2291
+ const existing = building.get(worker.label);
2292
+ if (existing) return existing;
2293
+ const task = (async () => {
2294
+ const { rolldown: rolldown4 } = await import("rolldown");
2295
+ const require2 = createRequire4(path13.resolve(resolvedConfig.root, "package.json"));
2296
+ let entry;
2297
+ try {
2298
+ entry = require2.resolve(worker.entry, { paths: [resolvedConfig.root] });
2299
+ } catch {
2300
+ entry = require2.resolve(worker.entry + ".js", { paths: [resolvedConfig.root] });
2301
+ }
2302
+ fs11.mkdirSync(cacheDir, { recursive: true });
2303
+ const bundle = await rolldown4({
2304
+ input: entry,
2305
+ platform: "browser"
2306
+ });
2307
+ await bundle.write({
2308
+ file: cacheFile,
2309
+ format: "iife",
2310
+ sourcemap: false,
2311
+ minify: true,
2312
+ inlineDynamicImports: true
2313
+ });
2314
+ await bundle.close();
2315
+ return cacheFile;
2316
+ })();
2317
+ building.set(worker.label, task);
2318
+ try {
2319
+ return await task;
2320
+ } finally {
2321
+ building.delete(worker.label);
2322
+ }
2323
+ }
2324
+ function runtimeInitScript() {
2325
+ let normalizedPrefix = publicPath;
2326
+ if (!isCDN(publicPath)) {
2327
+ const base = resolvedConfig.base.replace(/\/+$/, "") || "";
2328
+ const pub = publicPath.replace(/^\/+/, "");
2329
+ normalizedPrefix = base ? `${base}/${pub}` : `/${pub}`;
2330
+ }
2331
+ const map = {};
2332
+ for (const w of workers) {
2333
+ map[w.label] = `${normalizedPrefix}/${w.label}.worker.js`;
2334
+ }
2335
+ return `;(function () {
2336
+ var map = ${JSON.stringify(map)};
2337
+ function getWorkerUrl(_moduleId, label) {
2338
+ var url = map[label] || map['editorWorkerService'];
2339
+ if (/^(https?:)?\\/\\//.test(url)) {
2340
+ // \u8DE8\u57DF Worker \u9700\u7528 Blob + importScripts \u7ED5\u8FC7\u540C\u6E90\u9650\u5236
2341
+ var blob = new Blob(
2342
+ ['importScripts(' + JSON.stringify(url) + ');'],
2343
+ { type: 'application/javascript' }
2344
+ );
2345
+ return URL.createObjectURL(blob);
2346
+ }
2347
+ return url;
2348
+ }
2349
+ self.MonacoEnvironment = self.MonacoEnvironment || {};
2350
+ if (!self.MonacoEnvironment.getWorker && !self.MonacoEnvironment.getWorkerUrl) {
2351
+ self.MonacoEnvironment.getWorkerUrl = getWorkerUrl;
2352
+ }
2353
+ })();`;
2354
+ }
2355
+ return {
2356
+ name: "nasti:monaco-editor",
2357
+ enforce: "pre",
2358
+ configResolved(config) {
2359
+ resolvedConfig = config;
2360
+ const version = readMonacoVersion(config.root);
2361
+ const key = crypto2.createHash("sha1").update(version + "|" + publicPath).digest("hex").slice(0, 8);
2362
+ cacheDir = path13.resolve(config.root, "node_modules/.nasti/monaco", key);
2363
+ },
2364
+ async configureServer(server) {
2365
+ const shouldBuild = !isCDN(publicPath) || forceBuildCDN;
2366
+ const watcher = server.watcher;
2367
+ const monacoDir = path13.resolve(resolvedConfig.root, "node_modules/monaco-editor");
2368
+ try {
2369
+ watcher?.unwatch?.(monacoDir);
2370
+ } catch {
2371
+ }
2372
+ if (!shouldBuild) return;
2373
+ void Promise.all(
2374
+ workers.map(
2375
+ (w) => buildWorker(w).catch((e) => {
2376
+ console.warn(
2377
+ `[nasti:monaco-editor] worker build failed for "${w.label}": ${e.message}`
2378
+ );
2379
+ })
2380
+ )
2381
+ );
2382
+ const base = resolvedConfig.base.replace(/\/+$/, "") || "";
2383
+ const pub = publicPath.replace(/^\/+/, "");
2384
+ const prefix = (base ? `${base}/${pub}` : `/${pub}`) + "/";
2385
+ server.middlewares.use(
2386
+ async (req, res, next) => {
2387
+ if (req.method !== "GET") return next();
2388
+ const url = (req.url ?? "").split("?")[0];
2389
+ if (!url.startsWith(prefix)) return next();
2390
+ const name = url.slice(prefix.length).replace(/\.worker\.js$/, "");
2391
+ const worker = workers.find((w) => w.label === name);
2392
+ if (!worker) return next();
2393
+ try {
2394
+ const file = await buildWorker(worker);
2395
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
2396
+ res.setHeader("Cache-Control", "public, max-age=604800, immutable");
2397
+ fs11.createReadStream(file).pipe(res);
2398
+ } catch (e) {
2399
+ res.statusCode = 500;
2400
+ res.end(`Monaco worker build failed: ${e.message}`);
2401
+ }
2402
+ }
2403
+ );
2404
+ },
2405
+ transformIndexHtml(html) {
2406
+ const tags = [
2407
+ {
2408
+ tag: "script",
2409
+ children: runtimeInitScript(),
2410
+ injectTo: "head-prepend"
2411
+ }
2412
+ ];
2413
+ if (globalAPI) {
2414
+ tags.push({
2415
+ tag: "script",
2416
+ attrs: { type: "module" },
2417
+ children: `import * as monaco from 'monaco-editor';
2418
+ self.monaco = monaco;`,
2419
+ injectTo: "head"
2420
+ });
2421
+ }
2422
+ return { html, tags };
2423
+ },
2424
+ // 生产构建:把所有 Worker 预打包并拷到 outDir/<publicPath>/ 下
2425
+ async buildStart() {
2426
+ if (resolvedConfig.command !== "build") return;
2427
+ if (isCDN(publicPath) && !forceBuildCDN) return;
2428
+ const outDir = options.customDistPath ? options.customDistPath(
2429
+ resolvedConfig.root,
2430
+ resolvedConfig.build.outDir,
2431
+ resolvedConfig.base
2432
+ ) : isCDN(publicPath) ? path13.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : path13.resolve(
2433
+ resolvedConfig.root,
2434
+ resolvedConfig.build.outDir,
2435
+ publicPath.replace(/^\//, "")
2436
+ );
2437
+ fs11.mkdirSync(outDir, { recursive: true });
2438
+ for (const worker of workers) {
2439
+ try {
2440
+ const cacheFile = await buildWorker(worker);
2441
+ fs11.copyFileSync(cacheFile, path13.join(outDir, `${worker.label}.worker.js`));
2442
+ } catch (e) {
2443
+ throw new Error(
2444
+ `[nasti:monaco-editor] worker build failed for "${worker.label}": ${e.message}
2445
+ ${e.stack || ""}`
2446
+ );
2447
+ }
2448
+ }
2449
+ }
2450
+ };
2451
+ }
2031
2452
  export {
2032
2453
  build,
2033
2454
  buildElectron,
2034
2455
  createServer,
2035
2456
  defineConfig,
2036
2457
  electronPlugin,
2458
+ monacoEditorPlugin,
2037
2459
  resolveConfig,
2038
2460
  startElectronDev
2039
2461
  };