分享
Vue
源码分析
scripts-build

构建介绍

  • packages/* 的包构建

依赖第三方库

执行流程

run -> buildAll -> runParallel -> build -> checkAllSizes -> checkSize -> checkFileSize

run

async function run() {
  if (writeSize) await fs.mkdir(sizeDir, { recursive: true })
  const removeCache = scanEnums()
  try {
    const resolvedTargets = targets.length
      ? fuzzyMatchTarget(targets, buildAllMatching)
      : allTargets;
    // resolvedTargets = ['compiler-core', 'compiler-sfc', xxx] // packages/* 所有文件目录名称
    await buildAll(resolvedTargets)
    await checkAllSizes(resolvedTargets)
    if (buildTypes) {
      await execa(
        'pnpm',
        [
          'run',
          'build-dts',
          ...(targets.length
            ? ['--environment', `TARGETS:${resolvedTargets.join(',')}`]
            : [])
        ],
        {
          stdio: 'inherit'
        }
      )
    }
  } finally {
    removeCache()
  }
}

buildAll

async function buildAll(targets) {
  await runParallel(cpus().length, targets, build)
}

runParallel

  • 并发控制, 最底下有这段代码详细介绍,和测试代码
async function runParallel(maxConcurrency, source, iteratorFn) {
  const ret = []
  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)
}

build

async function build(target) {
  const pkgDir = path.resolve(`packages/${target}`)
  const pkg = require(`${pkgDir}/package.json`)
 
  // 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 && existsSync(`${pkgDir}/dist`)) {
    await fs.rm(`${pkgDir}/dist`, { recursive: true })
  }
 
  const env =
    (pkg.buildOptions && pkg.buildOptions.env) ||
    (devOnly ? 'development' : 'production')
  await execa(
    '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' }
  )
}

checkAllSizes

async function checkAllSizes(targets) {
  if (devOnly || (formats && !formats.includes('global'))) {
    return
  }
  console.log()
  for (const target of targets) {
    await checkSize(target)
  }
  console.log()
}

checkSize

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`)
  }
}

checkFileSize

async function checkFileSize(filePath) {
  if (!existsSync(filePath)) {
    return
  }
  const file = await fs.readFile(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)
    await fs.writeFile(
      path.resolve(sizeDir, `${fileName}.json`),
      JSON.stringify({
        file: fileName,
        size: file.length,
        gzip: gzipped.length,
        brotli: brotli.length
      }),
      'utf-8'
    )
}

测试 并发不得超过cpu数量

async function awaitPromise() {
  const timer = ((Math.random() * 10 + 1) | 0) * 500;
 
  return new Promise((resolve) => {
    setTimeout(resolve, timer);
    console.log(timer)
  })
}
 
async function build(name) {
  console.log('开始执行:', name)
  await awaitPromise();
  console.log('执行完毕:', name);
}
 
const testArr = [
  'compiler-core',
  'compiler-dom',
  'compiler-sfc',
  'compiler-ssr',
  'reactivity',
  'reactivity-transform',
  'runtime-core',
  'runtime-dom',
  'server-renderer',
  'shared',
  'template-explorer',
  'vue',
  'vue-compat'
]
 
const CPU_LENGTH = 5;
 
/* 控制并发量 */
async function runParallel(maxConcurrency, source, iteratorFn) {
  const ret = []
  // 任务队列
  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)
}
 
runParallel(CPU_LENGTH, testArr, build).then(() => {
  console.log('--------------- 全部执行完毕 ---------------');
});