bimba-cli 0.7.3 → 0.7.5

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 +26 -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.5",
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,23 +682,26 @@ 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')) {
694
+ const f = Bun.file(filePath)
695
+ if (!(await f.exists())) return null
763
696
  const out = await compileFile(filePath)
764
697
  if (out.errors?.length) return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
765
698
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
766
699
  }
767
700
  const f = Bun.file(filePath)
768
- if (await f.exists()) return new Response(f, { headers: { 'Content-Type': 'application/javascript' } })
769
- return null
701
+ if (!(await f.exists())) return null
702
+ const code = await f.text()
703
+ const wrapped = wrapCJS(code)
704
+ return new Response(wrapped || code, { headers: { 'Content-Type': 'application/javascript' } })
770
705
  }
771
706
 
772
707
  // Resolve entry point for root package requests (/node_modules/pkg/ or /node_modules/pkg)
@@ -788,11 +723,16 @@ export function serve(entrypoint, flags) {
788
723
  }
789
724
  }
790
725
 
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
726
+ // Subpath: try the exact path, then with extensions
727
+ const resp = await serveResolved(filepath)
728
+ if (resp) return resp
729
+
730
+ // Extensionless: try .imba, .js, .mjs
731
+ if (!filepath.includes('.', filepath.lastIndexOf('/') + 1)) {
732
+ for (const ext of ['.imba', '.js', '.mjs']) {
733
+ const resp = await serveResolved(filepath + ext)
734
+ if (resp) return resp
735
+ }
796
736
  }
797
737
  }
798
738