This section is a summary of the vue.js source code learning process, recommended to compare the source code scripts/build.js together look ~

When you type the NPM run build command, the vue.js build process starts by running “build”: “node scripts/build.js”, which enters the scripts/build.js file. Let’s start with build.js. Click here to open the source code.

build.js

When looking at the source of the file, it is recommended to see what modules are introduced first, to have an impression, and then read the code will be faster to understand.

Start by looking at what variables are introduced at the beginning of the build.js file.

const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const rollup = require('rollup')
const terser = require('terser')
Copy the code

Fs is the file system in Node.js; The path module provides utilities for handling paths to files and directories; The Zlib module provides compression using Gzip, Deflate/Inflate, and Brotli; Rollup is a JavaScript module wrapper; Terser is used to compress output in a production environment.

if(! fs.existsSync('dist')) {
  fs.mkdirSync('dist')}Copy the code

Next, execute the code above to determine if a dist directory exists and create one if not. Next comes getting the packaged resource:

let builds = require('./config').getAllBuilds()
Copy the code

GetAllBuilds is a method of the config.js file that outputs an array of build key-value pairs, each of which is a parameter object needed for rollup builds. That means that builds get an array set of packed objects in all cases. The contents of the specific object are described below in config.js without further ado.

Once you have all the build information, you can determine what package files to generate based on the commands you type.

A few things to prepare for: The Process object provides information about and controls the current Node.js process. The process.argv property returns an array containing the command-line arguments passed in when the Node.js process is started. The first element will be process.execPath. The second element will be the path to the JavaScript file being executed. The remaining elements will be any other command line arguments.

Take a look at the following code:

// 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

That is when we executenpm run buildIs take theelseThe logic will help us filter weeX packaging, weeX packaging files will not be generated. When we executebuild:ssrbuild:weexIn fact, in thepackage.jsonNPM run build — web-run-time CJS,web-server-renderer, and NPM run build — weexifTo get the ROLLup package information array of SSR or WEEX.

The resulting array of builds is in the following format 👇 :

Remember that the build object contains input, external, plugins, output (file, format, banner, name), and onwarn. Then it’s Rollup’s turn to start the process of packing builds one by one 👇.

Build, in turn, calls the buildEntry method, which uses the rollup.rollup API.

build(builds)

function build (builds) {
  let built = 0
  const total = builds.length
  const next = () = > {
    buildEntry(builds[built]).then(() = > {
      built++
      if (built < total) {
        next()
      }
    }).catch(logError)
  }

  next()
}

function buildEntry (config) {
  const output = config.output
  const { file, banner } = output
  const isProd = /(min|prod)\.js$/.test(file)
  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

Rollup returns a Promise that resolves a bundle object and calls bundle.generate to generate code (code is an object named key, The source code is value), if in production, will be given to Terser.minify () to get the compressed code.

Write, getSize, logError, Blue, and a few other methods are also called internally:

function write (dest, code, zip) {
  return new Promise((resolve, reject) = > {
    function report (extra) {
      console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ' '))
      resolve()
    }

    fs.writeFile(dest, code, err= > {
      if (err) return reject(err)
      if (zip) {
        zlib.gzip(code, (err, zipped) = > {
          if (err) return reject(err)
          report(' (gzipped: ' + getSize(zipped) + ') ')})}else {
        report()
      }
    })
  })
}

function getSize (code) {
  return (code.length / 1024).toFixed(2) + 'kb'
}

function logError (e) {
  console.log(e)
}

function blue (str) {
  return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
}
Copy the code

The write method takes three arguments: destination file dest, content code, and enable zip compression. Fs. writeFile asynchronously writes data to a file, replacing it if it already exists. There are also methods defined to view log information. GetSize and Blue print blue logs on the console when the command is run.

At this point, the entire build execution process is analyzed, and you can see the log output of the blue package file 👌 on the console.

So what are the specific entry files and generated files when you package? This is where the config.js content comes in.

config.js

To open the config.js source code, click here. The Builds array defines multiple key-value pairs including entry file entry, output file dest, output format format, output file header information banner, etc. The genConfig method is responsible for the conversion of this information into the format required by rollup. Post a file format difference:

  • amd– Asynchronous module definition for module loaders like RequireJS
  • cjs— CommonJS for Node and Browserify/Webpack
  • esm– Save the software package as an ES module file, which can be passed in modern browsers<script type=module>Tags introduced
  • iife– an autoexecute function, suitable as<script>The label. (If you’re creating a bundle for your application, you might want to use it because it makes the file size smaller.)
  • umd– Generic module definition toamd.cjs 和 iifeAs a whole
  • system– SystemJS loader format

Config.js introduces rollup plugins at the beginning of the file. See what these rollup plugins do:

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
  • Rollup-plugin-buble is an ES6 compilation plug-in for rollup that functions like a simplified version of Babel.
  • Rollup-plugin-alias Specifies the alias.
  • Rollup-plugin-commonjs converts CommonJS to ES6;
  • Rollup-plugin-replace Replaces the target character in the file;
  • Rollup-plugin-node-resolve resolves third-party modules with rollup-plugin-commonJS;
  • Rollup-plugin-flow-no-whitespace Clears flow syntax to prevent package errors.

