• Vue source code analysis and practice
  • Writer: Slow down

preface

When we are ready to look at the source code of a project, we can start from the following aspects:

  • To viewpackage.json, according to themainormoduleField to see the source code entry file
  • To viewpackage.jsonscriptsField to view the project’s packaging script, which can be used to locate the source file main entry for the build based on the build configuration

With that out of the way, now start analyzing the source code for the Vue2. X version. First, the overall code directory structure is understood.

Directory Structure

└ ─benchmarks // Performance test related ├─dist // Set of ├─flow //`vue`Related to some other NPM packages,`vue-server-render`.`vue-template-compiler`Such as ├ ─ scripts / / build scripts associated ├ ─ SRC / / code entry | ├ ─ Shared / / project used in some public variables, methods such as | ├ ─ SFC / / used to process single file components (. Vue) parsing logic | ├ ─ server / / The service side rendering code related | ├ ─ platforms / / the code between different platforms | ├ ─ the core of the core / / Vue** Runtime **Code | | ├ ─ vdom virtual dom related code / / | | ├ ─ util / / to use some of the tools in the Vue extracted | | ├ ─ the observer / / implementation reactive principle of code | | ├ ─ the instance / / | Vue instance related to the core logic | ├ ─ global - API / / global API Vue. The extend. Vue.com ponent etc. | | ├ ─ components / / built-in global components | ├ ─ compiler / / code associated with the template compilation ├ ─ types / / Typescript type declaration ├ ─ the test / / test related codeCopy the code

Introduction to the different versions of Vue

After viewing the scripts field and executing the build command in the Vue repository, we can see that dist types various suffixes. What are the differences between these packages? The following table is taken from the introduction of the different builds on the Vue website

UMD CommonJS ES Module ES Module (directly for browser)
The full version vue.js vue.common.js vue.esm.js vue.esm.browser.js
Only the runtime version is included vue.runtime.js vue.runtime.common.js vue.runtime.esm.js
Full version (production environment) vue.min.js vue.esm.browser.min.js
Include only the runtime version (production environment) vue.runtime.min.js

The term

  • Full version: Contains both compiler and runtime versions.
  • Compiler: mainly used to compile template strings into Javascript rendering functions.
  • Runtime: Code responsible for creating vue instances, rendering, processing the virtual DOM, and so on. Basically everything else is stripped out of the compiler.
  • UMDThe UMD version can be used directly in the browser via the TAB. JsDelivr of CDNcdn.jsdelivr.net/npm/vueThe default file is the UMD version of the runtime + compiler (vue.js).
  • CommonJSCommonJS versions are used to work with older packaging tools such asBrowserify 或 webpack 1. The default files for these packaging tools (pkg.main) is a run-time only version of CommonJS (vue.runtime.common.js).
  • ES Module: Starting in 2.6, Vue provides two ES Modules (ESM) buildfiles:

Runtime + compiler VS includes runtime only

If you need to compile a template on the client side (such as passing a string to the template option, or mounting it to an element and using HTML inside its DOM as the template), you will need to add a compiler, the full version:

// A compiler is required
new Vue({
  template: '<div>{{ hi }}</div>'
})

// No compiler is required
new Vue({
  render (h) {
    return h('div'.this.hi)
  }
})
Copy the code

When using vue-loader or Vueify, templates inside the *. Vue file are precompiled to Javascript at build time. You don’t really need a compiler in the final package, so just use the runtime version. That is, when we use the *. Vue file to develop, the *. Vue package results that actually do not have the template configuration, but have been turned into the render function.

Because the runtime version is about 30% smaller than the full version, you should use this version whenever possible. If you still want to use the full version, you need to configure an alias in the packaging tool:

webpack

