This tutorial is the last in a series of tutorials on rollup.js. I will analyze the vue.js source code packaging process based on vue.js framework and give you an in-depth understanding of how complex front-end frameworks are packaged using rollup.js. With this tutorial, you will be able to use rollup.js for your own projects.
Pre-learning — basics
To understand the package source of vue.js, you need to know the following:
fs
Module:Node.js
Built-in modules for local file system processing;path
Module:Node.js
Built-in module for local path resolution;buble
Module: forES6+
Syntax compilation;flow
Module: forJavascript
Source code static inspection;zlib
Module:Node.js
Built-in module for usegzip
Algorithm for file compression;terser
Module: forJavascript
Code compression and beautification.
I’ve compiled these basics into a pre-learning tutorial called “10 Minutes for Quick Mastery of Rollup.js — Pre-learning Basics” for those interested.
Pre-learning – rollup.js plugin
The rollup.js advanced tutorial explains some of the common plug-ins in rollup.js:
rollup-plugin-resolve
: Integrate external module code;rollup-plugin-commonjs
Support:CommonJS
Module;rollup-plugin-babel
Compile:ES6+
Syntax for theES2015
;rollup-plugin-json
Support:json
Module;rollup-plugin-uglify
: code compression (not supportedES
Module);
In order to understand the package source of vue.js, we also need to learn the following rollup.js plugin and knowledge:
rollup-plugin-buble
Plug-ins: compileES6+
Syntax for theES2015
, no configuration is requiredbabel
More light;rollup-plugin-alias
Plugins: replace aliases in module paths;rollup-plugin-flow-no-whitespace
Plugins: Removeflow
Static type checking code;rollup-plugin-replace
Plugins: replace variables in code with specified values;rollup-plugin-terser
Plug-ins: Code compression, replaceuglify
To supportES
The module.intro
andoutro
Configuration: Add code comments within a code block.
For those of you who are not familiar with these plugins, I have prepared another pre-learning tutorial: 10 Minutes to Quickly Master Rollup.js — Pre-learning The Rollup.js plugin.
Vue.js source code package
Clone the vue. js source code locally:
git clone https://github.com/vuejs/vue.git
Copy the code
Install dependencies:
cd vue
npm i
Copy the code
Open package.json to view scripts:
"scripts": {
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
}
Copy the code
Let’s start by packing with the build directive:
$NPM run build > [email protected] build/Users/Sam/WebstormProjects/vue > node scripts/build. Js Dist/vue.runtime.com mon. Js 209.20 KB dist/vue.com mon. Js 288.22 KB dist/vue runtime. Esm. Js 209.18 KB dist/vue esm. Js Dist /vue.runtime.js 216.9 KB dist/vue.runtime.min.js 216.10 KB (gzipped: Dist /vue.js dist/vue.min.js 13.86 KB) Packages/VUe-template-compiler /build.js 121.88 KB Packages/Vue-template-compiler /browser.js 228.17 KB Packages/vue - server - the renderer/build js 220.73 KB packages/vue - server - the renderer/basic. Js 304.00 KB Packages/vue - server - the renderer/server - the plugin. Js 2.92 KB packages/vue - server - the renderer/client - plugin. Js 3.03 KBCopy the code
After successful packaging, the following packaging files are created in the dist directory:
build:ssr
build:weex
build:ssr
$NPM run build: SSR > [email protected] build: SSR/Users/Sam/WebstormProjects/vue > NPM run build - Web - the runtime - CJS, web server - the renderer > [email protected] build/Users/Sam/WebstormProjects/vue > node scripts/build. Js"web-runtime-cjs,web-server-renderer"Dist/vue.runtime.com mon. Js 209.20 KB packages/vue - server - the renderer/build js 220.73 KB packages/vue - server - the renderer/basic. Js 304.00 KB Packages /vue-server-renderer/server-plugin.js 2.92 KB Packages /vue-server-renderer/client-plugin.js 3.03 KBCopy the code
Try build: Weex:
$NPM run build: weex > [email protected] build: weex/Users/Sam/WebstormProjects/vue > NPM run build - weex > [email protected] build/Users/Sam/WebstormProjects/vue > node scripts/build. Js"weex"Packages/weex - vue - framework/factory. Js 193.79 KB packages/weex - vue - framework/index. Js 5.68 KB Packages/weex - the template - the compiler/build js 109.11 KBCopy the code
Scripts /build.js are executed via Node, but the parameters attached are different:
node scripts/build.js # build
node scripts/build.js "web-runtime-cjs,web-server-renderer" # build:ssr
node scripts/build.js "weex" # build:weex
Copy the code
Scripts /build.js is the key to interpreting vue.js source code package. Here we will analyze vue.js source code packaging process.
Vue. Js packaging process analysis
The vue. js source code package is based on the ROLLup. js API, which can be roughly divided into five steps, as shown below:
- Step 1: Create the dist directory. Check whether the dist directory exists. If not, create it.
- Step 2: Generate
rollup
Configuration file. Generated through scripts/config.jsrollup
Configuration file; - Step 3:
rollup
Configure file filtering. Based on the parameters passed in, yesrollup
Filter the content of the configuration file to exclude unnecessary packaging items. - Step 4: Iterate through the configuration package and generate the package source code. Through the configuration file project, pass
rollup
API to package, and generate packaged source code. - Step 5: source code output file, gzip compression test. If the output is the final product, pass
terser
Perform minimal compression and passzlib
Gzip compression test, and output test results in the console, and finally output the source content to the specified file, complete packaging.
Vue.js package source code analysis
Below we will go into vue.js packaging source code, analysis of packaging principles and details.
Friendship tip: it is recommended to read all four tutorials provided before reading the source code:
- 10 minute Quick Start Rollup.js
- 10 minutes quick Progress Rollup.js
- Quick Mastery of Rollup.js in 10 Minutes: The Basics of Pre-learning
- Rollup. js plugin for Pre-learning rollup.js in 10 Minutes
Creating the dist directory
NPM run build starts with scripts/build.js:
// scripts/build.js
const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const rollup = require('rollup')
const terser = require('terser')
if(! fs.existsSync('dist')) {
fs.mkdirSync('dist')}Copy the code
The first five lines import five modules each, the purpose of which is detailed in the pre-learning tutorial. Line 7 uses a synchronization method to determine whether the dist directory exists, and if not, to create it.
Generate the rollup configuration
After generating the dist directory, the rollup configuration file is generated with the following code:
// scripts/build.js
let builds = require('./config').getAllBuilds()
Copy the code
The code is just one sentence, but it does a lot of things. It first loads the scripts/config.js module and then calls the getAllBuilds() method within it. Scripts /config.js is loaded with the following contents:
// scripts/config.js
const path = require('path')
const buble = require('rollup-plugin-buble')
const alias = require('rollup-plugin-alias')
const cjs = require('rollup-plugin-commonjs')
const replace = require('rollup-plugin-replace')
const node = require('rollup-plugin-node-resolve')
const flow = require('rollup-plugin-flow-no-whitespace')
Copy the code
The purpose and usage of these plug-ins are described in both the advanced and pre-tutorial tutorials.
const version = process.env.VERSION || require('.. /package.json').version
const weexVersion = process.env.WEEX_VERSION || require('.. /packages/weex-vue-framework/package.json').version
Copy the code
The above code gets the Vue version number and Weex version number from package.json.
const banner =
'/ *! \n' +
` * Vue.js v${version}\n` +
` * (c) 2014-The ${new Date().getFullYear()} Evan You\n` +
' * Released under the MIT License.\n' +
' */'
Copy the code
The above code generates the banner text, which is written at the top of the file after the Vue code is packaged.
const weexFactoryPlugin = {
intro () {
return 'module.exports = function weexFactory (exports, document) {'
},
outro () {
return '} '}}Copy the code
The above code is only used to package the weeX-Factory source code:
// Weex runtime factory
'weex-factory': {
weex: true.entry: resolve('weex/entry-runtime-factory.js'),
dest: resolve('packages/weex-vue-framework/factory.js'),
format: 'cjs'.plugins: [weexFactoryPlugin]
}
Copy the code
Next we import the scripts/alias.js module:
const aliases = require('./alias')
Copy the code
The alias.js module outputs an object in which all aliases and their corresponding absolute paths are defined:
// scripts/alias.js
const path = require('path')
const resolve = p= > path.resolve(__dirname, '.. / ', p)
module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
entries: resolve('src/entries'),
sfc: resolve('src/sfc')}Copy the code
The resolve() method is defined in this module to generate an absolute path:
const resolve = p= > path.resolve(__dirname, '.. / ', p)
Copy the code
__dirname is the path of the current module, i.e. Scripts/directory,.. / represents the upper-level directory, the root of the project, which is then combined with the relative path passed in to form the final result using the path.resolve() method. Back to the scripts/config.js module, we continue:
// scripts/config.js
const resolve = p= > {
// Get the alias of the path
const base = p.split('/') [0]
// Find if the alias exists
if (aliases[base]) {
// If the alias exists, merge the path of the alias with the file name
return path.resolve(aliases[base], p.slice(base.length + 1))}else {
// If the alias does not exist, merge the project root path with the incoming path
return path.resolve(__dirname, '.. / ', p)
}
}
Copy the code
Config.js also defines a resolve() method, which receives a path parameter P, assuming p is web/ entry-Runtime.js, then the base obtained in the first step is Web, and then the alias object aliases output by the alias module is checked to see if the corresponding alias exists. The corresponding alias for the Web module exists, and its value is:
web: resolve('src/platforms/web')
Copy the code
Therefore, the actual path of the alias is merged with the file name to obtain the real path of the file. The file name can be obtained by:
p.slice(base.length + 1)
Copy the code
If the incoming path is: dist/vue.runtime.com mon. Js, will find the alias dist, which doesn’t exist, so does the other path, the project root directory path joining together with the incoming parameters, which is to execute the following code:
return path.resolve(__dirname, '.. / ', p)
Copy the code
This is similar to the implementation of the scripts/alias.js module. The following builds variable is defined in the config.js module:
const builds = {
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.js'),
format: 'cjs',
banner
}
}
Copy the code
The config. Js module defines a genConfig(name) method to resolve the problem, since the config item has the name of the old rollup.js version and has been deprecated in the new version:
function genConfig (name) {
const opts = builds[name]
const config = {
input: opts.entry,
external: opts.external,
plugins: [
replace({
__WEEX__:!!!!! opts.weex,__WEEX_VERSION__: weexVersion,
__VERSION__: version
}),
flow(),
buble(),
alias(Object.assign({}, aliases, opts.alias))
].concat(opts.plugins || []),
output: {
file: opts.dest,
format: opts.format,
banner: opts.banner,
name: opts.moduleName || 'Vue'
},
onwarn: (msg, warn) = > {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
if (opts.env) {
config.plugins.push(replace({
'process.env.NODE_ENV': JSON.stringify(opts.env)
}))
}
Object.defineProperty(config, '_name', {
enumerable: false.value: name
})
return config
}
Copy the code
The purpose of this method is to convert the rollup.js configuration from the old version to the new version format. For the plugins part, each packaged project uses the Replace, Flow, Buble, and Alias plugins, and the rest of the custom plugins are merged into plugins using the following code:
plugins: [].concat(opts.plugins || []),
Copy the code
The genConfig() method also determines whether the environment variable NODE_ENV needs to be replaced:
if (opts.env) {
config.plugins.push(replace({
'process.env.NODE_ENV': JSON.stringify(opts.env)
}))
}
Copy the code
The above code determines if the env argument is present in the opts passed in, and if so, replaces the process.env.node_env part of the code with json.stringify (opts.env) : , if the env value passed is development, the result generated is development with double quotes
"development"
Copy the code
In addition, the genConfig() method also stores the key of the Builds object in the Config object:
Object.defineProperty(config, '_name', {
enumerable: false.value: name
})
Copy the code
If the key for builds is Web-runtime-cjs, the resulting config would be:
config = {
'_name': 'web-runtime-cjs'
}
Copy the code
Finally, the config.js module defines the getAllBuilds() method:
if (process.env.TARGET) {
module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
exports.getAllBuilds = (a)= > Object.keys(builds).map(genConfig)
}
Copy the code
The method first determines whether the environment variable TARGET is defined. The TARGET environment variable is not defined in the three build methods, so the logic in else will be executed and a getBuild() and getAllBuilds() method will be exposed. The getAllBuilds() method retrieves the key array of builds objects, iterates over and calls the genConfig() method to generate the configuration objects so that the rollup configuration is generated.
Rollup Configures filtering
Back to the scripts/build.js module, after the configuration is generated, the configuration items will be filtered, because each packaging mode will output different results, the source of the filtering section is as follows:
// scripts/build.js
// filter builds via command line arg
if (process.argv[2]) {
const filters = process.argv[2].split(', ')
builds = builds.filter(b= > {
return filters.some(f= > b.output.file.indexOf(f) > - 1 || b._name.indexOf(f) > - 1)})}else {
// filter out weex builds by default
builds = builds.filter(b= > {
return b.output.file.indexOf('weex') = = =- 1})}Copy the code
First, analyze the build command, which is actually executed as follows:
node scripts/build.js
Copy the code
So the contents of process.argv are:
[ '/ Users/Sam /. NVM/versions/node/v11.2.0 / bin/node'.'/Users/sam/WebstormProjects/vue/scripts/build.js' ]
Copy the code
Process. argv[2] is not present, so else is executed:
builds = builds.filter(b= > {
return b.output.file.indexOf('weex') = = =- 1
})
Copy the code
This code is used to exclude weex code packaging, by whether output.file contains WEEx string to determine whether weeX code. The build: SSR command is as follows:
node scripts/build.js "web-runtime-cjs,web-server-renderer"
Copy the code
The value of process.argv is:
[ '/ Users/Sam /. NVM/versions/node/v11.2.0 / bin/node'.'/Users/sam/WebstormProjects/vue/scripts/build.js'.'web-runtime-cjs,web-server-renderer' ]
Copy the code
Process. argv[2] has a value of Web-runtime-cjs and web-server-renderer, so the logic in if is executed:
const filters = process.argv[2].split(', ')
builds = builds.filter(b= > {
return filters.some(f= > b.output.file.indexOf(f) > - 1 || b._name.indexOf(f) > - 1)
Copy the code
This method first separates the parameters into an array of filters with commas and then iterates through the builds array looking for any configuration item in output.file or _NAME that contains any of the filters. For example, if the first element of the filters is web-runtime-cjs, it will look for the configuration item that contains the web-runtime-cjs in output.file or _name. The _name will point to the key of the configuration item.
'web-runtime-cjs': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.js'),
format: 'cjs',
banner
}
Copy the code
The configuration is then retained and eventually packaged.
A rollup packaging
After configuration filtering is complete, the packaging function is called:
build(builds)
Copy the code
The build function is defined as follows:
function build (builds) {
let built = 0 // Current package item number
const total = builds.length // The total number of times you need to pack
const next = (a)= > {
buildEntry(builds[built]).then((a)= > {
built++ // Add 1 after packing
if (built < total) {
next() // If the package number is less than the total number of packages, continue next()
}
}).catch(logError) // Output error information
}
next() // Call next()
}
Copy the code
The build() function takes builds, iterate over them, and calls buildEntry() to perform the actual packing logic. BuildEntry () returns a Promise object and calls logError(e) to print an error message if there is an error:
function logError (e) {
console.log(e)
}
Copy the code
The packaged core function is buildEntry(config)
function buildEntry (config) {
const output = config.output // Get the output of config
const { file, banner } = output // Get file and banner in output
const isProd = /min\.js$/.test(file) // Check whether file ends in min.js, if so, flag isProd as true
return rollup.rollup(config) // Perform rollup packaging
.then(bundle= > bundle.generate(output)) // Generate the source code for the packaged result
.then(({ code }) = > { // Get the source code generated by the package
if (isProd) { // Check whether it is isProd
const minified = (banner ? banner + '\n' : ' ') + terser.minify(code, { // Perform code minimization packaging and manually add the banner in the code title, as minimization of packaging causes comments to be removed
output: {
ascii_only: true // Only ASCII characters are supported
},
compress: {
pure_funcs: ['makeMap'] // Filter the makeMap function
}
}).code // Get the code to minimize packaging
return write(file, minified, true) // Write code to the output path
} else {
return write(file, code) // Write code to the output path}})}Copy the code
If you understand how rollup works and how terser is used, the above code is not difficult to understand. The difference is that we add banner annotations manually and output code files manually instead of using rollup. The previous approach we adopted was:
const bundle = await rollup.rollup(input) // Get the bundle object
bundle.write(output) // Output the packaged object to a file
Copy the code
The method adopted by vue.js is as follows:
const bundle = await rollup.rollup(input) // Get the bundle object
const { code, map } = await bundle.generate(output) // Generate the source code and source map from the bundle
Copy the code
Get the source code from the bundle and manually export it to a file.
Source output
The source code output is primarily a call to the write() function, which requires three arguments:
- Dest: the absolute path to the output file, which can be obtained from output.file.
- Code: indicates the source code
bundle.generate()
To obtain; - Zip: whether a gzip compression test is required. If isProd is true, zip is true; otherwise, zip is false.
function write (dest, code, zip) {
return new Promise((resolve, reject) = > {
function report (extra) { // Outputs the log function
console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ' ')) // Prints the file name, file capacity, and gzip compression test results
resolve()
}
fs.writeFile(dest, code, err => {
if (err) return reject(err) Reject () is called if an error is reported
if (zip) { // If isProd then gzip test
zlib.gzip(code, (err, zipped) => { // Use gzip to compress source code
if (err) return reject(err)
report(' (gzipped: ' + getSize(zipped) + ') ') // Get the gzip string length and giZp capacity after the test is successful})}else {
report() // Output logs}})})}Copy the code
There are a few details to note here. The first is to get the relative path of the current command line path to the final build file:
path.relative(process.cwd(), dest)
Copy the code
The second is to call the blue() function to generate blue text on the command line:
function blue (str) {
return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
}
Copy the code
The third is the method of obtaining file capacity:
function getSize (code) {
return (code.length / 1024).toFixed(2) + 'kb'
}
Copy the code
These three methods are not difficult to understand, but they are very practical, you can learn a lot in the development process.
conclusion
We can find that when we have the basic knowledge, and then analyze vue.js source code packaging process is not complex, so it is suggested that we can draw lessons from this way of learning in the work, the basic knowledge point out first, figure out after the complex source code. Rollup. Js10 minutes tutorial is over. If you have any suggestions for this tutorial, please leave a comment.