| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- // @ts-check
- /*
- Produces production builds and stitches together d.ts files.
- To specify the package to build, simply pass its name and the desired build
- formats to output (defaults to `buildOptions.formats` specified in that package,
- or "esm,cjs"):
- ```
- # name supports fuzzy match. will build all packages with name containing "dom":
- nr build dom
- # specify the format to output
- nr build core --formats cjs
- ```
- */
- import fs from 'node:fs'
- import { parseArgs } from 'node:util'
- import path from 'node:path'
- import { brotliCompressSync, gzipSync } from 'node:zlib'
- import pico from 'picocolors'
- import { cpus } from 'node:os'
- import { targets as allTargets, exec, fuzzyMatchTarget } from './utils.js'
- import { scanEnums } from './inline-enums.js'
- import prettyBytes from 'pretty-bytes'
- import { spawnSync } from 'node:child_process'
- const commit = spawnSync('git', ['rev-parse', '--short=7', 'HEAD'])
- .stdout.toString()
- .trim()
- const { values, positionals: targets } = parseArgs({
- allowPositionals: true,
- options: {
- formats: {
- type: 'string',
- short: 'f',
- },
- devOnly: {
- type: 'boolean',
- short: 'd',
- },
- prodOnly: {
- type: 'boolean',
- short: 'p',
- },
- withTypes: {
- type: 'boolean',
- short: 't',
- },
- sourceMap: {
- type: 'boolean',
- short: 's',
- },
- release: {
- type: 'boolean',
- },
- all: {
- type: 'boolean',
- short: 'a',
- },
- size: {
- type: 'boolean',
- },
- },
- })
- const {
- formats,
- all: buildAllMatching,
- devOnly,
- prodOnly,
- withTypes: buildTypes,
- sourceMap,
- release: isRelease,
- size: writeSize,
- } = values
- const sizeDir = path.resolve('temp/size')
- run()
- async function run() {
- if (writeSize) fs.mkdirSync(sizeDir, { recursive: true })
- const removeCache = scanEnums()
- try {
- const resolvedTargets = targets.length
- ? fuzzyMatchTarget(targets, buildAllMatching)
- : allTargets
- await buildAll(resolvedTargets)
- await checkAllSizes(resolvedTargets)
- if (buildTypes) {
- await exec(
- 'pnpm',
- [
- 'run',
- 'build-dts',
- ...(targets.length
- ? ['--environment', `TARGETS:${resolvedTargets.join(',')}`]
- : []),
- ],
- {
- stdio: 'inherit',
- },
- )
- }
- } finally {
- removeCache()
- }
- }
- /**
- * Builds all the targets in parallel.
- * @param {Array<string>} targets - An array of targets to build.
- * @returns {Promise<void>} - A promise representing the build process.
- */
- async function buildAll(targets) {
- await runParallel(cpus().length, targets, build)
- }
- /**
- * Runs iterator function in parallel.
- * @template T - The type of items in the data source
- * @param {number} maxConcurrency - The maximum concurrency.
- * @param {Array<T>} source - The data source
- * @param {(item: T) => Promise<void>} iteratorFn - The iteratorFn
- * @returns {Promise<void[]>} - A Promise array containing all iteration results.
- */
- async function runParallel(maxConcurrency, source, iteratorFn) {
- /**@type {Promise<void>[]} */
- const ret = []
- /**@type {Promise<void>[]} */
- const executing = []
- for (const item of source) {
- const p = Promise.resolve().then(() => iteratorFn(item))
- ret.push(p)
- if (maxConcurrency <= source.length) {
- const e = p.then(() => {
- executing.splice(executing.indexOf(e), 1)
- })
- executing.push(e)
- if (executing.length >= maxConcurrency) {
- await Promise.race(executing)
- }
- }
- }
- return Promise.all(ret)
- }
- const privatePackages = fs.readdirSync('packages-private')
- /**
- * Builds the target.
- * @param {string} target - The target to build.
- * @returns {Promise<void>} - A promise representing the build process.
- */
- async function build(target) {
- const pkgBase = privatePackages.includes(target)
- ? `packages-private`
- : `packages`
- const pkgDir = path.resolve(`${pkgBase}/${target}`)
- const pkg = JSON.parse(fs.readFileSync(`${pkgDir}/package.json`, 'utf-8'))
- // if this is a full build (no specific targets), ignore private packages
- if ((isRelease || !targets.length) && pkg.private) {
- return
- }
- // if building a specific format, do not remove dist.
- if (!formats && fs.existsSync(`${pkgDir}/dist`)) {
- fs.rmSync(`${pkgDir}/dist`, { recursive: true })
- }
- const env =
- (pkg.buildOptions && pkg.buildOptions.env) ||
- (devOnly ? 'development' : 'production')
- await exec(
- 'rollup',
- [
- '-c',
- '--environment',
- [
- `COMMIT:${commit}`,
- `NODE_ENV:${env}`,
- `TARGET:${target}`,
- formats ? `FORMATS:${formats}` : ``,
- prodOnly ? `PROD_ONLY:true` : ``,
- sourceMap ? `SOURCE_MAP:true` : ``,
- ]
- .filter(Boolean)
- .join(','),
- ],
- { stdio: 'inherit' },
- )
- }
- /**
- * Checks the sizes of all targets.
- * @param {string[]} targets - The targets to check sizes for.
- * @returns {Promise<void>}
- */
- async function checkAllSizes(targets) {
- if (devOnly || (formats && !formats.includes('global'))) {
- return
- }
- console.log()
- for (const target of targets) {
- await checkSize(target)
- }
- console.log()
- }
- /**
- * Checks the size of a target.
- * @param {string} target - The target to check the size for.
- * @returns {Promise<void>}
- */
- async function checkSize(target) {
- const pkgDir = path.resolve(`packages/${target}`)
- await checkFileSize(`${pkgDir}/dist/${target}.global.prod.js`)
- if (!formats || formats.includes('global-runtime')) {
- await checkFileSize(`${pkgDir}/dist/${target}.runtime.global.prod.js`)
- }
- }
- /**
- * Checks the file size.
- * @param {string} filePath - The path of the file to check the size for.
- * @returns {Promise<void>}
- */
- async function checkFileSize(filePath) {
- if (!fs.existsSync(filePath)) {
- return
- }
- const file = fs.readFileSync(filePath)
- const fileName = path.basename(filePath)
- const gzipped = gzipSync(file)
- const brotli = brotliCompressSync(file)
- console.log(
- `${pico.gray(pico.bold(fileName))} min:${prettyBytes(
- file.length,
- )} / gzip:${prettyBytes(gzipped.length)} / brotli:${prettyBytes(
- brotli.length,
- )}`,
- )
- if (writeSize)
- fs.writeFileSync(
- path.resolve(sizeDir, `${fileName}.json`),
- JSON.stringify({
- file: fileName,
- size: file.length,
- gzip: gzipped.length,
- brotli: brotli.length,
- }),
- 'utf-8',
- )
- }
|