@vrowzer/vite-plugin 0.0.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.
Files changed (39) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +290 -0
  3. package/dist/ide/assets/css.worker-uWEFNxvl.js +89 -0
  4. package/dist/ide/assets/html.worker-1tLoAl5Z.js +502 -0
  5. package/dist/ide/assets/json.worker-CL3sVQd6.js +58 -0
  6. package/dist/ide/assets/ts.worker-BFpNHPy5.js +67719 -0
  7. package/dist/ide/css-CPSW8DVZ.js +286 -0
  8. package/dist/ide/cssMode-B5DtFEwy.js +65 -0
  9. package/dist/ide/graphql-BteLL2CG.js +176 -0
  10. package/dist/ide/handlebars-BcqMM6pa.js +412 -0
  11. package/dist/ide/html-CxO5OaJl.js +333 -0
  12. package/dist/ide/htmlMode-DxpUG2jm.js +74 -0
  13. package/dist/ide/ide.css +2 -0
  14. package/dist/ide/ide.js +5523 -0
  15. package/dist/ide/javascript-CvtLx4_Z.js +21 -0
  16. package/dist/ide/jsonMode-LVtiYWKr.js +449 -0
  17. package/dist/ide/less-BubkcFWW.js +244 -0
  18. package/dist/ide/lspLanguageFeatures-BDyekQt2.js +1455 -0
  19. package/dist/ide/markdown-DPB5N4OB.js +264 -0
  20. package/dist/ide/mdx-Sun87_nd.js +329 -0
  21. package/dist/ide/monaco.contribution-DVj8eGe_.js +116 -0
  22. package/dist/ide/pug-BONHZNEg.js +302 -0
  23. package/dist/ide/scss-0r6NeK1P.js +419 -0
  24. package/dist/ide/shell-Vh_eZpea.js +225 -0
  25. package/dist/ide/toggleHighContrast-DI4ZeqFk.js +113328 -0
  26. package/dist/ide/tsMode-BKTkt1bt.js +567 -0
  27. package/dist/ide/typescript-CHC4VYBW.js +257 -0
  28. package/dist/ide/workers-IG_hD8Jy.js +47 -0
  29. package/dist/ide/xml-C0QFzMaz.js +130 -0
  30. package/dist/ide/yaml-o-RP7lYY.js +223 -0
  31. package/dist/index.d.mts +138 -0
  32. package/dist/index.d.mts.map +1 -0
  33. package/dist/index.mjs +1335 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/dist/manifest-generate.d.mts +89 -0
  36. package/dist/manifest-generate.d.mts.map +1 -0
  37. package/dist/manifest-generate.mjs +438 -0
  38. package/dist/manifest-generate.mjs.map +1 -0
  39. package/package.json +88 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1335 @@
