external
CLI
支持两种功能:
- tsbuild: 构建除了
dependencies
和peerDependencies
外的依赖; - tsbuild-node: 除了不构建
dependencies
和peerDependencies
的依赖外,还可以跳过所有的 Node.js 依赖包;
除了以上默认的使用外,还可以使用--external
避免打特定的包。
tsbuild 与 tsbuild-node
其中,package.json
中指定特定 CLI 入口:
"bin": {
"tsbuild": "dist/cli-default.js",
"tsbuild-node": "dist/cli-node.js"
}
两者区别在于tsbuild-node
传入了以下 option:
skipNodeModulesBundle: true,
此处因为esbuild
的external
不支持正则,创建externalPlugin
。
import { Plugin } from 'esbuild'
import { match, tsconfigPathsToRegExp } from 'bundle-require'
// Must not start with "/" or "./" or "../" or "C:\" or be the exact strings ".." or "."
const NON_NODE_MODULE_RE = /^[A-Z]:[\\/]|^\.{0,2}\/|^\.{1,2}$/
export function externalPlugin({
external,
noExternal,
skipNodeModulesBundle,
tsconfigResolvePaths,
}: {
external?: (string | RegExp)[]
noExternal?: (string | RegExp)[]
skipNodeModulesBundle?: boolean
tsconfigResolvePaths?: Record<string, string[]>
}): Plugin {
return {
name: `external`,
setup(build) {
if (skipNodeModulesBundle) {
const resolvePatterns = tsconfigPathsToRegExp(tsconfigResolvePaths || {})
build.onResolve({ filter: /.*/ }, (args) => {
// Resolve `paths` from tsconfig
if (match(args.path, resolvePatterns)) {
return
}
// Respect explicit external/noExternal conditions
if (match(args.path, noExternal)) {
return
}
if (match(args.path, external)) {
return { external: true }
}
// Exclude any other import that looks like a Node module
if (!NON_NODE_MODULE_RE.test(args.path)) {
return {
path: args.path,
external: true,
}
}
})
}
else {
build.onResolve({ filter: /.*/ }, (args) => {
// Respect explicit external/noExternal conditions
if (match(args.path, noExternal)) {
return
}
if (match(args.path, external)) {
return { external: true }
}
})
}
},
}
}
其中,针对skipNodeModulesBundle
为true
的情况下,额外过滤tsconfig
中所有path
的文件
const resolvePatterns = tsconfigPathsToRegExp(tsconfigResolvePaths || {})
if (match(args.path, resolvePatterns)) {
}
过滤 dependencies/peerDependencies 与 external 依赖
- 获取对应依赖
const external = [
// Exclude dependencies, e.g. `lodash`, `lodash/get`
...deps.map((dep) => new RegExp(`^${dep}($|\\/|\\\\)`)),
...(await generateExternal(options.external || [])),
- 获取要
external
的依赖项,先处理options.external
选项中的依赖,处理完后默认过滤线上依赖项
/**
* Support to exclude special package.json
*/
async function generateExternal(external: (string | RegExp)[]) {
const result: (string | RegExp)[] = []
for (const item of external) {
if (typeof item !== 'string' || !item.endsWith('package.json')) {
result.push(item)
continue
}
const pkgPath: string = path.isAbsolute(item)
? path.dirname(item)
: path.dirname(path.resolve(process.cwd(), item))
const deps = await getProductionDeps(pkgPath)
result.push(...deps)
}
return result
}
- 获取
dependencies
和peerDependencies
/*
* Production deps should be excluded from the bundle
*/
export async function getProductionDeps(cwd: string, clearCache: boolean = false) {
const data = await loadPkg(cwd, clearCache)
const deps = Array.from(
new Set([...Object.keys(data.dependencies || {}), ...Object.keys(data.peerDependencies || {})]),
)
return deps
}