I. Construction method

The Vue2 source code is built based on Rollup and is suitable for building JavaScript libraries without recognizing other resource types. If you need to identify other resource types, you need plug-ins for Rollup to recognize them. The Webpack build tool, on the other hand, is more suitable for use in projects.

Second, the construction process

With the above flow chart, how is Vue2 source code constructed?

Json file to execute the NPM script, as follows:

{
  "script": {
    "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

When we execute the command: NPM run build, we are actually executing the build.js file under the directory scripts.

As you can see from the above flowchart, the file scripts/build.js does three main things:

  • From the filescripts/config.jsLoad build Configurations (Builds)
  • Filter the build configuration based on command line parameters
  • Call a functionbuildExecute a build

So how do these three things work out internally?

Load build Configurations (Builds)

In the file scripts/build.js, the code to load the build configuration is as follows:

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

As can be seen from the code, call getAllBuilds in the file scripts/config.js as follows:

if (process.env.TARGET) {
    module.exports = genConfig(process.env.TARGET)
} else {
    exports.getBuild = genConfig
    exports.getAllBuilds = () = > Object.keys(builds).map(genConfig)
}
Copy the code

The build build () function returns build configurations, but builds build configurations are iterated over before returning, and configurations are generated by calling genConfig according to the Rollup build specification.

Vue can build builds for different purposes. The details are in variable builds. What builds can be built with a picture first?

Let’s take a look at the specific configuration and give an example:

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
    },
    'web-runtime-cjs-prod': {
        entry: resolve('web/entry-runtime.js'),
        dest: resolve('dist/vue.runtime.common.prod.js'),
        format: 'cjs'.env: 'production',
        banner
    },
    // Runtime+compiler CommonJS build (CommonJS)
    'web-full-cjs-dev': {
        entry: resolve('web/entry-runtime-with-compiler.js'),
        dest: resolve('dist/vue.common.dev.js'),
        format: 'cjs'.env: 'development'.alias: { he: './entity-decoder' },
        banner
    },
    'web-full-cjs-prod': {
        entry: resolve('web/entry-runtime-with-compiler.js'),
        dest: resolve('dist/vue.common.prod.js'),
        format: 'cjs'.env: 'production'.alias: { he: './entity-decoder' },
        banner
    },
    ...
}
Copy the code

Take web-runtime-cjs-prod as an example to describe the configuration parameters:

  • entry: represents the build entryJSAddress of the file
  • dest: indicates the builtJSAddress of the file
  • format: indicates the built file to followCommonJSformat
  • env: Specifies the build environment
  • banner: Build instructions

Enter and dest use the resolve function, so what is it? The code implementation is as follows:

const resolve = p= > {
    const base = p.split('/') [0]
    if (aliases[base]) {
        return path.resolve(aliases[base], p.slice(base.length + 1))}else {
        return path.resolve(__dirname, '.. / ', p)
    }
}
Copy the code

The p () function retrieves the full file path from the passed argument p.

For genConfig, its internal implementation is also quite simple, mainly according to the Rollup specification to regenerate the configuration for the subsequent package build, the code implementation is as follows:

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)
            }
        }
    }

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

Filter build configuration

Based on the build configuration obtained above, filter the build configuration according to the command line parameters. Its implementation logic is also quite simple, and the code implementation is as follows:

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

To build

With the basics in place, we can start building with a single line of code to implement the core logic:

build(builds)
Copy the code

All the build implementations are in the build function. Let’s look at the code implementation:

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

The next function is a nested function that calls buildEntry to build builds on builds and eventually builds vue.js artifacts for different uses. BuildEntry is implemented as follows:

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

The Rollup () function Rollup is used to package a build, and its output is bundle. Bundle. generate(output) generates a product that can be processed for different environments.

In a production environment, use Terser to compress the code and call the function write to write the product to the output directory. In a development environment, the function write is called to write the product to the output directory without any processing.

Runtime Only VS Runtime + Compiler

From the above analysis, we can see that Vue can build Runtime Only and Runtime + Compiler versions. What is the difference between the two versions?

Runtime Only

Run-time version only, parsed only by the Render function, not recognizing the template attribute.

Vue-cli 3.0+ uses Runtime Only by default, so it works in vue projects because the Template is compiled into the render function at compile time with the vue-template-compiler of Webpack. This layer of transformation takes place at compile time, so it contains only run-time code, which is relatively small.

For vue-CLI 3.0+ projects, if you want to use the Runtime + Compiler version, you can set the property runtimeCompiler to true in the configuration file vue.config.js:

module.exports = {
  runtimeCompiler: true
}
Copy the code

runtimeCompiler

  • Type: boolean

  • Default: false

    Whether to use a Vue build with a runtime compiler. Setting this to true allows you to use the template option in Vue components, but this will add an extra 10KB or so to your application.

Runtime + Compiler

If vue.js is introduced through CDN and no build tools such as Webpack are used in the project; If you pass in a string using the Vue attribute template, you need to compile the template on the client side and convert it to the render function before the browser can parse it. Therefore, you need to use the Vue version with the Compiler, namely Runtime + Compiler.

The Runtime Only vue.js version is recommended because this compilation takes place at Runtime and has a performance impact.

Refer to the link

  • Vue.js source code construction