This article comes from Nanyang, the front end team of Shangzhuang

Posted on Github blog, welcome to subscribe.

Various ways to split webPack configuration files

(a)

Write your configuration information to several separate files, and then specify the configuration file to load when executing webpack using the –config parameter. The configuration file is exported using moduleImports. You can see this in webpack/react-starter.

// Webpack configuration file

|-- webpack-dev-server.config.js
|-- webpack-hot-dev-server.config.js
|-- webpack-production.config.js
|-- webpack.config.jsCopy the code
/ / NPM command

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"."dev-server": "webpack-dev-server --config webpack-dev-server.config.js --progress --colors --port 2992 --inline"."hot-dev-server": "webpack-dev-server --config webpack-hot-dev-server.config.js --hot --progress --colors --port 2992 --inline"."build": "webpack --config webpack-production.config.js --progress --profile --colors"
  },Copy the code

(2)

Call the third-party WebPack tool and use its integrated API to facilitate WebPack configuration. HenrikJoreteg/ HJS-webpack this repo does just that.

var getConfig = require('hjs-webpack')


module.exports = getConfig({
  // entry point for the app
  in: 'src/app.js'.// Name or full path of output directory
  // commonly named `www` or `public`. This
  // is where your fully static site should
  // end up for simple deployment.
  out: 'public'.// This will destroy and re-create your
  // `out` folder before building so you always
  // get a fresh folder. Usually you want this
  // but since it's destructive we make it
  // false by default
  clearBeforeBuild: true
})Copy the code

3) Scalable webpack Configurations

ones that can be reused and combined with other partial configurations

Maintain configurations in a single configuration file, but differentiate conditional branches. Set different environment variables when calling different NPM commands, and then match in the branch to return the configuration file we need.

The advantage of this is that you can manage the logic of different NPM operations in one file and share the same configuration. The webpack-merge module is used to merge configurations.


const parts = require('./webpack-config/parts');

switch(process.env.npm_lifecycle_event) {
  case 'build': 
    config = merge(common, 
      parts.clean(PATHS.build),
      parts.setupSourceMapForBuild(),
      parts.setupCSS(PATHS.app),
      parts.extractBundle({
        name: 'vendor'.entries: ['react'.'vue'.'vuex']
      }),
      parts.setFreeVariable('process.env.NODE_ENV'.'production'),
      parts.minify()
      );
    break;
  default: 
    config = merge(common, 
      parts.setupSourceMapForDev(),
      parts.devServer(), 
      parts.setupCSS(PATHS.app));
}Copy the code
// minify example
exports.minify = function () {
  return {
    plugins: [
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false.drop_console: true
        },
        comments: false.beautify: false}}})]Copy the code

Automatic refresh in the development environment

webpack-dev-server

Webpack-dev-server starts the server based on Webpack’s Watch.

Webpack-dev-server is an in-memory development server that supports the advanced Webpack feature hot Module Replacement. This is handy for componentized development like React Vue.

Start the server with the webpack-dev-server command, with HMR and the ability to implement a partial browser refresh for code changes.

hot module replacement

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload. The HMR mechanism can modify, add, or remove modules while the application is running without refreshing the entire page.

The HMR mechanism is suitable for single-page applications.

To implement HMR mechanism, it is necessary to cooperate with webpack-dev-server, which itself realizes the ability to monitor changes of Watch files. Turning on HMR option will add the ability of changes of Watch module. This is the basis for the HMR mechanism to work.

From the WebPack compiler perspective

Each time a module is modified, webpack generates two parts, manifest.json and chunks that are compiled for the module update. Manifest.json stores hash values before and after the chunk change.

From the perspective of the compiler Webpack provides the raw material for HMR. For subsequent use.

From the module’s point of view

When a module changes, WebPack generates the two-part base file described earlier, but when do you apply the changed module to your app? This is where you need to write handlers in your application code to receive module changes. But you can’t write handlers in all modules, can you? This is where the message bubbling mechanism is used.

As shown in figure A.js and C.js have no relevant HMR codes, and B. Js has relevant HMR codes. If there is a change in module C and module C has no HMR, it will bubble to module A and b. Module B captured the message, and THE HMR runtime would perform some operations accordingly, while A. JS could not capture the information and would bubble to entry.js. Once there is a message bubble entry block, it means that HMR failed this time, and HMR will degrade to reload the whole page.

From the HMR runtime perspective

The HMR runtime is a set of related operation apis that support two methods: check and apply.

Check makes an HTTP request to retrieve the updated manifest, as well as some of the updated chunks.

Setting environment variables

var env = {
  'process.env.NODE_ENV': '"production"'
}
new webpack.DefinePlugin(env)Copy the code

Notice there’s a double quote here between single quotes why?

And how does the Webpack.defineplugin plugin work?

When you’re developing, you want to write a lot of code that only appears in the development environment, like interface mocks, that doesn’t exist after you build commands.

This is useful for framework or plug-in or component development. Vue, React, etc. There are many useful hints that can be provided in the dev mode of these frameworks.

Package file split

Why do you want to split packaged files?

For a single-page application project, it can be divided into business code and third-party code. Business code will change frequently, while third-party code generally changes less. If the user needs to download the whole JS file again every time the business code is modified, it is not desirable for loading performance. So we typically package our code separately into business code and third party code. Although there is an extra requested file, there is some network overhead, but compared to the browser can cache the file, this overhead is insignificant.

We define the entry of app in entry, and the corresponding business logic is encapsulated in this entry file. If we want the third-party code to be independent, we need to add another entry, and we are used to naming vendor.

// app.js

