abret 0.1.3 → 0.1.4
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.
|
@@ -7,15 +7,22 @@ interface TranspilerOptions {
|
|
|
7
7
|
vendorPath?: string;
|
|
8
8
|
/** Optional list of modules to bundle on startup */
|
|
9
9
|
prewarm?: string[];
|
|
10
|
+
/** Minify local modules. Defaults to false (recommended for dev) */
|
|
11
|
+
minify?: boolean;
|
|
12
|
+
/** Browser cache TTL for local modules in seconds. Defaults to 0 */
|
|
13
|
+
localMaxAge?: number;
|
|
14
|
+
/** Global identifier replacements */
|
|
15
|
+
define?: Record<string, string>;
|
|
16
|
+
/** Map modules to global variables (e.g., { 'react': 'React' }) */
|
|
17
|
+
globals?: Record<string, string>;
|
|
18
|
+
/** Automatically fallback to esm.sh if package is not found locally */
|
|
19
|
+
cdnFallback?: boolean;
|
|
20
|
+
/** Additional Bun plugins */
|
|
21
|
+
plugins?: any[];
|
|
10
22
|
}
|
|
11
23
|
/**
|
|
12
24
|
* Transpiler middleware that handles on-the-fly TS/TSX transpilation
|
|
13
25
|
* and automatic npm module bundling (vendor modules).
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* ```ts
|
|
17
|
-
* transpiler({ sourcePath: "./src", staticBasePath: "/_modules" })
|
|
18
|
-
* ```
|
|
19
26
|
*/
|
|
20
27
|
export declare const transpiler: (options: TranspilerOptions) => import("../..").Middleware<string, undefined>;
|
|
21
28
|
export {};
|
|
@@ -5,14 +5,20 @@ import {
|
|
|
5
5
|
import"../../chunk-xw5b0251.js";
|
|
6
6
|
|
|
7
7
|
// src/middleware/transpiler/index.ts
|
|
8
|
-
import { existsSync, mkdirSync } from "fs";
|
|
8
|
+
import { existsSync, mkdirSync, statSync } from "fs";
|
|
9
9
|
import path from "path";
|
|
10
10
|
var transpiler = (options) => {
|
|
11
11
|
const {
|
|
12
12
|
sourcePath,
|
|
13
13
|
staticBasePath,
|
|
14
14
|
vendorPath = "vendor",
|
|
15
|
-
prewarm = []
|
|
15
|
+
prewarm = [],
|
|
16
|
+
minify = false,
|
|
17
|
+
localMaxAge = 0,
|
|
18
|
+
define = {},
|
|
19
|
+
globals = {},
|
|
20
|
+
cdnFallback = false,
|
|
21
|
+
plugins = []
|
|
16
22
|
} = options;
|
|
17
23
|
const cacheDir = path.resolve(process.cwd(), "node_modules", ".transpiler");
|
|
18
24
|
const basePrefix = staticBasePath.endsWith("/") ? staticBasePath : `${staticBasePath}/`;
|
|
@@ -20,52 +26,109 @@ var transpiler = (options) => {
|
|
|
20
26
|
if (!existsSync(cacheDir)) {
|
|
21
27
|
mkdirSync(cacheDir, { recursive: true });
|
|
22
28
|
}
|
|
29
|
+
const publicEnv = {};
|
|
30
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
31
|
+
if (key.startsWith("PUBLIC_")) {
|
|
32
|
+
publicEnv[`process.env.${key}`] = JSON.stringify(value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const defaultDefine = {
|
|
36
|
+
"process.env.NODE_ENV": JSON.stringify("development"),
|
|
37
|
+
...publicEnv,
|
|
38
|
+
...define
|
|
39
|
+
};
|
|
40
|
+
const activeBundles = new Map;
|
|
41
|
+
function resolveModulePath(moduleName) {
|
|
42
|
+
if (globals[moduleName])
|
|
43
|
+
return moduleName;
|
|
44
|
+
try {
|
|
45
|
+
Bun.resolveSync(moduleName, process.cwd());
|
|
46
|
+
return `${basePrefix}${vendorPath.replace(/^\/|\/$/g, "")}/${moduleName}`;
|
|
47
|
+
} catch {
|
|
48
|
+
if (cdnFallback) {
|
|
49
|
+
return `https://esm.sh/${moduleName}`;
|
|
50
|
+
}
|
|
51
|
+
return `${basePrefix}${vendorPath.replace(/^\/|\/$/g, "")}/${moduleName}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
23
54
|
async function bundleVendorModule(moduleName) {
|
|
24
55
|
const cacheKey = moduleName.replace(/\//g, "__");
|
|
25
56
|
const cachedFile = path.join(cacheDir, `${cacheKey}.js`);
|
|
26
57
|
if (existsSync(cachedFile))
|
|
27
58
|
return;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
59
|
+
if (activeBundles.has(cacheKey)) {
|
|
60
|
+
return activeBundles.get(cacheKey);
|
|
61
|
+
}
|
|
62
|
+
const promise = (async () => {
|
|
63
|
+
if (existsSync(cachedFile))
|
|
64
|
+
return;
|
|
65
|
+
try {
|
|
66
|
+
const entryPoint = Bun.resolveSync(moduleName, process.cwd());
|
|
67
|
+
const globalsPlugin = {
|
|
68
|
+
name: "abret-globals",
|
|
69
|
+
setup(build) {
|
|
70
|
+
for (const moduleName2 of Object.keys(globals)) {
|
|
71
|
+
build.onResolve({ filter: new RegExp(`^${moduleName2}$`) }, () => ({
|
|
72
|
+
path: moduleName2,
|
|
73
|
+
namespace: "abret-globals"
|
|
74
|
+
}));
|
|
44
75
|
}
|
|
76
|
+
build.onLoad({ filter: /.*/, namespace: "abret-globals" }, (args) => {
|
|
77
|
+
const gName = globals[args.path];
|
|
78
|
+
return {
|
|
79
|
+
contents: `export default globalThis.${gName}; export const ${gName} = globalThis.${gName};`,
|
|
80
|
+
loader: "js"
|
|
81
|
+
};
|
|
82
|
+
});
|
|
45
83
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
};
|
|
85
|
+
const result = await Bun.build({
|
|
86
|
+
entrypoints: [entryPoint],
|
|
87
|
+
target: "browser",
|
|
88
|
+
format: "esm",
|
|
89
|
+
minify: true,
|
|
90
|
+
define: defaultDefine,
|
|
91
|
+
plugins: [
|
|
92
|
+
globalsPlugin,
|
|
93
|
+
{
|
|
94
|
+
name: "abret-external-vendor",
|
|
95
|
+
setup(build) {
|
|
96
|
+
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
97
|
+
if (args.path === moduleName || globals[args.path])
|
|
98
|
+
return null;
|
|
99
|
+
return { path: args.path, external: true };
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
...plugins
|
|
104
|
+
]
|
|
105
|
+
});
|
|
106
|
+
if (!result.success || result.outputs.length === 0) {
|
|
107
|
+
console.error(`[Abret] Failed to bundle vendor module: ${moduleName}`, result.logs);
|
|
108
|
+
return;
|
|
61
109
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
110
|
+
const output = result.outputs[0];
|
|
111
|
+
if (!output)
|
|
112
|
+
return;
|
|
113
|
+
const rawContent = await output.text();
|
|
114
|
+
const content = rawContent.replace(/((?:import|export)\s*[\s\S]*?from\s*['"]|import\s*\(['"])([^'"]+)(['"]\)?)/g, (match, prefix, path2, suffix) => {
|
|
115
|
+
if (/^(https?:|(?:\/\/))/.test(path2))
|
|
116
|
+
return match;
|
|
117
|
+
if (!path2.startsWith(".") && !path2.startsWith("/")) {
|
|
118
|
+
return `${prefix}${resolveModulePath(path2)}${suffix}`;
|
|
119
|
+
}
|
|
120
|
+
return match;
|
|
121
|
+
});
|
|
122
|
+
await Bun.write(cachedFile, content);
|
|
123
|
+
console.log(`[Abret] Pre-bundled: ${moduleName}`);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(`[Abret] Error bundling ${moduleName}:`, err);
|
|
126
|
+
} finally {
|
|
127
|
+
activeBundles.delete(cacheKey);
|
|
128
|
+
}
|
|
129
|
+
})();
|
|
130
|
+
activeBundles.set(cacheKey, promise);
|
|
131
|
+
return promise;
|
|
69
132
|
}
|
|
70
133
|
if (prewarm.length > 0) {
|
|
71
134
|
for (const moduleName of prewarm) {
|
|
@@ -102,51 +165,129 @@ var transpiler = (options) => {
|
|
|
102
165
|
return next();
|
|
103
166
|
}
|
|
104
167
|
const internalPath = pathname.slice(basePrefix.length);
|
|
105
|
-
const
|
|
106
|
-
const possibleExtensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
168
|
+
const extname = path.extname(internalPath);
|
|
107
169
|
let sourceFile = "";
|
|
108
|
-
|
|
109
|
-
|
|
170
|
+
let contentType = "application/javascript";
|
|
171
|
+
if (extname === ".css") {
|
|
172
|
+
const p = path.join(path.resolve(sourcePath), internalPath);
|
|
110
173
|
if (existsSync(p)) {
|
|
111
174
|
sourceFile = p;
|
|
112
|
-
|
|
175
|
+
contentType = "text/css";
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const baseFileName = internalPath.endsWith(".js") ? internalPath.slice(0, -3) : internalPath;
|
|
179
|
+
const possibleExtensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
180
|
+
for (const ext of possibleExtensions) {
|
|
181
|
+
const p = path.join(path.resolve(sourcePath), (baseFileName.startsWith("/") ? baseFileName.slice(1) : baseFileName) + ext);
|
|
182
|
+
if (existsSync(p)) {
|
|
183
|
+
sourceFile = p;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
113
186
|
}
|
|
114
187
|
}
|
|
115
188
|
if (sourceFile) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
189
|
+
const sourceStat = statSync(sourceFile);
|
|
190
|
+
const fastEtag = `W/"${sourceStat.size}-${sourceStat.mtimeMs}-${minify}"`;
|
|
191
|
+
if (req.headers.get("if-none-match") === fastEtag) {
|
|
192
|
+
return new Response(null, { status: 304 });
|
|
193
|
+
}
|
|
194
|
+
const lockKey = `local:${sourceFile}:${fastEtag}`;
|
|
195
|
+
if (activeBundles.has(lockKey)) {
|
|
196
|
+
const result = await activeBundles.get(lockKey);
|
|
197
|
+
return new Response(result.content, {
|
|
198
|
+
headers: {
|
|
199
|
+
"Content-Type": result.contentType,
|
|
200
|
+
ETag: fastEtag,
|
|
201
|
+
"Cache-Control": localMaxAge > 0 ? `public, max-age=${localMaxAge}` : "no-cache"
|
|
202
|
+
}
|
|
122
203
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
204
|
+
}
|
|
205
|
+
const buildPromise = (async () => {
|
|
206
|
+
try {
|
|
207
|
+
const buildResult = await Bun.build({
|
|
208
|
+
entrypoints: [sourceFile],
|
|
209
|
+
target: "browser",
|
|
210
|
+
format: "esm",
|
|
211
|
+
minify,
|
|
212
|
+
define: defaultDefine,
|
|
213
|
+
external: ["*"],
|
|
214
|
+
plugins: [
|
|
215
|
+
{
|
|
216
|
+
name: "abret-globals-local",
|
|
217
|
+
setup(build) {
|
|
218
|
+
for (const moduleName of Object.keys(globals)) {
|
|
219
|
+
build.onResolve({ filter: new RegExp(`^${moduleName}$`) }, () => ({
|
|
220
|
+
path: moduleName,
|
|
221
|
+
namespace: "abret-globals"
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
build.onLoad({ filter: /.*/, namespace: "abret-globals" }, (args) => {
|
|
225
|
+
const gName = globals[args.path];
|
|
226
|
+
return {
|
|
227
|
+
contents: `export default globalThis.${gName};`,
|
|
228
|
+
loader: "js"
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
...plugins
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
if (!buildResult.success || buildResult.outputs.length === 0) {
|
|
237
|
+
throw new Error(`Build failed: ${buildResult.logs.map((l) => l.message).join(", ")}`);
|
|
138
238
|
}
|
|
139
|
-
|
|
140
|
-
|
|
239
|
+
const output = buildResult.outputs[0];
|
|
240
|
+
if (!output)
|
|
241
|
+
throw new Error("No output generated");
|
|
242
|
+
const transpiledCode = await output.text();
|
|
243
|
+
if (contentType === "text/css") {
|
|
244
|
+
return { content: transpiledCode, contentType };
|
|
245
|
+
}
|
|
246
|
+
const finalCode = transpiledCode.replace(/((?:import|export)\s*[\s\S]*?from\s*['"]|import\s*\(['"])([^'"]+)(['"]\)?)/g, (match, prefix, path2, suffix) => {
|
|
247
|
+
if (/^(https?:|(?:\/\/))/.test(path2))
|
|
248
|
+
return match;
|
|
249
|
+
if (!path2.startsWith(".") && !path2.startsWith("/")) {
|
|
250
|
+
return `${prefix}${resolveModulePath(path2)}${suffix}`;
|
|
251
|
+
}
|
|
252
|
+
if (path2.startsWith(".") && !path2.split("/").pop()?.includes(".")) {
|
|
253
|
+
return `${prefix}${path2}.js${suffix}`;
|
|
254
|
+
}
|
|
255
|
+
return match;
|
|
256
|
+
});
|
|
257
|
+
return { content: finalCode, contentType };
|
|
258
|
+
} finally {
|
|
259
|
+
activeBundles.delete(lockKey);
|
|
260
|
+
}
|
|
261
|
+
})();
|
|
262
|
+
activeBundles.set(lockKey, buildPromise);
|
|
263
|
+
try {
|
|
264
|
+
const finalResult = await buildPromise;
|
|
265
|
+
return new Response(finalResult.content, {
|
|
266
|
+
headers: {
|
|
267
|
+
"Content-Type": finalResult.contentType,
|
|
268
|
+
ETag: fastEtag,
|
|
269
|
+
"Cache-Control": localMaxAge > 0 ? `public, max-age=${localMaxAge}` : "no-cache"
|
|
141
270
|
}
|
|
142
|
-
return match;
|
|
143
|
-
});
|
|
144
|
-
return new Response(finalCode, {
|
|
145
|
-
headers: { "Content-Type": "application/javascript" }
|
|
146
271
|
});
|
|
147
272
|
} catch (err) {
|
|
148
|
-
|
|
149
|
-
|
|
273
|
+
const errorMessage = err.message || "Unknown transpilation error";
|
|
274
|
+
console.error(`[Abret] ${errorMessage} for ${sourceFile}`);
|
|
275
|
+
return new Response(`console.error("[Abret] Build Error in ${sourceFile}:", ${JSON.stringify(errorMessage)});
|
|
276
|
+
if (typeof document !== 'undefined') {
|
|
277
|
+
const div = document.createElement('div');
|
|
278
|
+
div.style.position = 'fixed';
|
|
279
|
+
div.style.top = '0';
|
|
280
|
+
div.style.left = '0';
|
|
281
|
+
div.style.width = '100%';
|
|
282
|
+
div.style.padding = '1rem';
|
|
283
|
+
div.style.background = '#fee2e2';
|
|
284
|
+
div.style.color = '#991b1b';
|
|
285
|
+
div.style.borderBottom = '1px solid #ef4444';
|
|
286
|
+
div.style.zIndex = '999999';
|
|
287
|
+
div.style.fontFamily = 'monospace';
|
|
288
|
+
div.innerText = "[Abret] Build Error in ${sourceFile.split("/").pop()}: " + ${JSON.stringify(errorMessage)};
|
|
289
|
+
document.body.appendChild(div);
|
|
290
|
+
}`, { headers: { "Content-Type": "application/javascript" } });
|
|
150
291
|
}
|
|
151
292
|
}
|
|
152
293
|
return next();
|