module.exports = {
  // ...
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // to use webpack 1, use 'vue/dist/vue.com.js'}}}Copy the code

Rollup

const alias = require('rollup-plugin-alias')

rollup({
  // ...
  plugins: [
    alias({
      'vue': require.resolve('vue/dist/vue.esm.js')})]})Copy the code

Browserify

Add to your project’s package.json:

{
  // ...
  "browser": {
    "vue": "vue/dist/vue.common.js"}}Copy the code

Development environment vs. build environment

For UMD releases, the development/production mode is hard-coded: uncompressed code for development and compressed code for production.

CommonJS and ES Module versions are used to package tools, so we do not provide compressed versions. You need to compress the final package yourself.

The CommonJS and ES Module versions both retain the original process.env.node_env checks to determine what mode they should run in. You should replace these environment variables with the appropriate packaging tool configuration to control the mode in which Vue is running. Replacing process.env.node_env with a string literal also allows compression tools like UglifyJS to completely discard development-only code blocks, reducing the final file size.

webpack

In Webpack 4+, you can use the mode option:

module.exports = {
  mode: 'production'
}
Copy the code

But with WebPack 3 and below, you need to use DefinePlugin:

var webpack = require('webpack')

module.exports = {
  // ...
  plugins: [
    // ...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'}})]}Copy the code

Rollup

Use the rollup – plugin – replace:

const replace = require('rollup-plugin-replace')

rollup({
  // ...
  plugins: [
    replace({
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
}).then(...)
Copy the code

The above is taken from the vUE website explaining the different builds.

Vue’s packaging script logic

Last section we introduced the directory structure of Vue, now we follow the Vue source code to continue the analysis.

After throwing out the concepts of various build versions, let’s take a look at how files for different versions of the environment are built.

In package.json in the project, the main entry command for the script for packaging is:

"Scripts" : {// ...
	"build": "node scripts/build.js",}Copy the code

In other words, when NPM run build is executed, node scripts/build.js is executed, so let’s see what scripts/build.js does.

// Import all required modules
const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const rollup = require('rollup')
const terser = require('terser')

// no 'dist' directory is created
if(! fs.existsSync('dist')) {
  fs.mkdirSync('dist')}// 'getAllBuilds' gets all of the build builds under' scripts/config '.
// There are several main fields: define package entry 'entry', file output address 'dest', generate package format 'format', etc.
let builds = require('./config').getAllBuilds()

// filter builds via command line arg
// Check if additional parameters are passed, such as' node scripts/build.js -- web-running-cjs, web-server-renderer ',
// only web-runtime-cjs, web-server-renderer corresponding build configurations are packaged
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})}// Pack the main entrance
build(builds)

function build (builds) {
  let built = 0 // Use an index to point to which package needs to be built
  const total = builds.length
  
  // Define an iteration function, with each next function representing the execution of a build package
  // Form all build packages into a build queue and build one by one
  const next = () = > {
    buildEntry(builds[built]).then(() = > {
      built++
      if (built < total) {
        next()
      }
    }).catch(logError) // Error catch for promise
  }
  next()
}

function buildEntry (config) { // Package a single build
  const output = config.output
  const { file, banner } = output
  const isProd = /(min|prod)\.js$/.test(file)
  
  // Pomise is returned here, so use this to chain-iterate to generate different builds
  return rollup.rollup(config)
    .then(bundle= > bundle.generate(output))
    .then(({ output: [{ code }] }) = > {
      if (isProd) {
        const minified = (banner ? banner + '\n' : ' ') + terser.minify(code, {
          toplevel: true.output: {
            ascii_only: true
          },
          compress: {
            pure_funcs: ['makeMap']
          }
        }).code
        return write(file, minified, true)}else {
        return write(file, code)
      }
    })
}
Copy the code
// scripts/config.js
const builds = {
    'web-runtime-cjs-dev': {
        entry: resolve('web/entry-runtime.js'),
        dest: resolve('dist/vue.runtime.common.dev.js'),
        format: 'cjs'.env: 'development',
        banner
  	},
    'web-full-dev': {
        entry: resolve('web/entry-runtime-with-compiler.js'),
        dest: resolve('dist/vue.js'),
        format: 'umd'.env: 'development'.alias: { he: './entity-decoder' },
        banner
  },
};
exports.getAllBuilds = () = > Object.keys(builds).map(genConfig)
Copy the code

Through the above packaging script code, we can conclude that Vue mainly does the following steps when packaging:

  • Judge whether or notdistDirectory, if no, create
  • To get all buildsrollupPackaging configuration
  • Check whether the command has parameters passed. If yes, the specified package to be built is filtered out based on the parameters
  • Execute the package main functionbuildAn iterated function is created internallynextAnd use a pointerbuiltLet the package be built queue by queue

After looking at the packaging script above, let’s look at the differences between the different packaging script entries.

Compare the Web runtime version with the Full Web package:

// src/platforms/web/runtime/index.js
import Vue from 'core/index'

// public mount method
// Define Vue's core mount function $mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
export default Vue
Copy the code
// src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'

// A web runtime based Vue constructor for one more layer of encapsulation
// Extend the $mount mount function to have the ability to parse templates passed in through slicing programming
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  const options = this.$options
  // resolve template/el and convert to render function
  if(! options.render) {let template = options.template
    const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV ! = ='production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
    }, this)
    options.render = render
    options.staticRenderFns = staticRenderFns
  }
  return mount.call(this, el, hydrating)
}
Copy the code

From the configuration file above, we can see that each entry will eventually import the Vue constructor exported from SRC /core/index, and then extend additional capabilities based on this constructor, such as the build version with compiler. $mount, so that Vue instance has the ability to compile the template before mounting it into the render function and then mount it. The process of converting template into render will be introduced in the following chapters. Stay tuned for this series.

conclusion

In this paper, we mainly from the Vue repository code directory on the overall directory structure to understand, through the directory structure, can know the role of the code under different directories, and understand the difference between different build versions.

Then, we start from the package file entry, understand the construction process of Vue, from the entry file to see that the Vue core code is mainly exported from SRC /core/index.js Vue constructor, and different build versions of this constructor has different function extension. $mount extends the parsing capability for options.template to Vue. Prototype.

In the next chapter, we mainly study the core code file SRC /core/index.js of Vue, and learn the principle of data responsiveness of Vue2. X version. Stay tuned.