Skip to content

Commit ee1e3a9

Browse files
committed
build: bundle with esbuild
1 parent 235170f commit ee1e3a9

11 files changed

+1580
-207
lines changed

package-lock.json

+1,263-163
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+25-18
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@
4141
"scripts": {
4242
"fmt": "prettier --write .",
4343
"fmt:check": "prettier --check .",
44-
"build": "tsc --project tsconfig.prod.json",
44+
"build": "npm run build:js && npm run build:dts",
4545
"build:check": "tsc",
46+
"build:js": "node scripts/build-js.mjs --format=esm --entry='src/*' && npm run build:vendor",
47+
"build:vendor": "node scripts/build-js.mjs --format=esm --entry='src/vendor.ts' --bundle=all --banner",
48+
"build:dts": "tsc --project tsconfig.prod.json && node scripts/build-dts.mjs",
4649
"test": "npm run build && node ./test/all.test.js",
4750
"test:types": "tsd",
4851
"coverage": "c8 --check-coverage npm test",
@@ -51,29 +54,33 @@
5154
"version": "cat package.json | fx .version"
5255
},
5356
"dependencies": {
54-
"@types/fs-extra": "^11.0.1",
55-
"@types/minimist": "^1.2.2",
56-
"@types/node": "^18.16.3",
57-
"@types/ps-tree": "^1.1.2",
58-
"@types/which": "^3.0.0",
59-
"chalk": "^5.2.0",
60-
"fs-extra": "^11.1.1",
61-
"fx": "*",
62-
"globby": "^13.1.4",
63-
"minimist": "^1.2.8",
64-
"node-fetch": "3.3.1",
65-
"ps-tree": "^1.2.0",
66-
"webpod": "^0",
67-
"which": "^3.0.0",
68-
"yaml": "^2.2.2"
57+
"@types/fs-extra": "^11.0.4",
58+
"@types/node": ">=20.11.19"
6959
},
7060
"devDependencies": {
61+
"@types/ps-tree": "^1.1.6",
62+
"@types/minimist": "^1.2.5",
63+
"@types/which": "^3.0.3",
7164
"@stryker-mutator/core": "^6.4.2",
7265
"c8": "^7.13.0",
73-
"madge": "^6.0.0",
66+
"chalk": "^5.3.0",
67+
"dts-bundle-generator": "^9.3.1",
68+
"esbuild": "^0.20.1",
69+
"esbuild-node-externals": "^1.13.0",
70+
"esbuild-plugin-entry-chunks": "^0.1.8",
71+
"fs-extra": "^11.2.0",
72+
"fx": "*",
73+
"globby": "^14.0.1",
74+
"madge": "^6.1.0",
75+
"minimist": "^1.2.8",
76+
"node-fetch": "3.3.2",
7477
"prettier": "^2.8.8",
78+
"ps-tree": "^1.2.0",
7579
"tsd": "^0.28.1",
76-
"typescript": "^5.0.4"
80+
"typescript": "^5.0.4",
81+
"webpod": "^0",
82+
"which": "^3.0.0",
83+
"yaml": "^2.3.4"
7784
},
7885
"publishConfig": {
7986
"registry": "https://wombat-dressing-room.appspot.com"

scripts/build-dts.mjs

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env node
2+
3+
// Copyright 2024 Google LLC
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// https://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
import fs from 'fs/promises'
18+
import { generateDtsBundle } from 'dts-bundle-generator'
19+
20+
const entry = {
21+
filePath: './src/vendor.ts',
22+
outFile: './build/vendor.d.ts',
23+
libraries: {
24+
allowedTypesLibraries: ['node'], // args['external-types'],
25+
inlinedLibraries: [
26+
'@nodelib/fs.stat',
27+
'@nodelib/fs.scandir',
28+
'@nodelib/fs.walk',
29+
'fast-glob',
30+
'@types/jsonfile',
31+
'node-fetch',
32+
'chalk',
33+
'globby',
34+
'webpod',
35+
'@types/fs-extra',
36+
'@types/minimist',
37+
'@types/ps-tree',
38+
'@types/which',
39+
], // args['external-inlines'],
40+
},
41+
output: {
42+
inlineDeclareExternals: true,
43+
inlineDeclareGlobals: true,
44+
sortNodes: false,
45+
exportReferencedTypes: false, //args['export-referenced-types'],
46+
},
47+
}
48+
49+
const compilationOptions = {
50+
preferredConfigPath: './tsconfig.prod.json', // args.project,
51+
followSymlinks: true,
52+
}
53+
54+
let [result] = generateDtsBundle([entry], compilationOptions)
55+
56+
// generateDtsBundle cannot handle the circular refs on types inlining, so we need to help it manually:
57+
/*
58+
build/vendor.d.ts(163,7): error TS2456: Type alias 'Options' circularly references itself.
59+
build/vendor.d.ts(164,7): error TS2456: Type alias 'Entry' circularly references itself.
60+
build/vendor.d.ts(165,7): error TS2456: Type alias 'Task' circularly references itself.
61+
build/vendor.d.ts(166,7): error TS2456: Type alias 'Pattern' circularly references itself.
62+
build/vendor.d.ts(167,7): error TS2456: Type alias 'FileSystemAdapter' circularly references itself.
63+
build/vendor.d.ts(197,48): error TS2694: Namespace 'FastGlob' has no exported member 'FastGlobOptions
64+
*/
65+
66+
result = result
67+
.replace('type Options = Options;', 'export {Options};')
68+
.replace('type Task = Task;', 'export {Task};')
69+
.replace('type Pattern = Pattern;', 'export {Pattern};')
70+
.replace('FastGlob.FastGlobOptions', 'FastGlob.Options')
71+
.replace('type Entry =', 'export type Entry =')
72+
73+
await fs.writeFile(entry.outFile, result, 'utf8')
74+
75+
process.exit(0)

scripts/build-js.mjs

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env node
2+
3+
// Copyright 2024 Google LLC
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// https://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
import path from 'node:path'
18+
import esbuild from 'esbuild'
19+
import { nodeExternalsPlugin } from 'esbuild-node-externals'
20+
import { entryChunksPlugin } from 'esbuild-plugin-entry-chunks'
21+
import minimist from 'minimist'
22+
import glob from 'fast-glob'
23+
24+
const argv = minimist(process.argv.slice(2), {
25+
default: {
26+
entry: './src/index.ts',
27+
external: 'node:*',
28+
bundle: 'src', // 'all' | 'none'
29+
license: 'eof',
30+
minify: false,
31+
sourcemap: false,
32+
format: 'cjs,esm',
33+
cwd: process.cwd(),
34+
},
35+
boolean: ['minify', 'sourcemap', 'banner'],
36+
string: ['entry', 'external', 'bundle', 'license', 'format', 'map', 'cwd'],
37+
})
38+
const {
39+
entry,
40+
external,
41+
bundle,
42+
minify,
43+
sourcemap,
44+
license,
45+
format,
46+
cwd: _cwd,
47+
} = argv
48+
49+
const plugins = []
50+
const cwd = Array.isArray(_cwd) ? _cwd[_cwd.length - 1] : _cwd
51+
const entryPoints = entry.includes('*')
52+
? await glob(entry.split(/,\s?/), { absolute: false, onlyFiles: true, cwd })
53+
: entry.split(/,\s?/).map((e) => path.resolve(cwd, e))
54+
55+
const _bundle = bundle !== 'none' && !process.argv.includes('--no-bundle')
56+
const _external = _bundle ? external.split(',') : undefined // https://github.com/evanw/esbuild/issues/1466
57+
58+
if (_bundle && entryPoints.length > 1) {
59+
plugins.push(entryChunksPlugin())
60+
}
61+
62+
if (bundle === 'src') {
63+
// https://github.com/evanw/esbuild/issues/619
64+
// https://github.com/pradel/esbuild-node-externals/pull/52
65+
plugins.push(nodeExternalsPlugin())
66+
}
67+
68+
const formats = format.split(',')
69+
const banner =
70+
argv.banner && bundle === 'all'
71+
? {
72+
js: `
73+
const require = (await import("node:module")).createRequire(import.meta.url);
74+
const __filename = (await import("node:url")).fileURLToPath(import.meta.url);
75+
const __dirname = (await import("node:path")).dirname(__filename);
76+
`,
77+
}
78+
: {}
79+
80+
const esmConfig = {
81+
absWorkingDir: cwd,
82+
entryPoints,
83+
outdir: './build',
84+
bundle: _bundle,
85+
external: _external,
86+
minify,
87+
sourcemap,
88+
sourcesContent: false,
89+
platform: 'node',
90+
target: 'esnext',
91+
format: 'esm',
92+
outExtension: {
93+
// '.js': '.mjs'
94+
},
95+
plugins,
96+
legalComments: license,
97+
tsconfig: './tsconfig.json',
98+
//https://github.com/evanw/esbuild/issues/1921
99+
banner,
100+
}
101+
102+
const cjsConfig = {
103+
...esmConfig,
104+
outdir: './build',
105+
target: 'es6',
106+
format: 'cjs',
107+
banner: {},
108+
outExtension: {
109+
// '.js': '.cjs'
110+
},
111+
}
112+
113+
for (const format of formats) {
114+
const config = format === 'cjs' ? cjsConfig : esmConfig
115+
116+
await esbuild.build(config).catch(() => process.exit(1))
117+
}
118+
119+
process.exit(0)

src/cli.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414
// See the License for the specific language governing permissions and
1515
// limitations under the License.
1616

17-
import fs from 'fs-extra'
18-
import minimist from 'minimist'
1917
import { createRequire } from 'node:module'
2018
import { basename, dirname, extname, join, resolve } from 'node:path'
2119
import url from 'node:url'
22-
import { updateArgv } from './goods.js'
23-
import { $, chalk, fetch, ProcessOutput } from './index.js'
20+
import {
21+
$,
22+
ProcessOutput,
23+
updateArgv,
24+
fetch,
25+
chalk,
26+
minimist,
27+
fs,
28+
} from './index.js'
2429
import { startRepl } from './repl.js'
2530
import { randomId } from './util.js'
2631
import { installDeps, parseDeps } from './deps.js'

src/core.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ import { ChildProcess, spawn, StdioNull, StdioPipe } from 'node:child_process'
1717
import { AsyncLocalStorage, createHook } from 'node:async_hooks'
1818
import { Readable, Writable } from 'node:stream'
1919
import { inspect } from 'node:util'
20-
import { RequestInfo, RequestInit } from 'node-fetch'
21-
import chalk, { ChalkInstance } from 'chalk'
22-
import which from 'which'
20+
import {
21+
chalk,
22+
which,
23+
type ChalkInstance,
24+
RequestInfo,
25+
RequestInit,
26+
} from './vendor.js'
2327
import {
2428
Duration,
2529
errnoMessage,

src/goods.ts

+14-15
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,21 @@
1313
// limitations under the License.
1414

1515
import assert from 'node:assert'
16-
import * as globbyModule from 'globby'
17-
import minimist from 'minimist'
18-
import nodeFetch, { RequestInfo, RequestInit } from 'node-fetch'
1916
import { createInterface } from 'node:readline'
2017
import { $, within, ProcessOutput } from './core.js'
21-
import { Duration, isString, parseDuration } from './util.js'
22-
import chalk from 'chalk'
18+
import { type Duration, isString, parseDuration } from './util.js'
19+
import {
20+
chalk,
21+
minimist,
22+
globbyModule,
23+
GlobbyOptions,
24+
nodeFetch,
25+
RequestInfo,
26+
RequestInit,
27+
} from './vendor.js'
2328

24-
export { default as chalk } from 'chalk'
25-
export { default as fs } from 'fs-extra'
26-
export { default as which } from 'which'
27-
export { default as minimist } from 'minimist'
28-
export { default as YAML } from 'yaml'
2929
export { default as path } from 'node:path'
30-
export { default as os } from 'node:os'
31-
export { ssh } from 'webpod'
30+
export * as os from 'node:os'
3231

3332
export let argv = minimist(process.argv.slice(2))
3433
export function updateArgv(args: string[]) {
@@ -38,11 +37,11 @@ export function updateArgv(args: string[]) {
3837

3938
export const globby = Object.assign(function globby(
4039
patterns: string | readonly string[],
41-
options?: globbyModule.Options
40+
options?: GlobbyOptions
4241
) {
4342
return globbyModule.globby(patterns, options)
4443
},
45-
globbyModule)
44+
globbyModule) as (typeof globbyModule)['globby'] & typeof globbyModule
4645
export const glob = globby
4746

4847
export function sleep(duration: Duration) {
@@ -198,7 +197,7 @@ export async function spinner<T>(
198197
try {
199198
result = await callback!()
200199
} finally {
201-
clearInterval(id)
200+
clearInterval(id as NodeJS.Timeout)
202201
process.stderr.write(' '.repeat(process.stdout.columns - 1) + '\r')
203202
}
204203
return result

src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import { ProcessPromise } from './core.js'
1616

1717
export * from './core.js'
1818
export * from './goods.js'
19+
export { minimist, chalk, fs, which, YAML, ssh } from './vendor.js'
1920

20-
export { Duration, quote, quotePowerShell } from './util.js'
21+
export { type Duration, quote, quotePowerShell } from './util.js'
2122

2223
/**
2324
* @deprecated Use $.nothrow() instead.

src/util.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import chalk from 'chalk'
1615
import { promisify } from 'node:util'
17-
import psTreeModule from 'ps-tree'
16+
import { chalk, psTreeModule } from './vendor.js'
1817

1918
export const psTree = promisify(psTreeModule)
2019

0 commit comments

Comments
 (0)