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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/serve.js +42 -94
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.2",
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,13 +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, 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 entryPath = path.join(pkgDir, entry)
775
- const file = Bun.file(entryPath)
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: resolve via conditional exports (CJS→ESM)
784
- const esmPath = await resolveNodeModuleESM(filepath)
785
- if (esmPath) {
786
- const file = Bun.file(esmPath)
787
- if (await file.exists()) return new Response(file, {
788
- headers: { 'Content-Type': 'application/javascript' },
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 .js / .mjs extension for extensionless paths (e.g. node_modules imports)
743
+ // Try extensions for extensionless paths (e.g. node_modules imports)
800
744
  const lastSegment = pathname.split('/').pop()
801
745
  if (!lastSegment.includes('.')) {
802
- // For node_modules, ESM resolution above already handles extensionless
803
- // paths via wildcard exports matching (tries subpath + '.js').
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, {