bimba-cli 0.7.3 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/serve.js +24 -86
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
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
- // Given a request path inside node_modules (e.g. "node_modules/highlight.js/lib/languages/javascript"),
438
- // try to resolve via conditional exports to find the ESM version.
439
- // Returns the rewritten filesystem path, or null if no rewrite needed.
440
- async function resolveNodeModuleESM(filepath) {
441
- // Only rewrite JS files (or extensionless .js)
442
- const parts = filepath.split('/')
443
- const nmIdx = parts.indexOf('node_modules')
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,14 +682,13 @@ export function serve(entrypoint, flags) {
750
682
  }
751
683
  }
752
684
 
753
- // node_modules: all smart resolution happens here.
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
- // This block handles: entry point resolution, conditional exports
756
- // (CJS→ESM), subpath resolution, .imba compilation.
687
+ // All smart resolution happens here at request time.
757
688
  if (pathname.startsWith('/node_modules/')) {
758
689
  const filepath = '.' + pathname
759
690
 
760
- // Serve a resolved file compile .imba on the fly, pass JS through
691
+ // Serve a resolved file: compile .imba, wrap CJS as ESM, pass ESM through
761
692
  const serveResolved = async (filePath) => {
762
693
  if (filePath.endsWith('.imba')) {
763
694
  const out = await compileFile(filePath)
@@ -765,8 +696,10 @@ export function serve(entrypoint, flags) {
765
696
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
766
697
  }
767
698
  const f = Bun.file(filePath)
768
- if (await f.exists()) return new Response(f, { headers: { 'Content-Type': 'application/javascript' } })
769
- return null
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' } })
770
703
  }
771
704
 
772
705
  // Resolve entry point for root package requests (/node_modules/pkg/ or /node_modules/pkg)
@@ -788,11 +721,16 @@ export function serve(entrypoint, flags) {
788
721
  }
789
722
  }
790
723
 
791
- // Subpath: resolve via conditional exports (CJS→ESM)
792
- const esmPath = await resolveNodeModuleESM(filepath)
793
- if (esmPath) {
794
- const resp = await serveResolved(esmPath)
795
- if (resp) return resp
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
+ }
796
734
  }
797
735
  }
798
736