• Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

  • This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

background

While learning Vue3, we also need to pay attention to the subcontracting mechanism of Vue3. Vue3 subcontracts each module for management. The relationship between packages is greatly decoupled, which improves the maintainability of each package. Because each package exists as an independent entity, each package can run independently and is not completely dependent on the VUE environment. For example, @vue/ reActivity is very responsive and makes it run in a different environment. Some people in the community run it in the React environment. I wasn’t surprised when I read this, because I had read the # Vue3 source code to parse the principle of responsiveness — Reactivity is not dependent on any environment and can run properly in any environment.

package.json

The package.json file is probably the first thing I look at when I get my project. Since this file clearly shows project dependencies, script commands, etc., let’s look at scripts first

In fact, vue dev, build and other commands are js files executed by Node, so we can know the packaging process of Vue

build

When we CD into the build.js file, we can see that only the entry function run is being executed, and the rest are function functions

run

What does the run function do

async function run() { if (isRelease) { // remove build cache for release builds to avoid outdated enum values await fs.remove(path.resolve(__dirname, '.. /node_modules/.rts2_cache')) } if (! Targets. Length) {await buildAll(allTargets) // package core checkAllSizes(allTargets)} else {await buildAll(fuzzyMatchTarget(targets, buildAllMatching)) checkAllSizes(fuzzyMatchTarget(targets, buildAllMatching)) } }Copy the code

You can see that the run function mainly checks for isRelease and targets

When isRelease === true, indicating a released version, the packaged cache is removed to avoid stale enumerations

When targets is an empty array, it indicates full packing, i.e. packing all modules.

Now let’s look at allTargets

allTargets

From the above we can see that allTargets is imported from the utils file

const targets = (exports.targets = fs.readdirSync('packages').filter(f => { if (! fs.statSync(`packages/${f}`).isDirectory()) { return false } const pkg = require(`.. /packages/${f}/package.json`) if (pkg.private && ! pkg.buildOptions) { return false } return true }))Copy the code

Use the FS module to read the Packages folder and filter the file types

We can see that each package is stored in folder format, so targets also targets isDirectory() to distinguish whether the current file is a VUE package

The package.json file contents of each package are then distinguished

Is it a private package or does it have a buildOptions field

For example, the two packages above will have different judgment logic

So we end up with an array of package names that we need to package

buildAll

Look at the source

async function buildAll(targets) {
  await runParallel(require('os').cpus().length, targets, build)
}
Copy the code

A very simple piece of code that gives all the functionality to runParallel

runParallel

Step by step, let’s look at the source of runParallel

async function runParallel(maxConcurrency, source, iteratorFn) {
  const ret = []
  const executing = []
  for (const item of source) {
    const p = Promise.resolve().then(() => iteratorFn(item, source))
    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)
}
Copy the code

We can see that runParallel is essentially looping through our package array (allTargets above) and then distributing the packing behavior to the iteratorFn function (build), which wraps them with promises and wraps them centrally with promise.all

build= = =iteratorFn

The build function is the real package action. We can see that in runParallel,

const p = Promise.resolve().then(() => iteratorFn(item, source))
Copy the code

So the parameter to our build function is the name of the package to be packed

Let’s look at the source code

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) { await fs.remove(`${pkgDir}/dist`) } 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}` : ``, buildTypes ? `TYPES:true` : ``, prodOnly ? `PROD_ONLY:true` : ', sourceMap? 'SOURCE_MAP:true' : '].filter(Boolean).join(',')], {stdio: 'inherit'} // Share the information of the child with the parent process)}Copy the code

Finally, use execa to execute our rollup command for packaging

rollup.config.js

Rollup packages naturally have our configuration files.

const packageConfigs = process.env.PROD_ONLY ? [] : packageFormats.map(format => createConfig(format, outputConfigs[format])) if (process.env.NODE_ENV === 'production') { packageFormats.forEach(format => { if (packageOptions.prod === false) { return } if (format === 'cjs') { packageConfigs.push(createProductionConfig(format)) }  if (/^(global|esm-browser)(-runtime)? /.test(format)) { packageConfigs.push(createMinifiedConfig(format)) } }) } export default packageConfigsCopy the code

Focus on packageConfigs, which is the rollup.config.js export

packageFormats.map(format => createConfig(format, outputConfigs[format]))
Copy the code

Use this code to create a configuration file

The packageFormats are the buildOptions packaging format for each package

Formats’ types also have an enumeration, rollup, to package the output file type

const outputConfigs = {
  'esm-bundler': {
    file: resolve(`dist/${name}.esm-bundler.js`),
    format: `es`
  },
  'esm-browser': {
    file: resolve(`dist/${name}.esm-browser.js`),
    format: `es`
  },
  cjs: {
    file: resolve(`dist/${name}.cjs.js`),
    format: `cjs`
  },
  global: {
    file: resolve(`dist/${name}.global.js`),
    format: `iife`
  },
  // runtime-only builds, for main "vue" package only
  'esm-bundler-runtime': {
    file: resolve(`dist/${name}.runtime.esm-bundler.js`),
    format: `es`
  },
  'esm-browser-runtime': {
    file: resolve(`dist/${name}.runtime.esm-browser.js`),
    format: 'es'
  },
  'global-runtime': {
    file: resolve(`dist/${name}.runtime.global.js`),
    format: 'iife'
  }
}
Copy the code

Knowing the values of these two types of parameters, we can clearly read the createConfig function

Function createConfig(format, output, plugins = []) {return {input: resolve(entryFile), // Global and Browser ESM builds inlines everything so that they can be // used alone. external, plugins: [ json({ namedExports: false }), tsPlugin, createReplacePlugin( isProductionBuild, isBundlerESMBuild, isBrowserESMBuild, // isBrowserBuild? (isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) && ! packageOptions.enableNonBrowserBranches, isGlobalBuild, isNodeBuild, isCompatBuild ), ...nodePlugins, ...plugins ], output, onwarn: (msg, warn) => { if (! /Circular/.test(msg)) { warn(msg) } }, treeshake: { moduleSideEffects: false } } }Copy the code

There are too many decisions in the configuration file, please read the source code for details.

We only care about its return, what configuration file is created

To see its entrance is actually

let entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
Copy the code

Output is configured in the omitted code and can be viewed

And then you apply some plugins, some other configuration

conclusion

The subcontracting process of Vue3 is probably like this. The description in this paper may not be too detailed, so I hope it can give you a reference for ideas, or I hope you can debug the detailed process by yourself.

I also try to handwritten Vue3 source, git address: github.com/chris-zhu/r…

Please correct any mistakes