Let’s take an example of Runtime only packaging information from 👆 build and compare the packaging information defined in vue.js with the packaging information required in rollup:

What vue.js defines:

const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.dev.js'),
    format: 'cjs'.env: 'development',
    banner
  },
}
Copy the code

Rollup receives:

function genConfig (name) {
  const opts = builds[name]
  const config = {
    input: opts.entry,
    external: opts.external,
    plugins: [
      flow(),
      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)
      }
    }
  }
Copy the code

You can see that the genConfig method does some formatting transformations. In addition to the conversion format, some environment variables 👇 have been added.

  // built-in vars
  const vars = {
    __WEEX__:!!!!! opts.weex,__WEEX_VERSION__: weexVersion,
    __VERSION__: version
  }
  // feature flags
  Object.keys(featureFlags).forEach(key= > {
    vars[`process.env.${key}`] = featureFlags[key]
  })
  // build-specific env
  if (opts.env) {
    vars['process.env.NODE_ENV'] = JSON.stringify(opts.env)
  }
  config.plugins.push(replace(vars))

  if(opts.transpile ! = =false) {
    config.plugins.push(buble())
  }

  Object.defineProperty(config, '_name', {
    enumerable: false.value: name
  })

  return config
}
Copy the code

Runtime only and Runtime + Compiler

Go back to the specific packaging information defined in vue.js. Various build entry files are defined in the build mode of config.js. The most popular entry files are Web/entry-runtime-with-Compiler. js (Runtime+compiler) and Web/entry-Runtime.js (runtime only).

So what are Runtime only and Runtime + compiler?

In Runtime + compiler mode, the process is template -> ast -> render -> vDOM -> UI, while the Runtime only process is render -> vDOM -> UI.

// Compiler is needed in this case
new Vue({
    template:'<div>{{ h }}</div>'
})
// Compiler is not required in this case, Runtime only is used
new Vue({
    render (h) {
        return b('div'.this.h) 
    }
})
Copy the code

The difference is that the Runtime + compiler includes the compilation process, putting the compiled code at Runtime. Runtime only requires that the template be compiled into the render function in conjunction with vue-template-compiler (see the source code below 👇). Runtime Only runs faster because it lacks some compilation.

Vue-cli also uses the only mode by default, which can be seen in the generated code in main.js. In addition, the package.json also installs vue-template-complier for us.

Js and entry-runtime.js. They both import the same Vue from /runtime/index, while complier does a little more processing.

Entry-runtime-with-compiler.js contains the following contents:

/* @flow */

import config from 'core/config'
import { warn, cached } from 'core/util/index' // WARN Can issue [Vue WARN]: warning messages. Cached is the cache that creates a pure function method.
import { mark, measure } from 'core/util/perf' // Browser performance related: create timestamps and measures.

import Vue from './runtime/index'
import { query } from './util/index' // Query the element, or return it if it is a DOM element.
import { compileToFunctions } from './compiler/index' Parse template -> ast -> optimize -> generate -> render
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat' // Check whether the current environment requires character encoding

const idToTemplate = cached(id= > {
  const el = query(id) // Query elements against selectors
  return el && el.innerHTML
})

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function ( // vue.prototype.$mount Returns a Component.el? : string | Element, hydrating? : boolean) :Component {
  el = el && query(el)

  /* istanbul ignore if */
  // Check whether the hanging element is HTML or body, issue a warning.
  if (el === document.body || el === document.documentElement) { process.env.NODE_ENV ! = ='production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  // Convert template/el to render function
  if(! options.render) {let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) = = =The '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if(process.env.NODE_ENV ! = ='production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`.this)}}}else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if(process.env.NODE_ENV ! = ='production') {
          warn('invalid template option:' + template, this)}return this}}else if (el) {
      template = getOuterHTML(el)
    }
    // Get the HTML fragment after template serialization.
    if (template) {
      /* istanbul ignore if */
      // Type a timestamp called compile.
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile')}// Parse template to get render
      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

      /* istanbul ignore if */
      // Get the performance of the entire compilation process
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile end')
        measure(`vue The ${this._name} compile`.'compile'.'compile end')}}}return mount.call(this, el, hydrating)
}

/** * Get outerHTML of elements, taking care * of SVG elements in IE as well. * Get serialized HTML fragments describing elements (including their descendants), For example: '< div id = "d" > < p > the Content < / p > < p > Further Elaborated < / p > < / div >'. * /
function getOuterHTML (el: Element) :string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

Vue.compile = compileToFunctions // get {ast, render, staticRenderFns}

export default Vue
Copy the code

Entry-runtime.js has only these contents:

/* @flow */

import Vue from './runtime/index'

export default Vue
Copy the code

See the implementation of compileToFunctions in/SRC /util/compat for details. The process is from parse template -> ast -> optimize -> generate -> render.

conclusion

This is how vue.js + Rollup builds, defining entry and exit file information, and determining how modules should be packaged in different environments. What needs to be clarified is the difference between Runtime only and Runtime + compiler.

Since I have been reading the source code of vue. js recently, all the notes and summaries in the learning process will be recorded in my GitHub, thank you for your attention.

Reference documentation

  • Node. Js Chinese website
  • Rollup. js Chinese document