├── .gitignore ├── test └── fixtures │ ├── simple │ ├── c.js │ ├── a.js │ ├── b.js │ ├── index.js │ └── package.json │ ├── multi-entry │ ├── c.js │ ├── b.js │ ├── index.js │ ├── a.js │ └── package.json │ └── patterns │ ├── lib │ ├── components │ │ ├── Box.tsx │ │ ├── Text.tsx │ │ └── Image.tsx │ ├── style.ts │ └── theme.ts │ ├── tsconfig.json │ ├── global.d.ts │ ├── index.ts │ └── package.json ├── tsconfig.json ├── .editorconfig ├── package.json ├── scripts └── build.mjs ├── README.md ├── pnpm-lock.yaml └── src └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | -------------------------------------------------------------------------------- /test/fixtures/simple/c.js: -------------------------------------------------------------------------------- 1 | export const c = 'c'; 2 | -------------------------------------------------------------------------------- /test/fixtures/multi-entry/c.js: -------------------------------------------------------------------------------- 1 | export const c = 'c'; 2 | -------------------------------------------------------------------------------- /test/fixtures/simple/a.js: -------------------------------------------------------------------------------- 1 | export const a = 'a'; 2 | export {c} from './c'; 3 | -------------------------------------------------------------------------------- /test/fixtures/simple/b.js: -------------------------------------------------------------------------------- 1 | import {a} from './a'; 2 | console.log('from b: ', a); 3 | export const b = 'b'; 4 | -------------------------------------------------------------------------------- /test/fixtures/patterns/lib/components/Box.tsx: -------------------------------------------------------------------------------- 1 | export function Box(props: any) { 2 | return
3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/patterns/lib/components/Text.tsx: -------------------------------------------------------------------------------- 1 | export function Text(props: any) { 2 | return 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/multi-entry/b.js: -------------------------------------------------------------------------------- 1 | import {a} from 'multi-entry/a'; 2 | console.log('from b: ', a); 3 | export const b = 'b'; 4 | -------------------------------------------------------------------------------- /test/fixtures/patterns/lib/components/Image.tsx: -------------------------------------------------------------------------------- 1 | export function Image(props: any) { 2 | return 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/patterns/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "jsxImportSource": "preact" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/simple/index.js: -------------------------------------------------------------------------------- 1 | import {a, c} from './a'; 2 | import {b} from './b'; 3 | 4 | function lib() { 5 | console.log('lib'); 6 | } 7 | 8 | export default {lib, a, b, c}; 9 | -------------------------------------------------------------------------------- /test/fixtures/multi-entry/index.js: -------------------------------------------------------------------------------- 1 | import {a, c} from 'multi-entry/a'; 2 | import {b} from 'multi-entry/b'; 3 | 4 | function lib() { 5 | console.log('lib'); 6 | } 7 | 8 | export default {lib, a, b, c}; 9 | -------------------------------------------------------------------------------- /test/fixtures/patterns/global.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace JSX { 3 | interface IntrinsicElements { 4 | span: {}; 5 | img: {}; 6 | div: {}; 7 | } 8 | } 9 | } 10 | export {}; 11 | -------------------------------------------------------------------------------- /test/fixtures/patterns/lib/style.ts: -------------------------------------------------------------------------------- 1 | export function style(obj: Record) { 2 | let out = ''; 3 | for (const key in obj) out += `${out ? ' ' : ''}${key}: ${obj[key]};`; 4 | return out; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple", 3 | "type": "module", 4 | "exports": { 5 | "types": "./dist/lib.d.ts", 6 | "import": "./dist/lib.js", 7 | "default": "./dist/lib.cjs" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "Bundler", 4 | "module": "ESNext", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "strict": true, 8 | "target": "ES2020", 9 | "jsx": "preserve" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/patterns/lib/theme.ts: -------------------------------------------------------------------------------- 1 | export const themes = ['default', 'dark', 'light'] as const; 2 | 3 | export type Theme = typeof themes[number]; 4 | 5 | export let theme: Theme = 'default'; 6 | 7 | export function setTheme(newTheme: Theme) { 8 | theme = newTheme; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/multi-entry/a.js: -------------------------------------------------------------------------------- 1 | export const a = 'a'; 2 | export const c = async () => (await import('./c')).c; 3 | // export const c = async () => (await import('./c').catch(() => ({c:0}))).c; 4 | // export const c = async () => { 5 | // const {c} = await import('./c'); 6 | // return c; 7 | // }; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,.*rc,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /test/fixtures/patterns/index.ts: -------------------------------------------------------------------------------- 1 | export * from 'patterns/lib/theme'; 2 | export * as style from 'patterns/lib/style'; 3 | 4 | import {Box} from 'patterns/components/Box'; 5 | import {Image} from 'patterns/components/Image'; 6 | import {Text} from 'patterns/components/Text'; 7 | 8 | import {setTheme} from 'patterns/lib/theme'; 9 | export {setTheme} from 'patterns/lib/theme'; 10 | 11 | export function init() { 12 | setTheme('default'); 13 | } 14 | 15 | export {Box, Image, Text}; 16 | -------------------------------------------------------------------------------- /test/fixtures/multi-entry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-entry", 3 | "type": "module", 4 | "exports": { 5 | ".": { 6 | "types": "./dist/lib.d.ts", 7 | "import": "./dist/lib.js", 8 | "default": "./dist/lib.cjs" 9 | }, 10 | "./a": { 11 | "types": "./dist/a.d.ts", 12 | "import": "./dist/a.js", 13 | "default": "./dist/a.cjs" 14 | }, 15 | "./b": { 16 | "types": "./dist/b.d.ts", 17 | "import": "./dist/b.js", 18 | "default": "./dist/b.cjs" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/fixtures/patterns/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patterns", 3 | "type": "module", 4 | "exports": { 5 | ".": { 6 | "types": "./build/index.d.ts", 7 | "import": "./build/index.js", 8 | "default": "./build/index.cjs" 9 | }, 10 | "./lib/*": { 11 | "types": "./build/lib/*.d.ts", 12 | "import": "./build/lib/*.js", 13 | "default": "./build/lib/*.cjs" 14 | }, 15 | "./components/*": { 16 | "source": "./lib/components/*.tsx", 17 | "types": "./build/components/*.d.ts", 18 | "import": "./build/components/*.js", 19 | "default": "./build/components/*.cjs" 20 | } 21 | }, 22 | "dependencies": { 23 | "preact": "^10.22.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microbundle-2", 3 | "version": "0.2.0", 4 | "description": "", 5 | "main": "dist/microbundle.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "bin": { 10 | "microbundle-2": "dist/microbundle.js", 11 | "microbundle": "dist/microbundle.js" 12 | }, 13 | "scripts": { 14 | "build": "node scripts/build.mjs", 15 | "watch": "node scripts/build.mjs --watch" 16 | }, 17 | "keywords": [], 18 | "author": "Jason Miller ", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@types/node": "^20.14.2", 22 | "chalk": "^5.3.0", 23 | "es-module-lexer": "^1.5.3", 24 | "esbuild": "^0.23.0", 25 | "magic-string": "^0.30.10", 26 | "oxlint": "^0.4.3", 27 | "preact": "^10.22.1", 28 | "pretty-bytes": "^6.1.1", 29 | "sade": "^1.8.1", 30 | "terser": "^5.31.1" 31 | }, 32 | "peerDependencies": { 33 | "esbuild": "*" 34 | }, 35 | "prettier": { 36 | "singleQuote": true 37 | }, 38 | "packageManager": "pnpm@8.15.9" 39 | } 40 | -------------------------------------------------------------------------------- /scripts/build.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import {context} from 'esbuild'; 3 | import sade from 'sade'; 4 | import prettyBytes from 'pretty-bytes'; 5 | 6 | /** @param {Record} args */ 7 | async function main(args) { 8 | const cwd = process.cwd(); 9 | 10 | const pkg = JSON.parse(await fs.readFile('package.json', 'utf-8')); 11 | 12 | const external = Object.keys(pkg.dependencies || []).concat(Object.keys(pkg.peerDependencies || [])) 13 | 14 | const ctx = await context({ 15 | absWorkingDir: cwd, 16 | // drop: ['console'], 17 | entryPoints: ['src/index.ts'], 18 | bundle: true, 19 | minify: true, 20 | mangleProps: /SUBCLASSES|_clone|_walk|_children_backwards/, 21 | // pure: ['wasmURL', 'wasmModule', '$documentation', '$propdoc'], 22 | legalComments: 'none', 23 | color: true, 24 | treeShaking: true, 25 | platform: 'node', 26 | target: ['node16'], 27 | mainFields: ['module', 'main'], 28 | // packages: 'external', 29 | external, 30 | sourcemap: false, 31 | format: 'cjs', 32 | outfile: 'dist/microbundle.js', 33 | metafile: true, 34 | plugins: [ 35 | { 36 | name: 'library-optimization', 37 | setup(build) { 38 | build.onLoad({filter: /es-module-lexer/}, async (args) => { 39 | const code = await fs.readFile(args.path, 'utf-8'); 40 | const out = code.replace(/(WebAssembly\.compile\()\(\w+="([^"]+)".*?CodeAt\(0\)\)\)\)/, '$1Buffer.from(`$2`,"base64")'); 41 | return {contents: out}; 42 | }); 43 | build.onLoad({filter: /terser/}, async (args) => { 44 | const code = await fs.readFile(args.path, 'utf-8'); 45 | const out = code.replace(/(?:\$documentation: *(['"]).*?\1|\$propdoc: *\{[^}]+\})[,\n]/gs, ''); 46 | return {contents: out}; 47 | }); 48 | } 49 | } 50 | ] 51 | }); 52 | 53 | let building = false; 54 | async function build() { 55 | if (building) await ctx.cancel(); 56 | building = true; 57 | const start = performance.now(); 58 | const result = await ctx.rebuild(); 59 | const dur = performance.now() - start; 60 | const stats = Object.entries(result.metafile.outputs).map(([filename, output]) => `${filename} ${prettyBytes(output.bytes, {maximumFractionDigits: 4})}`); 61 | console.log(`built in ${dur.toPrecision(4)}ms:\n ` + stats.join('\n ')) 62 | // console.log({ warnings: result.warnings, errors: result.errors }); 63 | building = false; 64 | } 65 | 66 | try { 67 | await build(); 68 | } catch(err) {} 69 | 70 | if (args.watch) { 71 | for await (const change of fs.watch('src', {recursive:true})) { 72 | // change.eventType; change.filename; 73 | build().catch(() => {}); 74 | } 75 | } else { 76 | await ctx.dispose(); 77 | } 78 | } 79 | 80 | sade('_build', true).action(main).parse(process.argv); 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microbundle 2 2 | 3 | What if microbundle could figure out how to bundle your library based on the `"exports"` field you already have to define in your package.json? 4 | 5 | And what if it was also absurdly fast, and one 500kb file with a single native dependency? 6 | 7 | ```sh 8 | npm i -g microbundle-2 9 | # or 10 | npx microbundle-2 11 | ``` 12 | 13 | This is a reimplementation of [Microbundle](https://github.com/developit/microbundle) 14 | built on [ESBuild](https://esbuild.github.io). 15 | 16 | help 17 | 18 | 19 | ## Simple Example 20 | 21 | You write a package.json that looks like this: 22 | 23 | ```jsonc 24 | { 25 | "name": "simple", 26 | "type": "module", 27 | "exports": { 28 | "import": "./dist/lib.js", 29 | "default": "./dist/lib.cjs" 30 | } 31 | } 32 | ``` 33 | 34 | simple example package 35 | 36 | 37 | ## Multiple entries 38 | 39 | Just define your package exports the way you already have to for Node/Vite/etc: 40 | 41 | ```jsonc 42 | { 43 | "name": "multi-entry", 44 | "type": "module", 45 | "exports": { 46 | ".": { 47 | "types": "./dist/lib.d.ts", 48 | "import": "./dist/lib.js", 49 | "default": "./dist/lib.cjs" 50 | }, 51 | "./a": { 52 | "types": "./dist/a.d.ts", 53 | "import": "./dist/a.js", 54 | "default": "./dist/a.cjs" 55 | }, 56 | "./b": { 57 | "types": "./dist/b.d.ts", 58 | "import": "./dist/b.js", 59 | "default": "./dist/b.cjs" 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | simple multi-entry package example 66 | 67 | This example has a dynamic import, which you can see produced a `./c` chunk. Both the ESM and CJS versions work the same way! 68 | 69 | ## Wildcards/patterns 70 | 71 | Wildcard/pattern exports are also supported: 72 | 73 | ```jsonc 74 | { 75 | "name": "patterns", 76 | "type": "module", 77 | "exports": { 78 | ".": { 79 | "types": "./build/index.d.ts", 80 | "import": "./build/index.js", 81 | "default": "./build/index.cjs" 82 | }, 83 | "./lib/*": { 84 | "types": "./build/lib/*.d.ts", 85 | "import": "./build/lib/*.js", 86 | "default": "./build/lib/*.cjs" 87 | }, 88 | "./components/*": { 89 | "source": "./lib/components/*.tsx", 90 | "types": "./build/components/*.d.ts", 91 | "import": "./build/components/*.js", 92 | "default": "./build/components/*.cjs" 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | complex multi-entry example using wildcard/pattern exports 99 | 100 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | '@types/node': 9 | specifier: ^20.14.2 10 | version: 20.19.9 11 | chalk: 12 | specifier: ^5.3.0 13 | version: 5.4.1 14 | es-module-lexer: 15 | specifier: ^1.5.3 16 | version: 1.7.0 17 | esbuild: 18 | specifier: ^0.23.0 19 | version: 0.23.1 20 | magic-string: 21 | specifier: ^0.30.10 22 | version: 0.30.17 23 | oxlint: 24 | specifier: ^0.4.3 25 | version: 0.4.4 26 | preact: 27 | specifier: ^10.22.1 28 | version: 10.26.9 29 | pretty-bytes: 30 | specifier: ^6.1.1 31 | version: 6.1.1 32 | sade: 33 | specifier: ^1.8.1 34 | version: 1.8.1 35 | terser: 36 | specifier: ^5.31.1 37 | version: 5.43.1 38 | 39 | packages: 40 | 41 | /@esbuild/aix-ppc64@0.23.1: 42 | resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} 43 | engines: {node: '>=18'} 44 | cpu: [ppc64] 45 | os: [aix] 46 | requiresBuild: true 47 | dev: true 48 | optional: true 49 | 50 | /@esbuild/android-arm64@0.23.1: 51 | resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} 52 | engines: {node: '>=18'} 53 | cpu: [arm64] 54 | os: [android] 55 | requiresBuild: true 56 | dev: true 57 | optional: true 58 | 59 | /@esbuild/android-arm@0.23.1: 60 | resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} 61 | engines: {node: '>=18'} 62 | cpu: [arm] 63 | os: [android] 64 | requiresBuild: true 65 | dev: true 66 | optional: true 67 | 68 | /@esbuild/android-x64@0.23.1: 69 | resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} 70 | engines: {node: '>=18'} 71 | cpu: [x64] 72 | os: [android] 73 | requiresBuild: true 74 | dev: true 75 | optional: true 76 | 77 | /@esbuild/darwin-arm64@0.23.1: 78 | resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} 79 | engines: {node: '>=18'} 80 | cpu: [arm64] 81 | os: [darwin] 82 | requiresBuild: true 83 | dev: true 84 | optional: true 85 | 86 | /@esbuild/darwin-x64@0.23.1: 87 | resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} 88 | engines: {node: '>=18'} 89 | cpu: [x64] 90 | os: [darwin] 91 | requiresBuild: true 92 | dev: true 93 | optional: true 94 | 95 | /@esbuild/freebsd-arm64@0.23.1: 96 | resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} 97 | engines: {node: '>=18'} 98 | cpu: [arm64] 99 | os: [freebsd] 100 | requiresBuild: true 101 | dev: true 102 | optional: true 103 | 104 | /@esbuild/freebsd-x64@0.23.1: 105 | resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} 106 | engines: {node: '>=18'} 107 | cpu: [x64] 108 | os: [freebsd] 109 | requiresBuild: true 110 | dev: true 111 | optional: true 112 | 113 | /@esbuild/linux-arm64@0.23.1: 114 | resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} 115 | engines: {node: '>=18'} 116 | cpu: [arm64] 117 | os: [linux] 118 | requiresBuild: true 119 | dev: true 120 | optional: true 121 | 122 | /@esbuild/linux-arm@0.23.1: 123 | resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} 124 | engines: {node: '>=18'} 125 | cpu: [arm] 126 | os: [linux] 127 | requiresBuild: true 128 | dev: true 129 | optional: true 130 | 131 | /@esbuild/linux-ia32@0.23.1: 132 | resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} 133 | engines: {node: '>=18'} 134 | cpu: [ia32] 135 | os: [linux] 136 | requiresBuild: true 137 | dev: true 138 | optional: true 139 | 140 | /@esbuild/linux-loong64@0.23.1: 141 | resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} 142 | engines: {node: '>=18'} 143 | cpu: [loong64] 144 | os: [linux] 145 | requiresBuild: true 146 | dev: true 147 | optional: true 148 | 149 | /@esbuild/linux-mips64el@0.23.1: 150 | resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} 151 | engines: {node: '>=18'} 152 | cpu: [mips64el] 153 | os: [linux] 154 | requiresBuild: true 155 | dev: true 156 | optional: true 157 | 158 | /@esbuild/linux-ppc64@0.23.1: 159 | resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} 160 | engines: {node: '>=18'} 161 | cpu: [ppc64] 162 | os: [linux] 163 | requiresBuild: true 164 | dev: true 165 | optional: true 166 | 167 | /@esbuild/linux-riscv64@0.23.1: 168 | resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} 169 | engines: {node: '>=18'} 170 | cpu: [riscv64] 171 | os: [linux] 172 | requiresBuild: true 173 | dev: true 174 | optional: true 175 | 176 | /@esbuild/linux-s390x@0.23.1: 177 | resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} 178 | engines: {node: '>=18'} 179 | cpu: [s390x] 180 | os: [linux] 181 | requiresBuild: true 182 | dev: true 183 | optional: true 184 | 185 | /@esbuild/linux-x64@0.23.1: 186 | resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} 187 | engines: {node: '>=18'} 188 | cpu: [x64] 189 | os: [linux] 190 | requiresBuild: true 191 | dev: true 192 | optional: true 193 | 194 | /@esbuild/netbsd-x64@0.23.1: 195 | resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} 196 | engines: {node: '>=18'} 197 | cpu: [x64] 198 | os: [netbsd] 199 | requiresBuild: true 200 | dev: true 201 | optional: true 202 | 203 | /@esbuild/openbsd-arm64@0.23.1: 204 | resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} 205 | engines: {node: '>=18'} 206 | cpu: [arm64] 207 | os: [openbsd] 208 | requiresBuild: true 209 | dev: true 210 | optional: true 211 | 212 | /@esbuild/openbsd-x64@0.23.1: 213 | resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} 214 | engines: {node: '>=18'} 215 | cpu: [x64] 216 | os: [openbsd] 217 | requiresBuild: true 218 | dev: true 219 | optional: true 220 | 221 | /@esbuild/sunos-x64@0.23.1: 222 | resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} 223 | engines: {node: '>=18'} 224 | cpu: [x64] 225 | os: [sunos] 226 | requiresBuild: true 227 | dev: true 228 | optional: true 229 | 230 | /@esbuild/win32-arm64@0.23.1: 231 | resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} 232 | engines: {node: '>=18'} 233 | cpu: [arm64] 234 | os: [win32] 235 | requiresBuild: true 236 | dev: true 237 | optional: true 238 | 239 | /@esbuild/win32-ia32@0.23.1: 240 | resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} 241 | engines: {node: '>=18'} 242 | cpu: [ia32] 243 | os: [win32] 244 | requiresBuild: true 245 | dev: true 246 | optional: true 247 | 248 | /@esbuild/win32-x64@0.23.1: 249 | resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} 250 | engines: {node: '>=18'} 251 | cpu: [x64] 252 | os: [win32] 253 | requiresBuild: true 254 | dev: true 255 | optional: true 256 | 257 | /@jridgewell/gen-mapping@0.3.12: 258 | resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} 259 | dependencies: 260 | '@jridgewell/sourcemap-codec': 1.5.4 261 | '@jridgewell/trace-mapping': 0.3.29 262 | dev: true 263 | 264 | /@jridgewell/resolve-uri@3.1.2: 265 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 266 | engines: {node: '>=6.0.0'} 267 | dev: true 268 | 269 | /@jridgewell/source-map@0.3.10: 270 | resolution: {integrity: sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==} 271 | dependencies: 272 | '@jridgewell/gen-mapping': 0.3.12 273 | '@jridgewell/trace-mapping': 0.3.29 274 | dev: true 275 | 276 | /@jridgewell/sourcemap-codec@1.5.4: 277 | resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} 278 | dev: true 279 | 280 | /@jridgewell/trace-mapping@0.3.29: 281 | resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} 282 | dependencies: 283 | '@jridgewell/resolve-uri': 3.1.2 284 | '@jridgewell/sourcemap-codec': 1.5.4 285 | dev: true 286 | 287 | /@oxlint/darwin-arm64@0.4.4: 288 | resolution: {integrity: sha512-3XqHh9uz9IROFRSJTkH81BDHERglneH4YzcchWOlkckCTHh/V5/THbTY6sA7FK3a7uJVysKJu0zgRhsyXoUNUA==} 289 | cpu: [arm64] 290 | os: [darwin] 291 | requiresBuild: true 292 | dev: true 293 | optional: true 294 | 295 | /@oxlint/darwin-x64@0.4.4: 296 | resolution: {integrity: sha512-41/as9u+MRTmY0egr42j9Q7RMNoKEy1Uw/v+4K/5gJp1cnY99K7pGsqgbD6nMvaDbenbt8vwNSYoF2UzmUDcoQ==} 297 | cpu: [x64] 298 | os: [darwin] 299 | requiresBuild: true 300 | dev: true 301 | optional: true 302 | 303 | /@oxlint/linux-arm64-gnu@0.4.4: 304 | resolution: {integrity: sha512-LTOtEliKPWJzVC6qs1M0IoND0yFchWkKS6agFSmzGmfe523geC+eATq3slFvUPdSzdLAgk6v2juol7JxaI2+vg==} 305 | cpu: [arm64] 306 | os: [linux] 307 | requiresBuild: true 308 | dev: true 309 | optional: true 310 | 311 | /@oxlint/linux-arm64-musl@0.4.4: 312 | resolution: {integrity: sha512-hTmCi57YnUUxUxOWSdLEdN7IFkcJylsAZuWcVLjMCiPtTcyTeY6wPrUFqNSGLG+Gvqj/2g0aqzKt3eJBfDSOzg==} 313 | cpu: [arm64] 314 | os: [linux] 315 | requiresBuild: true 316 | dev: true 317 | optional: true 318 | 319 | /@oxlint/linux-x64-gnu@0.4.4: 320 | resolution: {integrity: sha512-nqQr/Zj86F56EvpcbZMXEsYpUS+wpEAH1jahg70ApAEeq+EoVBqMZPv4sV1vAIy/R20f00a891fz13RGkorMnw==} 321 | cpu: [x64] 322 | os: [linux] 323 | requiresBuild: true 324 | dev: true 325 | optional: true 326 | 327 | /@oxlint/linux-x64-musl@0.4.4: 328 | resolution: {integrity: sha512-MdyAoQgLQf/ZzneV+cCWD++6ATKWEEp1VPa61664uCYtJIKRKiavPwT5BN+nQ197Bn7n/k6pOHmo/3n8+PCFNg==} 329 | cpu: [x64] 330 | os: [linux] 331 | requiresBuild: true 332 | dev: true 333 | optional: true 334 | 335 | /@oxlint/win32-arm64@0.4.4: 336 | resolution: {integrity: sha512-m1azJG8XD7Po1Las7ftYFvtSUaVju1V9ic771x6/Su+8vt1Iz2pDwG7jDK+5hHbS17w8qHCYcg/IDRFHuFsOeQ==} 337 | cpu: [arm64] 338 | os: [win32] 339 | requiresBuild: true 340 | dev: true 341 | optional: true 342 | 343 | /@oxlint/win32-x64@0.4.4: 344 | resolution: {integrity: sha512-4ZyCtwNHqfOPzSPoEUHuFxcYCxRGMTB9ZmDJCVZGVbxwE462FPz6IfoinEqaiesgnuaBqN78CkLqW5kfhnxm+A==} 345 | cpu: [x64] 346 | os: [win32] 347 | requiresBuild: true 348 | dev: true 349 | optional: true 350 | 351 | /@types/node@20.19.9: 352 | resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} 353 | dependencies: 354 | undici-types: 6.21.0 355 | dev: true 356 | 357 | /acorn@8.15.0: 358 | resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 359 | engines: {node: '>=0.4.0'} 360 | hasBin: true 361 | dev: true 362 | 363 | /buffer-from@1.1.2: 364 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 365 | dev: true 366 | 367 | /chalk@5.4.1: 368 | resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} 369 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 370 | dev: true 371 | 372 | /commander@2.20.3: 373 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 374 | dev: true 375 | 376 | /es-module-lexer@1.7.0: 377 | resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 378 | dev: true 379 | 380 | /esbuild@0.23.1: 381 | resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} 382 | engines: {node: '>=18'} 383 | hasBin: true 384 | requiresBuild: true 385 | optionalDependencies: 386 | '@esbuild/aix-ppc64': 0.23.1 387 | '@esbuild/android-arm': 0.23.1 388 | '@esbuild/android-arm64': 0.23.1 389 | '@esbuild/android-x64': 0.23.1 390 | '@esbuild/darwin-arm64': 0.23.1 391 | '@esbuild/darwin-x64': 0.23.1 392 | '@esbuild/freebsd-arm64': 0.23.1 393 | '@esbuild/freebsd-x64': 0.23.1 394 | '@esbuild/linux-arm': 0.23.1 395 | '@esbuild/linux-arm64': 0.23.1 396 | '@esbuild/linux-ia32': 0.23.1 397 | '@esbuild/linux-loong64': 0.23.1 398 | '@esbuild/linux-mips64el': 0.23.1 399 | '@esbuild/linux-ppc64': 0.23.1 400 | '@esbuild/linux-riscv64': 0.23.1 401 | '@esbuild/linux-s390x': 0.23.1 402 | '@esbuild/linux-x64': 0.23.1 403 | '@esbuild/netbsd-x64': 0.23.1 404 | '@esbuild/openbsd-arm64': 0.23.1 405 | '@esbuild/openbsd-x64': 0.23.1 406 | '@esbuild/sunos-x64': 0.23.1 407 | '@esbuild/win32-arm64': 0.23.1 408 | '@esbuild/win32-ia32': 0.23.1 409 | '@esbuild/win32-x64': 0.23.1 410 | dev: true 411 | 412 | /magic-string@0.30.17: 413 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} 414 | dependencies: 415 | '@jridgewell/sourcemap-codec': 1.5.4 416 | dev: true 417 | 418 | /mri@1.2.0: 419 | resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 420 | engines: {node: '>=4'} 421 | dev: true 422 | 423 | /oxlint@0.4.4: 424 | resolution: {integrity: sha512-YF4rEASRYWCJBaCd0yDavYJ1qCA5H2l6piXlAeDnIKjqKHQ7V3Hwu5w5gIkgZne73iXFe6baqcMSlZNdNlqCeA==} 425 | engines: {node: '>=14.*'} 426 | hasBin: true 427 | optionalDependencies: 428 | '@oxlint/darwin-arm64': 0.4.4 429 | '@oxlint/darwin-x64': 0.4.4 430 | '@oxlint/linux-arm64-gnu': 0.4.4 431 | '@oxlint/linux-arm64-musl': 0.4.4 432 | '@oxlint/linux-x64-gnu': 0.4.4 433 | '@oxlint/linux-x64-musl': 0.4.4 434 | '@oxlint/win32-arm64': 0.4.4 435 | '@oxlint/win32-x64': 0.4.4 436 | dev: true 437 | 438 | /preact@10.26.9: 439 | resolution: {integrity: sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==} 440 | dev: true 441 | 442 | /pretty-bytes@6.1.1: 443 | resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} 444 | engines: {node: ^14.13.1 || >=16.0.0} 445 | dev: true 446 | 447 | /sade@1.8.1: 448 | resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} 449 | engines: {node: '>=6'} 450 | dependencies: 451 | mri: 1.2.0 452 | dev: true 453 | 454 | /source-map-support@0.5.21: 455 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 456 | dependencies: 457 | buffer-from: 1.1.2 458 | source-map: 0.6.1 459 | dev: true 460 | 461 | /source-map@0.6.1: 462 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 463 | engines: {node: '>=0.10.0'} 464 | dev: true 465 | 466 | /terser@5.43.1: 467 | resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} 468 | engines: {node: '>=10'} 469 | hasBin: true 470 | dependencies: 471 | '@jridgewell/source-map': 0.3.10 472 | acorn: 8.15.0 473 | commander: 2.20.3 474 | source-map-support: 0.5.21 475 | dev: true 476 | 477 | /undici-types@6.21.0: 478 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 479 | dev: true 480 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { promises as fs } from 'node:fs'; 4 | import { relative, resolve, posix } from 'node:path'; 5 | import sade from 'sade'; 6 | // import {context, analyzeMetafile, type BuildOptions, type Format} from 'esbuild'; 7 | import { initialize, context, type BuildOptions, type Format } from 'esbuild'; 8 | import MagicString from 'magic-string'; 9 | import { parse } from 'es-module-lexer'; 10 | import { minify, type MinifyOptions } from 'terser'; 11 | 12 | // start the esbuild service early 13 | initialize({}); 14 | 15 | // import type {MinifyOptions} from 'terser'; 16 | // const slowDeps = (async () => { 17 | // const {default: MagicString} = await import('magic-string'); 18 | // const {parse} = await import('es-module-lexer'); 19 | // const {minify} = await import('terser'); 20 | // return {MagicString, parse, minify}; 21 | // })(); 22 | 23 | const color = 24 | (code: number, end: number) => (str: string | TemplateStringsArray) => 25 | `\x1B[${code}m${str}\x1B[${end}m`; 26 | const bold = color(1, 22); 27 | const underline = color(4, 24); 28 | const dim = color(2, 22); 29 | // const red = color(31, 39); 30 | const brightBlue = color(94, 39); 31 | const brightWhite = color(97, 39); 32 | const deAnsi = (str: string) => str.replace(/\x1B\[\d+m/g, ''); 33 | const padAnsi = (str: string, len: number, padding?: string) => { 34 | const count = len - deAnsi(str).length; 35 | return str.padEnd(str.length + count, padding); 36 | }; 37 | 38 | function prettyBytes(value: number) { 39 | if (value > 1.5e6) return `${Number((value / 1e6).toFixed(1))}${dim('mb')}`; 40 | if (value > 1.5e3) return `${Number((value / 1e3).toFixed(1))}${dim('kb')}`; 41 | return `${value}${dim('b')}`; 42 | } 43 | 44 | interface Args { 45 | cwd: string; 46 | sourcemap?: boolean; 47 | minify?: boolean; 48 | entry?: string; 49 | cjs?: 'flat' | 'default'; 50 | } 51 | 52 | interface BuildArgs extends Args { 53 | watch?: boolean; 54 | format?: string; 55 | external?: string; 56 | } 57 | 58 | type ExportCondition = 59 | | 'import' 60 | | 'require' 61 | | 'default' 62 | | 'module' 63 | | 'types' 64 | | 'typings'; 65 | 66 | type PackageExports = 67 | | ({ [K in ExportCondition]: PackageExports } & { 68 | [K in ExportCondition]?: PackageExports; 69 | }) 70 | | { [K in '.' | `./${string}`]: PackageExports } 71 | | string; 72 | 73 | // function getExports(exports: PackageExports, map: Record = {}, path = '.', conditions: string[] = []): {path: string, mapped: string, conditions: string[]}[] { 74 | function getExports( 75 | exports: PackageExports, 76 | map: Record = {}, 77 | path = '.', 78 | conditions: string[] = [] 79 | ) { 80 | if (typeof exports === 'string') { 81 | conditions = Array.from(new Set(conditions)).sort(); 82 | // return [{path, mapped: exports, conditions}]; 83 | let c = map[path] || (map[path] = []); 84 | if (!c.find((c) => c.conditions.join('\n') === conditions.join('\n'))) { 85 | c.push({ mapped: exports, conditions }); 86 | } 87 | return map; 88 | } 89 | // if ('.' in exports) return getExports(exports['.'], map, path, conditions); 90 | let isPath: boolean | undefined; 91 | for (let key in exports) { 92 | if (isPath === undefined) isPath = key[0] === '.'; 93 | if ((key[0] === '.') !== isPath) { 94 | throw Error( 95 | `Package Exports cannot contain mixed conditions and paths: ${Object.keys( 96 | exports 97 | )}` 98 | ); 99 | } 100 | if (isPath) { 101 | getExports(exports[key], map, posix.join(path, key), conditions.slice()); 102 | } else { 103 | const childConditions = 104 | key === 'default' ? conditions.slice() : conditions.concat(key); 105 | getExports(exports[key as ExportCondition], map, path, childConditions); 106 | } 107 | } 108 | return map; 109 | // return results.flat(); 110 | } 111 | 112 | /* 113 | console.log( 114 | getExports('./test.js') 115 | ); 116 | console.log( 117 | getExports({ 118 | '.': './test.js', 119 | }) 120 | ); 121 | console.log( 122 | getExports({ 123 | module: './test.js', 124 | })['.'] 125 | ); 126 | console.log( 127 | getExports({ 128 | import: './test.mjs', 129 | default: './test.js', 130 | })['.'] 131 | ); 132 | */ 133 | 134 | async function looseResolve(cwd: string, file: string) { 135 | if (file.includes('*')) return `./${relative(cwd, file)}`; 136 | const stat = await fs.stat(file).catch(() => null); 137 | if (stat?.isDirectory()) return looseResolve(cwd, `${file}/index`); 138 | if (file.match(/\.[cm]js$/)) return `./${relative(cwd, file)}`; 139 | return ( 140 | ( 141 | await Promise.all( 142 | ['.ts', '.tsx', '.jsx', '.mjs', '.cjs', '.js'].map(async (ext) => { 143 | const f = `./${relative(cwd, file + ext)}`; 144 | try { 145 | if ((await fs.stat(resolve(cwd, f))).isFile()) return f; 146 | } catch {} 147 | }) 148 | ) 149 | ).find(Boolean) ?? file 150 | ); 151 | } 152 | 153 | async function build(args: BuildArgs) { 154 | const cwd = args.cwd || process.cwd(); 155 | 156 | const pkg = JSON.parse( 157 | await fs.readFile(resolve(cwd, 'package.json'), 'utf-8') 158 | ); 159 | const external = Object.keys(pkg.dependencies || []).concat( 160 | Object.keys(pkg.peerDependencies || []) 161 | ); 162 | if (args.external === 'none') external.length = 0; 163 | 164 | let exports = getExports(pkg.exports || {}); 165 | 166 | await Promise.all( 167 | Object.keys(exports).map(async (key) => { 168 | if (!key.includes('*')) return; 169 | const mapping = exports[key]; 170 | const explicitSrc = mapping.find( 171 | (m) => m.conditions[0] === 'source' 172 | )?.mapped; 173 | const src = explicitSrc || key; 174 | const [before, after] = src.split('*'); 175 | const list = await fs.readdir(resolve(cwd, before)); 176 | await Promise.all( 177 | list.map(async (item) => { 178 | if (item === '.') return; 179 | if (after && after[0] !== '/') { 180 | if (item.endsWith(after)) item = item.slice(0, -after.length); 181 | else return; 182 | } 183 | const stats = await fs 184 | .stat(resolve(cwd, before + item + after)) 185 | .catch(() => null); 186 | if (!stats || stats.isDirectory()) return; 187 | const itemNoExt = explicitSrc 188 | ? item 189 | : item.replace(/\.([mc]?[jt]sx?|d\.ts)$/, ''); 190 | exports[key.replace('*', itemNoExt)] = mapping.map((m) => ({ 191 | mapped: m.mapped.replace('*', itemNoExt), 192 | conditions: m.conditions.slice(), 193 | })); 194 | }) 195 | ); 196 | delete exports[key]; 197 | }) 198 | ); 199 | 200 | // console.log(exports); 201 | 202 | if (Object.keys(exports).length === 0) { 203 | const inferredExports: Partial = {}; 204 | // console.log(cwd, pkg.main); 205 | // const main = await looseResolve(cwd, pkg.main || 'index'); 206 | let main = pkg.main || 'index'; 207 | let ext = main.match(/\.[mc]?js$/)?.[0]; 208 | if (!ext) main += ext = '.js'; 209 | if (ext === '.mjs' || (pkg.type === 'module' && ext === '.js')) { 210 | inferredExports.import = inferredExports.module = main; 211 | } else { 212 | inferredExports.default = main; 213 | } 214 | if (pkg.module) { 215 | inferredExports.import = inferredExports.module = await looseResolve( 216 | cwd, 217 | pkg.module 218 | ); 219 | } 220 | console.log('inferring exports', inferredExports); 221 | exports = getExports(inferredExports); 222 | } 223 | 224 | // console.log('exports: ', exports); 225 | 226 | // const esmEntries = exports.filter((({mapped, conditions}) => { 227 | // if (conditions.includes('require')) return false; 228 | // if (conditions.includes('import') || conditions.includes('module')) return true; 229 | // // if (!conditions.includes('default') && conditions.length > 0) return false; 230 | // if (pkg.type === 'module' && mapped.endsWith('.js')) return true; 231 | // return mapped.endsWith('.mjs'); 232 | // })); 233 | 234 | // const entryPaths = Array.from(new Set(Object.keys(exports).filter(entry => !entry.endsWith('/') && !entry.includes('*')))); 235 | const entryPaths = await Promise.all( 236 | Array.from(new Set(Object.keys(exports))).map((file) => { 237 | return file; 238 | }) 239 | ); 240 | // console.log('entryPaths: ', entryPaths); 241 | 242 | let src = cwd; 243 | if ((await fs.stat(resolve(cwd, 'src')).catch(() => null))?.isDirectory()) { 244 | src += '/src'; 245 | } 246 | const entryPoints = await Promise.all( 247 | entryPaths.map((file) => { 248 | const explicitSrc = exports[file].find( 249 | (m) => m.conditions[0] === 'source' 250 | )?.mapped; 251 | return looseResolve( 252 | cwd, 253 | explicitSrc || `${src}/${file === '.' ? 'index' : file}` 254 | ); 255 | }) 256 | ); 257 | 258 | // console.log('entries: ', entryPoints); 259 | 260 | external.push(pkg.name); 261 | for (const entry of entryPaths) { 262 | if (entry !== '.') external.push(pkg.name + '/' + entry); 263 | } 264 | 265 | // console.log('external: ', external); 266 | 267 | let formats: Format[] = ['esm', 'cjs']; 268 | if (args.format) formats = args.format.split(/\s*,\s*/); 269 | 270 | // const entryPoints = ['a']; 271 | let mangleCache: BuildOptions['mangleCache'] = {}; 272 | // let outdir = 'dist'; 273 | 274 | const baseOptions: BuildOptions = { 275 | absWorkingDir: resolve(cwd), 276 | entryPoints, 277 | bundle: true, 278 | // minify: !!args.minify, 279 | // minifyIdentifiers: true, 280 | minifySyntax: !!args.minify, 281 | minifyWhitespace: !!args.minify, 282 | legalComments: args.minify ? 'none' : 'inline', 283 | treeShaking: true, 284 | sourcemap: args.sourcemap && (args.minify ? 'inline' : true), 285 | // target: ['es2017'], 286 | target: ['es2020'], 287 | platform: 'browser', 288 | external, 289 | outbase: relative(resolve(cwd), resolve(src)), 290 | outdir: '.', 291 | // splitting: true, 292 | chunkNames: '[dir]/[name]-[hash]', 293 | assetNames: '[dir]/[name]-[hash]', 294 | entryNames: '[dir]/[name]', 295 | // jsx: 'automatic', 296 | jsxSideEffects: false, 297 | color: true, 298 | // outdir, 299 | mangleProps: /(^_|_$)/, 300 | mangleCache, 301 | metafile: true, 302 | // write: args.minify !== 'terser' 303 | }; 304 | 305 | const madeDirs = new Set(); 306 | const mkdir = (dir: string) => { 307 | dir = resolve(cwd, dir); 308 | if (madeDirs.has(dir)) return; 309 | madeDirs.add(dir); 310 | return fs.mkdir(dir, { recursive: true }).catch(Boolean); 311 | }; 312 | 313 | const extForFormat = (format: Format) => { 314 | if (format === 'cjs' && pkg.type === 'module') return '.cjs'; 315 | if (format === 'esm' && pkg.type !== 'module') return '.mjs'; 316 | return '.js'; 317 | }; 318 | 319 | const formatsWithoutCjs = formats.filter((f) => f !== 'cjs'); 320 | const contexts = await Promise.all( 321 | formatsWithoutCjs.map((format) => { 322 | const ext = extForFormat(format); 323 | 324 | const eps = entryPoints.map((input, index) => ({ 325 | in: input, 326 | out: exports[entryPaths[index]] 327 | .find((m) => { 328 | if (format === 'cjs' && m.conditions.includes('require')) 329 | return true; 330 | if ( 331 | format === 'esm' && 332 | (m.conditions.includes('module') || 333 | m.conditions.includes('import')) 334 | ) 335 | return true; 336 | return ( 337 | m.conditions.includes('default') || m.conditions.length === 0 338 | ); 339 | }) 340 | ?.mapped.replace(/\.[mc]?js$/, '')!, 341 | })); 342 | 343 | // find common dir prefix: 344 | const mkdirDone = Promise.all( 345 | eps.map((ep) => mkdir(posix.dirname(ep.out))) 346 | ); 347 | const outdir = eps 348 | .map((ep) => ep.out.replace(/(^\.\/|\/[^/]+$)/g, '').split('/')) 349 | .reduce((last, next) => { 350 | for (let i = 0; i < last.length; i++) { 351 | if (last[i] !== next[i]) { 352 | last.length = i; 353 | break; 354 | } 355 | } 356 | return last; 357 | }) 358 | .join('/'); 359 | for (const ep of eps) ep.out = relative(outdir, ep.out); 360 | // const mkdirDone = mkdir(outdir); 361 | 362 | // console.log(format, outdir, eps); 363 | return context({ 364 | ...baseOptions, 365 | // outExtension: { 366 | // '.js': '', 367 | // '.cjs': '', 368 | // '.mjs': '', 369 | // }, 370 | splitting: format === 'esm', 371 | outExtension: { 372 | '.js': ext, 373 | }, 374 | outdir, 375 | entryPoints: eps, 376 | format, 377 | write: format !== 'esm', 378 | // write: format !== 'cjs', 379 | // plugins: format === 'cjs' ? [ 380 | plugins: 381 | format === 'esm' && formats.includes('cjs') 382 | ? [ 383 | { 384 | name: 'esm-to-cjs', 385 | setup(build) { 386 | build.onEnd(async (result) => { 387 | // const {default: MagicString} = await import('magic-string'); 388 | // const {parse} = await import('es-module-lexer'); 389 | // const {minify} = await import('terser'); 390 | await mkdirDone; 391 | const _exports = exports; 392 | const outputs = Object.values(result.metafile!.outputs); 393 | const terserOpts: MinifyOptions = { 394 | ecma: 2020, 395 | compress: { 396 | unsafe: true, 397 | unsafe_proto: true, // for eliding unreferenced Object.prototype.x 398 | passes: 10, 399 | }, 400 | format: { 401 | shebang: true, 402 | shorthand: true, 403 | comments: false, 404 | }, 405 | nameCache: mangleCache, 406 | module: true, 407 | mangle: { 408 | properties: { 409 | regex: /^_/, 410 | }, 411 | }, 412 | }; 413 | await Promise.all( 414 | result.outputFiles!.map(async (file, index) => { 415 | if (file.path.endsWith('.css')) return; 416 | 417 | const output = outputs[index]; 418 | let code = file.text; 419 | // bugfix: esbuild uses a fairly brutal workaround for object spread, 420 | // which is imported by almost every file. 421 | code = code.replace( 422 | /\b__spreadValues *= *\(a, *b\) *=> *\{.*?\};/gs, 423 | '__spreadValues=(a,b)=>({__proto__: null, ...a, ...b});' 424 | ); 425 | if (args.minify) { 426 | try { 427 | var minified = await minify(code, { 428 | ...terserOpts, 429 | mangle: { 430 | ...Object(terserOpts.mangle), 431 | reserved: output.exports, 432 | }, 433 | sourceMap: args.sourcemap && { 434 | content: 'inline', 435 | }, 436 | }); 437 | code = minified.code!; 438 | } catch (err) { 439 | const line = code.split('\n')[err.line - 1]; 440 | console.error(`Terser error at ${file.path}:${err.line}:${err.col}:\n\n ${line}\n ${'-'.repeat(err.col)}^\n\n${err.message}`); 441 | throw err; 442 | } 443 | } 444 | // update metafile size with terser-minified size: 445 | result.metafile!.outputs[ 446 | relative(cwd, file.path) 447 | ].bytes = code.length; 448 | const esmWritten = fs.writeFile(file.path, code); 449 | // const esmWritten = fs.writeFile(file.path, file.contents); 450 | const exportConfig = 451 | (output.entryPoint && 452 | _exports[output.entryPoint]) || 453 | null; 454 | // const exportConfig = _exports[entryPaths[index]]; 455 | const cjsFilename = 456 | exportConfig?.find((m) => { 457 | return ( 458 | m.conditions.includes('require') || 459 | m.conditions.includes('default') || 460 | m.conditions.length === 0 461 | ); 462 | })?.mapped || 463 | file.path.replace( 464 | /\.[mc]?js$/, 465 | extForFormat('cjs') 466 | ); 467 | 468 | const out = new MagicString(code, { 469 | filename: file.path, 470 | }); 471 | const [imports, exports] = await parse( 472 | code, 473 | file.path 474 | ); 475 | // const exports = _exports.slice(); 476 | let beforeStart = 0; 477 | // let wildcard = 0; 478 | const reexports = []; 479 | for (const imp of imports) { 480 | // const exp = exports.find(exp => exp.s >= imp.ss && exp.e <= imp.se); 481 | if (imp.t === 2) { 482 | let req = `require(${code.slice(imp.s, imp.e)})`; 483 | // if this is an `await import()` with no Promise chaining, 484 | // we don't need to wrap it in a Promise - await works fine 485 | // and sync throw in async function gets coerced. 486 | const before = code.slice(beforeStart, imp.ss); 487 | const after = code.slice(imp.se, imp.se + 10); 488 | if ( 489 | !/await\s+/s.test(before) || 490 | !/^\s*[)\],;\n]/.test(after) 491 | ) { 492 | out.overwrite(imp.ss, imp.se, req); 493 | req = `new Promise(r=>r(${req}))`; 494 | } 495 | out.overwrite(imp.ss, imp.se, req); 496 | } else { 497 | const rawSpec = code.substring(imp.s, imp.e); 498 | const spec = JSON.stringify( 499 | rawSpec.replace( 500 | /\.[mc]?js$/, 501 | extForFormat('cjs') 502 | ) 503 | ); 504 | let s = code 505 | .substring(imp.ss + 6, imp.s - 1) 506 | .replace(/\s*from\s*/g, '') 507 | .replace(/\*\s*as\s*/g, '') 508 | .replace(/\s*as\s*/g, ':') 509 | .trim(); 510 | // convert wildcard reexports to `$_wc_$0=require()` for later reexport via 511 | // Object.defineProperties(exports,Object.getOwnPropertyDescriptors($_wc_$0)) 512 | if (s === '*' && code[imp.ss] === 'e') { 513 | s = `$_wc_$${reexports.length}`; 514 | reexports.push(s); 515 | } 516 | const r = `${ 517 | s ? `const ${s} = ` : '' 518 | }require(${spec})`; 519 | out.overwrite(imp.ss, imp.se, r); 520 | } 521 | beforeStart = imp.se; 522 | } 523 | const defaultExport = exports.find( 524 | (p) => p.n === 'default' 525 | ); 526 | const namedExports = exports.filter( 527 | (p) => p.n !== 'default' 528 | ); 529 | const hasNamed = !!namedExports.length; 530 | let suffix: string[] = []; 531 | 532 | if (args.cjs === 'flat' && defaultExport) { 533 | // "flat" mode, where named exports are properties of the default export 534 | suffix.push(`module.exports=${defaultExport.ln}`); 535 | for (const exp of namedExports) { 536 | suffix.push( 537 | `module.exports[${exp.n}]=${exp.ln || exp.n}` 538 | ); 539 | } 540 | } else if ( 541 | defaultExport && 542 | !hasNamed && 543 | args.cjs !== 'default' 544 | ) { 545 | // default-only CJS optimization 546 | suffix.push(`module.exports=${defaultExport.ln}`); 547 | } else { 548 | // mixed default+named, or named-only, or default-as-named 549 | const list = exports.map( 550 | (exp) => 551 | `${exp.n}${ 552 | exp.ln && exp.ln !== exp.n ? ':' + exp.ln : '' 553 | }` 554 | ); 555 | if (list.length) 556 | suffix.push(`module.exports={${list.join(',')}}`); 557 | } 558 | 559 | if (exports[0]) { 560 | out.overwrite( 561 | code.slice(0, exports[0].s).lastIndexOf('export'), 562 | code.indexOf('}', exports.at(-1)!.e) + 1, 563 | suffix.join(';') 564 | ); 565 | } 566 | 567 | if (reexports) { 568 | const mapped = reexports.map( 569 | (exp) => 570 | `Object.defineProperties(module.exports,Object.getOwnPropertyDescriptors(${exp}))` 571 | ); 572 | out.append(`\n${mapped.join(';')}`); 573 | // const descs = reexports.map(exp => `...Object.getOwnPropertyDescriptors(${exp})`); 574 | // out.append(`Object.defineProperties(module.exports,{${descs.join(',')}})`); 575 | } 576 | 577 | const text = out.toString(); 578 | // result.outputFiles!.push({ 579 | // path: cjsFilename, 580 | // text, 581 | // get contents() { 582 | // const value = Buffer.from(text, 'utf-8'); 583 | // Object.defineProperty(this, 'contents', {value}); 584 | // return value; 585 | // }, 586 | // hash: file.hash, 587 | // }); 588 | result.metafile!.outputs[relative(cwd, cjsFilename)] = 589 | { 590 | ...output, 591 | bytes: text.length, 592 | }; 593 | 594 | await fs.writeFile(cjsFilename, text); 595 | await esmWritten; 596 | // await fs.writeFile(file.path, out.toString()); 597 | }) 598 | ); 599 | // console.log(result.outputFiles); 600 | }); 601 | }, 602 | }, 603 | ] 604 | : [], 605 | // outfile: exports['x'], 606 | // plugins: format === 'cjs' ? [ 607 | // { 608 | // name: 'cjs', 609 | // setup(build) { 610 | // build.onLoad({ 611 | // filter: /./, 612 | // namespace: 'customcjs' 613 | // }, (args) => { 614 | // console.log(args); 615 | // return { 616 | // contents: `export default require(${JSON.stringify(args.path)})`, 617 | // loader: 'js' 618 | // }; 619 | // }); 620 | // build.onResolve({ 621 | // filter: new RegExp(`^${pkg.name}(\/|$)`) 622 | // }, (args) => { 623 | // console.log(args); 624 | // return { 625 | // external: true, 626 | // // external: args.namespace === 'customcjs', 627 | // // external: args.kind === 'require-call', 628 | // namespace: 'customcjs', 629 | // path: args.path 630 | // } 631 | // }); 632 | // }, 633 | // } 634 | // ] : [], 635 | }); 636 | }) 637 | ); 638 | 639 | async function cancel() { 640 | clearTimeout(timer); 641 | // @todo - probably fine to not await cancellation 642 | await Promise.all(contexts.map((ctx) => ctx.cancel())); 643 | } 644 | 645 | let isFirst = true; 646 | let timer: NodeJS.Timeout; 647 | let rebuilding = false; 648 | async function runBuild() { 649 | if (rebuilding) await cancel(); 650 | rebuilding = true; 651 | if (isFirst) isFirst = false; 652 | else console.log('rebuilding...'); 653 | const start = performance.now(); 654 | // const terserOpts: import('terser').MinifyOptions = { 655 | // ecma: 2020, 656 | // compress: {unsafe: true}, 657 | // format: {shebang: true, shorthand: true, comments: false}, 658 | // nameCache: mangleCache, 659 | // mangle: { 660 | // properties: { 661 | // regex: /^_/ 662 | // } 663 | // }, 664 | // }; 665 | const results = await Promise.all( 666 | contexts.map(async (ctx, index) => { 667 | const result = await ctx.rebuild(); 668 | if (index === 0) mangleCache = result.mangleCache; 669 | // const format = formats[index]; 670 | // if (format === 'cjs') { 671 | // await Promise.all(result.outputFiles!.map(async file => { 672 | // const code = file.text 673 | // .replace(/\b__toCommonJS\((.*?)\)/g, `Object.defineProperty($1,'__esModule',{value:true})`) 674 | // .replace('var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);', '') 675 | // .replace(/var __copyProps =.*?};/gs, ''); 676 | // const {minify} = await import('terser'); 677 | // await fs.writeFile(file.path, (await minify(code,{compress:true,mangle:false,toplevel:true})).code!); 678 | // // await fs.writeFile(file.path, code); 679 | // })); 680 | // } 681 | 682 | // const format = formats[index]; 683 | // if (args.minify === 'terser') { 684 | // const {minify} = await import('terser'); 685 | // await Promise.all(result.outputFiles!.map(async file => { 686 | // const minified = await minify(file.text, { 687 | // ...terserOpts, 688 | // module: format === 'esm', 689 | // }); 690 | // await fs.writeFile(file.path, minified.code!); 691 | // })); 692 | // } 693 | return result; 694 | }) 695 | ); 696 | const dur = performance.now() - start; 697 | console.log(`built in ${dur | 0}ms:`); 698 | const table: { 699 | name: string; 700 | type: string; 701 | formats: { [k in Format]?: number }; 702 | }[] = []; 703 | for (const result of results) { 704 | const { inputs, outputs } = result.metafile!; 705 | // console.log(outputs); 706 | const exported = new Set(); 707 | for (const exp in exports) { 708 | const spec = posix.join(pkg.name, exp); 709 | const mappings = exports[exp]; 710 | const formats: { [k in Format]?: number } = {}; 711 | for (const mapping of mappings) { 712 | const id = posix.normalize(mapping.mapped); 713 | const output = outputs[id]; 714 | if (!output) continue; 715 | exported.add(id); 716 | const cd = mapping.conditions.join('+'); 717 | const ext = id.match(/[mc]?js$/)?.[0]; 718 | const fallback = args.format 719 | ? args.format 720 | : ext === 'mjs' || (ext === 'js' && pkg.type === 'module') 721 | ? 'esm' 722 | : 'cjs'; 723 | const type = /(import|module)/.test(cd) 724 | ? 'esm' 725 | : /require/.test(cd) 726 | ? 'cjs' 727 | : fallback; 728 | formats[type] = output.bytes; 729 | } 730 | table.push({ 731 | type: 'entry', 732 | name: spec.replace(pkg.name + '/', dim(`${pkg.name}/`)), 733 | formats, 734 | }); 735 | } 736 | const chunks: Record< 737 | string, 738 | { id: string; output: (typeof outputs)[0] }[] 739 | > = {}; 740 | for (const id in outputs) { 741 | if (exported.has(id)) continue; 742 | const output = outputs[id]; 743 | const ep = output.entryPoint || id.replace(/\.[mc]?js$/, ''); 744 | (chunks[ep] || (chunks[ep] = [])).push({ id, output }); 745 | } 746 | for (const ep in chunks) { 747 | const outputs = chunks[ep]; 748 | const formats: { [k in Format]?: number } = {}; 749 | outputs.map(({ id, output }) => { 750 | const ext = id.match(/[mc]?js$/)?.[0]; 751 | const type = 752 | ext === 'mjs' || (ext === 'js' && pkg.type === 'module') 753 | ? 'esm' 754 | : args.format || 'cjs'; 755 | formats[type] = output.bytes; 756 | }); 757 | table.push({ type: 'chunk', name: dim('./') + ep, formats }); 758 | } 759 | } 760 | const flatTable = table.map((item) => { 761 | return [ 762 | item.type === 'entry' 763 | ? `📦 ${brightWhite(item.name)}` 764 | : ` ↳ ${item.name}`, 765 | ...formats.map((format) => prettyBytes(item.formats[format])), 766 | // prettyBytes(item.formats.esm), 767 | // prettyBytes(item.formats.cjs) 768 | ]; 769 | }); 770 | // flatTable.unshift(['', 'esm', 'cjs']); 771 | flatTable.unshift(['', ...formats]); 772 | const widths = flatTable.reduce( 773 | (widths, row) => { 774 | for (let i = 0; i < row.length; i++) { 775 | widths[i] = Math.max(widths[i] || 0, deAnsi(row[i]).length); 776 | } 777 | return widths; 778 | }, 779 | [0] 780 | ); 781 | const text = flatTable 782 | .map((row, index) => { 783 | const text = row 784 | .map((cell, i) => padAnsi(cell, widths[i] + 1)) 785 | .join(' '); 786 | if (index === 0) return bold(brightBlue(text)); 787 | return text; 788 | }) 789 | .join('\n'); 790 | process.stdout.write(text + '\n'); 791 | // const analysis = await Promise.all(results.map(result => analyzeMetafile(result.metafile!, {color:true}))); 792 | // process.stdout.write(analysis + '\n'); 793 | rebuilding = false; 794 | } 795 | 796 | try { 797 | await runBuild(); 798 | } catch (err) { 799 | console.error(err); 800 | for (const ctx of contexts) await ctx.dispose(); 801 | process.exit(1); 802 | } 803 | 804 | if (args.watch) { 805 | for await (const change of fs.watch(args.cwd, { recursive: true })) { 806 | console.log(change); 807 | clearTimeout(timer); 808 | if (rebuilding) cancel(); 809 | timer = setTimeout(() => runBuild().catch(() => {}), 10); 810 | } 811 | } 812 | 813 | process.exit(0); 814 | } 815 | 816 | async function buildAction(entry: string | undefined, opts: BuildArgs) { 817 | opts.entry = entry; 818 | if (!opts.cwd) opts.cwd = process.cwd(); 819 | try { 820 | await build(opts); 821 | } catch (err: any) { 822 | process.stderr.write(err?.message ?? String(err)); 823 | } 824 | } 825 | 826 | const indent = ' '.repeat(22); 827 | const cli = sade('microbundle') 828 | .version('1.0.0') 829 | .option( 830 | '--cjs', 831 | 'Customize CommonJS output:' + 832 | dim(` 833 | • "flat" merges named exports into the default export 834 | • "default" forces require("x").default even when there are no named exports`).replace( 835 | /^\s*/gm, 836 | indent 837 | ) 838 | ) 839 | .option('--minify', 'minify generated code', true) 840 | .option('--cwd', 'run in the given directory (default: $PWD)'); 841 | 842 | cli 843 | .command('build [entry]', dim`Build once and exit`, { default: true }) 844 | .action(buildAction); 845 | 846 | cli 847 | .command('watch [entry]', dim`Build once, then rebuild when files change`) 848 | .action((entry, opts) => buildAction(entry, { ...opts, watch: true })); 849 | 850 | cli.parse(process.argv); 851 | --------------------------------------------------------------------------------