@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.
- package/LICENSE +20 -0
- package/README.md +290 -0
- package/dist/ide/assets/css.worker-uWEFNxvl.js +89 -0
- package/dist/ide/assets/html.worker-1tLoAl5Z.js +502 -0
- package/dist/ide/assets/json.worker-CL3sVQd6.js +58 -0
- package/dist/ide/assets/ts.worker-BFpNHPy5.js +67719 -0
- package/dist/ide/css-CPSW8DVZ.js +286 -0
- package/dist/ide/cssMode-B5DtFEwy.js +65 -0
- package/dist/ide/graphql-BteLL2CG.js +176 -0
- package/dist/ide/handlebars-BcqMM6pa.js +412 -0
- package/dist/ide/html-CxO5OaJl.js +333 -0
- package/dist/ide/htmlMode-DxpUG2jm.js +74 -0
- package/dist/ide/ide.css +2 -0
- package/dist/ide/ide.js +5523 -0
- package/dist/ide/javascript-CvtLx4_Z.js +21 -0
- package/dist/ide/jsonMode-LVtiYWKr.js +449 -0
- package/dist/ide/less-BubkcFWW.js +244 -0
- package/dist/ide/lspLanguageFeatures-BDyekQt2.js +1455 -0
- package/dist/ide/markdown-DPB5N4OB.js +264 -0
- package/dist/ide/mdx-Sun87_nd.js +329 -0
- package/dist/ide/monaco.contribution-DVj8eGe_.js +116 -0
- package/dist/ide/pug-BONHZNEg.js +302 -0
- package/dist/ide/scss-0r6NeK1P.js +419 -0
- package/dist/ide/shell-Vh_eZpea.js +225 -0
- package/dist/ide/toggleHighContrast-DI4ZeqFk.js +113328 -0
- package/dist/ide/tsMode-BKTkt1bt.js +567 -0
- package/dist/ide/typescript-CHC4VYBW.js +257 -0
- package/dist/ide/workers-IG_hD8Jy.js +47 -0
- package/dist/ide/xml-C0QFzMaz.js +130 -0
- package/dist/ide/yaml-o-RP7lYY.js +223 -0
- package/dist/index.d.mts +138 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1335 -0
- package/dist/index.mjs.map +1 -0
- package/dist/manifest-generate.d.mts +89 -0
- package/dist/manifest-generate.d.mts.map +1 -0
- package/dist/manifest-generate.mjs +438 -0
- package/dist/manifest-generate.mjs.map +1 -0
- 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
|