├── .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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------