mirror of
https://github.com/bytedance/xgplayer.git
synced 2025-04-05 03:05:02 +08:00
394 lines
12 KiB
JavaScript
394 lines
12 KiB
JavaScript
const path = require('path')
|
|
const fs = require('fs-extra')
|
|
const walk = require('klaw-sync')
|
|
const execa = require('execa')
|
|
const { prompt } = require('enquirer')
|
|
const { cyan, green, blue, bold } = require('colorette')
|
|
const zlib = require('zlib')
|
|
const { Project, ts } = require('ts-morph')
|
|
const brotli = require('brotli-size')
|
|
const ui = require('cliui')({ width: process.stdout.columns || 80 })
|
|
const { build: viteBuild } = require('vite')
|
|
const ctx = require('../../context')
|
|
const { getUmdName, getJsEntry, getUmdGlobals, getEsBuildConfig, getBuildConfig, splitSubArrays } = require('../../utils')
|
|
const os = require('os')
|
|
const numCPUs = os.cpus().length
|
|
|
|
const { isMainThread, parentPort, Worker } = require('worker_threads')
|
|
|
|
|
|
if (!isMainThread) {
|
|
parentPort.once('message', async (target) => {
|
|
try {
|
|
await build(target)
|
|
} catch (e) {
|
|
console.error(e.message)
|
|
parentPort.postMessage('failed')
|
|
}
|
|
parentPort.postMessage('finished')
|
|
})
|
|
}
|
|
|
|
const buildInWorker = async (target) => {
|
|
let resolve, reject
|
|
const result = new Promise((res, rej) => {
|
|
resolve = res
|
|
reject = rej
|
|
})
|
|
|
|
const worker = new Worker(path.resolve(__dirname, './index.js'))
|
|
worker.postMessage(target)
|
|
worker.on('message', (result) => {
|
|
worker.terminate()
|
|
if (result === 'failed') {
|
|
reject(new Error(target + ' build failed'))
|
|
} else {
|
|
resolve()
|
|
|
|
}
|
|
|
|
})
|
|
return result
|
|
}
|
|
|
|
async function buildAll () {
|
|
const pkgNames = splitSubArrays(await ctx.getPkgDeps(), Math.ceil(numCPUs / 2))
|
|
|
|
for (const curPkgs of pkgNames) {
|
|
try {
|
|
await Promise.all(curPkgs.map(buildInWorker))
|
|
|
|
} catch (e) {
|
|
console.error(e.message)
|
|
// process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function build (target, { all } = { all: false }) {
|
|
|
|
if (!target && all) {
|
|
return buildAll()
|
|
}
|
|
|
|
if (!target && ctx.isMonorepo) {
|
|
const ret = await prompt({
|
|
type: 'autocomplete',
|
|
name: 'name',
|
|
message: 'Select package',
|
|
choices: Object.values(ctx.pkgs).map(x => x.dir)
|
|
})
|
|
target = ret.name
|
|
if (!target) throw new Error('pkg name is required')
|
|
}
|
|
const pkgDir = ctx.isMonorepo ? path.resolve(ctx.pkgsPath, target) : ctx.rootPath
|
|
const pkgJsonDir = path.resolve(pkgDir, 'package.json')
|
|
const pkgInfo = fs.readJsonSync(pkgJsonDir)
|
|
const config = ctx.getConfig(pkgInfo)
|
|
const distDir = path.resolve(pkgDir, 'dist')
|
|
const esDir = path.resolve(pkgDir, 'es')
|
|
|
|
const isTs = ctx.isTs(pkgDir)
|
|
const [entryEs, entryUmd] = getJsEntry(pkgDir, isTs)
|
|
|
|
const dependencies = Object.keys(pkgInfo.dependencies || {})
|
|
if (
|
|
config.legacy && config.legacy.enabled && config.legacy.needPolyfills && !dependencies.includes('core-js')
|
|
) {
|
|
dependencies.push('core-js')
|
|
}
|
|
const peerDependencies = Object.keys(pkgInfo.peerDependencies || {})
|
|
const peerExternal = peerDependencies.map(x => new RegExp(`^${x}(?:/[^/\\\\]+)*$`))
|
|
const allExternal = peerExternal.concat(dependencies.map(x => new RegExp(`^${x}(?:/[^/\\\\]+)*$`)))
|
|
|
|
fs.removeSync(distDir)
|
|
fs.removeSync(esDir)
|
|
|
|
const banner = ctx.getBanner(config, target, pkgInfo)
|
|
const replace = ctx.getReplace(config, pkgInfo, false)
|
|
process.env.BROWSERSLIST_CONFIG = pkgInfo.browserslist ? pkgJsonDir : ctx.rootPkgPath
|
|
|
|
if (config.buildFormats !== false) {
|
|
const formats = config.buildFormats = config.buildFormats || []
|
|
|
|
if (formats.length === 1) {
|
|
formats[0].output = formats[0].output || 'index.min.js'
|
|
formats[0].outputCss = formats[0].outputCss || 'index.min.css'
|
|
}
|
|
|
|
if (!formats.length) {
|
|
formats.push({
|
|
format: 'umd',
|
|
input: entryUmd,
|
|
output: 'index.min.js',
|
|
outputCss: 'index.min.css'
|
|
})
|
|
}
|
|
|
|
await Promise.all(formats.map(async f => {
|
|
if (typeof f === 'string') {
|
|
f = { format: f, output: `index.${f}.min.js` }
|
|
}
|
|
f.format = f.format || 'umd'
|
|
const isGlobal = f.format === 'umd' || f.format === 'iife'
|
|
f.input = f.input || (isGlobal ? entryUmd : entryEs)
|
|
f.name = f.name || isGlobal ? getUmdName(pkgInfo, config) : undefined
|
|
f.output = f.output || `index.${f.format}.min.js`
|
|
f.outputCss = f.outputCss || `index.${f.format}.min.css`
|
|
const external = f.browser === false ? allExternal : peerExternal
|
|
|
|
const buildFormat = async (dev, filename) => {
|
|
let cfg = await getBuildConfig(false, {
|
|
legacy: f.legacy ?? config.legacy.enabled,
|
|
needPolyfills: f.needPolyfills ?? config.legacy.needPolyfills,
|
|
babelExclude: config.legacy.exclude,
|
|
replace: { ...replace, __DEV__: dev },
|
|
banner,
|
|
cssPreprocessorOptions: config.cssPreprocessorOptions,
|
|
plugins: config.plugins,
|
|
externals: config.externals,
|
|
visualizer: config.visualizer
|
|
})
|
|
cfg.build.minify = 'terser'
|
|
cfg.build.terserOptions = { module: f.format === 'es', output: { comments: false } }
|
|
cfg.build.outDir = distDir
|
|
cfg.build.sourcemap = !!config.prodSourceMap
|
|
cfg.build.lib = {
|
|
entry: f.input,
|
|
name: f.name,
|
|
fileName: () => filename || f.output,
|
|
formats: [f.format]
|
|
}
|
|
cfg.build.rollupOptions = { external, output: {} }
|
|
if (isGlobal) {
|
|
cfg.build.rollupOptions.output.globals = getUmdGlobals(peerDependencies, config.umdGlobals)
|
|
}
|
|
cfg.build.rollupOptions.output.assetFileNames = f.outputCss
|
|
cfg = ctx.runPreBuild(cfg, target, pkgInfo) || cfg
|
|
console.log(`Building [${f.format + (dev ? '/dev' : '/prod')}] ${cyan(f.input)}`)
|
|
await viteBuild(cfg)
|
|
}
|
|
|
|
if (f.keepDev) {
|
|
const devName = `index.${f.format}.dev.min.js`
|
|
const prodName = `index.${f.format}.prod.min.js`
|
|
await buildFormat(true, devName)
|
|
await buildFormat(false, prodName)
|
|
fs.writeFileSync(path.join(distDir, f.output), getDistJSCode(devName, prodName))
|
|
} else {
|
|
await buildFormat(false)
|
|
}
|
|
}))
|
|
}
|
|
|
|
if (config.es !== false) {
|
|
config.es = config.es || {
|
|
keepCss: true,
|
|
keepDev: true
|
|
}
|
|
if (config.es.enabled !== false) {
|
|
const rep = { ...replace }
|
|
rep.__DEV__ = false
|
|
if (config.es.keepDev) {
|
|
// eslint-disable-next-line quotes
|
|
rep.__DEV__ = `(process.env.NODE_ENV !== 'production')`
|
|
}
|
|
|
|
let cfg = await getEsBuildConfig(pkgDir, target, entryEs, allExternal, {
|
|
legacy: config.legacy.esEnabled,
|
|
needPolyfills: config.legacy.esNeedPolyfills,
|
|
replace: rep,
|
|
banner,
|
|
cssPreprocessorOptions: config.cssPreprocessorOptions,
|
|
plugins: config.plugins
|
|
})
|
|
cfg = ctx.runPreBuild(cfg, target, pkgInfo) || cfg
|
|
console.log(`Building [ES] ${cyan(entryEs)}`)
|
|
await viteBuild(cfg)
|
|
|
|
if (config.es.keepCss) {
|
|
const srcDir = path.resolve(pkgDir, 'src')
|
|
walk(srcDir, {
|
|
nodir: true,
|
|
traverseAll: true,
|
|
filter ({ path }) {
|
|
return path.endsWith('.less') || path.endsWith('.scss') || path.endsWith('.sass')
|
|
}
|
|
}).forEach(x => {
|
|
fs.copySync(x.path, path.resolve(esDir, path.relative(srcDir, x.path)))
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config.declaration) {
|
|
await buildTsType(pkgDir, target)
|
|
if (config.bundleDts) {
|
|
execa.sync('dts-bundle-generator', [
|
|
'--no-banner',
|
|
'--no-check',
|
|
'--silent',
|
|
'-o',
|
|
path.resolve(pkgDir, 'dist', typeof config.bundleDts === 'string' ? config.bundleDts : 'index.d.ts'),
|
|
path.resolve(esDir, 'index.d.ts')
|
|
], {
|
|
stdio: 'inherit',
|
|
preferLocal: true,
|
|
cwd: pkgDir
|
|
})
|
|
|
|
if (!config.buildEs) fs.removeSync(esDir)
|
|
}
|
|
}
|
|
|
|
process.on('exit', () => reportSize(pkgDir))
|
|
|
|
ctx.runPostBuild(target, pkgInfo)
|
|
}
|
|
|
|
function reportSize (pkgDir) {
|
|
const distFiles = walk(path.resolve(pkgDir, 'dist'), {
|
|
nodir: true,
|
|
traverseAll: true,
|
|
filter ({ path }) {
|
|
return path.endsWith('.js') || path.endsWith('.css')
|
|
}
|
|
}).map(x => {
|
|
return { path: x.path, size: x.stats.size, name: path.basename(x.path), code: fs.readFileSync(x.path) }
|
|
})
|
|
|
|
const formatSize = size => (size / 1024).toFixed(2) + ' KiB'
|
|
const gzipSize = code => formatSize(zlib.gzipSync(code).length)
|
|
const brotliSize = code => formatSize(brotli.sync(code))
|
|
const row = (a, b, c, d) => `${a}\t ${b}\t${c}\t${d}`
|
|
|
|
ui.div(
|
|
row(
|
|
bold(cyan(`File(${path.basename(pkgDir)})`)),
|
|
bold(cyan('Size')),
|
|
bold(cyan('Gzip')),
|
|
bold(cyan('Brotli'))
|
|
) + '\n\n' +
|
|
distFiles.map(file => row(
|
|
/js$/.test(file.name)
|
|
? green(file.name)
|
|
: blue(file.name),
|
|
formatSize(file.size),
|
|
gzipSize(file.code),
|
|
brotliSize(file.code)
|
|
)).join('\n')
|
|
)
|
|
|
|
console.log('\n\n' + ui.toString() + '\n\n')
|
|
ui.resetOutput()
|
|
}
|
|
|
|
async function buildTsType (cwd, target) {
|
|
const tsDir = path.resolve(cwd, 'tsconfig.json')
|
|
const tsExist = fs.existsSync(tsDir)
|
|
const srcDir = path.resolve(cwd, 'src')
|
|
const esDir = path.resolve(cwd, 'es')
|
|
|
|
const project = new Project({
|
|
skipFileDependencyResolution: true,
|
|
skipLoadingLibFiles: false,
|
|
tsConfigFilePath: tsExist ? tsDir : undefined,
|
|
compilerOptions: {
|
|
rootDir: ctx.isMonorepo ? ctx.rootPath : srcDir,
|
|
emitDeclarationOnly: true,
|
|
declaration: true,
|
|
declarationMap: false,
|
|
declarationDir: esDir,
|
|
allowJs: true,
|
|
noEmit: false
|
|
}
|
|
})
|
|
|
|
if (!tsExist) {
|
|
project.addSourceFilesAtPaths(srcDir + '/**/*.js')
|
|
}
|
|
|
|
const cssRE = /\.(?:s[ca]ss|less|css)$/
|
|
|
|
const result = project.emitToMemory({
|
|
emitOnlyDtsFiles: true,
|
|
customTransformers: {
|
|
afterDeclarations: [(ctx) => {
|
|
return {
|
|
transformSourceFile (node) {
|
|
const p = node.fileName
|
|
return ts.visitNode(node, (n) => {
|
|
return ts.visitEachChild(n, (x) => {
|
|
if (ts.isImportDeclaration(x)) {
|
|
let newNode = null
|
|
x.forEachChild(c => {
|
|
if (ts.isStringLiteral(c)) {
|
|
const v = c.text
|
|
if (cssRE.test(v)) {
|
|
newNode = undefined
|
|
} else if (v.startsWith('@/')) {
|
|
newNode = ctx.factory.createImportDeclaration(x.decorators, x.modifiers, x.importClause, ctx.factory.createStringLiteral('./' + path.relative(
|
|
path.resolve(p, '..'),
|
|
path.resolve(srcDir, v.slice(2))
|
|
)), x.assertClause)
|
|
}
|
|
}
|
|
})
|
|
if (newNode) return newNode
|
|
if (newNode === undefined) return undefined
|
|
}
|
|
else if (ts.isExportDeclaration(x)) {
|
|
let hasFrom = false
|
|
let newNode = null
|
|
x.forEachChild(x => {
|
|
if (!hasFrom) hasFrom = x.kind === ts.SyntaxKind.FromKeyword
|
|
})
|
|
if (hasFrom) {
|
|
x.forEachChild(s => {
|
|
if (s.kind === ts.SyntaxKind.FromKeyword && s.text.startsWith('@/')) {
|
|
newNode = ctx.factory.createExportDeclaration(x.decorators, x.modifiers, x.isTypeOnly, x.exportClause, ctx.factory.createStringLiteral('./' + path.relative(
|
|
path.resolve(p, '..'),
|
|
path.resolve(srcDir, s.text.slice(2))
|
|
)), x.assertClause)
|
|
}
|
|
})
|
|
}
|
|
if (newNode) return newNode
|
|
}
|
|
return x
|
|
}, ctx)
|
|
})
|
|
}
|
|
}
|
|
}]
|
|
}
|
|
})
|
|
|
|
const files = result.getFiles()
|
|
if (!files.length) return
|
|
|
|
if (ctx.isMonorepo) {
|
|
const mark = `/packages/${target}/src/`
|
|
const writeProject = new Project()
|
|
files.forEach((file) => {
|
|
if (file.filePath.includes(mark)) {
|
|
writeProject.createSourceFile(file.filePath.replace(mark, '/'), file.text, { overwrite: true })
|
|
}
|
|
})
|
|
await writeProject.save()
|
|
} else {
|
|
await result.saveFiles()
|
|
}
|
|
}
|
|
|
|
function getDistJSCode (dev, prod) {
|
|
return `if (process.env.NODE_ENV === 'production') {
|
|
module.exports = require('./${prod}')
|
|
} else {
|
|
module.exports = require('./v}')
|
|
}
|
|
`
|
|
}
|
|
|
|
module.exports = build
|