Skip to content

Commit

Permalink
build: generate js and dts bundles (#722)
Browse files Browse the repository at this point in the history
* ci: enable autotests for all pushes

* build: bundle with esbuild

* test: ignore vendor.js coverage

* fix: use vendor chunk for repl

* build(dts): rm redundant triple slashes

* build: move external typings to optional deps

relates #712

* chore(core): relax Options type

* perf: replace node-fetch with node-fetch-native

* build: turn off minify
  • Loading branch information
antongolub committed Feb 24, 2024
1 parent 906786a commit 2acb0f2
Show file tree
Hide file tree
Showing 13 changed files with 1,609 additions and 212 deletions.
1,440 changes: 1,277 additions & 163 deletions package-lock.json

Large diffs are not rendered by default.

49 changes: 29 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,39 +41,48 @@
"scripts": {
"fmt": "prettier --write .",
"fmt:check": "prettier --check .",
"build": "tsc --project tsconfig.prod.json",
"build": "npm run build:js && npm run build:dts",
"build:check": "tsc",
"build:js": "node scripts/build-js.mjs --format=esm --entry=src/*.ts && npm run build:vendor",
"build:vendor": "node scripts/build-js.mjs --format=esm --entry=src/vendor.ts --bundle=all --banner",
"build:dts": "tsc --project tsconfig.prod.json && node scripts/build-dts.mjs",
"test": "npm run build && node ./test/all.test.js",
"test:types": "tsd",
"coverage": "c8 --check-coverage npm test",
"coverage": "c8 -x build/vendor.js -x 'test/**' -x scripts --check-coverage npm test",
"mutation": "stryker run",
"circular": "madge --circular src/*",
"version": "cat package.json | fx .version"
},
"dependencies": {
"@types/fs-extra": "^11.0.1",
"@types/minimist": "^1.2.2",
"@types/node": "^18.16.3",
"@types/ps-tree": "^1.1.2",
"@types/which": "^3.0.0",
"chalk": "^5.2.0",
"fs-extra": "^11.1.1",
"fx": "*",
"globby": "^13.1.4",
"minimist": "^1.2.8",
"node-fetch": "3.3.1",
"ps-tree": "^1.2.0",
"webpod": "^0",
"which": "^3.0.0",
"yaml": "^2.2.2"
"optionalDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": ">=20.11.19"
},
"devDependencies": {
"@stryker-mutator/core": "^6.4.2",
"@types/fs-extra": "^11.0.4",
"@types/minimist": "^1.2.5",
"@types/node": ">=20.11.19",
"@types/ps-tree": "^1.1.6",
"@types/which": "^3.0.3",
"c8": "^7.13.0",
"madge": "^6.0.0",
"chalk": "^5.3.0",
"dts-bundle-generator": "^9.3.1",
"esbuild": "^0.20.1",
"esbuild-node-externals": "^1.13.0",
"esbuild-plugin-entry-chunks": "^0.1.8",
"fs-extra": "^11.2.0",
"fx": "*",
"globby": "^14.0.1",
"madge": "^6.1.0",
"minimist": "^1.2.8",
"node-fetch-native": "^1.6.2",
"prettier": "^2.8.8",
"ps-tree": "^1.2.0",
"tsd": "^0.28.1",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"webpod": "^0",
"which": "^3.0.0",
"yaml": "^2.3.4"
},
"publishConfig": {
"registry": "https://wombat-dressing-room.appspot.com"
Expand Down
86 changes: 86 additions & 0 deletions scripts/build-dts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env node

// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import fs from 'fs/promises'
import { generateDtsBundle } from 'dts-bundle-generator'
import glob from 'fast-glob'

const entry = {
filePath: './src/vendor.ts',
outFile: './build/vendor.d.ts',
libraries: {
allowedTypesLibraries: ['node'], // args['external-types'],
inlinedLibraries: [
'@nodelib/fs.stat',
'@nodelib/fs.scandir',
'@nodelib/fs.walk',
'fast-glob',
'@types/jsonfile',
'node-fetch-native',
'chalk',
'globby',
'webpod',
'@types/fs-extra',
'@types/minimist',
'@types/ps-tree',
'@types/which',
], // args['external-inlines'],
},
output: {
inlineDeclareExternals: true,
inlineDeclareGlobals: true,
sortNodes: false,
exportReferencedTypes: false, //args['export-referenced-types'],
},
}

const compilationOptions = {
preferredConfigPath: './tsconfig.prod.json', // args.project,
followSymlinks: true,
}

let [result] = generateDtsBundle([entry], compilationOptions)

// generateDtsBundle cannot handle the circular refs on types inlining, so we need to help it manually:
/*
build/vendor.d.ts(163,7): error TS2456: Type alias 'Options' circularly references itself.
build/vendor.d.ts(164,7): error TS2456: Type alias 'Entry' circularly references itself.
build/vendor.d.ts(165,7): error TS2456: Type alias 'Task' circularly references itself.
build/vendor.d.ts(166,7): error TS2456: Type alias 'Pattern' circularly references itself.
build/vendor.d.ts(167,7): error TS2456: Type alias 'FileSystemAdapter' circularly references itself.
build/vendor.d.ts(197,48): error TS2694: Namespace 'FastGlob' has no exported member 'FastGlobOptions
*/

result = result
.replace('type Options = Options;', 'export {Options};')
.replace('type Task = Task;', 'export {Task};')
.replace('type Pattern = Pattern;', 'export {Pattern};')
.replace('FastGlob.FastGlobOptions', 'FastGlob.Options')
.replace('type Entry =', 'export type Entry =')

await fs.writeFile(entry.outFile, result, 'utf8')

// Replaces redundant triple-slash directives
for (const dts of await glob(['build/**/*.d.ts', '!build/vendor.d.ts'])) {
const contents = (await fs.readFile(dts, 'utf8'))
.split('\n')
.filter((line) => !line.startsWith('/// <reference types'))
.join('\n')

await fs.writeFile(dts, contents, 'utf8')
}

process.exit(0)
123 changes: 123 additions & 0 deletions scripts/build-js.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env node

// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import path from 'node:path'
import esbuild from 'esbuild'
import { nodeExternalsPlugin } from 'esbuild-node-externals'
import { entryChunksPlugin } from 'esbuild-plugin-entry-chunks'
import minimist from 'minimist'
import glob from 'fast-glob'

const argv = minimist(process.argv.slice(2), {
default: {
entry: './src/index.ts',
external: 'node:*',
bundle: 'src', // 'all' | 'none'
license: 'eof',
minify: false,
sourcemap: false,
format: 'cjs,esm',
cwd: process.cwd(),
},
boolean: ['minify', 'sourcemap', 'banner'],
string: ['entry', 'external', 'bundle', 'license', 'format', 'map', 'cwd'],
})
const {
entry,
external,
bundle,
minify,
sourcemap,
license,
format,
cwd: _cwd,
} = argv

const plugins = []
const cwd = Array.isArray(_cwd) ? _cwd[_cwd.length - 1] : _cwd
const entries = entry.split(/,\s?/)
const entryPoints = entry.includes('*')
? await glob(entries, { absolute: false, onlyFiles: true, cwd, root: cwd })
: entries.map((p) => path.relative(cwd, path.resolve(cwd, p)))

console.log('cwd=', cwd)
console.log('entryPoints=', entryPoints)

const _bundle = bundle !== 'none' && !process.argv.includes('--no-bundle')
const _external = _bundle ? external.split(',') : undefined // https://github.com/evanw/esbuild/issues/1466

if (_bundle && entryPoints.length > 1) {
plugins.push(entryChunksPlugin())
}

if (bundle === 'src') {
// https://github.com/evanw/esbuild/issues/619
// https://github.com/pradel/esbuild-node-externals/pull/52
plugins.push(nodeExternalsPlugin())
}

const formats = format.split(',')
const banner =
argv.banner && bundle === 'all'
? {
js: `
const require = (await import("node:module")).createRequire(import.meta.url);
const __filename = (await import("node:url")).fileURLToPath(import.meta.url);
const __dirname = (await import("node:path")).dirname(__filename);
`,
}
: {}

const esmConfig = {
absWorkingDir: cwd,
entryPoints,
outdir: './build',
bundle: _bundle,
external: _external,
minify,
sourcemap,
sourcesContent: false,
platform: 'node',
target: 'esnext',
format: 'esm',
outExtension: {
// '.js': '.mjs'
},
plugins,
legalComments: license,
tsconfig: './tsconfig.json',
//https://github.com/evanw/esbuild/issues/1921
banner,
}

const cjsConfig = {
...esmConfig,
outdir: './build',
target: 'es6',
format: 'cjs',
banner: {},
outExtension: {
// '.js': '.cjs'
},
}

for (const format of formats) {
const config = format === 'cjs' ? cjsConfig : esmConfig

await esbuild.build(config).catch(() => process.exit(1))
}

process.exit(0)
13 changes: 9 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import fs from 'fs-extra'
import minimist from 'minimist'
import { createRequire } from 'node:module'
import { basename, dirname, extname, join, resolve } from 'node:path'
import url from 'node:url'
import { updateArgv } from './goods.js'
import { $, chalk, fetch, ProcessOutput } from './index.js'
import {
$,
ProcessOutput,
updateArgv,
fetch,
chalk,
minimist,
fs,
} from './index.js'
import { startRepl } from './repl.js'
import { randomId } from './util.js'
import { installDeps, parseDeps } from './deps.js'
Expand Down
12 changes: 8 additions & 4 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ import { ChildProcess, spawn, StdioNull, StdioPipe } from 'node:child_process'
import { AsyncLocalStorage, createHook } from 'node:async_hooks'
import { Readable, Writable } from 'node:stream'
import { inspect } from 'node:util'
import { RequestInfo, RequestInit } from 'node-fetch'
import chalk, { ChalkInstance } from 'chalk'
import which from 'which'
import {
chalk,
which,
type ChalkInstance,
RequestInfo,
RequestInit,
} from './vendor.js'
import {
Duration,
errnoMessage,
Expand All @@ -39,7 +43,7 @@ export type Shell = (

const processCwd = Symbol('processCwd')

export type Options = {
export interface Options {
[processCwd]: string
cwd?: string
verbose: boolean
Expand Down
29 changes: 14 additions & 15 deletions src/goods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,21 @@
// limitations under the License.

import assert from 'node:assert'
import * as globbyModule from 'globby'
import minimist from 'minimist'
import nodeFetch, { RequestInfo, RequestInit } from 'node-fetch'
import { createInterface } from 'node:readline'
import { $, within, ProcessOutput } from './core.js'
import { Duration, isString, parseDuration } from './util.js'
import chalk from 'chalk'
import { type Duration, isString, parseDuration } from './util.js'
import {
chalk,
minimist,
globbyModule,
GlobbyOptions,
nodeFetch,
RequestInfo,
RequestInit,
} from './vendor.js'

export { default as chalk } from 'chalk'
export { default as fs } from 'fs-extra'
export { default as which } from 'which'
export { default as minimist } from 'minimist'
export { default as YAML } from 'yaml'
export { default as path } from 'node:path'
export { default as os } from 'node:os'
export { ssh } from 'webpod'
export * as os from 'node:os'

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

export const globby = Object.assign(function globby(
patterns: string | readonly string[],
options?: globbyModule.Options
options?: GlobbyOptions
) {
return globbyModule.globby(patterns, options)
},
globbyModule)
globbyModule) as (typeof globbyModule)['globby'] & typeof globbyModule
export const glob = globby

export function sleep(duration: Duration) {
Expand Down Expand Up @@ -198,7 +197,7 @@ export async function spinner<T>(
try {
result = await callback!()
} finally {
clearInterval(id)
clearInterval(id as NodeJS.Timeout)
process.stderr.write(' '.repeat(process.stdout.columns - 1) + '\r')
}
return result
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import { ProcessPromise } from './core.js'

export * from './core.js'
export * from './goods.js'
export { minimist, chalk, fs, which, YAML, ssh } from './vendor.js'

export { Duration, quote, quotePowerShell } from './util.js'
export { type Duration, quote, quotePowerShell } from './util.js'

/**
* @deprecated Use $.nothrow() instead.
Expand Down
Loading

0 comments on commit 2acb0f2

Please sign in to comment.