bimba-cli 0.7.9 → 0.7.11
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/README.md +3 -3
- package/index.js +28 -16
- package/package.json +2 -2
- package/serve.js +188 -168
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ For a deep dive into how Imba compiles tags, how the render cache works, and how
|
|
|
74
74
|
|
|
75
75
|
Static files are resolved relative to the HTML file's directory first, then from the project root (for `node_modules`, `src`, etc.). Extensionless imports (common in `node_modules`) are resolved by trying `.js` and `.mjs` extensions automatically.
|
|
76
76
|
|
|
77
|
-
**npm package resolution:** The
|
|
77
|
+
**npm package resolution:** The dev server maps package imports to `__bimba_vendor__/*` URLs and bundles those modules on demand with Bun (`target: "browser"`). Imba source files still compile separately for HMR, while Bun owns dependency resolution, `exports`, `browser` fields, nested `node_modules`, and CommonJS interop.
|
|
78
78
|
|
|
79
79
|
---
|
|
80
80
|
|
|
@@ -82,7 +82,7 @@ Static files are resolved relative to the HTML file's directory first, then from
|
|
|
82
82
|
|
|
83
83
|
To compile and bundle your source code from .imba to .js:
|
|
84
84
|
```bash
|
|
85
|
-
bunx bimba src/index.imba --outdir public/js
|
|
85
|
+
bunx bimba src/index.imba --outdir public/js
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
With watch:
|
|
@@ -100,7 +100,7 @@ bunx bimba src/index.imba --outdir public/js --watch --clearcache
|
|
|
100
100
|
|
|
101
101
|
`--clearcache` — delete the cache directory on exit (Ctrl+C). Works only in watch mode.
|
|
102
102
|
|
|
103
|
-
`--minify` —
|
|
103
|
+
`--no-minify` — disable minification. Bundle mode minifies by default.
|
|
104
104
|
|
|
105
105
|
`--sourcemap <inline|external|none>` — how to include source maps in the output (default: `none`).
|
|
106
106
|
|
package/index.js
CHANGED
|
@@ -14,7 +14,7 @@ let entrypoint = ''
|
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
16
|
const { values, positionals } = parseArgs({
|
|
17
|
-
args: Bun.argv,
|
|
17
|
+
args: Bun.argv.slice(2),
|
|
18
18
|
options: {
|
|
19
19
|
watch: { type: 'boolean' },
|
|
20
20
|
outdir: { type: 'string' },
|
|
@@ -29,11 +29,12 @@ try {
|
|
|
29
29
|
port: { type: 'string' },
|
|
30
30
|
html: { type: 'string' },
|
|
31
31
|
},
|
|
32
|
+
allowNegative: true,
|
|
32
33
|
strict: true,
|
|
33
34
|
allowPositionals: true,
|
|
34
35
|
});
|
|
35
36
|
flags = values;
|
|
36
|
-
entrypoint =
|
|
37
|
+
entrypoint = positionals[0] || '';
|
|
37
38
|
}
|
|
38
39
|
catch (error) {
|
|
39
40
|
if (error instanceof Error)
|
|
@@ -43,16 +44,21 @@ catch (error) {
|
|
|
43
44
|
process.exit(0);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
const bunfigPath = path.join(process.cwd(), 'bunfig.toml');
|
|
48
|
-
const preloadLine = 'preload = ["bimba-cli/plugin.js"]';
|
|
49
|
-
|
|
50
|
-
fs.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
fs.appendFileSync(bunfigPath, (content.endsWith('\n') ? '' : '\n') + preloadLine + '\n');
|
|
47
|
+
function ensureBunfigPreload() {
|
|
48
|
+
const bunfigPath = path.join(process.cwd(), 'bunfig.toml');
|
|
49
|
+
const preloadLine = 'preload = ["bimba-cli/plugin.js"]';
|
|
50
|
+
|
|
51
|
+
if (!fs.existsSync(bunfigPath)) {
|
|
52
|
+
console.log(theme.action("note: ") + theme.filename("bunfig.toml was not found, so bimba left it unchanged."));
|
|
53
|
+
console.log(theme.action(" ") + `Add ${theme.flags(preloadLine)} manually if you want Bun to preload the plugin.`);
|
|
54
|
+
return;
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
const content = fs.readFileSync(bunfigPath, 'utf8');
|
|
58
|
+
if (content.includes(preloadLine)) return;
|
|
59
|
+
|
|
60
|
+
console.log(theme.action("note: ") + theme.filename("bunfig.toml already exists, so bimba did not edit it automatically."));
|
|
61
|
+
console.log(theme.action(" ") + `Add ${theme.flags(preloadLine)} manually if you want Bun to preload the plugin.`);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
// help: more on bun building params here: https://bun.sh/docs/bundler
|
|
@@ -62,7 +68,7 @@ if(flags.help) {
|
|
|
62
68
|
console.log("For example like this: "+theme.filedir('bimba file.imba --outdir public'));
|
|
63
69
|
console.log("");
|
|
64
70
|
console.log(" "+theme.flags('--outdir <folder>')+" Compile imba files to the specified folder");
|
|
65
|
-
console.log(" "+theme.flags('--minify')+"
|
|
71
|
+
console.log(" "+theme.flags('--no-minify')+" Disable minification for compiled .js files");
|
|
66
72
|
console.log(" "+theme.flags('--sourcemap <inline|external|none>')+" How should sourcemap files be included in the .js");
|
|
67
73
|
console.log(" "+theme.flags('--target <browser|node>')+" Target platform for both Imba compiler and Bun bundler");
|
|
68
74
|
console.log(" "+theme.flags('--external <package>')+" Exclude package from bundle (repeatable, e.g. --external ws --external node-pty)");
|
|
@@ -88,6 +94,7 @@ if (flags.serve) {
|
|
|
88
94
|
console.log("");
|
|
89
95
|
process.exit(1);
|
|
90
96
|
}
|
|
97
|
+
ensureBunfigPreload();
|
|
91
98
|
serve(entrypoint, { port: parseInt(flags.port) || 5200, html: flags.html });
|
|
92
99
|
}
|
|
93
100
|
// no entrypoint or outdir
|
|
@@ -100,7 +107,9 @@ else if(!entrypoint || !flags.outdir) {
|
|
|
100
107
|
}
|
|
101
108
|
// build
|
|
102
109
|
else {
|
|
103
|
-
|
|
110
|
+
ensureBunfigPreload();
|
|
111
|
+
const success = await bundle();
|
|
112
|
+
if (!success && !flags.watch) process.exit(1);
|
|
104
113
|
watch(bundle);
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -126,7 +135,7 @@ async function bundle() {
|
|
|
126
135
|
|
|
127
136
|
if (!fs.existsSync(entrypoint)) {
|
|
128
137
|
console.log(theme.failure('Error.') + ` The specified entrypoint does not exist: ${theme.filedir(entrypoint)}`);
|
|
129
|
-
|
|
138
|
+
return false;
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
stats.failed = 0
|
|
@@ -150,7 +159,7 @@ async function bundle() {
|
|
|
150
159
|
outdir: flags.outdir,
|
|
151
160
|
target: buildTarget,
|
|
152
161
|
sourcemap: flags.sourcemap || 'none',
|
|
153
|
-
minify: true,
|
|
162
|
+
minify: flags.minify ?? true,
|
|
154
163
|
splitting: flags.splitting || false,
|
|
155
164
|
plugins: [imbaPlugin]
|
|
156
165
|
};
|
|
@@ -182,6 +191,8 @@ async function bundle() {
|
|
|
182
191
|
console.log(log);
|
|
183
192
|
}
|
|
184
193
|
}
|
|
194
|
+
|
|
195
|
+
return result.success && !stats.failed && !stats.errors;
|
|
185
196
|
}
|
|
186
197
|
catch(error) {
|
|
187
198
|
console.log(theme.folder("──────────────────────────────────────────────────────────────────────"));
|
|
@@ -189,8 +200,9 @@ async function bundle() {
|
|
|
189
200
|
console.log(error)
|
|
190
201
|
console.log(theme.folder("──────────────────────────────────────────────────────────────────────"));
|
|
191
202
|
console.log(theme.failure(" Failure ") + theme.filename(' Bun found an error in the compiled JS file'))
|
|
203
|
+
return false;
|
|
192
204
|
}
|
|
193
205
|
finally {
|
|
194
206
|
bundling = false;
|
|
195
207
|
};
|
|
196
|
-
}
|
|
208
|
+
}
|
package/package.json
CHANGED
package/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { serve as bunServe } from 'bun'
|
|
2
2
|
import * as compiler from 'imba/compiler'
|
|
3
|
-
import { watch, existsSync } from 'fs'
|
|
3
|
+
import { watch, existsSync, statSync } from 'fs'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { theme } from './utils.js'
|
|
6
6
|
import { printerr } from './plugin.js'
|
|
@@ -256,57 +256,6 @@ const _compileCache = new Map() // filepath → { mtime, result }
|
|
|
256
256
|
const _prevJs = new Map() // filepath → compiled js — for change detection
|
|
257
257
|
const _prevSlots = new Map() // filepath → previous symbol slot count
|
|
258
258
|
|
|
259
|
-
// ─── Import dependency graph ──────────────────────────────────────────────────
|
|
260
|
-
//
|
|
261
|
-
// When a non-tag module (utility functions, constants, shared state) is edited,
|
|
262
|
-
// the existing class-prototype patching does nothing for the modules that
|
|
263
|
-
// imported it — they hold their own captured references. To make those
|
|
264
|
-
// updates flow into the UI, we track who imports whom and, on every change,
|
|
265
|
-
// re-broadcast updates for the transitive importer set. The client's existing
|
|
266
|
-
// HMR queue then re-imports each in turn; their top-level code reruns, picks
|
|
267
|
-
// up the new symbols, and any tag re-registrations patch instances in place.
|
|
268
|
-
//
|
|
269
|
-
// Keys are absolute, normalized paths (path.resolve). Edges are added during
|
|
270
|
-
// compilation by scanning the produced JS for relative .imba imports.
|
|
271
|
-
|
|
272
|
-
const _imports = new Map() // absFile → Set<absFile> (what it imports)
|
|
273
|
-
const _importers = new Map() // absFile → Set<absFile> (who imports it)
|
|
274
|
-
|
|
275
|
-
function extractImports(js, fromAbs) {
|
|
276
|
-
const dir = path.dirname(fromAbs)
|
|
277
|
-
const out = new Set()
|
|
278
|
-
const re = /(?:^|[\s;])(?:import|from)\s*['"]([^'"]+)['"]/g
|
|
279
|
-
let m
|
|
280
|
-
while ((m = re.exec(js))) {
|
|
281
|
-
const spec = m[1]
|
|
282
|
-
if (!spec.startsWith('.') && !spec.startsWith('/')) continue
|
|
283
|
-
if (!spec.endsWith('.imba')) continue
|
|
284
|
-
const resolved = spec.startsWith('/')
|
|
285
|
-
? path.resolve('.' + spec)
|
|
286
|
-
: path.resolve(dir, spec)
|
|
287
|
-
out.add(resolved)
|
|
288
|
-
}
|
|
289
|
-
return out
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function updateImportGraph(fromAbs, newDeps) {
|
|
293
|
-
const old = _imports.get(fromAbs)
|
|
294
|
-
if (old) {
|
|
295
|
-
for (const d of old) {
|
|
296
|
-
if (newDeps.has(d)) continue
|
|
297
|
-
const set = _importers.get(d)
|
|
298
|
-
if (set) { set.delete(fromAbs); if (!set.size) _importers.delete(d) }
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
for (const d of newDeps) {
|
|
302
|
-
let set = _importers.get(d)
|
|
303
|
-
if (!set) { set = new Set(); _importers.set(d, set) }
|
|
304
|
-
set.add(fromAbs)
|
|
305
|
-
}
|
|
306
|
-
_imports.set(fromAbs, newDeps)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
259
|
// Imba compiles tag render-cache slots as anonymous local Symbols at module top
|
|
311
260
|
// level: `var $4 = Symbol(), $11 = Symbol(), ...; let c$0 = Symbol();`. Each
|
|
312
261
|
// re-import of the file creates fresh Symbol objects, so old slot data on live
|
|
@@ -373,7 +322,6 @@ async function compileFile(filepath) {
|
|
|
373
322
|
const prev = _prevSlots.get(abs)
|
|
374
323
|
result.slots = (prev === undefined || prev === slotCount) ? 'stable' : 'shifted'
|
|
375
324
|
_prevSlots.set(abs, slotCount)
|
|
376
|
-
updateImportGraph(abs, extractImports(js, abs))
|
|
377
325
|
}
|
|
378
326
|
|
|
379
327
|
// Bake errors as an own property so caching/spreading preserves them.
|
|
@@ -392,85 +340,181 @@ function findHtml(flagHtml) {
|
|
|
392
340
|
return candidates.find(p => existsSync(p)) || './index.html';
|
|
393
341
|
}
|
|
394
342
|
|
|
395
|
-
// ───
|
|
343
|
+
// ─── Vendor modules ──────────────────────────────────────────────────────────
|
|
396
344
|
|
|
397
|
-
const
|
|
345
|
+
const _vendorCache = new Map() // entrypoint → { mtime, code }
|
|
398
346
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
347
|
+
function vendorUrl(specifier) {
|
|
348
|
+
return '/__bimba_vendor__/' + specifier
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function vendorPrefixUrl(specifier) {
|
|
352
|
+
return vendorUrl(specifier) + '/'
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function resolveFileCandidate(filepath) {
|
|
356
|
+
const candidates = [
|
|
357
|
+
filepath,
|
|
358
|
+
filepath + '.js',
|
|
359
|
+
filepath + '.mjs',
|
|
360
|
+
filepath + '.cjs',
|
|
361
|
+
filepath + '.imba',
|
|
362
|
+
filepath + '.css',
|
|
363
|
+
path.join(filepath, 'index.js'),
|
|
364
|
+
path.join(filepath, 'index.mjs'),
|
|
365
|
+
path.join(filepath, 'index.cjs'),
|
|
366
|
+
path.join(filepath, 'index.imba'),
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
for (const candidate of candidates) {
|
|
370
|
+
if (!existsSync(candidate)) continue
|
|
371
|
+
try {
|
|
372
|
+
if (statSync(candidate).isFile()) return candidate
|
|
373
|
+
} catch (_) {
|
|
374
|
+
// ignore vanished files and continue resolving
|
|
375
|
+
}
|
|
408
376
|
}
|
|
377
|
+
|
|
378
|
+
return null
|
|
409
379
|
}
|
|
410
380
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (/^\s*(export\s|import\s)/m.test(code)) return null
|
|
417
|
-
return `var module = { exports: {} }, exports = module.exports;\n${code}\nexport default module.exports;\nexport { module };`
|
|
381
|
+
function vendorSpecifierFromPath(pathname) {
|
|
382
|
+
const prefix = '/__bimba_vendor__/'
|
|
383
|
+
if (!pathname.startsWith(prefix)) return null
|
|
384
|
+
const specifier = decodeURIComponent(pathname.slice(prefix.length))
|
|
385
|
+
return specifier || null
|
|
418
386
|
}
|
|
419
387
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
388
|
+
async function bundleVendor(entrypoint) {
|
|
389
|
+
try {
|
|
390
|
+
const stat = path.isAbsolute(entrypoint) && existsSync(entrypoint) ? statSync(entrypoint) : null
|
|
391
|
+
const mtime = stat?.mtimeMs || 0
|
|
392
|
+
const cached = _vendorCache.get(entrypoint)
|
|
393
|
+
if (cached && cached.mtime === mtime) return cached
|
|
394
|
+
|
|
395
|
+
const result = await Bun.build({
|
|
396
|
+
entrypoints: [entrypoint],
|
|
397
|
+
target: 'browser',
|
|
398
|
+
format: 'esm',
|
|
399
|
+
write: false,
|
|
400
|
+
minify: false,
|
|
401
|
+
sourcemap: 'none',
|
|
402
|
+
packages: 'bundle',
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
if (!result.success) {
|
|
406
|
+
return { mtime, errors: result.logs.map(log => String(log)) }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const output = result.outputs.find(output => output.path.endsWith('.js')) || result.outputs[0]
|
|
410
|
+
if (!output) return { mtime, errors: ['Bun.build did not return a JavaScript output'] }
|
|
411
|
+
|
|
412
|
+
let code = await output.text()
|
|
413
|
+
const css = (await Promise.all(
|
|
414
|
+
result.outputs
|
|
415
|
+
.filter(output => output.path.endsWith('.css'))
|
|
416
|
+
.map(output => output.text())
|
|
417
|
+
)).join('\n')
|
|
418
|
+
if (css) {
|
|
419
|
+
const id = JSON.stringify('vendor:' + entrypoint)
|
|
420
|
+
code = [
|
|
421
|
+
`const __bimba_vendor_css_id = ${id};`,
|
|
422
|
+
`let __bimba_vendor_css = document.querySelector('style[data-bimba-css=' + JSON.stringify(__bimba_vendor_css_id) + ']');`,
|
|
423
|
+
`if (!__bimba_vendor_css) { __bimba_vendor_css = document.createElement('style'); __bimba_vendor_css.setAttribute('data-bimba-css', __bimba_vendor_css_id); document.head.appendChild(__bimba_vendor_css); }`,
|
|
424
|
+
`__bimba_vendor_css.textContent = ${JSON.stringify(css)};`,
|
|
425
|
+
code,
|
|
426
|
+
].join('\n')
|
|
433
427
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
428
|
+
const bundled = { mtime, code }
|
|
429
|
+
_vendorCache.set(entrypoint, bundled)
|
|
430
|
+
return bundled
|
|
431
|
+
} catch (error) {
|
|
432
|
+
return { mtime: 0, errors: [error?.message || String(error)] }
|
|
437
433
|
}
|
|
438
|
-
// Fallback to legacy fields
|
|
439
|
-
return depPkg.module || depPkg.browser || depPkg.main;
|
|
440
434
|
}
|
|
441
435
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
// CJS→ESM, entry points, extensions) happens on the server side.
|
|
446
|
-
async function buildImportMap() {
|
|
447
|
-
const imports = {
|
|
448
|
-
'imba/runtime': 'https://esm.sh/imba/runtime',
|
|
449
|
-
'imba': 'https://esm.sh/imba',
|
|
450
|
-
};
|
|
436
|
+
async function buildGeneratedImportMap() {
|
|
437
|
+
const imports = {}
|
|
438
|
+
|
|
451
439
|
try {
|
|
452
|
-
const pkg = JSON.parse(await Bun.file('./package.json').text())
|
|
453
|
-
for (const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
440
|
+
const pkg = JSON.parse(await Bun.file('./package.json').text())
|
|
441
|
+
for (const field of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
442
|
+
for (const name of Object.keys(pkg[field] || {})) {
|
|
443
|
+
imports[name] ||= vendorUrl(name)
|
|
444
|
+
imports[name + '/'] ||= vendorPrefixUrl(name)
|
|
445
|
+
}
|
|
457
446
|
}
|
|
458
447
|
} catch(_) { /* no package.json */ }
|
|
459
448
|
|
|
460
|
-
|
|
449
|
+
imports['imba'] ||= vendorUrl('imba')
|
|
450
|
+
imports['imba/'] ||= vendorPrefixUrl('imba')
|
|
451
|
+
imports['imba/runtime'] ||= vendorUrl('imba/runtime')
|
|
452
|
+
|
|
453
|
+
return { imports }
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function extractUserImportMap(html) {
|
|
457
|
+
const merged = { imports: {}, scopes: {} }
|
|
458
|
+
|
|
459
|
+
html = html.replace(/<script\s+type=["']importmap["'][^>]*>([\s\S]*?)<\/script>/gi, (_match, json) => {
|
|
460
|
+
try {
|
|
461
|
+
const parsed = JSON.parse(json)
|
|
462
|
+
Object.assign(merged.imports, parsed.imports || {})
|
|
463
|
+
for (const [scope, specifiers] of Object.entries(parsed.scopes || {})) {
|
|
464
|
+
merged.scopes[scope] = { ...(merged.scopes[scope] || {}), ...specifiers }
|
|
465
|
+
}
|
|
466
|
+
} catch (_) {
|
|
467
|
+
// ignore invalid import maps and keep serving the page
|
|
468
|
+
}
|
|
469
|
+
return ''
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
if (!Object.keys(merged.scopes).length) delete merged.scopes
|
|
473
|
+
return { html, importMap: merged }
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function mergeImportMaps(generated, user) {
|
|
477
|
+
const merged = {
|
|
478
|
+
imports: { ...(generated.imports || {}), ...(user.imports || {}) },
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const scopeKeys = new Set([
|
|
482
|
+
...Object.keys(generated.scopes || {}),
|
|
483
|
+
...Object.keys(user.scopes || {}),
|
|
484
|
+
])
|
|
485
|
+
|
|
486
|
+
if (scopeKeys.size) {
|
|
487
|
+
merged.scopes = {}
|
|
488
|
+
for (const key of scopeKeys) {
|
|
489
|
+
merged.scopes[key] = {
|
|
490
|
+
...((generated.scopes || {})[key] || {}),
|
|
491
|
+
...((user.scopes || {})[key] || {}),
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return merged
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function renderImportMapTag(importMap) {
|
|
500
|
+
return `\t\t<script type="importmap">\n\t\t\t${JSON.stringify(importMap, null, '\t\t\t\t')}\n\t\t</script>`
|
|
461
501
|
}
|
|
462
502
|
|
|
463
503
|
// Rewrite production HTML for the dev server:
|
|
464
|
-
// strips existing importmap + data-entrypoint script, injects importmap +
|
|
504
|
+
// strips existing importmap + data-entrypoint script, injects merged importmap +
|
|
465
505
|
// entrypoint module + HMR client before </head>.
|
|
466
|
-
function transformHtml(html, entrypoint,
|
|
467
|
-
|
|
468
|
-
html = html
|
|
469
|
-
|
|
506
|
+
function transformHtml(html, entrypoint, generatedImportMap) {
|
|
507
|
+
const extracted = extractUserImportMap(html)
|
|
508
|
+
html = extracted.html
|
|
509
|
+
html = html.replace(/<script([^>]*)\bdata-entrypoint\b([^>]*)><\/script>/gi, '')
|
|
510
|
+
|
|
511
|
+
const entryUrl = '/' + entrypoint.replace(/^\.\//, '').replaceAll('\\', '/')
|
|
512
|
+
const importMapTag = renderImportMapTag(mergeImportMaps(generatedImportMap, extracted.importMap))
|
|
513
|
+
|
|
470
514
|
html = html.replace('</head>',
|
|
471
515
|
`${importMapTag}\n\t\t<script type='module' src='${entryUrl}'></script>\n${hmrClient}\n\t</head>`
|
|
472
|
-
)
|
|
473
|
-
return html
|
|
516
|
+
)
|
|
517
|
+
return html
|
|
474
518
|
}
|
|
475
519
|
|
|
476
520
|
// ─── Dev server ───────────────────────────────────────────────────────────────
|
|
@@ -481,7 +525,7 @@ export function serve(entrypoint, flags) {
|
|
|
481
525
|
const htmlDir = path.dirname(htmlPath)
|
|
482
526
|
const srcDir = path.dirname(entrypoint)
|
|
483
527
|
const sockets = new Set()
|
|
484
|
-
let
|
|
528
|
+
let generatedImportMap = null
|
|
485
529
|
|
|
486
530
|
// ── Status line (prints current compile result, fades out on success) ──────
|
|
487
531
|
|
|
@@ -601,12 +645,22 @@ export function serve(entrypoint, flags) {
|
|
|
601
645
|
if (server.upgrade(req)) return undefined
|
|
602
646
|
}
|
|
603
647
|
|
|
648
|
+
if (pathname.startsWith('/__bimba_vendor__/')) {
|
|
649
|
+
const specifier = vendorSpecifierFromPath(pathname)
|
|
650
|
+
const bundled = specifier ? await bundleVendor(specifier) : null
|
|
651
|
+
if (bundled?.code) {
|
|
652
|
+
return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return new Response((bundled?.errors || [`Could not bundle vendor module: ${specifier}`]).join('\n'), { status: 500 })
|
|
656
|
+
}
|
|
657
|
+
|
|
604
658
|
// HTML: index or any .html file
|
|
605
659
|
if (pathname === '/' || pathname.endsWith('.html')) {
|
|
606
660
|
const htmlFile = pathname === '/' ? htmlPath : '.' + pathname
|
|
607
661
|
let html = await Bun.file(htmlFile).text()
|
|
608
|
-
if (!
|
|
609
|
-
return new Response(transformHtml(html, entrypoint,
|
|
662
|
+
if (!generatedImportMap) generatedImportMap = await buildGeneratedImportMap()
|
|
663
|
+
return new Response(transformHtml(html, entrypoint, generatedImportMap), {
|
|
610
664
|
headers: { 'Content-Type': 'text/html' },
|
|
611
665
|
})
|
|
612
666
|
}
|
|
@@ -653,57 +707,23 @@ export function serve(entrypoint, flags) {
|
|
|
653
707
|
}
|
|
654
708
|
}
|
|
655
709
|
|
|
656
|
-
// node_modules
|
|
657
|
-
//
|
|
658
|
-
//
|
|
710
|
+
// Direct node_modules URLs (from user import maps or explicit imports)
|
|
711
|
+
// are bundled through Bun too, so browser/cjs/exports handling stays
|
|
712
|
+
// in one place.
|
|
659
713
|
if (pathname.startsWith('/node_modules/')) {
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const f = Bun.file(filePath)
|
|
666
|
-
if (!(await f.exists())) return null
|
|
667
|
-
const out = await compileFile(filePath)
|
|
668
|
-
if (out.errors?.length) return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
669
|
-
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
670
|
-
}
|
|
671
|
-
const f = Bun.file(filePath)
|
|
672
|
-
if (!(await f.exists())) return null
|
|
673
|
-
const code = await f.text()
|
|
674
|
-
const wrapped = wrapCJS(code)
|
|
675
|
-
return new Response(wrapped || code, { headers: { 'Content-Type': 'application/javascript' } })
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Resolve entry point for root package requests (/node_modules/pkg/ or /node_modules/pkg)
|
|
679
|
-
const parts = pathname.slice(1).split('/') // ['node_modules', 'pkg', ...]
|
|
680
|
-
const isScoped = parts[1]?.startsWith('@')
|
|
681
|
-
const pkgParts = isScoped ? 3 : 2 // node_modules/@scope/pkg or node_modules/pkg
|
|
682
|
-
const subParts = parts.slice(pkgParts)
|
|
683
|
-
const isRootRequest = subParts.length === 0 || (subParts.length === 1 && subParts[0] === '')
|
|
684
|
-
|
|
685
|
-
if (isRootRequest) {
|
|
686
|
-
const pkgDir = './' + parts.slice(0, pkgParts).join('/')
|
|
687
|
-
const depPkg = await readPkgJson(pkgDir)
|
|
688
|
-
if (depPkg) {
|
|
689
|
-
const entry = resolveEntry(depPkg)
|
|
690
|
-
if (entry) {
|
|
691
|
-
const resp = await serveResolved(path.join(pkgDir, entry))
|
|
692
|
-
if (resp) return resp
|
|
693
|
-
}
|
|
694
|
-
}
|
|
714
|
+
const resolved = resolveFileCandidate('.' + pathname)
|
|
715
|
+
if (resolved?.endsWith('.imba')) {
|
|
716
|
+
const out = await compileFile(resolved)
|
|
717
|
+
if (out.errors?.length) return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
718
|
+
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
695
719
|
}
|
|
696
720
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
// Extensionless: try .imba, .js, .mjs
|
|
702
|
-
if (!filepath.includes('.', filepath.lastIndexOf('/') + 1)) {
|
|
703
|
-
for (const ext of ['.imba', '.js', '.mjs']) {
|
|
704
|
-
const resp = await serveResolved(filepath + ext)
|
|
705
|
-
if (resp) return resp
|
|
721
|
+
if (resolved) {
|
|
722
|
+
const bundled = await bundleVendor(path.resolve(resolved))
|
|
723
|
+
if (bundled?.code) {
|
|
724
|
+
return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
|
|
706
725
|
}
|
|
726
|
+
return new Response((bundled?.errors || [`Could not bundle ${pathname}`]).join('\n'), { status: 500 })
|
|
707
727
|
}
|
|
708
728
|
}
|
|
709
729
|
|
|
@@ -731,13 +751,13 @@ export function serve(entrypoint, flags) {
|
|
|
731
751
|
}
|
|
732
752
|
|
|
733
753
|
// SPA fallback for extension-less paths
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
754
|
+
if (!lastSegment.includes('.')) {
|
|
755
|
+
let html = await Bun.file(htmlPath).text()
|
|
756
|
+
if (!generatedImportMap) generatedImportMap = await buildGeneratedImportMap()
|
|
757
|
+
return new Response(transformHtml(html, entrypoint, generatedImportMap), {
|
|
758
|
+
headers: { 'Content-Type': 'text/html' },
|
|
759
|
+
})
|
|
760
|
+
}
|
|
741
761
|
|
|
742
762
|
return new Response('Not Found', { status: 404 })
|
|
743
763
|
},
|