1
+ import { generateManifest } from "./manifest-generate.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import path, { dirname, extname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import inject from "@rollup/plugin-inject";
7
+ import ServiceWorker from "@vrowzer/unplugin-service-worker/vite";
8
+ import { createDebug } from "obug";
9
+ import { minifySync, parseSync } from "rolldown/experimental";
10
+ import { rolldown } from "rolldown";
11
+ import { createBirpc } from "birpc";
12
+ import { WebSocketServer } from "ws";
13
+
14
+ //#region rolldown:runtime
15
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
16
+
17
+ //#endregion
18
+ //#region src/auto-manifest.ts
19
+ /**
20
+ * Auto-manifest plugin for Vrowzer.
21
+ *
22
+ * When `auto: true`, this plugin:
23
+ * 1. Auto-generates the vrowzer manifest in `configResolved`
24
+ * 2. Caches results in `node_modules/.vrowzer-manifest/`
25
+ * 3. Provides the manifest via `virtual:vrowzer-manifest` virtual module
26
+ *
27
+ * @module auto-manifest
28
+ */
29
+ /**
30
+ * @author kazuya kawaguchi (a.k.a. kazupon)
31
+ * @license MIT
32
+ */
33
+ const debug$8 = createDebug("vite-plugin-vrowzer:auto-manifest");
34
+ const VIRTUAL_MODULE_ID = "virtual:vrowzer-manifest";
35
+ const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
36
+ const CACHE_DIR_NAME = ".vrowzer-manifest";
37
+ const MANIFEST_FILENAME = "manifest.json";
38
+ const HASH_FILENAME = "_hash";
39
+ const LOCKFILE_NAMES = [
40
+ "pnpm-lock.yaml",
41
+ "package-lock.json",
42
+ "yarn.lock",
43
+ "bun.lock"
44
+ ];
45
+ const MINIFIABLE_EXTENSIONS$1 = new Set([
46
+ ".js",
47
+ ".mjs",
48
+ ".cjs"
49
+ ]);
50
+ /**
51
+ * Simple 32-bit string hash (same algorithm as unplugin-service-worker hash).
52
+ */
53
+ function hash(input) {
54
+ let h = 0;
55
+ for (let i = 0; i < input.length; i++) {
56
+ const char = input.charCodeAt(i);
57
+ h = (h << 5) - h + char;
58
+ h = h & h;
59
+ }
60
+ return Math.abs(h).toString(36).slice(0, 8);
61
+ }
62
+ /**
63
+ * Compute cache key from package.json dependencies, lockfile, and manifest options.
64
+ */
65
+ function computeCacheHash(root, manifestOptions) {
66
+ const parts = [];
67
+ if (manifestOptions?.sourceDir) parts.push(`sourceDir:${manifestOptions.sourceDir}`);
68
+ if (manifestOptions?.targets) parts.push(`targets:${manifestOptions.targets.join(",")}`);
69
+ const pkgPath = join(root, "package.json");
70
+ if (existsSync(pkgPath)) try {
71
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
72
+ parts.push(JSON.stringify(pkg.dependencies || {}));
73
+ parts.push(JSON.stringify(pkg.devDependencies || {}));
74
+ } catch {}
75
+ for (const lockfile of LOCKFILE_NAMES) {
76
+ const lockPath = join(root, lockfile);
77
+ if (existsSync(lockPath)) {
78
+ try {
79
+ parts.push(readFileSync(lockPath, "utf-8"));
80
+ } catch {}
81
+ break;
82
+ }
83
+ }
84
+ return hash(parts.join("\n"));
85
+ }
86
+ function getCacheDir(root) {
87
+ return resolve(root, "node_modules", CACHE_DIR_NAME);
88
+ }
89
+ function readCachedHash(cacheDir) {
90
+ const hashPath = join(cacheDir, HASH_FILENAME);
91
+ if (existsSync(hashPath)) try {
92
+ return readFileSync(hashPath, "utf-8").trim();
93
+ } catch {
94
+ return null;
95
+ }
96
+ return null;
97
+ }
98
+ function readCachedManifest(cacheDir) {
99
+ const manifestPath = join(cacheDir, MANIFEST_FILENAME);
100
+ if (existsSync(manifestPath)) try {
101
+ return JSON.parse(readFileSync(manifestPath, "utf-8"));
102
+ } catch {
103
+ return null;
104
+ }
105
+ return null;
106
+ }
107
+ function writeCache(cacheDir, manifest, cacheHash) {
108
+ mkdirSync(cacheDir, { recursive: true });
109
+ writeFileSync(join(cacheDir, MANIFEST_FILENAME), JSON.stringify(manifest, null, 2) + "\n");
110
+ writeFileSync(join(cacheDir, HASH_FILENAME), cacheHash + "\n");
111
+ }
112
+ /**
113
+ * Resolve manifest path references to actual file contents.
114
+ * This is equivalent to what VrowzerManifest() does for manual manifests.
115
+ */
116
+ function resolveManifestContents(manifest, manifestDir) {
117
+ function resolveFiles(files, minify) {
118
+ if (!files) return {};
119
+ const resolved = {};
120
+ for (const [virtualPath, relPath] of Object.entries(files)) try {
121
+ let content = readFileSync(resolve(manifestDir, relPath), "utf-8");
122
+ if (minify && MINIFIABLE_EXTENSIONS$1.has(extname(virtualPath))) {
123
+ const result = minifySync(virtualPath, content);
124
+ if (result.code) content = result.code;
125
+ }
126
+ resolved[virtualPath] = content;
127
+ } catch {
128
+ debug$8("failed to read %s", relPath);
129
+ }
130
+ return resolved;
131
+ }
132
+ return {
133
+ name: manifest.name,
134
+ files: resolveFiles(manifest.files, false),
135
+ nodeModules: resolveFiles(manifest.nodeModules, true),
136
+ activeFile: manifest.activeFile
137
+ };
138
+ }
139
+ /**
140
+ * Create the auto-manifest plugin.
141
+ *
142
+ * This plugin is included in the `Vrowzer()` array when `auto: true`.
143
+ */
144
+ function autoManifestPlugin(manifestOptions) {
145
+ let sourceDir;
146
+ let manifest = null;
147
+ return {
148
+ name: "vrowzer:auto-manifest",
149
+ resolveId(id) {
150
+ if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;
151
+ },
152
+ async configResolved(config) {
153
+ const root = config.root;
154
+ sourceDir = manifestOptions?.sourceDir ? resolve(root, manifestOptions.sourceDir) : root;
155
+ const pkgDir = manifestOptions?.pkgDir ? resolve(root, manifestOptions.pkgDir) : root;
156
+ const cacheDir = getCacheDir(root);
157
+ const currentHash = computeCacheHash(pkgDir, manifestOptions);
158
+ const cachedHash = readCachedHash(cacheDir);
159
+ if (currentHash === cachedHash) {
160
+ manifest = readCachedManifest(cacheDir);
161
+ if (manifest) {
162
+ debug$8("cache hit (hash: %s), using cached manifest", currentHash);
163
+ return;
164
+ }
165
+ }
166
+ debug$8("cache miss (current: %s, cached: %s), generating manifest...", currentHash, cachedHash);
167
+ manifest = await generateManifest({
168
+ pkgDir,
169
+ sourceDir,
170
+ ...manifestOptions?.targets ? { targets: manifestOptions.targets } : {}
171
+ }, (msg) => debug$8(msg));
172
+ writeCache(cacheDir, manifest, currentHash);
173
+ debug$8("manifest cached to %s", cacheDir);
174
+ },
175
+ load(id) {
176
+ if (id !== RESOLVED_VIRTUAL_MODULE_ID) return;
177
+ if (!manifest) {
178
+ debug$8("no manifest available");
179
+ return {
180
+ code: "export default {}",
181
+ moduleType: "js"
182
+ };
183
+ }
184
+ const resolved = resolveManifestContents(manifest, sourceDir);
185
+ debug$8("virtual module loaded: %s (%d files, %d nodeModules)", resolved.name, Object.keys(resolved.files).length, Object.keys(resolved.nodeModules || {}).length);
186
+ return {
187
+ code: `export default ${JSON.stringify(resolved)}`,
188
+ moduleType: "js"
189
+ };
190
+ }
191
+ };
192
+ }
193
+
194
+ //#endregion
195
+ //#region src/alias.ts
196
+ /**
197
+ * Node.js builtin → browser polyfill alias mappings.
198
+ *
199
+ * Shared between env.ts (host Vite config) and prebundle.ts (Worker config bundling).
200
+ *
201
+ * @module alias
202
+ */
203
+ /**
204
+ * @author kazuya kawaguchi (a.k.a. kazupon)
205
+ * @license MIT
206
+ */
207
+ /**
208
+ * Node.js builtin module → browser polyfill mapping.
209
+ * Each entry maps both `node:xxx` and bare `xxx` specifiers.
210
+ */
211
+ const NODE_POLYFILL_MAP = {
212
+ events: "@vrowzer/node-polyfill/events",
213
+ path: "pathe",
214
+ stream: "readable-stream/lib/stream",
215
+ buffer: "buffer",
216
+ dns: "@vrowzer/node-polyfill/dns",
217
+ fs: "@vrowzer/fs",
218
+ "fs/promises": "@vrowzer/fs/promises",
219
+ url: "@vrowzer/node-polyfill/url",
220
+ readline: "@vrowzer/node-polyfill/readline",
221
+ util: "@vrowzer/node-polyfill/util",
222
+ perf_hooks: "@vrowzer/node-polyfill/perf_hooks",
223
+ crypto: "@vrowzer/node-polyfill/crypto",
224
+ tty: "@vrowzer/node-polyfill/tty",
225
+ module: "@vrowzer/node-polyfill/module",
226
+ os: "@vrowzer/node-polyfill/os",
227
+ net: "@vrowzer/node-polyfill/net"
228
+ };
229
+ /**
230
+ * Build a flat alias record from NODE_POLYFILL_MAP + additional aliases.
231
+ * Generates both `node:xxx` and bare `xxx` entries for each builtin.
232
+ *
233
+ * @param extra - Additional alias entries to merge (e.g. `{ process: '...', 'process/': '...' }`)
234
+ */
235
+ function resolveAliases(extra) {
236
+ const aliases = {};
237
+ for (const [mod, polyfill] of Object.entries(NODE_POLYFILL_MAP)) {
238
+ aliases[`node:${mod}`] = polyfill;
239
+ aliases[mod] = polyfill;
240
+ }
241
+ if (extra) Object.assign(aliases, extra);
242
+ return aliases;
243
+ }
244
+
245
+ //#endregion
246
+ //#region src/env.ts
247
+ /**
248
+ * Environment plugin — Node.js polyfills, CORS headers, and Worker config
249
+ * for browser/Worker environments.
250
+ *
251
+ * @module env
252
+ */
253
+ /**
254
+ * @author kazuya kawaguchi (a.k.a. kazupon)
255
+ * @license MIT
256
+ */
257
+ const debug$7 = createDebug("vite-plugin-vrowzer:env");
258
+ const picocolorsBrowser = resolve(dirname(createRequire(import.meta.url).resolve("picocolors")), "picocolors.browser.js");
259
+ function envPlugin(_options) {
260
+ return {
261
+ name: "vrowzer:env",
262
+ options(inputOptions) {
263
+ inputOptions.transform ??= {};
264
+ inputOptions.transform.inject = {
265
+ ...inputOptions.transform.inject ?? {},
266
+ process: "@vrowzer/node-polyfill/process"
267
+ };
268
+ debug$7("options hook: inputOptions.transform.inject ", inputOptions.transform.inject);
269
+ },
270
+ config(_config, _env) {
271
+ return {
272
+ define: { "import.meta.env.DEBUG": JSON.stringify(process.env.DEBUG || "") },
273
+ resolve: { alias: resolveAliases({
274
+ "node:process": "@vrowzer/node-polyfill/process",
275
+ "process/": "@vrowzer/node-polyfill/process",
276
+ process: "@vrowzer/node-polyfill/process",
277
+ picocolors: picocolorsBrowser
278
+ }) },
279
+ worker: { format: "es" },
280
+ server: { headers: {
281
+ "Service-Worker-Allowed": "/",
282
+ "Cross-Origin-Opener-Policy": "same-origin",
283
+ "Cross-Origin-Embedder-Policy": "credentialless"
284
+ } },
285
+ preview: { headers: {
286
+ "Service-Worker-Allowed": "/",
287
+ "Cross-Origin-Opener-Policy": "same-origin",
288
+ "Cross-Origin-Embedder-Policy": "credentialless"
289
+ } }
290
+ };
291
+ }
292
+ };
293
+ }
294
+
295
+ //#endregion
296
+ //#region src/ide.ts
297
+ /**
298
+ * Browser IDE plugin for Vrowzer.
299
+ *
300
+ * When `experimental.ide` is enabled, serves a pre-built browser IDE at `/__vrowzer__/`.
301
+ * The IDE is a self-contained Vue app with Monaco Editor, File Explorer, and Preview,
302
+ * bundled into dist/ide/ at build time.
303
+ *
304
+ * Phase 3: birpc WebSocket for file sync (write-back to local FS).
305
+ *
306
+ * @module ide
307
+ */
308
+ /**
309
+ * @author kazuya kawaguchi (a.k.a. kazupon)
310
+ * @license MIT
311
+ */
312
+ const debug$6 = createDebug("vite-plugin-vrowzer:ide");
313
+ const IDE_BASE = "/__vrowzer__";
314
+ const IDE_CLIENT_PATH = `${IDE_BASE}/client.js`;
315
+ const __dir = dirname(fileURLToPath(import.meta.url));
316
+ const ideDistDir = resolve(__dir, __dir.endsWith("/dist") ? "ide" : "../dist/ide");
317
+ const MIME_TYPES = {
318
+ ".js": "application/javascript",
319
+ ".mjs": "application/javascript",
320
+ ".css": "text/css",
321
+ ".html": "text/html",
322
+ ".json": "application/json",
323
+ ".svg": "image/svg+xml",
324
+ ".png": "image/png",
325
+ ".woff": "font/woff",
326
+ ".woff2": "font/woff2",
327
+ ".ttf": "font/ttf"
328
+ };
329
+ function generateIdeClientCode(basePath, rpcPort, devtoolsUrl) {
330
+ return `
331
+ import { Vrowzer } from 'vrowzer'
332
+ import manifest from 'virtual:vrowzer-manifest'
333
+
334
+ // mountIde is loaded via script tag in HTML and exposed as global
335
+ window.__vrowzer_ide_mount__({
336
+ manifest,
337
+ basePath: '${basePath}',
338
+ Vrowzer,
339
+ rpcPort: ${rpcPort},
340
+ devtoolsUrl: ${devtoolsUrl ? `'${devtoolsUrl}'` : "null"}
341
+ })
342
+ `;
343
+ }
344
+ function generateIdeHtml(base, cssFile) {
345
+ return `<!doctype html>
346
+ <html lang="en">
347
+ <head>
348
+ <meta charset="UTF-8" />
349
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
350
+ <title>Vrowzer IDE</title>
351
+ ${cssFile ? `<link rel="stylesheet" href="${base}${IDE_BASE.slice(1)}/dist/${cssFile}" />` : ""}
352
+ <style>
353
+ * { margin: 0; padding: 0; box-sizing: border-box; }
354
+ html, body, #app { height: 100%; }
355
+ body { font-family: system-ui, -apple-system, sans-serif; overflow: hidden; }
356
+ </style>
357
+ </head>
358
+ <body>
359
+ <div id="app"></div>
360
+ <script type="module" src="${base}${IDE_BASE.slice(1)}/dist/ide.js"><\/script>
361
+ <script type="module" src="${base}${IDE_BASE.slice(1)}/client.js"><\/script>
362
+ </body>
363
+ </html>`;
364
+ }
365
+ function findAvailablePort(preferredPort) {
366
+ return new Promise((resolve$1, reject) => {
367
+ const { createServer } = __require("node:net");
368
+ const server = createServer();
369
+ const port = preferredPort ?? 7900;
370
+ server.listen(port, () => {
371
+ server.close(() => resolve$1(port));
372
+ });
373
+ server.on("error", () => {
374
+ server.close();
375
+ const next = createServer();
376
+ next.listen(0, () => {
377
+ const addr = next.address();
378
+ const p = typeof addr === "object" && addr ? addr.port : 0;
379
+ next.close(() => resolve$1(p));
380
+ });
381
+ next.on("error", reject);
382
+ });
383
+ });
384
+ }
385
+ function idePlugin(options) {
386
+ let viteBase = "/";
387
+ let ideCssFile = null;
388
+ let rpcPort = 0;
389
+ let projectRoot = "";
390
+ let sourceDir = "";
391
+ let devtoolsUrl = null;
392
+ if (existsSync(ideDistDir)) try {
393
+ ideCssFile = readdirSync(ideDistDir).find((f) => f.endsWith(".css")) ?? null;
394
+ } catch {}
395
+ return {
396
+ name: "vrowzer:ide",
397
+ apply: "serve",
398
+ async configResolved(config) {
399
+ viteBase = config.base || "/";
400
+ projectRoot = config.root;
401
+ sourceDir = options.manifest?.sourceDir ? resolve(projectRoot, options.manifest.sourceDir) : projectRoot;
402
+ rpcPort = await findAvailablePort(options.ide.port);
403
+ debug$6("RPC port:", rpcPort);
404
+ if (options.ide.devtools) {
405
+ if (config.plugins.some((p) => p.name === "vite:devtools:server")) {
406
+ devtoolsUrl = "/.devtools/";
407
+ debug$6("DevTools detected, URL:", devtoolsUrl);
408
+ }
409
+ }
410
+ },
411
+ resolveId(id) {
412
+ if (id === IDE_CLIENT_PATH) return id;
413
+ },
414
+ load(id) {
415
+ if (id === IDE_CLIENT_PATH) return generateIdeClientCode(options.basePath, rpcPort, devtoolsUrl);
416
+ },
417
+ configureServer(server) {
418
+ const ideUrl = `${IDE_BASE}/`;
419
+ if (devtoolsUrl) {
420
+ server.middlewares.use((req, res, next) => {
421
+ if ((req.url ?? "").startsWith("/.devtools")) {
422
+ const originalWriteHead = res.writeHead.bind(res);
423
+ res.writeHead = function(statusCode, ...args) {
424
+ res.setHeader("Cross-Origin-Embedder-Policy", "credentialless");
425
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
426
+ return originalWriteHead(statusCode, ...args);
427
+ };
428
+ }
429
+ next();
430
+ });
431
+ debug$6("COEP middleware added for /.devtools*");
432
+ }
433
+ const wss = new WebSocketServer({ port: rpcPort });
434
+ debug$6("birpc WebSocket server listening on port", rpcPort);
435
+ wss.on("connection", (ws) => {
436
+ debug$6("IDE client connected");
437
+ const rpc = createBirpc({ async writeFile(path$1, content) {
438
+ const absPath = resolve(sourceDir, path$1.startsWith("/") ? path$1.slice(1) : path$1);
439
+ debug$6("writeFile:", absPath);
440
+ writeFileSync(absPath, content, "utf-8");
441
+ } }, {
442
+ post: (data) => ws.send(data),
443
+ on: (handler) => ws.on("message", handler),
444
+ serialize: (v) => JSON.stringify(v),
445
+ deserialize: (v) => JSON.parse(String(v))
446
+ });
447
+ const watcher = server.watcher;
448
+ const onFileChange = (filePath) => {
449
+ if (filePath.startsWith(sourceDir) && !filePath.includes("node_modules")) {
450
+ const relPath = "/" + filePath.slice(sourceDir.length + 1).replace(/\\/g, "/");
451
+ try {
452
+ const content = readFileSync(filePath, "utf-8");
453
+ debug$6("external file change:", relPath);
454
+ rpc.onFileChanged(relPath, content);
455
+ } catch {}
456
+ }
457
+ };
458
+ watcher.on("change", onFileChange);
459
+ ws.on("close", () => {
460
+ debug$6("IDE client disconnected");
461
+ watcher.off("change", onFileChange);
462
+ });
463
+ });
464
+ server.httpServer?.on("close", () => {
465
+ wss.close();
466
+ debug$6("birpc WebSocket server closed");
467
+ });
468
+ server.httpServer?.once("listening", () => {
469
+ const info = server.config.server;
470
+ const protocol = info.https ? "https" : "http";
471
+ const host = typeof info.host === "string" ? info.host : "localhost";
472
+ const port = info.port || 5173;
473
+ setTimeout(() => {
474
+ server.config.logger.info(` \x1b[36m➜\x1b[0m \x1b[1mVrowzer IDE\x1b[0m: \x1b[36m${protocol}://${host}:${port}${ideUrl}\x1b[0m`);
475
+ }, 100);
476
+ });
477
+ server.middlewares.use((req, res, next) => {
478
+ const url = req.url ?? "";
479
+ if (url === IDE_BASE || url === ideUrl) {
480
+ debug$6("serving IDE HTML");
481
+ res.writeHead(200, {
482
+ "Content-Type": "text/html; charset=utf-8",
483
+ "Cross-Origin-Opener-Policy": "same-origin",
484
+ "Cross-Origin-Embedder-Policy": "credentialless"
485
+ });
486
+ res.end(generateIdeHtml(viteBase, ideCssFile));
487
+ return;
488
+ }
489
+ if (url.startsWith(`${IDE_BASE}/dist/`)) {
490
+ const assetName = url.slice(`${IDE_BASE}/dist/`.length);
491
+ const assetPath = join(ideDistDir, assetName);
492
+ if (existsSync(assetPath)) {
493
+ const mime = MIME_TYPES[extname(assetName)] || "application/octet-stream";
494
+ debug$6("serving IDE asset:", assetName);
495
+ res.writeHead(200, {
496
+ "Content-Type": mime,
497
+ "Cross-Origin-Opener-Policy": "same-origin",
498
+ "Cross-Origin-Embedder-Policy": "credentialless",
499
+ "Cache-Control": "no-cache"
500
+ });
501
+ res.end(readFileSync(assetPath));
502
+ return;
503
+ }
504
+ }
505
+ next();
506
+ });
507
+ }
508
+ };
509
+ }
510
+
511
+ //#endregion
512
+ //#region src/extract.ts
513
+ /**
514
+ * Static analysis of vite.config.ts for Worker plugin extraction.
515
+ *
516
+ * Parses the user's vite.config.ts with OXC (via rolldown/experimental),
517
+ * removes Vrowzer() calls and their imports,
518
+ * and generates a Worker-compatible config source.
519
+ *
520
+ * @module extract
521
+ */
522
+ /**
523
+ * @author kazuya kawaguchi (a.k.a. kazupon)
524
+ * @license MIT
525
+ */
526
+ const debug$5 = createDebug("vite-plugin-vrowzer:extract");
527
+ /**
528
+ * Packages that should be excluded from Worker config.
529
+ * These are host-only plugins that cannot run in Web Worker.
530
+ */
531
+ const WORKER_EXCLUDED_SOURCES = [
532
+ "@vrowzer/vite-plugin",
533
+ "@vrowzer/vite-plugin/config",
534
+ "@vitejs/devtools"
535
+ ];
536
+ /**
537
+ * Check if an import source should be excluded from Worker config.
538
+ */
539
+ function isWorkerExcludedImport(source) {
540
+ return WORKER_EXCLUDED_SOURCES.some((s) => source === s || source.startsWith(`${s}/`));
541
+ }
542
+ /**
543
+ * Check if an import source is from Vite (should be excluded from Worker config).
544
+ */
545
+ function isViteImport(source) {
546
+ return source === "vite" || source.startsWith("vite/");
547
+ }
548
+ /**
549
+ * Extract Worker config source from vite.config.ts.
550
+ *
551
+ * 1. Parse the source with OXC
552
+ * 2. Collect all imports
553
+ * 3. Find `export default defineConfig(...)` or `export default { ... }`
554
+ * 4. Extract plugins array
555
+ * 5. Remove Vrowzer() calls
556
+ * 6. Generate Worker config source
557
+ */
558
+ function extractWorkerConfig(source, configPath, _options = {}) {
559
+ const unsupported = [];
560
+ const ast = parseSync(configPath, source).program;
561
+ const imports = collectImports(ast);
562
+ debug$5("imports", imports.map((i) => `${i.localName} from ${i.source}`));
563
+ const exportDefault = ast.body.find((n) => n.type === "ExportDefaultDeclaration");
564
+ if (!exportDefault) return {
565
+ code: generateFallbackCode(),
566
+ unsupported: ["no export default found"]
567
+ };
568
+ const configObj = unwrapDefineConfig(exportDefault.declaration);
569
+ if (!configObj || configObj.type !== "ObjectExpression") return {
570
+ code: generateFallbackCode(),
571
+ unsupported: ["config is not an object expression"]
572
+ };
573
+ const pluginsProp = configObj.properties.find((p) => p.type === "Property" && p.key.type === "Identifier" && p.key.name === "plugins");
574
+ if (!pluginsProp || pluginsProp.value.type !== "ArrayExpression") return {
575
+ code: generateFallbackCode(),
576
+ unsupported: ["plugins is not an array"]
577
+ };
578
+ const pluginsArray = pluginsProp.value;
579
+ const pluginCalls = [];
580
+ for (const element of pluginsArray.elements) {
581
+ if (element === null) continue;
582
+ if (element.type === "SpreadElement") {
583
+ unsupported.push(`spread element: ${source.slice(element.start, element.end)}`);
584
+ continue;
585
+ }
586
+ const expr = element;
587
+ if (expr.type === "CallExpression") {
588
+ const info = analyzeCallExpression(expr, imports);
589
+ if (info) pluginCalls.push(info);
590
+ else unsupported.push(`unanalyzable call: ${source.slice(expr.start, expr.end)}`);
591
+ } else if (expr.type === "ConditionalExpression" || expr.type === "LogicalExpression") unsupported.push(`conditional plugin: ${source.slice(expr.start, expr.end)}`);
592
+ else unsupported.push(`non-call plugin: ${source.slice(expr.start, expr.end)}`);
593
+ }
594
+ if (unsupported.length > 0) debug$5("unsupported patterns", unsupported);
595
+ const workerPlugins = pluginCalls.filter((p) => {
596
+ if (!p.importSource) return true;
597
+ return !isWorkerExcludedImport(p.importSource);
598
+ });
599
+ debug$5("workerPlugins", workerPlugins.map((p) => p.calleeName));
600
+ const neededImportSources = /* @__PURE__ */ new Set();
601
+ const neededLocalNames = /* @__PURE__ */ new Set();
602
+ for (const plugin of workerPlugins) {
603
+ if (plugin.importSource) neededImportSources.add(plugin.importSource);
604
+ neededLocalNames.add(plugin.calleeName);
605
+ }
606
+ for (const plugin of workerPlugins) if (plugin.hasArgs) {
607
+ const argSource = source.slice(plugin.start, plugin.end);
608
+ for (const imp of imports) {
609
+ if (imp.isTypeOnly) continue;
610
+ if (isViteImport(imp.source)) continue;
611
+ if (isWorkerExcludedImport(imp.source)) continue;
612
+ if (argSource.includes(imp.localName)) {
613
+ neededImportSources.add(imp.source);
614
+ neededLocalNames.add(imp.localName);
615
+ }
616
+ }
617
+ }
618
+ const localPluginNames = workerPlugins.filter((p) => !p.importSource).map((p) => p.calleeName);
619
+ if (localPluginNames.length > 0) {
620
+ const localFuncSources = [];
621
+ for (const stmt of ast.body) if (stmt.type === "FunctionDeclaration" && stmt.id) {
622
+ const funcName = stmt.id.name;
623
+ if (localPluginNames.includes(funcName)) localFuncSources.push(source.slice(stmt.start, stmt.end));
624
+ }
625
+ for (const funcSource of localFuncSources) for (const imp of imports) {
626
+ if (imp.isTypeOnly) continue;
627
+ if (isViteImport(imp.source)) continue;
628
+ if (isWorkerExcludedImport(imp.source)) continue;
629
+ if (funcSource.includes(imp.localName)) {
630
+ neededImportSources.add(imp.source);
631
+ neededLocalNames.add(imp.localName);
632
+ }
633
+ }
634
+ for (const stmt of ast.body) {
635
+ if (stmt.type !== "VariableDeclaration") continue;
636
+ for (const decl of stmt.declarations) {
637
+ if (!decl.id?.name) continue;
638
+ const varName = decl.id.name;
639
+ if (localPluginNames.includes(varName)) continue;
640
+ if (localFuncSources.some((funcSrc) => funcSrc.includes(varName))) {
641
+ const varSource = source.slice(stmt.start, stmt.end);
642
+ for (const imp of imports) {
643
+ if (imp.isTypeOnly) continue;
644
+ if (isViteImport(imp.source)) continue;
645
+ if (isWorkerExcludedImport(imp.source)) continue;
646
+ if (varSource.includes(imp.localName)) {
647
+ neededImportSources.add(imp.source);
648
+ neededLocalNames.add(imp.localName);
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
654
+ }
655
+ return {
656
+ code: generateWorkerSource(source, ast, imports, workerPlugins, neededImportSources, neededLocalNames),
657
+ unsupported
658
+ };
659
+ }
660
+ function collectImports(ast) {
661
+ const imports = [];
662
+ for (const node of ast.body) {
663
+ if (node.type !== "ImportDeclaration") continue;
664
+ const decl = node;
665
+ const source = decl.source.value;
666
+ const isTypeOnly = decl.importKind === "type";
667
+ for (const spec of decl.specifiers) if (spec.type === "ImportDefaultSpecifier") imports.push({
668
+ source,
669
+ localName: spec.local.name,
670
+ importedName: null,
671
+ start: decl.start,
672
+ end: decl.end,
673
+ isTypeOnly
674
+ });
675
+ else if (spec.type === "ImportSpecifier") {
676
+ const importedName = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value;
677
+ imports.push({
678
+ source,
679
+ localName: spec.local.name,
680
+ importedName,
681
+ start: decl.start,
682
+ end: decl.end,
683
+ isTypeOnly: isTypeOnly || spec.importKind === "type"
684
+ });
685
+ } else if (spec.type === "ImportNamespaceSpecifier") imports.push({
686
+ source,
687
+ localName: spec.local.name,
688
+ importedName: "*",
689
+ start: decl.start,
690
+ end: decl.end,
691
+ isTypeOnly
692
+ });
693
+ }
694
+ return imports;
695
+ }
696
+ function unwrapDefineConfig(expr) {
697
+ if (expr.type === "CallExpression") {
698
+ const call = expr;
699
+ if (call.callee.type === "Identifier" && call.callee.name === "defineConfig") return call.arguments[0];
700
+ }
701
+ if (expr.type === "ObjectExpression") return expr;
702
+ return null;
703
+ }
704
+ function analyzeCallExpression(call, imports) {
705
+ let calleeName = null;
706
+ if (call.callee.type === "Identifier") calleeName = call.callee.name;
707
+ if (!calleeName) return null;
708
+ const matchingImport = imports.find((i) => i.localName === calleeName && !i.isTypeOnly);
709
+ return {
710
+ calleeName,
711
+ importSource: matchingImport?.source ?? null,
712
+ start: call.start,
713
+ end: call.end,
714
+ hasArgs: call.arguments.length > 0
715
+ };
716
+ }
717
+ function generateWorkerSource(source, ast, imports, plugins, neededImportSources, neededLocalNames) {
718
+ const lines = [];
719
+ const emittedSources = /* @__PURE__ */ new Set();
720
+ for (const imp of imports) {
721
+ if (imp.isTypeOnly) continue;
722
+ if (isViteImport(imp.source)) continue;
723
+ if (isWorkerExcludedImport(imp.source)) continue;
724
+ if (!neededImportSources.has(imp.source)) continue;
725
+ if (!neededLocalNames.has(imp.localName)) continue;
726
+ if (emittedSources.has(`${imp.source}:${imp.localName}`)) continue;
727
+ emittedSources.add(`${imp.source}:${imp.localName}`);
728
+ if (imp.importedName === null) lines.push(`import ${imp.localName} from '${imp.source}'`);
729
+ else if (imp.importedName === "*") lines.push(`import * as ${imp.localName} from '${imp.source}'`);
730
+ else if (imp.importedName === imp.localName) lines.push(`import { ${imp.localName} } from '${imp.source}'`);
731
+ else lines.push(`import { ${imp.importedName} as ${imp.localName} } from '${imp.source}'`);
732
+ }
733
+ const localPluginNames = plugins.filter((p) => !p.importSource).map((p) => p.calleeName);
734
+ const localFuncSources = [];
735
+ for (const stmt of ast.body) if (stmt.type === "FunctionDeclaration" && stmt.id) {
736
+ const funcName = stmt.id.name;
737
+ if (localPluginNames.includes(funcName)) localFuncSources.push(source.slice(stmt.start, stmt.end));
738
+ }
739
+ const emittedVarNames = /* @__PURE__ */ new Set();
740
+ for (const stmt of ast.body) {
741
+ if (stmt.type !== "VariableDeclaration") continue;
742
+ for (const decl of stmt.declarations) {
743
+ if (!decl.id?.name) continue;
744
+ const varName = decl.id.name;
745
+ if (localFuncSources.some((funcSrc) => funcSrc.includes(varName)) && !localPluginNames.includes(varName)) {
746
+ if (!emittedVarNames.has(varName)) {
747
+ emittedVarNames.add(varName);
748
+ lines.push("");
749
+ lines.push(source.slice(stmt.start, stmt.end));
750
+ const varSource = source.slice(stmt.start, stmt.end);
751
+ for (const imp of imports) {
752
+ if (imp.isTypeOnly) continue;
753
+ if (isViteImport(imp.source)) continue;
754
+ if (isWorkerExcludedImport(imp.source)) continue;
755
+ if (varSource.includes(imp.localName)) {
756
+ neededImportSources.add(imp.source);
757
+ neededLocalNames.add(imp.localName);
758
+ }
759
+ }
760
+ }
761
+ break;
762
+ }
763
+ }
764
+ }
765
+ for (const stmt of ast.body) {
766
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
767
+ const funcName = stmt.id.name;
768
+ if (localPluginNames.includes(funcName)) {
769
+ lines.push("");
770
+ lines.push(source.slice(stmt.start, stmt.end));
771
+ }
772
+ }
773
+ if (stmt.type === "VariableDeclaration") {
774
+ for (const decl of stmt.declarations) if (decl.id?.name && localPluginNames.includes(decl.id.name)) {
775
+ lines.push("");
776
+ lines.push(source.slice(stmt.start, stmt.end));
777
+ break;
778
+ }
779
+ }
780
+ }
781
+ lines.push("");
782
+ lines.push("export default {");
783
+ lines.push(" plugins: [");
784
+ for (const plugin of plugins) {
785
+ const callSource = source.slice(plugin.start, plugin.end);
786
+ lines.push(` ${callSource},`);
787
+ }
788
+ lines.push(" ]");
789
+ lines.push("}");
790
+ return lines.join("\n");
791
+ }
792
+ function generateFallbackCode() {
793
+ return "export default { plugins: [] }";
794
+ }
795
+
796
+ //#endregion
797
+ //#region src/options.ts
798
+ /**
799
+ * vite-plugin-vrowzer options
800
+ *
801
+ * @module options
802
+ */
803
+ /**
804
+ * @author kazuya kawaguchi (a.k.a. kazupon)
805
+ * @license MIT
806
+ */
807
+ function resolveDefaultServiceWorkerEntry() {
808
+ try {
809
+ return fileURLToPath(import.meta.resolve("vrowzer/service-worker"));
810
+ } catch {
811
+ return "";
812
+ }
813
+ }
814
+ function resolveOptions(options) {
815
+ const ide = options.experimental?.ide;
816
+ return {
817
+ auto: options.auto ?? true,
818
+ manifest: options.manifest,
819
+ ide: {
820
+ enabled: !!ide,
821
+ port: typeof ide === "object" ? ide.port : void 0,
822
+ devtools: options.experimental?.devtools ?? false
823
+ },
824
+ basePath: options.basePath ?? "/__preview__/",
825
+ serviceWorkerScope: options.serviceWorkerScope ?? "/",
826
+ serviceWorkerVersion: options.serviceWorkerVersion ?? "SERVICE_WORKER_VERSION",
827
+ serviceWorkerEntry: options.serviceWorkerEntry ?? resolveDefaultServiceWorkerEntry(),
828
+ resolve: options.resolve
829
+ };
830
+ }
831
+
832
+ //#endregion
833
+ //#region src/prebundle.ts
834
+ /**
835
+ * Pre-bundle Worker config using rolldown.
836
+ *
837
+ * Takes the extracted Worker source from extract.ts and bundles it
838
+ * into node_modules/.vrowzer/ for Worker consumption.
839
+ *
840
+ * @module prebundle
841
+ */
842
+ /**
843
+ * @author kazuya kawaguchi (a.k.a. kazupon)
844
+ * @license MIT
845
+ */
846
+ const debug$4 = createDebug("vite-plugin-vrowzer:prebundle");
847
+ const OUTPUT_DIR_NAME = ".vrowzer";
848
+ const BUNDLED_FILENAME = "config.bundled.mjs";
849
+ /**
850
+ * Resolve the output directory path for prebundled Worker config.
851
+ */
852
+ function resolveOutputDir(root) {
853
+ return resolve(root, "node_modules", OUTPUT_DIR_NAME);
854
+ }
855
+ /**
856
+ * Remove the prebundle output directory.
857
+ */
858
+ function cleanOutputDir(root) {
859
+ const outputDir = resolveOutputDir(root);
860
+ if (existsSync(outputDir)) {
861
+ rmSync(outputDir, { recursive: true });
862
+ debug$4("cleaned output dir:", outputDir);
863
+ }
864
+ }
865
+ /**
866
+ * Pre-bundle Worker config source using rolldown.
867
+ *
868
+ * @returns Absolute path to the bundled config file.
869
+ */
870
+ async function prebundleWorkerConfig(options) {
871
+ const { workerSource, root, configDir } = options;
872
+ const outputDir = resolveOutputDir(root);
873
+ const bundledPath = resolve(outputDir, BUNDLED_FILENAME);
874
+ debug$4("prebundling worker config...");
875
+ mkdirSync(outputDir, { recursive: true });
876
+ const entryPath = resolve(outputDir, "_entry.mts");
877
+ writeFileSync(entryPath, workerSource);
878
+ await (await rolldown({
879
+ input: entryPath,
880
+ external: [
881
+ /* @__PURE__ */ new RegExp("^@vrowzer/"),
882
+ "assert",
883
+ "v8"
884
+ ],
885
+ transform: {
886
+ define: {
887
+ "process.env.NODE_ENV": JSON.stringify("development"),
888
+ global: "globalThis"
889
+ },
890
+ inject: { process: "@vrowzer/node-polyfill/process" }
891
+ },
892
+ resolve: {
893
+ alias: resolveAliases({ "node:process": "@vrowzer/node-polyfill/process" }),
894
+ mainFields: ["module", "main"],
895
+ conditionNames: [
896
+ "browser",
897
+ "import",
898
+ "default"
899
+ ]
900
+ },
901
+ platform: "neutral",
902
+ plugins: [
903
+ viteAliasPlugin(),
904
+ inlineReadFileSyncPlugin(configDir),
905
+ inlineCreateRequirePlugin()
906
+ ]
907
+ })).write({
908
+ format: "esm",
909
+ dir: outputDir,
910
+ entryFileNames: BUNDLED_FILENAME,
911
+ chunkFileNames: "chunks/[name].mjs",
912
+ minify: false
913
+ });
914
+ debug$4("prebundle complete:", bundledPath);
915
+ return bundledPath;
916
+ }
917
+ /**
918
+ * Rolldown plugin to redirect `vite` imports to `@vrowzer/vite-dev-server/vite`.
919
+ * Handles both exact `vite` and subpaths like `vite/internal`.
920
+ */
921
+ function viteAliasPlugin() {
922
+ const VITE_INTERNAL_ID = "\0vrowzer:vite-internal-stub";
923
+ return {
924
+ name: "vrowzer:vite-alias",
925
+ resolveId(id) {
926
+ if (id === "vite") return {
927
+ id: "@vrowzer/vite-dev-server/vite",
928
+ external: true
929
+ };
930
+ if (id === "vite/internal") return {
931
+ id: VITE_INTERNAL_ID,
932
+ external: false
933
+ };
934
+ if (id.startsWith("vite/")) return {
935
+ id: id.replace(/^vite\//, "@vrowzer/vite-dev-server/vite/"),
936
+ external: true
937
+ };
938
+ },
939
+ load(id) {
940
+ if (id === VITE_INTERNAL_ID) return "export {}";
941
+ }
942
+ };
943
+ }
944
+ /**
945
+ * Rolldown plugin to inline `readFileSync(...)` calls at prebundle time.
946
+ *
947
+ * When the Worker config source contains `readFileSync(path, 'utf-8')`,
948
+ * this plugin evaluates the call at prebundle time (Node.js) and replaces
949
+ * it with the file content as a string literal. This is necessary because
950
+ * Worker environments cannot access the host filesystem.
951
+ *
952
+ * Supported patterns:
953
+ * readFileSync('literal/path', 'utf-8')
954
+ * readFileSync(resolve(import.meta.dirname, 'path'), 'utf-8')
955
+ */
956
+ function inlineReadFileSyncPlugin(configDir) {
957
+ const RE = /readFileSync\(\s*([\s\S]+?)\s*,\s*['"]utf-?8['"]\s*\)/g;
958
+ return {
959
+ name: "vrowzer:inline-readFileSync",
960
+ transform(code, id) {
961
+ if (!id.includes("_entry.mt") && !id.includes(".vrowzer/")) return;
962
+ if (!code.includes("readFileSync")) return;
963
+ let modified = false;
964
+ const result = code.replace(RE, (match, pathExpr) => {
965
+ const resolvedPath = tryEvalPathExpr(pathExpr.trim(), configDir);
966
+ if (!resolvedPath) {
967
+ debug$4("inlineReadFileSync: could not evaluate path expr:", pathExpr);
968
+ return match;
969
+ }
970
+ try {
971
+ const content = readFileSync(resolvedPath, "utf-8");
972
+ modified = true;
973
+ debug$4("inlineReadFileSync: inlined", resolvedPath, `(${content.length} bytes)`);
974
+ return JSON.stringify(content);
975
+ } catch (e) {
976
+ debug$4("inlineReadFileSync: failed to read:", resolvedPath, e);
977
+ return match;
978
+ }
979
+ });
980
+ if (modified) return {
981
+ code: result.replace(/import\s*\{[^}]*readFileSync[^}]*\}\s*from\s*['"]node:fs['"]\s*;?\n?/g, "").replace(/import\s*\{[^}]*resolve[^}]*\}\s*from\s*['"]node:path['"]\s*;?\n?/g, ""),
982
+ map: null
983
+ };
984
+ }
985
+ };
986
+ }
987
+ /**
988
+ * Try to evaluate a path expression to an absolute path string.
989
+ */
990
+ function tryEvalPathExpr(expr, configDir) {
991
+ const strMatch = expr.match(/^['"](.+)['"]$/);
992
+ if (strMatch) return resolve(configDir, strMatch[1]);
993
+ const resolveMatch = expr.match(/^resolve\(\s*(?:import\.meta\.dirname|__dirname)\s*,\s*['"](.+)['"]\s*\)$/);
994
+ if (resolveMatch) return resolve(configDir, resolveMatch[1]);
995
+ return null;
996
+ }
997
+ /**
998
+ * Rolldown plugin to inline `createRequire(...)("pkg/path")` calls at prebundle time.
999
+ *
1000
+ * Some plugins (e.g. @sveltejs/vite-plugin-svelte) use `createRequire` at module
1001
+ * init time to load package.json files. This fails in Worker environments where
1002
+ * `require()` is not available. This plugin detects the pattern and replaces it
1003
+ * with the actual file content at prebundle time.
1004
+ */
1005
+ function inlineCreateRequirePlugin() {
1006
+ const RE = /createRequire\([^)]+\)\(\s*['"]([^'"]+)['"]\s*\)/g;
1007
+ return {
1008
+ name: "vrowzer:inline-createRequire",
1009
+ transform(code, id) {
1010
+ if (!code.includes("createRequire")) return;
1011
+ let modified = false;
1012
+ const result = code.replace(RE, (match, specifier) => {
1013
+ if (!specifier.endsWith(".json")) return match;
1014
+ try {
1015
+ const content = readFileSync(createRequire(id).resolve(specifier), "utf-8");
1016
+ modified = true;
1017
+ debug$4("inlineCreateRequire: inlined", specifier, "from", id);
1018
+ return JSON.stringify(JSON.parse(content));
1019
+ } catch {
1020
+ debug$4("inlineCreateRequire: could not resolve", specifier, "from", id);
1021
+ return match;
1022
+ }
1023
+ });
1024
+ if (modified) return {
1025
+ code: result,
1026
+ map: null
1027
+ };
1028
+ }
1029
+ };
1030
+ }
1031
+
1032
+ //#endregion
1033
+ //#region src/rolldown.ts
1034
+ /**
1035
+ * rolldown processing
1036
+ *
1037
+ * @module rolldown
1038
+ */
1039
+ /**
1040
+ * @author kazuya kawaguchi (a.k.a. kazupon)
1041
+ * @license MIT
1042
+ */
1043
+ const debug$3 = createDebug("vite-plugin-vrowzer:rolldown");
1044
+ const rolldownDistDir = path.resolve(path.dirname(fileURLToPath(import.meta.resolve("@vrowzer/rolldown/package.json"))), "dist");
1045
+ debug$3("rolldownDistDir ", rolldownDistDir);
1046
+ function rolldownPlugin(_options) {
1047
+ let resolvedOutDir = "";
1048
+ return {
1049
+ name: "vrowzer:rolldown",
1050
+ configResolved(config) {
1051
+ resolvedOutDir = path.resolve(config.root, config.build.outDir);
1052
+ },
1053
+ writeBundle() {
1054
+ const assetsDir = path.resolve(resolvedOutDir, "assets");
1055
+ debug$3("copy-rolldown-wasm: assetsDir ", assetsDir);
1056
+ const wasmSrc = path.resolve(rolldownDistDir, "rolldown-binding.wasm32-wasi.wasm");
1057
+ debug$3("copy-rolldown-wasm: wasmSrc ", wasmSrc);
1058
+ const workerSrc = path.resolve(rolldownDistDir, "worker.js");
1059
+ debug$3("copy-rolldown-wasm: workerSrc ", workerSrc);
1060
+ if (existsSync(wasmSrc)) copyFileSync(wasmSrc, path.resolve(assetsDir, "rolldown-binding.wasm32-wasi.wasm"));
1061
+ if (existsSync(workerSrc)) copyFileSync(workerSrc, path.resolve(assetsDir, "rolldown-worker.js"));
1062
+ }
1063
+ };
1064
+ }
1065
+
1066
+ //#endregion
1067
+ //#region src/server.ts
1068
+ /**
1069
+ * server middleware
1070
+ *
1071
+ * @module server
1072
+ */
1073
+ /**
1074
+ * @author kazuya kawaguchi (a.k.a. kazupon)
1075
+ * @license MIT
1076
+ */
1077
+ const debug$2 = createDebug("vite-plugin-vrowzer:server");
1078
+ /**
1079
+ * NOTE(kazupon):
1080
+ * Prevent Vite's SPA fallback from serving index.html for preview URL (e.g '/__preview__/') requests.
1081
+ * When service worker is not yet controlling the page (e.g. after hard reload),
1082
+ * preview requests bypass service worker and hit Vite directly.
1083
+ * Without this guard, Vite returns the main page HTML, causing recursive display.
1084
+ */
1085
+ function previewGuardMiddleware(previewBase = "/__preview__") {
1086
+ return (req, res, next) => {
1087
+ debug$2("previewGuardMiddleware: previewBase ", previewBase, " req.url ", req.url);
1088
+ if (req.url?.startsWith(previewBase)) {
1089
+ res.writeHead(503, {
1090
+ "Content-Type": "text/html",
1091
+ "Retry-After": "1"
1092
+ });
1093
+ res.end(`<!doctype html><html><head><meta charset="utf-8"><title>Preview</title></head><body>
1094
+ <script>setTimeout(() => location.reload(), 1000)<\/script>
1095
+ <p>Waiting for Service Worker...</p></body></html>`);
1096
+ return;
1097
+ }
1098
+ next();
1099
+ };
1100
+ }
1101
+ function serverMiddlewarePlugin(options) {
1102
+ const middleware = previewGuardMiddleware(normalizeBasePath(options.basePath));
1103
+ return {
1104
+ name: "vrowzer:server-middleware",
1105
+ configureServer(server) {
1106
+ server.middlewares.use(middleware);
1107
+ },
1108
+ configurePreviewServer(server) {
1109
+ server.middlewares.use(middleware);
1110
+ }
1111
+ };
1112
+ }
1113
+ function normalizeBasePath(basePath) {
1114
+ debug$2("normalizeBasePath: basePath ", basePath);
1115
+ if (basePath.endsWith("/")) return basePath.slice(0, -1);
1116
+ else return basePath;
1117
+ }
1118
+
1119
+ //#endregion
1120
+ //#region src/virtual.ts
1121
+ function generateWebWorkerEntry(configPath, resolve$1) {
1122
+ return `
1123
+ import { initWebWorker } from 'vrowzer/web-worker-core'
1124
+ import config from '${configPath}'
1125
+ const resolved = config.default ?? config
1126
+ ${resolve$1 ? `\nconst workerResolve = ${JSON.stringify(resolve$1)}\nObject.assign(resolved, { resolve: workerResolve })` : ""}
1127
+ initWebWorker(resolved)
1128
+ `;
1129
+ }
1130
+
1131
+ //#endregion
1132
+ //#region src/manifest.ts
1133
+ /**
1134
+ * Vite plugin that transforms vrowzer-manifest.json imports.
1135
+ *
1136
+ * Replaces file path values with actual file contents so that
1137
+ * the imported manifest can be passed directly to Vrowzer.ready().
1138
+ *
1139
+ * Use the `?vrowzer` query suffix to trigger this plugin:
1140
+ * import manifest from './vrowzer-manifest.json?vrowzer'
1141
+ *
1142
+ * @module manifest
1143
+ */
1144
+ /**
1145
+ * @author kazuya kawaguchi (a.k.a. kazupon)
1146
+ * @license MIT
1147
+ */
1148
+ const MINIFIABLE_EXTENSIONS = new Set([
1149
+ ".js",
1150
+ ".mjs",
1151
+ ".cjs"
1152
+ ]);
1153
+ const debug$1 = createDebug("vite-plugin-vrowzer:manifest");
1154
+ function parseId(id) {
1155
+ try {
1156
+ const url = new URL(id, "file://");
1157
+ return {
1158
+ filePath: url.pathname,
1159
+ isVrowzer: url.searchParams.has("vrowzer")
1160
+ };
1161
+ } catch {
1162
+ return {
1163
+ filePath: id,
1164
+ isVrowzer: false
1165
+ };
1166
+ }
1167
+ }
1168
+ function VrowzerManifest() {
1169
+ return {
1170
+ name: "vrowzer:manifest-loader",
1171
+ resolveId(id) {
1172
+ if (parseId(id).isVrowzer) {
1173
+ debug$1("resolveId:", id);
1174
+ return id;
1175
+ }
1176
+ },
1177
+ load(id) {
1178
+ const { filePath, isVrowzer } = parseId(id);
1179
+ if (!isVrowzer) return;
1180
+ debug$1("loading manifest:", filePath);
1181
+ const raw = readFileSync(filePath, "utf-8");
1182
+ const manifest = JSON.parse(raw);
1183
+ const manifestDir = dirname(filePath);
1184
+ function resolveFiles(field, files, minify = false) {
1185
+ if (!files) return {};
1186
+ const resolved = {};
1187
+ for (const [virtualPath, relPath] of Object.entries(files)) try {
1188
+ let content = readFileSync(resolve(manifestDir, relPath), "utf-8");
1189
+ if (minify && MINIFIABLE_EXTENSIONS.has(extname(virtualPath))) {
1190
+ const result$1 = minifySync(virtualPath, content);
1191
+ if (result$1.code) content = result$1.code;
1192
+ }
1193
+ resolved[virtualPath] = content;
1194
+ } catch (e) {
1195
+ debug$1("failed to read %s %s: %s", field, relPath, e.message);
1196
+ }
1197
+ debug$1("%s: %d files resolved", field, Object.keys(resolved).length);
1198
+ return resolved;
1199
+ }
1200
+ const result = {
1201
+ name: manifest.name,
1202
+ files: resolveFiles("files", manifest.files),
1203
+ vendor: resolveFiles("vendor", manifest.vendor, true),
1204
+ nodeModules: resolveFiles("nodeModules", manifest.nodeModules, true),
1205
+ activeFile: manifest.activeFile
1206
+ };
1207
+ debug$1("manifest loaded: %s (%d files, %d vendor, %d nodeModules)", result.name, Object.keys(result.files).length, Object.keys(result.vendor).length, Object.keys(result.nodeModules).length);
1208
+ return {
1209
+ code: `export default ${JSON.stringify(result)}`,
1210
+ moduleType: "js"
1211
+ };
1212
+ }
1213
+ };
1214
+ }
1215
+
1216
+ //#endregion
1217
+ //#region src/index.ts
1218
+ /**
1219
+ * vite-plugin-vrowzer entry
1220
+ *
1221
+ * @module default
1222
+ */
1223
+ /**
1224
+ * @author kazuya kawaguchi (a.k.a. kazupon)
1225
+ * @license MIT
1226
+ */
1227
+ const debug = createDebug("vite-plugin-vrowzer:index");
1228
+ function Vrowzer(options = {}) {
1229
+ const resolvedOptions = resolveOptions(options);
1230
+ const root = process.cwd();
1231
+ let bundledConfigPath = null;
1232
+ let isBuild = false;
1233
+ function workerEntryTransform(code, id) {
1234
+ if (!bundledConfigPath) return;
1235
+ const cleanId = id.split("?")[0];
1236
+ if (cleanId?.endsWith("web-worker.ts") && !cleanId.endsWith("web-worker-core.ts") && code.includes("initWebWorker()")) return {
1237
+ code: generateWebWorkerEntry(bundledConfigPath, resolvedOptions.resolve),
1238
+ map: null
1239
+ };
1240
+ }
1241
+ const plugins = [
1242
+ {
1243
+ name: "vrowzer:config",
1244
+ resolveId(id) {
1245
+ if (id.startsWith("@vrowzer/")) try {
1246
+ return fileURLToPath(import.meta.resolve(id));
1247
+ } catch {}
1248
+ },
1249
+ config() {
1250
+ const workerPlugins = [
1251
+ {
1252
+ name: "vrowzer:worker-resolve",
1253
+ resolveId(id) {
1254
+ if (id.startsWith("@vrowzer/")) try {
1255
+ return fileURLToPath(import.meta.resolve(id));
1256
+ } catch {}
1257
+ }
1258
+ },
1259
+ {
1260
+ name: "vrowzer:worker-process-inject",
1261
+ options(inputOptions) {
1262
+ inputOptions.transform ??= {};
1263
+ inputOptions.transform.inject = {
1264
+ ...inputOptions.transform.inject,
1265
+ process: "@vrowzer/node-polyfill/process"
1266
+ };
1267
+ }
1268
+ },
1269
+ {
1270
+ name: "vrowzer:web-worker-config-inject",
1271
+ transform: workerEntryTransform
1272
+ }
1273
+ ];
1274
+ return {
1275
+ resolve: { alias: [{
1276
+ find: /^vite$/,
1277
+ replacement: "@vrowzer/vite-dev-server/vite"
1278
+ }] },
1279
+ worker: { plugins: () => workerPlugins }
1280
+ };
1281
+ },
1282
+ async configResolved(config) {
1283
+ isBuild = config.command === "build";
1284
+ const viteConfigPath = config.configFile;
1285
+ if (!viteConfigPath) {
1286
+ debug("no vite.config.ts found, skipping extraction");
1287
+ return;
1288
+ }
1289
+ debug("extracting worker config from:", viteConfigPath);
1290
+ cleanOutputDir(config.root);
1291
+ const configDir = dirname(viteConfigPath);
1292
+ const { code: workerSource, unsupported } = extractWorkerConfig(readFileSync(viteConfigPath, "utf-8"), viteConfigPath);
1293
+ if (unsupported.length > 0) debug("unsupported patterns found:", unsupported);
1294
+ debug("generated worker source:\n", workerSource);
1295
+ bundledConfigPath = await prebundleWorkerConfig({
1296
+ workerSource,
1297
+ root: config.root,
1298
+ configDir
1299
+ });
1300
+ debug("bundled config path:", bundledConfigPath);
1301
+ },
1302
+ closeBundle() {
1303
+ if (isBuild && bundledConfigPath) {
1304
+ cleanOutputDir(root);
1305
+ debug("cleaned up prebundle output after build");
1306
+ }
1307
+ },
1308
+ transform(code, id) {
1309
+ return workerEntryTransform(code, id);
1310
+ }
1311
+ },
1312
+ serverMiddlewarePlugin(resolvedOptions),
1313
+ {
1314
+ ...inject({
1315
+ process: "@vrowzer/node-polyfill/process",
1316
+ exclude: [/node_modules\/\.vite\//, /node_modules\/\.vrowzer\//]
1317
+ }),
1318
+ apply: "serve"
1319
+ },
1320
+ envPlugin(resolvedOptions),
1321
+ rolldownPlugin(resolvedOptions),
1322
+ ServiceWorker({
1323
+ serviceWorkerAllowed: "/",
1324
+ format: "esm",
1325
+ ...resolvedOptions.serviceWorkerEntry ? { entry: resolvedOptions.serviceWorkerEntry } : {}
1326
+ })
1327
+ ];
1328
+ if (resolvedOptions.auto) plugins.unshift(autoManifestPlugin(resolvedOptions.manifest));
1329
+ if (resolvedOptions.ide.enabled) plugins.push(idePlugin(resolvedOptions));
1330
+ return plugins;
1331
+ }
1332
+
1333
+ //#endregion
1334
+ export { Vrowzer, VrowzerManifest, generateManifest };
1335
+ //# sourceMappingURL=index.mjs.map