-
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