This repository has been archived by the owner on Mar 28, 2023. It is now read-only.
/
pack.ts
158 lines (157 loc) · 7.15 KB
/
pack.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import { program } from 'commander'
import fg from 'fast-glob'
import { execaCommand } from 'execa'
import { build, BuildOptions } from 'esbuild'
import pAll from 'p-all'
import log from '@techor/log'
import path from 'path'
import { readPackage } from '../utils/read-package'
import { changeFilePath } from '../utils/change-file-path'
import literal from '@master/literal'
import type { PackageJson } from 'pkg-types'
import prettyBytes from 'pretty-bytes'
import normalizePath from 'normalize-path'
program.command('pack [entryPaths...]')
.allowUnknownOption()
.option('-f, --format [formats...]', 'The output format for the generated JavaScript files `iife`, `cjs`, `esm`')
.option('-b, --bundle', 'To bundle a file means to inline any imported dependencies into the file itself')
.option('-m, --minify', 'The generated code will be minified instead of pretty-printed')
.option('-w, --watch', 'Rebuild whenever a file changes')
.option('-s, --sourcemap', 'Emit a source map')
.option('-p, --platform <node,browser,neutral>', 'Platform target')
.option('-t, --type', 'Emit typescript declarations')
.option('-o, --outdir <dir>', 'The output directory for the build operation')
.option('--srcdir <dir>', 'The source directory')
.action(async function (entries: string[]) {
const pkg: PackageJson = readPackage()
const { dependencies, peerDependencies } = pkg
const pkgEntry = pkg.main || pkg.module || pkg.browser
/** Extract external dependencies to prevent bundling */
const externalDependencies = []
dependencies && externalDependencies.push(...Object.keys(dependencies))
peerDependencies && externalDependencies.push(...Object.keys(peerDependencies))
const options = Object.assign({
format: '',
watch: false,
minify: true,
bundle: true,
type: !!pkg.types,
srcdir: 'src',
outdir: path.dirname(pkgEntry) || 'dist',
externals: externalDependencies
}, this.opts())
const tasks = []
const event = options.watch ? 'watching' : 'building'
const addBuildTask = async (eachEntries: string[], eachFormat: string, eachExt?: string) => {
const eachEntryPoints = fg.sync(
[...new Set(eachEntries)].map((eachEntry) => normalizePath(eachEntry))
)
const isCSSTask = eachFormat === 'css'
if (!eachEntryPoints.length) {
log.e`[${eachFormat}] Cannot find any entry file specified **${eachEntries}**`
process.exit()
}
tasks.push(
async () => {
const { metafile } = await build({
entryPoints: eachEntryPoints,
outExtension: isCSSTask
? { '.css': '.css' }
: {
'.js': eachExt
? eachExt
: { cjs: '.cjs', esm: '.mjs', iife: '.js' }[eachFormat]
},
external: externalDependencies,
watch: options.watch ? {
onRebuild(error, result) {
// make esbuild log mute and depend on `tsx`
}
} : false,
logLevel: 'silent',
outdir: options.outdir,
bundle: options.bundle,
minify: options.minify,
sourcemap: options.sourcemap,
platform: options.platform,
metafile: true,
format: isCSSTask ? undefined : eachFormat
} as BuildOptions)
if (metafile) {
for (const outputFilePath in metafile.outputs) {
const eachOutput = metafile.outputs[outputFilePath]
log.i`[${eachFormat}] **${outputFilePath}** ++${prettyBytes(eachOutput.bytes)}++ ..${Object.keys(eachOutput.inputs)?.length}.. ..inputs..`
}
}
}
)
}
let formats: string[] = []
if (entries.length) {
const isCSSEntry = entries.find((eachEntry) => eachEntry.includes('.css'))
if (isCSSEntry) {
addBuildTask(entries, 'css')
formats.push('css')
} else {
formats = (options.format || 'cjs,esm').split(',')
formats.map((eachFormat: string) => addBuildTask(entries, eachFormat))
}
} else {
if (pkg.style) {
addBuildTask([changeFilePath(pkg.main, options.srcdir, '.css')], 'css')
formats.push('css')
}
if (pkg.main && !pkg.main.endsWith('.css')) {
addBuildTask([changeFilePath(pkg.main, options.srcdir, '.{tsx,ts}')], 'cjs', path.extname(pkg.main))
formats.push('cjs')
}
if (pkg.module) {
addBuildTask([changeFilePath(pkg.module, options.srcdir, '.{tsx,ts}')], 'esm', path.extname(pkg.module))
formats.push('esm')
}
if (pkg.browser) {
addBuildTask([changeFilePath(pkg.browser, options.srcdir, '.{tsx,ts}')], 'iife', path.extname(pkg.browser))
formats.push('iife')
}
if (!tasks.length) {
formats = (options.format || 'cjs,esm').split(',')
formats.map((eachFormat: string) => addBuildTask([path.join(options.srcdir, 'index.ts')], eachFormat))
}
}
options.format = formats.join(',')
if (options.type) {
tasks.push(
() => new Promise<void>((resolve) => {
if (options.watch) {
log.i`[type] type declarations`
}
execaCommand(literal`
npx tsc --emitDeclarationOnly --preserveWatchOutput --declaration
--outDir ${options.outdir}
${options.watch && '--watch'}
`, {
stdio: 'inherit',
stripFinalNewline: false
})
.catch((reason) => {
process.exit()
})
.finally(() => {
log.i`[type] type declarations`
resolve()
})
})
)
}
if (Object.keys(pkg).length) {
log`📦 extract and merge options from +${pkg.name}+ **package.json**`
}
log.tree(options)
const formatLogText = formats.join(', ').toUpperCase() + (options.type ? ', Type Declarations' : '')
log.info`[${event}] ${options.watch ? ' ' : formatLogText}`
await pAll(tasks)
if (!options.watch) {
log.success`${formatLogText} ..${options.outdir}..`
console.log('')
}
})