bimba-cli 0.7.2 → 0.7.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.
- package/package.json +1 -1
- package/serve.js +42 -94
package/package.json
CHANGED
package/serve.js
CHANGED
|
@@ -409,16 +409,6 @@ function findHtml(flagHtml) {
|
|
|
409
409
|
}
|
|
410
410
|
|
|
411
411
|
// ─── Node modules resolution ─────────────────────────────────────────────────
|
|
412
|
-
//
|
|
413
|
-
// Packages with conditional exports (e.g. highlight.js) map subpaths like
|
|
414
|
-
// "./lib/languages/*" to different files for `require` vs `import`. The browser
|
|
415
|
-
// import map uses a simple trailing-slash prefix mapping, so
|
|
416
|
-
// `highlight.js/lib/languages/javascript` → `/node_modules/highlight.js/lib/languages/javascript`.
|
|
417
|
-
// But that's the CJS file — the browser needs the ESM one under `es/`.
|
|
418
|
-
//
|
|
419
|
-
// We solve this at the HTTP level: when serving a JS file from node_modules,
|
|
420
|
-
// check if the file is CJS and the package has an ESM alternative via its
|
|
421
|
-
// `exports` field. If so, rewrite to the ESM path.
|
|
422
412
|
|
|
423
413
|
const _pkgJsonCache = new Map() // pkg root dir → parsed package.json
|
|
424
414
|
|
|
@@ -434,71 +424,13 @@ async function readPkgJson(pkgDir) {
|
|
|
434
424
|
}
|
|
435
425
|
}
|
|
436
426
|
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (nmIdx === -1) return null
|
|
445
|
-
|
|
446
|
-
const pkgName = parts[nmIdx + 1].startsWith('@')
|
|
447
|
-
? parts[nmIdx + 1] + '/' + parts[nmIdx + 2]
|
|
448
|
-
: parts[nmIdx + 1]
|
|
449
|
-
const pkgDir = parts.slice(0, nmIdx + 1 + (pkgName.includes('/') ? 2 : 1)).join('/')
|
|
450
|
-
const depPkg = await readPkgJson(pkgDir)
|
|
451
|
-
if (!depPkg?.exports || typeof depPkg.exports === 'string') return null
|
|
452
|
-
|
|
453
|
-
// Build the subpath relative to pkg root: "./lib/languages/javascript"
|
|
454
|
-
const pkgParts = pkgName.includes('/') ? 2 : 1
|
|
455
|
-
const subParts = parts.slice(nmIdx + 1 + pkgParts)
|
|
456
|
-
const subpath = './' + subParts.join('/')
|
|
457
|
-
|
|
458
|
-
const exp = depPkg.exports
|
|
459
|
-
|
|
460
|
-
// Try exact match first: exports["./lib/core"]
|
|
461
|
-
if (exp[subpath]) {
|
|
462
|
-
const target = exp[subpath]
|
|
463
|
-
const esm = typeof target === 'string' ? target : (target?.import || target?.default)
|
|
464
|
-
if (esm) {
|
|
465
|
-
const resolved = path.join(pkgDir, esm)
|
|
466
|
-
if (existsSync(resolved)) return resolved
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Try with .js extension: exports["./lib/core"] for request "./lib/core"
|
|
471
|
-
const subpathJs = subpath + '.js'
|
|
472
|
-
if (exp[subpathJs]) {
|
|
473
|
-
const target = exp[subpathJs]
|
|
474
|
-
const esm = typeof target === 'string' ? target : (target?.import || target?.default)
|
|
475
|
-
if (esm) {
|
|
476
|
-
const resolved = path.join(pkgDir, esm)
|
|
477
|
-
if (existsSync(resolved)) return resolved
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Try wildcard match: exports["./lib/languages/*"]
|
|
482
|
-
for (const [pattern, target] of Object.entries(exp)) {
|
|
483
|
-
if (!pattern.includes('*')) continue
|
|
484
|
-
const prefix = pattern.slice(0, pattern.indexOf('*'))
|
|
485
|
-
const suffix = pattern.slice(pattern.indexOf('*') + 1)
|
|
486
|
-
|
|
487
|
-
// Check both with and without .js extension
|
|
488
|
-
for (const sp of [subpath, subpathJs]) {
|
|
489
|
-
if (!sp.startsWith(prefix)) continue
|
|
490
|
-
if (suffix && !sp.endsWith(suffix)) continue
|
|
491
|
-
const stem = sp.slice(prefix.length, suffix ? -suffix.length || undefined : undefined)
|
|
492
|
-
|
|
493
|
-
const esm = typeof target === 'string' ? target : (target?.import || target?.default)
|
|
494
|
-
if (!esm || !esm.includes('*')) continue
|
|
495
|
-
|
|
496
|
-
const resolved = path.join(pkgDir, esm.replace('*', stem))
|
|
497
|
-
if (existsSync(resolved)) return resolved
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
return null
|
|
427
|
+
// Wrap a CommonJS file as an ESM module so the browser can import it.
|
|
428
|
+
// Detects CJS by checking for `module.exports` or top-level `exports.` usage.
|
|
429
|
+
function wrapCJS(code) {
|
|
430
|
+
if (!code.includes('module.exports') && !code.includes('exports.')) return null
|
|
431
|
+
// Already has ESM syntax — don't wrap (dual-format files)
|
|
432
|
+
if (/^\s*(export\s|import\s)/m.test(code)) return null
|
|
433
|
+
return `var module = { exports: {} }, exports = module.exports;\n${code}\nexport default module.exports;\nexport { module };`
|
|
502
434
|
}
|
|
503
435
|
|
|
504
436
|
// Resolve the ESM entry point for an npm package.
|
|
@@ -750,13 +682,26 @@ export function serve(entrypoint, flags) {
|
|
|
750
682
|
}
|
|
751
683
|
}
|
|
752
684
|
|
|
753
|
-
// node_modules:
|
|
685
|
+
// node_modules: entry point resolution, .imba compilation, CJS→ESM wrapping.
|
|
754
686
|
// The import map just maps bare specifiers to /node_modules/pkg/ URLs.
|
|
755
|
-
//
|
|
756
|
-
// (CJS→ESM), subpath resolution, extensionless paths.
|
|
687
|
+
// All smart resolution happens here at request time.
|
|
757
688
|
if (pathname.startsWith('/node_modules/')) {
|
|
758
689
|
const filepath = '.' + pathname
|
|
759
690
|
|
|
691
|
+
// Serve a resolved file: compile .imba, wrap CJS as ESM, pass ESM through
|
|
692
|
+
const serveResolved = async (filePath) => {
|
|
693
|
+
if (filePath.endsWith('.imba')) {
|
|
694
|
+
const out = await compileFile(filePath)
|
|
695
|
+
if (out.errors?.length) return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
696
|
+
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
697
|
+
}
|
|
698
|
+
const f = Bun.file(filePath)
|
|
699
|
+
if (!(await f.exists())) return null
|
|
700
|
+
const code = await f.text()
|
|
701
|
+
const wrapped = wrapCJS(code)
|
|
702
|
+
return new Response(wrapped || code, { headers: { 'Content-Type': 'application/javascript' } })
|
|
703
|
+
}
|
|
704
|
+
|
|
760
705
|
// Resolve entry point for root package requests (/node_modules/pkg/ or /node_modules/pkg)
|
|
761
706
|
const parts = pathname.slice(1).split('/') // ['node_modules', 'pkg', ...]
|
|
762
707
|
const isScoped = parts[1]?.startsWith('@')
|
|
@@ -765,28 +710,27 @@ export function serve(entrypoint, flags) {
|
|
|
765
710
|
const isRootRequest = subParts.length === 0 || (subParts.length === 1 && subParts[0] === '')
|
|
766
711
|
|
|
767
712
|
if (isRootRequest) {
|
|
768
|
-
// Bare import: resolve entry point from package.json
|
|
769
713
|
const pkgDir = './' + parts.slice(0, pkgParts).join('/')
|
|
770
714
|
const depPkg = await readPkgJson(pkgDir)
|
|
771
715
|
if (depPkg) {
|
|
772
716
|
const entry = resolveEntry(depPkg)
|
|
773
717
|
if (entry) {
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
if (await file.exists()) return new Response(file, {
|
|
777
|
-
headers: { 'Content-Type': 'application/javascript' },
|
|
778
|
-
})
|
|
718
|
+
const resp = await serveResolved(path.join(pkgDir, entry))
|
|
719
|
+
if (resp) return resp
|
|
779
720
|
}
|
|
780
721
|
}
|
|
781
722
|
}
|
|
782
723
|
|
|
783
|
-
// Subpath:
|
|
784
|
-
const
|
|
785
|
-
if (
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
724
|
+
// Subpath: try the exact path, then with extensions
|
|
725
|
+
const resp = await serveResolved(filepath)
|
|
726
|
+
if (resp) return resp
|
|
727
|
+
|
|
728
|
+
// Extensionless: try .imba, .js, .mjs
|
|
729
|
+
if (!filepath.includes('.', filepath.lastIndexOf('/') + 1)) {
|
|
730
|
+
for (const ext of ['.imba', '.js', '.mjs']) {
|
|
731
|
+
const resp = await serveResolved(filepath + ext)
|
|
732
|
+
if (resp) return resp
|
|
733
|
+
}
|
|
790
734
|
}
|
|
791
735
|
}
|
|
792
736
|
|
|
@@ -796,11 +740,15 @@ export function serve(entrypoint, flags) {
|
|
|
796
740
|
const inRoot = Bun.file('.' + pathname)
|
|
797
741
|
if (await inRoot.exists()) return new Response(inRoot)
|
|
798
742
|
|
|
799
|
-
// Try
|
|
743
|
+
// Try extensions for extensionless paths (e.g. node_modules imports)
|
|
800
744
|
const lastSegment = pathname.split('/').pop()
|
|
801
745
|
if (!lastSegment.includes('.')) {
|
|
802
|
-
//
|
|
803
|
-
|
|
746
|
+
// Try .imba first (compile on the fly), then .js/.mjs
|
|
747
|
+
const imbaPath = '.' + pathname + '.imba'
|
|
748
|
+
if (existsSync(imbaPath)) {
|
|
749
|
+
const out = await compileFile(imbaPath)
|
|
750
|
+
if (!out.errors?.length) return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
751
|
+
}
|
|
804
752
|
for (const ext of ['.js', '.mjs']) {
|
|
805
753
|
const withExt = Bun.file('.' + pathname + ext)
|
|
806
754
|
if (await withExt.exists()) return new Response(withExt, {
|