@zappdev/vite 0.2.0 → 0.5.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +180 -228
- package/package.json +12 -14
- package/src/index.ts +246 -0
- package/README.md +0 -37
- package/dist/index.d.ts +0 -8
package/dist/index.js
CHANGED
|
@@ -1,237 +1,189 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
2
5
|
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { mkdir, readdir, readFile } from "node:fs/promises";
|
|
8
|
+
var WORKER_PATTERN = /new\s+(?:SharedWorker|Worker)\s*\(\s*(?:new\s+URL\(\s*["'`](.+?)["'`]\s*,\s*import\.meta\.url\s*\)|["'`](.+?)["'`])/g;
|
|
9
|
+
async function scanDir(dir) {
|
|
10
|
+
const results = [];
|
|
11
|
+
try {
|
|
12
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
6
13
|
for (const entry of entries) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
if (!/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(entry.name))
|
|
17
|
-
continue;
|
|
18
|
-
out.push(full);
|
|
19
|
-
}
|
|
20
|
-
return out;
|
|
21
|
-
};
|
|
22
|
-
const discoverWorkerEntries = async (srcRoot) => {
|
|
23
|
-
const sourceFiles = await scanSourceFiles(srcRoot);
|
|
24
|
-
const found = new Map();
|
|
25
|
-
for (const file of sourceFiles) {
|
|
26
|
-
const content = await fs.readFile(file, "utf8");
|
|
27
|
-
let match;
|
|
28
|
-
while ((match = WORKER_PATTERN.exec(content)) != null) {
|
|
29
|
-
const spec = match[1] ?? match[2];
|
|
30
|
-
if (!spec || !/\.(ts|tsx|js|jsx|mjs)$/.test(spec))
|
|
31
|
-
continue;
|
|
32
|
-
const entryPath = path.resolve(path.dirname(file), spec);
|
|
33
|
-
found.set(entryPath, spec);
|
|
34
|
-
}
|
|
14
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "zapp")
|
|
15
|
+
continue;
|
|
16
|
+
const full = path.join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
results.push(...await scanDir(full));
|
|
19
|
+
} else if (/\.(ts|tsx|js|jsx|mjs)$/.test(entry.name)) {
|
|
20
|
+
results.push(full);
|
|
21
|
+
}
|
|
35
22
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
23
|
+
} catch {}
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
async function discoverWorkers(srcDir) {
|
|
27
|
+
const files = await scanDir(srcDir);
|
|
28
|
+
const found = new Map;
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const content = await readFile(file, "utf-8");
|
|
31
|
+
let match;
|
|
32
|
+
WORKER_PATTERN.lastIndex = 0;
|
|
33
|
+
while ((match = WORKER_PATTERN.exec(content)) !== null) {
|
|
34
|
+
const spec = match[1] || match[2];
|
|
35
|
+
if (!spec || found.has(spec))
|
|
36
|
+
continue;
|
|
37
|
+
const sourcePath = path.resolve(path.dirname(file), spec);
|
|
38
|
+
const baseName = path.basename(spec).replace(/\.[^.]+$/, "");
|
|
39
|
+
found.set(spec, {
|
|
40
|
+
specifier: spec,
|
|
41
|
+
sourcePath,
|
|
42
|
+
outputName: `${baseName}.mjs`,
|
|
43
|
+
outputUrl: `/_workers/${baseName}.mjs`
|
|
44
|
+
});
|
|
54
45
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const result = await globalThis.Bun.build({
|
|
76
|
-
entrypoints: [entryPath],
|
|
77
|
-
outdir: path.dirname(tmpFile),
|
|
78
|
-
naming: path.basename(tmpFile),
|
|
79
|
-
target: "browser",
|
|
80
|
-
format: "esm",
|
|
81
|
-
sourcemap: devServer ? "inline" : "none",
|
|
82
|
-
minify: devServer ? false : minify,
|
|
83
|
-
plugins: [{
|
|
84
|
-
name: "zapp-alias",
|
|
85
|
-
setup(build) {
|
|
86
|
-
for (const [find, replacement] of Object.entries(viteAliases)) {
|
|
87
|
-
build.onResolve({ filter: new RegExp(`^${find}$`) }, () => ({ path: replacement }));
|
|
88
|
-
build.onResolve({ filter: new RegExp(`^${find}/(.*)`) }, (args) => ({
|
|
89
|
-
path: path.join(replacement, args.path.slice(find.length + 1))
|
|
90
|
-
}));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}],
|
|
94
|
-
});
|
|
95
|
-
if (!result.success) {
|
|
96
|
-
const lines = (result.logs ?? [])
|
|
97
|
-
.map((log) => log?.message)
|
|
98
|
-
.filter(Boolean)
|
|
99
|
-
.join("\n");
|
|
100
|
-
throw new Error(lines || `bun build failed for ${entryPath}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
const { build: esbuild } = await import("esbuild");
|
|
105
|
-
await esbuild({
|
|
106
|
-
entryPoints: [entryPath],
|
|
107
|
-
bundle: true,
|
|
108
|
-
format: "esm",
|
|
109
|
-
platform: "browser",
|
|
110
|
-
target: "es2022",
|
|
111
|
-
sourcemap: devServer ? "inline" : false,
|
|
112
|
-
minify: devServer ? false : minify,
|
|
113
|
-
outfile: tmpFile,
|
|
114
|
-
alias: viteAliases,
|
|
115
|
-
});
|
|
46
|
+
}
|
|
47
|
+
return [...found.values()];
|
|
48
|
+
}
|
|
49
|
+
async function bundleWorker(entry, outDir, aliases) {
|
|
50
|
+
try {
|
|
51
|
+
const vite = await import("vite");
|
|
52
|
+
await vite.build({
|
|
53
|
+
configFile: false,
|
|
54
|
+
logLevel: "silent",
|
|
55
|
+
build: {
|
|
56
|
+
outDir,
|
|
57
|
+
emptyOutDir: false,
|
|
58
|
+
minify: true,
|
|
59
|
+
rollupOptions: {
|
|
60
|
+
input: entry.sourcePath,
|
|
61
|
+
output: {
|
|
62
|
+
format: "es",
|
|
63
|
+
entryFileNames: entry.outputName,
|
|
64
|
+
dir: outDir
|
|
65
|
+
}
|
|
116
66
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
67
|
+
},
|
|
68
|
+
resolve: { alias: aliases }
|
|
69
|
+
});
|
|
70
|
+
return true;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error(`[zapp] worker bundle failed: ${entry.specifier}`, e);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function zappWorkers() {
|
|
77
|
+
let root = "";
|
|
78
|
+
let srcDir = "";
|
|
79
|
+
let workers = [];
|
|
80
|
+
let backendEntry = null;
|
|
81
|
+
let aliases = {};
|
|
82
|
+
let isDev = false;
|
|
83
|
+
let outDir = "";
|
|
84
|
+
return {
|
|
85
|
+
name: "zapp-workers",
|
|
86
|
+
enforce: "pre",
|
|
87
|
+
configResolved(config) {
|
|
88
|
+
root = config.root;
|
|
89
|
+
srcDir = path.join(root, "src");
|
|
90
|
+
outDir = path.join(root, "dist", "_workers");
|
|
91
|
+
isDev = config.command === "serve";
|
|
92
|
+
const resolvedAlias = config.resolve?.alias;
|
|
93
|
+
if (resolvedAlias && typeof resolvedAlias === "object" && !Array.isArray(resolvedAlias)) {
|
|
94
|
+
aliases = resolvedAlias;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
async buildStart() {
|
|
98
|
+
workers = await discoverWorkers(srcDir);
|
|
99
|
+
const backendPath = path.join(srcDir, "backend.ts");
|
|
100
|
+
if (existsSync(backendPath)) {
|
|
101
|
+
backendEntry = {
|
|
102
|
+
specifier: "./backend.ts",
|
|
103
|
+
sourcePath: backendPath,
|
|
104
|
+
outputName: "backend.mjs",
|
|
105
|
+
outputUrl: "/_workers/backend.mjs"
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (workers.length > 0) {
|
|
109
|
+
console.log(`[zapp] discovered ${workers.length} worker(s)`);
|
|
110
|
+
}
|
|
111
|
+
if (backendEntry) {
|
|
112
|
+
console.log("[zapp] backend worker: src/backend.ts");
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
transform(code, id) {
|
|
116
|
+
if (!id.endsWith(".ts") && !id.endsWith(".tsx") && !id.endsWith(".js") && !id.endsWith(".jsx"))
|
|
117
|
+
return null;
|
|
118
|
+
if (id.includes("node_modules"))
|
|
119
|
+
return null;
|
|
120
|
+
let modified = false;
|
|
121
|
+
let result = code;
|
|
122
|
+
for (const entry of workers) {
|
|
123
|
+
const escaped = entry.specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
124
|
+
const regex = new RegExp(`(new\\s+(?:SharedWorker|Worker)\\s*\\(\\s*["'\`])${escaped}(["'\`])`, "g");
|
|
125
|
+
const replaced = result.replace(regex, `$1${entry.outputUrl}$2`);
|
|
126
|
+
if (replaced !== result) {
|
|
127
|
+
result = replaced;
|
|
128
|
+
modified = true;
|
|
123
129
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
130
|
+
}
|
|
131
|
+
return modified ? { code: result, map: null } : null;
|
|
132
|
+
},
|
|
133
|
+
async configureServer(server) {
|
|
134
|
+
const devOutDir = path.join(root, ".zapp", "workers");
|
|
135
|
+
await mkdir(devOutDir, { recursive: true });
|
|
136
|
+
workers = await discoverWorkers(srcDir);
|
|
137
|
+
const backendPath = path.join(srcDir, "backend.ts");
|
|
138
|
+
if (existsSync(backendPath)) {
|
|
139
|
+
backendEntry = {
|
|
140
|
+
specifier: "./backend.ts",
|
|
141
|
+
sourcePath: backendPath,
|
|
142
|
+
outputName: "backend.mjs",
|
|
143
|
+
outputUrl: "/_workers/backend.mjs"
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const allEntries = [...workers];
|
|
147
|
+
if (backendEntry)
|
|
148
|
+
allEntries.push(backendEntry);
|
|
149
|
+
for (const entry of allEntries) {
|
|
150
|
+
const ok = await bundleWorker(entry, devOutDir, aliases);
|
|
151
|
+
if (ok)
|
|
152
|
+
console.log(`[zapp] dev-bundled worker: ${entry.outputName}`);
|
|
153
|
+
}
|
|
154
|
+
server.middlewares.use(async (req, res, next) => {
|
|
155
|
+
if (!req.url?.startsWith("/_workers/"))
|
|
156
|
+
return next();
|
|
157
|
+
const fileName = req.url.slice("/_workers/".length);
|
|
158
|
+
const filePath = path.join(devOutDir, fileName);
|
|
159
|
+
if (existsSync(filePath)) {
|
|
160
|
+
const content = await readFile(filePath, "utf-8");
|
|
161
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
162
|
+
res.end(content);
|
|
163
|
+
} else {
|
|
164
|
+
res.statusCode = 404;
|
|
165
|
+
res.end("Worker not found");
|
|
146
166
|
}
|
|
147
|
-
|
|
148
|
-
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
async generateBundle() {
|
|
170
|
+
if (isDev)
|
|
171
|
+
return;
|
|
172
|
+
await mkdir(outDir, { recursive: true });
|
|
173
|
+
const allEntries = [...workers];
|
|
174
|
+
if (backendEntry)
|
|
175
|
+
allEntries.push(backendEntry);
|
|
176
|
+
for (const entry of allEntries) {
|
|
177
|
+
const ok = await bundleWorker(entry, outDir, aliases);
|
|
178
|
+
if (ok) {
|
|
179
|
+
console.log(`[zapp] bundled worker: ${entry.outputName}`);
|
|
149
180
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (typeof a.find === "string" && typeof a.replacement === "string") {
|
|
159
|
-
viteAliases[a.find] = a.replacement;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else if (config.resolve?.alias) {
|
|
164
|
-
Object.assign(viteAliases, config.resolve.alias);
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
async buildStart() {
|
|
168
|
-
await buildWorkers();
|
|
169
|
-
},
|
|
170
|
-
async transformIndexHtml(html) {
|
|
171
|
-
const outDir = getOutDir();
|
|
172
|
-
const manifestPath = path.join(outDir, "manifest.json");
|
|
173
|
-
let workers = {};
|
|
174
|
-
try {
|
|
175
|
-
const raw = await fs.readFile(manifestPath, "utf8");
|
|
176
|
-
workers = JSON.parse(raw).workers ?? {};
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
workers = {};
|
|
180
|
-
}
|
|
181
|
-
const serialized = JSON.stringify(workers).replace(/</g, "\\u003c");
|
|
182
|
-
const script = `<script>(function(){globalThis[Symbol.for('zapp.workerManifest')]=${serialized};})();</script>`;
|
|
183
|
-
return html.includes("</head>") ? html.replace("</head>", `${script}</head>`) : `${script}${html}`;
|
|
184
|
-
},
|
|
185
|
-
configureServer(server) {
|
|
186
|
-
devServer = true;
|
|
187
|
-
const outDir = getOutDir();
|
|
188
|
-
const srcRoot = path.resolve(root, sourceRoot);
|
|
189
|
-
const srcRootNorm = srcRoot.replace(/\\/g, "/");
|
|
190
|
-
server.middlewares.use((req, res, next) => {
|
|
191
|
-
const url = req.url ?? "";
|
|
192
|
-
if (!url.startsWith("/zapp-workers/"))
|
|
193
|
-
return next();
|
|
194
|
-
const fileName = url.slice("/zapp-workers/".length).split("?")[0];
|
|
195
|
-
if (!fileName)
|
|
196
|
-
return next();
|
|
197
|
-
const filePath = path.join(outDir, fileName);
|
|
198
|
-
fs.readFile(filePath, "utf8").then((content) => {
|
|
199
|
-
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
200
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
201
|
-
res.end(content);
|
|
202
|
-
}).catch(() => next());
|
|
203
|
-
});
|
|
204
|
-
const rebuild = (filePath) => {
|
|
205
|
-
const norm = path.resolve(filePath).replace(/\\/g, "/");
|
|
206
|
-
if (!norm.startsWith(srcRootNorm + "/"))
|
|
207
|
-
return;
|
|
208
|
-
if (timer)
|
|
209
|
-
clearTimeout(timer);
|
|
210
|
-
timer = setTimeout(() => {
|
|
211
|
-
buildWorkers().catch((error) => {
|
|
212
|
-
server.config.logger.error(`[zapp-workers] ${error.message}`);
|
|
213
|
-
});
|
|
214
|
-
}, 200);
|
|
215
|
-
};
|
|
216
|
-
server.watcher.on("add", rebuild);
|
|
217
|
-
server.watcher.on("change", rebuild);
|
|
218
|
-
server.watcher.on("unlink", rebuild);
|
|
219
|
-
},
|
|
220
|
-
async writeBundle(outputOptions) {
|
|
221
|
-
if (devServer)
|
|
222
|
-
return;
|
|
223
|
-
const workerDir = getOutDir();
|
|
224
|
-
const distDir = outputOptions.dir ?? path.join(root, "dist");
|
|
225
|
-
const workerDistDir = path.join(distDir, "zapp-workers");
|
|
226
|
-
await fs.mkdir(workerDistDir, { recursive: true });
|
|
227
|
-
try {
|
|
228
|
-
const files = await fs.readdir(workerDir);
|
|
229
|
-
for (const file of files) {
|
|
230
|
-
await fs.copyFile(path.join(workerDir, file), path.join(workerDistDir, file));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
catch { }
|
|
234
|
-
},
|
|
235
|
-
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
var src_default = zappWorkers;
|
|
186
|
+
export {
|
|
187
|
+
zappWorkers,
|
|
188
|
+
src_default as default
|
|
236
189
|
};
|
|
237
|
-
export default zapp;
|
package/package.json
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zappdev/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Vite plugin for Zapp desktop apps",
|
|
6
5
|
"main": "dist/index.js",
|
|
7
|
-
"types": "
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
8
10
|
"files": [
|
|
9
|
-
"dist/"
|
|
11
|
+
"dist/",
|
|
12
|
+
"src/"
|
|
10
13
|
],
|
|
11
14
|
"scripts": {
|
|
12
|
-
"build": "
|
|
13
|
-
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"esbuild": "^0.25.10"
|
|
16
|
-
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"@types/node": "^22.0.0",
|
|
19
|
-
"typescript": "^5.0.0",
|
|
20
|
-
"vite": "^6.0.0"
|
|
15
|
+
"build": "bun build src/index.ts --outdir dist --format esm --target node",
|
|
16
|
+
"prepack": "bun run build"
|
|
21
17
|
},
|
|
22
|
-
"
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"vite": ">=5.0.0"
|
|
20
|
+
}
|
|
23
21
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zapp Vite Plugin — bundles workers and backend scripts.
|
|
3
|
+
*
|
|
4
|
+
* Discovers `new Worker("./path")` and `new SharedWorker("./path")` patterns,
|
|
5
|
+
* bundles each as a separate entry, outputs to dist/_workers/.
|
|
6
|
+
* Also handles the backend worker convention (src/backend.ts).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { zappWorkers } from "@zappdev/vite";
|
|
11
|
+
* export default defineConfig({ plugins: [zappWorkers()] });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Plugin, ViteDevServer } from "vite";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { mkdir, readdir, stat, readFile } from "node:fs/promises";
|
|
19
|
+
|
|
20
|
+
const WORKER_PATTERN =
|
|
21
|
+
/new\s+(?:SharedWorker|Worker)\s*\(\s*(?:new\s+URL\(\s*["'`](.+?)["'`]\s*,\s*import\.meta\.url\s*\)|["'`](.+?)["'`])/g;
|
|
22
|
+
|
|
23
|
+
interface WorkerEntry {
|
|
24
|
+
/** Original specifier from source: "./worker.ts" */
|
|
25
|
+
specifier: string;
|
|
26
|
+
/** Absolute path to source file */
|
|
27
|
+
sourcePath: string;
|
|
28
|
+
/** Output name: "worker.mjs" */
|
|
29
|
+
outputName: string;
|
|
30
|
+
/** Output URL path: "/_workers/worker.mjs" */
|
|
31
|
+
outputUrl: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Recursively scan for source files. */
|
|
35
|
+
async function scanDir(dir: string): Promise<string[]> {
|
|
36
|
+
const results: string[] = [];
|
|
37
|
+
try {
|
|
38
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "zapp") continue;
|
|
41
|
+
const full = path.join(dir, entry.name);
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
results.push(...await scanDir(full));
|
|
44
|
+
} else if (/\.(ts|tsx|js|jsx|mjs)$/.test(entry.name)) {
|
|
45
|
+
results.push(full);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Discover worker entries by scanning source files for new Worker() patterns. */
|
|
53
|
+
async function discoverWorkers(srcDir: string): Promise<WorkerEntry[]> {
|
|
54
|
+
const files = await scanDir(srcDir);
|
|
55
|
+
const found = new Map<string, WorkerEntry>();
|
|
56
|
+
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const content = await readFile(file, "utf-8");
|
|
59
|
+
let match;
|
|
60
|
+
WORKER_PATTERN.lastIndex = 0;
|
|
61
|
+
while ((match = WORKER_PATTERN.exec(content)) !== null) {
|
|
62
|
+
const spec = match[1] || match[2];
|
|
63
|
+
if (!spec || found.has(spec)) continue;
|
|
64
|
+
const sourcePath = path.resolve(path.dirname(file), spec);
|
|
65
|
+
const baseName = path.basename(spec).replace(/\.[^.]+$/, "");
|
|
66
|
+
found.set(spec, {
|
|
67
|
+
specifier: spec,
|
|
68
|
+
sourcePath,
|
|
69
|
+
outputName: `${baseName}.mjs`,
|
|
70
|
+
outputUrl: `/_workers/${baseName}.mjs`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [...found.values()];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Bundle a single worker entry using Vite's build API (Rolldown in Vite 8). */
|
|
79
|
+
async function bundleWorker(entry: WorkerEntry, outDir: string, aliases: Record<string, string>): Promise<boolean> {
|
|
80
|
+
try {
|
|
81
|
+
const vite = await import("vite");
|
|
82
|
+
await vite.build({
|
|
83
|
+
configFile: false,
|
|
84
|
+
logLevel: "silent",
|
|
85
|
+
build: {
|
|
86
|
+
outDir,
|
|
87
|
+
emptyOutDir: false,
|
|
88
|
+
minify: true,
|
|
89
|
+
rollupOptions: {
|
|
90
|
+
input: entry.sourcePath,
|
|
91
|
+
output: {
|
|
92
|
+
format: "es",
|
|
93
|
+
entryFileNames: entry.outputName,
|
|
94
|
+
dir: outDir,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
resolve: { alias: aliases },
|
|
99
|
+
});
|
|
100
|
+
return true;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.error(`[zapp] worker bundle failed: ${entry.specifier}`, e);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function zappWorkers(): Plugin {
|
|
108
|
+
let root = "";
|
|
109
|
+
let srcDir = "";
|
|
110
|
+
let workers: WorkerEntry[] = [];
|
|
111
|
+
let backendEntry: WorkerEntry | null = null;
|
|
112
|
+
let aliases: Record<string, string> = {};
|
|
113
|
+
let isDev = false;
|
|
114
|
+
let outDir = "";
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
name: "zapp-workers",
|
|
118
|
+
enforce: "pre",
|
|
119
|
+
|
|
120
|
+
configResolved(config) {
|
|
121
|
+
root = config.root;
|
|
122
|
+
srcDir = path.join(root, "src");
|
|
123
|
+
outDir = path.join(root, "dist", "_workers");
|
|
124
|
+
isDev = config.command === "serve";
|
|
125
|
+
|
|
126
|
+
// Extract alias paths for worker bundling
|
|
127
|
+
const resolvedAlias = config.resolve?.alias;
|
|
128
|
+
if (resolvedAlias && typeof resolvedAlias === "object" && !Array.isArray(resolvedAlias)) {
|
|
129
|
+
aliases = resolvedAlias as Record<string, string>;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async buildStart() {
|
|
134
|
+
// Discover workers from source
|
|
135
|
+
workers = await discoverWorkers(srcDir);
|
|
136
|
+
|
|
137
|
+
// Check for backend worker (convention: src/backend.ts)
|
|
138
|
+
const backendPath = path.join(srcDir, "backend.ts");
|
|
139
|
+
if (existsSync(backendPath)) {
|
|
140
|
+
backendEntry = {
|
|
141
|
+
specifier: "./backend.ts",
|
|
142
|
+
sourcePath: backendPath,
|
|
143
|
+
outputName: "backend.mjs",
|
|
144
|
+
outputUrl: "/_workers/backend.mjs",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (workers.length > 0) {
|
|
149
|
+
console.log(`[zapp] discovered ${workers.length} worker(s)`);
|
|
150
|
+
}
|
|
151
|
+
if (backendEntry) {
|
|
152
|
+
console.log("[zapp] backend worker: src/backend.ts");
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Rewrite new Worker("./worker.ts") → new Worker("/_workers/worker.mjs")
|
|
157
|
+
transform(code, id) {
|
|
158
|
+
if (!id.endsWith(".ts") && !id.endsWith(".tsx") && !id.endsWith(".js") && !id.endsWith(".jsx")) return null;
|
|
159
|
+
if (id.includes("node_modules")) return null;
|
|
160
|
+
|
|
161
|
+
let modified = false;
|
|
162
|
+
let result = code;
|
|
163
|
+
|
|
164
|
+
for (const entry of workers) {
|
|
165
|
+
// Replace the specifier in new Worker("./worker.ts") with the output URL
|
|
166
|
+
const escaped = entry.specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
167
|
+
const regex = new RegExp(`(new\\s+(?:SharedWorker|Worker)\\s*\\(\\s*["'\`])${escaped}(["'\`])`, "g");
|
|
168
|
+
const replaced = result.replace(regex, `$1${entry.outputUrl}$2`);
|
|
169
|
+
if (replaced !== result) {
|
|
170
|
+
result = replaced;
|
|
171
|
+
modified = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return modified ? { code: result, map: null } : null;
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Dev: bundle workers eagerly so they exist on disk before the native
|
|
179
|
+
// binary launches. The backend worker is loaded directly by native code
|
|
180
|
+
// and never goes through HTTP, so a lazy-on-request middleware would never
|
|
181
|
+
// bundle it. Eager bundling also lets native fall back to filesystem.
|
|
182
|
+
//
|
|
183
|
+
// configureServer runs before buildStart, so we re-discover workers here
|
|
184
|
+
// (buildStart's results aren't yet available).
|
|
185
|
+
async configureServer(server: ViteDevServer) {
|
|
186
|
+
const devOutDir = path.join(root, ".zapp", "workers");
|
|
187
|
+
await mkdir(devOutDir, { recursive: true });
|
|
188
|
+
|
|
189
|
+
workers = await discoverWorkers(srcDir);
|
|
190
|
+
const backendPath = path.join(srcDir, "backend.ts");
|
|
191
|
+
if (existsSync(backendPath)) {
|
|
192
|
+
backendEntry = {
|
|
193
|
+
specifier: "./backend.ts",
|
|
194
|
+
sourcePath: backendPath,
|
|
195
|
+
outputName: "backend.mjs",
|
|
196
|
+
outputUrl: "/_workers/backend.mjs",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const allEntries = [...workers];
|
|
201
|
+
if (backendEntry) allEntries.push(backendEntry);
|
|
202
|
+
|
|
203
|
+
for (const entry of allEntries) {
|
|
204
|
+
const ok = await bundleWorker(entry, devOutDir, aliases);
|
|
205
|
+
if (ok) console.log(`[zapp] dev-bundled worker: ${entry.outputName}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Still expose middleware so WebView fetch of /_workers/<name>.mjs works
|
|
209
|
+
// (e.g. native engines that pull via dev URL like jsc.m).
|
|
210
|
+
server.middlewares.use(async (req, res, next) => {
|
|
211
|
+
if (!req.url?.startsWith("/_workers/")) return next();
|
|
212
|
+
|
|
213
|
+
const fileName = req.url.slice("/_workers/".length);
|
|
214
|
+
const filePath = path.join(devOutDir, fileName);
|
|
215
|
+
|
|
216
|
+
if (existsSync(filePath)) {
|
|
217
|
+
const content = await readFile(filePath, "utf-8");
|
|
218
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
219
|
+
res.end(content);
|
|
220
|
+
} else {
|
|
221
|
+
res.statusCode = 404;
|
|
222
|
+
res.end("Worker not found");
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// Prod: bundle all workers to dist/_workers/
|
|
228
|
+
async generateBundle() {
|
|
229
|
+
if (isDev) return;
|
|
230
|
+
|
|
231
|
+
await mkdir(outDir, { recursive: true });
|
|
232
|
+
|
|
233
|
+
const allEntries = [...workers];
|
|
234
|
+
if (backendEntry) allEntries.push(backendEntry);
|
|
235
|
+
|
|
236
|
+
for (const entry of allEntries) {
|
|
237
|
+
const ok = await bundleWorker(entry, outDir, aliases);
|
|
238
|
+
if (ok) {
|
|
239
|
+
console.log(`[zapp] bundled worker: ${entry.outputName}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export default zappWorkers;
|
package/README.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# @zappdev/vite
|
|
2
|
-
|
|
3
|
-
Vite plugin for Zapp desktop apps. Handles worker discovery, source bundling, and output configuration so your Vite-based frontend integrates seamlessly with the Zapp native runtime.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
bun add -D @zappdev/vite
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```sh
|
|
12
|
-
npm install -D @zappdev/vite
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
// vite.config.ts
|
|
19
|
-
import { defineConfig } from "vite";
|
|
20
|
-
import zapp from "@zappdev/vite";
|
|
21
|
-
|
|
22
|
-
export default defineConfig({
|
|
23
|
-
plugins: [zapp()],
|
|
24
|
-
});
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Options
|
|
28
|
-
|
|
29
|
-
| Option | Type | Description |
|
|
30
|
-
| ------------ | --------- | ---------------------------------- |
|
|
31
|
-
| `outDir` | `string` | Output directory for bundled files |
|
|
32
|
-
| `sourceRoot` | `string` | Root directory of source files |
|
|
33
|
-
| `minify` | `boolean` | Enable minification |
|
|
34
|
-
|
|
35
|
-
## License
|
|
36
|
-
|
|
37
|
-
MIT
|