require('vue');
require('vuex');Copy the code
// webpack.config.js


entry: {
    app: 'app/app.js'.vendor: ['vue'.'vuex'],},Copy the code

Vendor entry parameters are passed as an array, which is a very convenient way to inject multiple dependencies and bundle them together into a chunk. And you don’t have to manually create a real entry file.

This is equivalent to:

// vendor.js

require('vue');
require('vuex');

// app.js

require('vue');
require('vuex');Copy the code
// webpack.config.js


entry: {
    app: 'app/app.js'.vendor: 'app/vendor.js',},Copy the code

However, this only declares a vendor entry. For the app entry, the packaged file will still have vUE and VUex dependencies, and the newly packaged file will also have vUE and VUex dependencies. The module dependencies are shown below.

The A here can stand for vUE dependencies, and the resulting package files are two parallel files that both contain VUE dependencies.

In this case, the CommonsChunkPlugin needs to be introduced

This is a pretty complex plugin. It fundamentally allows us to extract all the common modules from different bundles and add them to the common bundle. If a common bundle does not exist, then it creates a new one.

This is a fairly complex plug-in, and its basic function is to allow us to separate identical modules from different packaging files and add them to the common packaging file. If a common package file does not exist, one is added. The plugin also moves the runtime to a public chunk package.

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    names: ['vendor'.'manifest'"})"Copy the code

In this case, the name can select an existing block, which is the vendor block because we use the vendor block as an entry point to manage third-party code.

Names is passed in an array containing two trunk names, which means that the CommonsChunkPlugin executes this method twice, the first time removing the common third-party code and moving it into the vendor block. This process is also done by moving the runtime runtime into the Vendor block, and the second execution is done by moving the runtime runtime out into the Manifest block. This step resolves the cache problem.

Js is the business code, vendor is the public third-party code, and manifest.js is the runtime.

Chunk Type Indicates the block type

The chunk type in webpack1.0 is a mouthful of the chunk type, so I’ll read it here.

Chunk is one of the most basic concepts in Webpack, and chunk is often confused with entry. In the “package file split section” we define two entry points — app and vendor, and with some configuration, WebPack generates the final package files. The last files generated in this example are app.js, vendor.js, manifest.js. These files are called chunks.

Entry & Chunk can be simply defined as an entry and an exit

There are three types of Chunk for Webpack in the official 1.0 documentation:

  1. Entry Chunk Indicates the entry chunk
  2. Normal chunk Normal chunk
  3. Initial chunk Initial chunk

Entry Chunk Indicates the entry chunk

Entry chunk An entry chunk is a file that is compiled from an entry chunk. It is described on the official website

An entry chunk contains the runtime plus a bunch of modules

The chunk that contains the Runtime runtime can be called an Entry chunk. Once the Entry chunk that originally contained the Runtime loses the runtime, the chunk becomes an Initial chunk.

Normal chunk Normal chunk

A normal chunk contains no runtime. It only contains a bunch of modules.

A normal block does not contain the runtime, only a series of modules. However, normal blocks can be loaded dynamically while the application is running. It is typically loaded as a JSONP wrapper. Code Splitting mainly uses plain blocks.

Initial chunk Initial chunk

An initial chunk is a normal chunk.

The official definition of Initial chunk is very simple. The initial chunk is a normal chunk and does not contain the runtime. The difference is that the initial chunk is calculated within the initial loading process time. As mentioned in the introduction of Entry Chunk, once an entry chunk loses its run-time, it becomes the original chunk. This transformation is often implemented by the CommonsChunkPlugin plugin.

Example explanation

Again, take the package file split code as an example,

// app.js

require('vue');
require('vuex');Copy the code
// webpack.config.js


entry: {
    app: 'app/app.js'.vendor: ['vue'.'vuex'],},Copy the code

Before using the CommonsChunkPlugin, two entries are packaged into two chunks, each of which contains the runtime, which is called the entry Chunk entry block.

Once CommonsChunkPlugin is used, the runtime is eventually transferred to the manifest.js file, and the resulting three chunkapp.js, Vendor. js, and manifest.js are packaged. App. js and Vendor. js lose their runtime, so the entry block becomes the initial block.

code splitting

As mentioned earlier, splitting dependencies helps browser caching and improves user loading speed, but as business complexity increases, the amount of code is always a problem. In this case, the dynamic loading capability of normal Chunk is required.

It allows you to split your code into various bundles which you can then load on demand — like when a user navigates to A matching route, or on an event from the user. code splitting allows us to split code into different packages that can be loaded on demand. When a user navigates to a route, or when the user triggers an event, Load the corresponding code asynchronously.

We need to manually add some split points in the business logic that indicate where the event logic is followed by asynchronous loading of the code block.


// test
window.addEventListener('click'.function () {
  require.ensure(['vue'.'vuex'].function (require) {})})Copy the code

This code shows that when the user clicks, it asynchronously requests a JS file that contains vue Vuex dependencies.

After packaging, a package file will be generated according to the information of manual segmentation points, which is the file starting with 0 in the first line in the figure. This file is also called asynchronously loaded file.

Here is an example of a previous VUE project where the file was reduced from 212KB to 137KB and the style file was also reduced from 58KB to 7KB after several routes were extracted with code Splitting and loaded asynchronously. For the first screen rendering, there is a significant performance increase.

Reference:

  • itsclem.com/?p=1036
  • webpack.js.org/concepts/
  • Blog.oyyd.net/post/how_do…
  • Webpack. Making. IO/docs/hot – mo…
  • Survivejs.com/webpack/int…
  • webpack.js.org/