The opening

Many people have used Webpack more or less, but few people can systematically learn webPack configuration, encounter errors will be confused, do not know where to check? Performance optimization also do not know what to do, online optimization tutorial is not in line with their own project? And so on a series of questions! This article is a step-by-step process from the most basic configuration to a complete large-scale project. Let you never fear webpack, let it really become your right-hand man!

This paper from the following topics to achieve

  • Topic 1: A preliminary study of Webpack? Explore webpack packaging principles
  • Project 2: Set up development environment and production environment
  • Topic 3: Loader for basic configuration
  • Lesson 4: Webpack performance optimization
  • 课时 5: handwriting loader implementation of optional chain
  • Class 6: Webpack compilation and optimization
  • Lesson 7: Multi-page configuration
  • 课时 8: hand write a webpack plug-in
  • 课时 9: building SSR

The project address

Github.com/luoxue-vict…

I’ve cut each lesson into a different branch, so you can learn it step by step

The scaffold

npm i -g webpack-box
Copy the code

use

webpack-box dev   # Development environment
webpack-box build # Production environment
webpack-box dll   # compile error subcontracting
webpack-box dev index   # specify page compile (multiple pages)
webpack-box build index # specify page compile (multiple pages)
webpack-box build index --report # Enable package analysis
webpack-box build:ssr  # compiler SSR
webpack-box ssr:server # run on the server
Copy the code

Use in package.json

{
  "scripts": {
    "dev": "webpack-box dev"."build": "webpack-box build"."dll": "webpack-box dll"."build:ssr": "webpack-box build:ssr"."ssr:server": "webpack-box ssr:server"}}Copy the code

use

npm run build --report # Enable package analysis
Copy the code

The extension configuration

box.config.js

module.exports = function (config) {
  / * * *@param {object} DLL open differential subcontracting *@param {object} The Pages multi-page configuration uses box Run /build index to use *@param {function} chainWebpack 
   * @param {string} Entry entry *@param {string} The output export *@param {string} publicPath 
   * @param {string} port 
   */
  return {
    entry: 'src/main.js'.output: 'dist'.publicPath: '/common/'.port: 8888.dll: {
      venders: ['vue'.'react']},pages: {
      index: {
        entry: 'src/main.js'.template: 'public/index.html'.filename: 'index.html',},index2: {
        entry: 'src/main.js'.template: 'public/index2.html'.filename: 'index2.html',}},chainWebpack(config){}}}Copy the code

Topic 1: A preliminary study of Webpack? Explore webpack packaging principles

To learn Webpack well, we must first understand the mechanism of Webpack, we start from JS loading CSS study.

Let’s start with this little exercise to get into webpack

Add index.css to index.js

const css = require('./index.css')
console.log(css)
Copy the code

CSS files are not recognized by JS, and Webpack is no exception

How do we make WebPack recognize CSS? The answer is that Webpack gives us a loader mechanism that allows us to use the Loader to convert arbitrary files into files that WebPack can recognize

This chapter mainly explains

  1. Basic WebPack configuration
  2. How does the bundle load the module
  3. Dynamic import loading principle
  4. Rewrite the configuration using webpack-chain
  5. Lesson 1 Summary

Basic WebPack configuration

Dependency packages required

package.json

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack".// Development environment
    "build": "cross-env NODE_ENV=production webpack" // Production environment
  },
  "dependencies": {
    "cross-env": "^ 6.0.3".// Compatible with various environments
    "css-loader": "^ 3.2.0"."rimraf": "^ 3.0.0".// Delete files
    "webpack": "^ 4.41.2"
  },
  "devDependencies": {
    "webpack-cli": "^ 3.3.10"}}Copy the code

Basic WebPack configuration

webpack.config.js

const path = require('path');
const rimraf = require('rimraf');

// Delete the dist directory
rimraf.sync('dist');

/ / webpack configuration
module.exports = {
  entry: './src/index'.mode: process.env.NODE_ENV,
  output: {
    filename: 'bundle.js'.path: path.resolve(__dirname, 'dist')}};Copy the code

CSS is introduced into JS

src/index.js

const css = require('css-loader! ./index.css');
const a = 100;
console.log(a, css);
Copy the code

Test the CSS

src/index.css

body {
  width: 100%;
  height: 100vh;
  background-color: orange;
}
Copy the code

How does the bundle load the module

I removed some comments and some distractions to make it look clearer

  • bundleIs an immediate function that you can think of as a giant module that bundles all the modules together.
  • webpackAll modules are packaged asbundleIs injected through an object
  • 0 moduleIs the entrance
  • webpackthrough__webpack_require__The introduction of the module
  • __webpack_require__That’s what we userequire,webpackEncapsulate a layer

dist/bundle.js

(function(modules) {
  function __webpack_require__(moduleId) {
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    var module = (installedModules[moduleId] = {
      i: moduleId,
      l: false.exports: {}}); modules[moduleId].call(module.exports,
      module.module.exports,
      __webpack_require__
    );

    module.l = true;

    return module.exports;
  }
  return __webpack_require__((__webpack_require__.s = 0)); ({})'./src/index.js': function(module.exports, __webpack_require__) {
    eval(` const css = __webpack_require__("./src/style/index.css") const a = 100; console.log(a, css) `);
  },

  './src/style/index.css': function(module.exports, __webpack_require__) {
    eval(` exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false); exports.push([module.i, "body { width: 100%; height: 100vh; background-color: orange; }", ""]); `);
  },

  0: function(module.exports, __webpack_require__) {
    module.exports = __webpack_require__('./src/index.js'); }});Copy the code

Dynamic import loading principle

What happens if we change the require of index.js to import?

We know that the difference between import and require is that import is dynamically loaded and only gets loaded when it’s needed, whereas require gets loaded when it’s declared, and Webpack gets to require and loads it as a module into the bundle’s dependencies

The question is, if we use import to reference a module, how does it load?

The require to import ()

src/index.js

// const css = require('css-loader! ./index.css');
const css = import('css-loader! ./index.css');
const a = 100;
console.log(a, css);
Copy the code

Load the package results dynamically

In addition to the normal bundle, we can also see a 0.boundle.js

Boundle. js is our dynamically loaded index.css module

|-- bundle.js
|-- 0.boundle.js
Copy the code

Dynamic module

0.boundle.js

This file simply puts the modules we import into a separate JS file

(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
  [0] and {'./node_modules/css-loader/dist/runtime/api.js': function(
      module.exports,
      __webpack_require__
    ) {
      'use strict';
      eval(`... `);
    },

    './src/style/index.css': function(module.exports, __webpack_require__) {
      eval(` exports = module.exports = __webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false)); exports.push([module.i, \`body { width: 100%; height: 100vh; background-color: orange; },"\`] `); }}]);Copy the code

Dynamic module loading logic

Dist /bundle.js

It’s easy to understand. I’ve removed most of the code and comments

The principle is very simple, using jSONP implementation principles to load modules, but in this case not from the server but from other modules

  1. When the module is calledwindowSign up for onewebpackJsonpArrays, the window [‘ webpackJsonp] = window [‘ webpackJsonp] | | []
  2. When weimportWhen,webpackWill be called__webpack_require__.e(0)The method, which is equal torequireEnsure
  3. webpackOne will be created dynamicallyscriptTag to load the module, which will be injected into thewebpackJsonp
  4. webpackJsonp.pushWill be calledwebpackJsonpCallbackTo get the module
  5. Use the module after it is loaded__webpack_require__Acquisition module
(function(modules) {
  function webpackJsonpCallback(data) {
    var chunkIds = data[0];
    var moreModules = data[1];
    var moduleId,
      chunkId,
      i = 0,
      resolves = [];
    for (; i < chunkIds.length; i++) {
      chunkId = chunkIds[i];
      if (
        Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&
        installedChunks[chunkId]
      ) {
        resolves.push(installedChunks[chunkId][0]);
      }
      // The module is installed
      installedChunks[chunkId] = 0;
    }
    for (moduleId in moreModules) {
      if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; }}if (parentJsonpFunction) parentJsonpFunction(data);
    while (resolves.length) {
      // Execute all promise's resolve functionsresolves.shift()(); }}function jsonpScriptSrc(chunkId) {
    return __webpack_require__.p + ' ' + ({}[chunkId] || chunkId) + '.bundle.js';
  }

  function __webpack_require__(moduleId) {
    // ...
  }

  __webpack_require__.e = function requireEnsure(chunkId) {
    var promises = [];
    // ...
    var script = document.createElement('script');
    var onScriptComplete;
    script.charset = 'utf-8';
    script.timeout = 120;
    script.src = jsonpScriptSrc(chunkId);

    onScriptComplete = function(event) {
      // Handle exceptions and eliminate side effects
      // ...
    };
    var timeout = setTimeout(function() {
      onScriptComplete({ type: 'timeout'.target: script });
    }, 120000);
    script.onerror = script.onload = onScriptComplete;
    document.head.appendChild(script);
    // ...
    // Dynamically load modules
    return Promise.all(promises);
  };

  var jsonpArray = (window['webpackJsonp'] = window['webpackJsonp'] | | []);// Override the array push method
  jsonpArray.push = webpackJsonpCallback;
  jsonpArray = jsonpArray.slice();
  for (var i = 0; i < jsonpArray.length; i++)
    webpackJsonpCallback(jsonpArray[i]);

  return __webpack_require__((__webpack_require__.s = 0)); ({})'./src/index.js': function(module.exports, __webpack_require__) {
    eval(` const css = __webpack_require__.e(0).then(__webpack_require__.t.bind(null, "./src/style/index.css", 7)) const a = 100; console.log(a, css) `);
  },
  0: function(module.exports, __webpack_require__) {
    eval(`module.exports = __webpack_require__("./src/index.js"); `); }});Copy the code

Rewrite the configuration using webpack-chain

We use webpack-chain to write the webpack configuration because the webpack-chain approach is more flexible

The official explanation

Webpack-chain attempts to create and modify webPack configurations by providing chained or downstream apis. The Key part of the API can be referenced by a user-specified name, which helps standardize how configurations are modified across projects.

const path = require('path');
const rimraf = require('rimraf');
const Config = require('webpack-chain');
const config = new Config();
const resolve = src= > {
  return path.join(process.cwd(), src);
};

// Delete the dist directory
rimraf.sync('dist');

config
  / / the entry
  .entry('src/index')
  .add(resolve('src/index.js'))
  .end()
  / / mode
  //.mode(process.env.node_env) is equivalent below
  .set('mode', process.env.NODE_ENV)
  / / export
  .output.path(resolve('dist'))
  .filename('[name].bundle.js');

config.module
  .rule('css')
  .test(/\.css$/)
  .use('css')
  .loader('css-loader');

module.exports = config.toConfig();
Copy the code

Lesson 1 Summary

So far period 1 has ended, we have mainly done the following things

  1. Basic WebPack configuration
  2. Package CSS into JS using CSS-loader
  3. How does the bundle load the module
  4. How does Webpack implement dynamic loading of modules

To learn a tool, we should not only understand its configuration, but also understand its principle. Only by learning the essence of the framework, we can cope with the rapid development of today’s big front end.


Project 2: Set up development environment and production environment

Summary of this chapter:

  • directory
  • Implement pluggable configuration
  • Build the production environment
  • Building a Development Environment (devServer)
  • Extract the CSS
  • Automatic HTML generation
  • Project test

directory

│─ build │ ├ ─ base.js// Public section│ │ ─ ─ build. Js │ └ ─ ─ dev. Js │ ─ ─ the config │ │ ─ ─ base. Js// Basic configuration│ │ ─ ─ CSS, js/ / CSS configuration│ │ ─ ─ HtmlWebpackPlugin. Js/ / HTML configuration│ └ ─ ─ MiniCssExtractPlugin. Js/ / CSS│ ─ ─ the public// Public resources│ └ ─ ─ index. HTML/ / HTML template└ ─ ─ the SRC// Develop the directory│─ style │ ├ ─ ├ ─ sci-1.txt/ / the main entry
Copy the code

Implement pluggable configuration

package.json

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development node build/dev.js"."build": "cross-env NODE_ENV=production node build/build.js"
  },
  "dependencies": {
    "cross-env": "^ 6.0.3"."css-loader": "^ 3.2.0"."cssnano": "^ 4.1.10"."ora": "^ 4.0.3"."rimraf": "^ 3.0.0"."webpack": "^ 4.41.2"
  },
  "devDependencies": {
    "extract-text-webpack-plugin": "^ 3.0.2." "."html-webpack-plugin": "^ 3.2.0"."mini-css-extract-plugin": "^ 0.8.0"."vue-cli-plugin-commitlint": "^" 1.0.4."webpack-chain": "^ 6.0.0"."webpack-cli": "^ 3.3.10"."webpack-dev-server": "^ 3.9.0"}}Copy the code

build/base.js

const { findSync } = require('.. /lib');
const Config = require('webpack-chain');
const config = new Config();
const files = findSync('config');
const path = require('path');
const resolve = p= > {
  return path.join(process.cwd(), p);
};

module.exports = () = > {
  const map = new Map(a); files.map(_= > {
    const name = _.split('/')
      .pop()
      .replace('.js'.' ');
    return map.set(name, require(_)(config, resolve));
  });

  map.forEach(v= > v());

  return config;
};
Copy the code

Build the production environment

build/build.js

const rimraf = require('rimraf');
const ora = require('ora');
const chalk = require('chalk');
const path = require('path');
// Delete the dist directory
rimraf.sync(path.join(process.cwd(), 'dist'));

const config = require('./base') ();const webpack = require('webpack');
const spinner = ora('Start building the project... ');
spinner.start();

webpack(config.toConfig(), function(err, stats) {
  spinner.stop();
  if (err) throw err;
  process.stdout.write(
    stats.toString({
      colors: true.modules: false.children: false.chunks: false.chunkModules: false
    }) + '\n\n'
  );

  if (stats.hasErrors()) {
    console.log(chalk.red('Build failed \n'));
    process.exit(1);
  }

  console.log(chalk.cyan('build complete \ n'));
});
Copy the code

Building a Development Environment (devServer)

build/dev.js

const config = require('./base') ();const webpack = require('webpack');
const chalk = require('chalk');
const WebpackDevServer = require('webpack-dev-server');
const port = 8080;
const publicPath = '/common/';

config.devServer
  .quiet(true)
  .hot(true)
  .https(false)
  .disableHostCheck(true)
  .publicPath(publicPath)
  .clientLogLevel('none');

const compiler = webpack(config.toConfig());
// Get the devServer parameter
const chainDevServer = compiler.options.devServer;
const server = new WebpackDevServer(
  compiler,
  Object.assign(chainDevServer, {})
);

['SIGINT'.'SIGTERM'].forEach(signal= > {
  process.on(signal, () = > {
    server.close(() = > {
      process.exit(0);
    });
  });
});
// Listen on the port
server.listen(port);

new Promise(() = > {
  compiler.hooks.done.tap('dev'.stats= > {
    const empty = ' ';
    const common = 'App running at: - Local: http://127.0.0.1:${port}${publicPath}\n`;
    console.log(chalk.cyan('\n' + empty + common));
  });
});
Copy the code

Extract the CSS

config/css.js

The CSS extracts loader configuration

module.exports = (config, resolve) = > {
  return (lang, test) = > {
    const baseRule = config.module.rule(lang).test(test);
    const normalRule = baseRule.oneOf('normal');
    applyLoaders(normalRule);
    function applyLoaders(rule) {
      rule
        .use('extract-css-loader')
        .loader(require('mini-css-extract-plugin').loader)
        .options({
          publicPath: '/'
        });
      rule
        .use('css-loader')
        .loader('css-loader') .options({}); }}; };Copy the code

CSS extraction plugin MiniCssExtractPlugin

config/MiniCssExtractPlugin.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (config, resolve) = > {
  return () = > {
    config
      .oneOf('normal')
      .plugin('mini-css-extract')
      .use(MiniCssExtractPlugin);
  };
};
Copy the code

Automatic HTML generation

config/HtmlWebpackPlugin.js

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (config, resolve) = > {
  return () = > {
    config.plugin('html').use(HtmlWebpackPlugin, [
      {
        template: 'public/index.html'}]); }; };Copy the code

Project test

Testing the HTML template

public/index.html

<! DOCTYPEhtml>
<html>
  <head>
    <meta charset="UTF-8">
    <title>learn_webpack</title>
  <body></body>
</html>
Copy the code

Testing the CSS Template

src/style/index.css

.test {
  width: 200px;
  height: 200px;
  color: red;
  background-color: orange;
}
Copy the code

Program entrance

src/main.js

require('./style/index.css');

const h2 = document.createElement('h2');
h2.className = 'test';
h2.innerText = 'test';
document.body.append(h2);
Copy the code

Topic 3: Loader for basic configuration

Summary of this chapter:

  • Configure the Babel
  • Configure TS using Babel
  • Ts Static type check
  • Friendly error prompt plugin
  • Configure style, style, CSS, less, SASS, PostCSS, etc
  • Postcss configuration
  • Compare the CSS before and after compilation
  • Configuration autoprefixer
  • Open source map

directory

Add the following files

│ ─ ─ ─ ─ the config// Configure the directory│ │ ─ ─ babelLoader. Js/ / the Babel - loader configuration│ │ ─ ─ ForkTsChecker. Js// ts static check│ │ ─ ─ FriendlyErrorsWebpackPlugin. Js// Friendly error│ ├ ─ ├ ─ SRC// Develop the directory│ │ ├ ─ style │ │ ├ ─ CSS │ │ ├ ─ less/ / test less│ │ │ ─ ─ index. SCSS/ / test sass│ │ └ ─ ─ index. Postcss/ / test postcss│ ├ ─ garbage ─ garbageTs / / test│ ─ ─ Babel. Js │ ─ ─ postcss. Config. Js/ / postcss configuration│ ─ ─ tsconfig. Json/ / ts configuration└ ─ ─ ─ ─ dist// The packaged directory│─ app.bundle.js │─ app.css └─ index.htmlCopy the code

Configure the Babel

config/babelLoader.js

module.exports = (config, resolve) = > {
  const baseRule = config.module.rule('js').test(/. Js │. TSX? $/);
  const babelPath = resolve('babel.js');
  const babelConf = require(babelPath);
  const version = require(resolve('node_modules/@babel/core/package.json'))
    .version;
  return () = > {
    baseRule
      .use('babel')
      .loader(require.resolve('babel-loader'))
      .options(babelConf({ version }));
  };
};
Copy the code

Configure TS using Babel

Here we use the Babel plugin @babel/preset-typescript to convert TS to JS, And using ForkTsCheckerWebpackPlugin, ForkTsCheckerNotifierWebpackPlugin plug-ins for errors.

babel.js

module.exports = function(api) {
  return {
    presets: [['@babel/preset-env',
        {
          targets: {
            chrome: 59.edge: 13.firefox: 50.safari: 8}}], ['@babel/preset-typescript',
        {
          allExtensions: true}]],plugins: [
      '@babel/plugin-transform-typescript'.'transform-class-properties'.'@babel/proposal-object-rest-spread']}; };Copy the code

Ts Static type check

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');

module.exports = (config, resolve) = > {
  return () = > {
    config.plugin('ts-fork').use(ForkTsCheckerWebpackPlugin, [
      {
        // Setting Async to false prevents Webpack emit from waiting for the type checker /linter and adds errors to Webpack compilation.
        async: false}]);// Pop up TypeScript type checking errors
    // Fork-ts-checker-webpack-plugin async is false
    // Otherwise, it is recommended to use it for easy error detection
    config.plugin('ts-notifier').use(ForkTsCheckerNotifierWebpackPlugin, [
      {
        title: 'TypeScript'.excludeWarnings: true.skipSuccessful: true}]); }; };Copy the code

Friendly error prompt plugin

config/FriendlyErrorsWebpackPlugin.js

const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

module.exports = (config, resolve) = > {
  return () = > {
    config.plugin('error').use(FriendlyErrorsWebpackPlugin);
  };
};
Copy the code

Configure style, style, CSS, less, SASS, PostCSS, etc

module.exports = (config, resolve) = > {
  const createCSSRule = (lang, test, loader, options = {}) = > {
    const baseRule = config.module.rule(lang).test(test);
    const normalRule = baseRule.oneOf('normal');
    normalRule
      .use('extract-css-loader')
      .loader(require('mini-css-extract-plugin').loader)
      .options({
        hmr: process.env.NODE_ENV === 'development'.publicPath: '/'
      });
    normalRule
      .use('css-loader')
      .loader(require.resolve('css-loader'))
      .options({});
    normalRule.use('postcss-loader').loader(require.resolve('postcss-loader'));
    if (loader) {
      const rs = require.resolve(loader); normalRule .use(loader) .loader(rs) .options(options); }};return () = > {
    createCSSRule('css'./\.css$/.'css-loader'{}); createCSSRule('less'./\.less$/.'less-loader'{}); createCSSRule('scss'./\.scss$/.'sass-loader'{}); createCSSRule('postcss'./\.p(ost)? css$/);
  };
};
Copy the code

Postcss configuration

module.exports = {
  plugins: {
    'postcss-px-to-viewport': {
      unitToConvert: 'px'.viewportWidth: 750.unitPrecision: 5.propList: [The '*'].viewportUnit: 'vw'.fontViewportUnit: 'vw'.selectorBlackList: [].minPixelValue: 1.mediaQuery: false.replace: true.exclude: [].landscape: false.landscapeUnit: 'vw'.landscapeWidth: 568}}};Copy the code

Compare the CSS before and after compilation

src/style/index.less

/* index.less */
.test {
  width: 300px;
}
Copy the code

dist/app.css

/* index.css */
.test {
  width: 36.66667 vw;
  height: 26.66667 vw;
  color: red;
  background-color: orange;
}
/* app.css */
.test {
  font-size: 8vw;
}
/* index.less */
.test {
  width: 40vw;
}

/* index.scss */
.test {
  height: 40vw;
}
/* index.postcss */
.test {
  background: green;
  height: 26.66667 vw;
}
Copy the code

Configuration autoprefixer

Automatically add CSS prefixes

postcss.config.js

module.exports = {
  plugins: {
    autoprefixer: {
      overrideBrowserslist: [
        '> 1%'.'last 3 versions'.'iOS >= 8'.'Android >= 4'.'Chrome >= 40']}}};Copy the code

Transformation before

/* index.css */
.test {
  width: 200px;
  height: 200px;
  color: red;
  display: flex;
  background-color: orange;
}
Copy the code

After the transformation

/* index.css */
.test {
  width: 26.66667 vw;
  height: 26.66667 vw;
  color: red;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  background-color: orange;
}
Copy the code

Open source map

config.devtool('cheap-source-map');
Copy the code
└ ─ ─ dist │ ─ ─ app. Bundle. Js │ ─ ─ app. Bundle. Js. Map │ ─ ─ app. CSS │ ─ ─ app. The CSS. The map └ ─ ─ index. The HTMLCopy the code

There is a comment under the source file to prove that Sourcemap is turned on

/*# sourceMappingURL=app.css.map*/
Copy the code

Lesson 4: Webpack performance optimization

This chapter explain

  1. The separation of the Manifest
  2. Code Splitting
  3. Bundle Splitting
  4. Tree Shaking (Removing dead code)
  5. Open the gzip

The separation of the Manifest

module.exports = (config, resolve) = > {
  return () = > {
    config
      .optimization
      .runtimeChunk({
        name: "manifest"}}})Copy the code

Code Splitting

  1. Use the dynamic import or require.ensure syntax, as explained in the first section
  2. usebabel-plugin-importPlug-ins introduce component libraries on demand

Bundle Splitting

Extract common packages into chunk-vendors, e.g., if you require(‘vue’), and Webpack the vue into chunk-vendors. Bundle.js

module.exports = (config, resolve) = > {
  return () = > {
    config
      .optimization.splitChunks({
        chunks: 'async'.minSize: 30000.minChunks: 1.maxAsyncRequests: 3.maxInitialRequests: 3.cacheGroups: {
          vendors: {
            name: `chunk-vendors`.test: /[\\/]node_modules[\\/]/,
            priority: -10.chunks: 'initial'
          },
          common: {
            name: `chunk-common`.minChunks: 2.priority: -20.chunks: 'initial'.reuseExistingChunk: true
          }
        }
      })
    config.optimization.usedExports(true)}}Copy the code

Tree Shaking

config/optimization.js

config.optimization.usedExports(true);
Copy the code

src/treeShaking.js

export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}
Copy the code

Only cube is referenced in main.js

import { cube } from './treeShaking';

console.log(cube(2));
Copy the code

Tree Shaking is not used

{
  "./src/treeShaking.js": function(
    module,
    __webpack_exports__,
    __webpack_require__
  ) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, "square".function() {
      return square;
    });
    __webpack_require__.d(__webpack_exports__, "cube".function() {
      return cube;
    });
    function square(x) {
      return x * x;
    }
    function cube(x) {
      returnx * x * x; }}}Copy the code

Tree Shaking is used

Only the cube function is exported, not square

Of course, you can see that the square function is still in the bundle, but it will be destroyed during compression because it is not referenced

{
  "./src/treeShaking.js": function(
    module,
    __webpack_exports__,
    __webpack_require__
  ) {
    "use strict";
    __webpack_require__.d(__webpack_exports__, "a".function() {
      return cube;
    });
    function square(x) {
      return x * x;
    }
    function cube(x) {
      returnx * x * x; }}}Copy the code

It is safe to shaking only when a function is given an input and produces an output that does not modify anything external

How to use tree-shaking?

  1. Make sure the code is in ES6 format, i.e. Export, import
  2. In package.json, set sideEffects
  3. Make sure tree-shaking functions have no side effects
  4. Babelrc presets [[“@babel/preset-env”, {“modules”: false}]] disable conversion modules and favor Webpack for modularization
  5. Combined with uglifyjs webpack — the plugin

We don’t need to do this in WebPack 4, because WebPack already adds it by default in production, right out of the box!

Open the gzip

CompressionWebpackPlugin.js

const CompressionWebpackPlugin = require('compression-webpack-plugin');

module.exports = (config, resolve) = > {
  return () = > {
    config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [
      {
        algorithm: 'gzip'.test: /\.js(\? . *)? $/i,
        threshold: 10240.minRatio: 0.8}]); }; };Copy the code

课时 5: handwriting loader implementation of optional chain

This chapter content

  1. What is webpack Loader
  2. Optional chain introduction
  3. Loader implements optional chains

What is webpack Loader

Webpack loader is an intermediate layer for Webpack to process various types of files. Webpack is essentially a Node module, which cannot process files other than JS, so Loader helps WebPack to do a layer conversion. Turn all files into strings, which you can manipulate/modify as much as you like, and then return webPack an object containing the string and let WebPack do the rest. If webpack is a garbage factory, then loader is the garbage sorting of this factory!

Optional chain introduction

Here is not optional chain in the pure sense, because Babel and TS have been supported, there is no need for us to write a complete optional chain, but to deepen our understanding of Loader. What can Loader help us to do in work?

Use When we access an object attribute do not have to worry about the object is undefined and the error, resulting in the program can not continue to execute

Explain in? All previous access links are valid and no error is reported

const obj = {
  foo: {
    bar: {
      baz: 2}}}console.log(obj.foo.bar? .baz)/ / 2
// is converted to obj && obj.foo && obj.foo.bar && obj.foo.bar.baz
console.log(obj.foo.err? .baz)// undefined
// is converted to obj && obj.foo && obj.foo.err && obj.foo.err
Copy the code

Loader implements optional chains

Configure loader, options-chain-loader

config/OptionsChainLoader.js

module.exports = (config, resolve) = > {
  const baseRule = config.module.rule('js').test(/.js|.tsx? $/);
  const normalRule = baseRule.oneOf('normal');
  return () = > {
    normalRule
      .use('options-chain')
      .loader(resolve('options-chain-loader'))}}Copy the code

The loader will convert the entire file to a string, and the content is the content of the entire file. After modifying the content, a new content will be returned to complete a Loader conversion. Isn’t that easy?

The following operation means that we match obj.foo.bar? And convert it to obj && obj.foo && obj.foo.bar && obj.foo.bar.

options-chain-loader.js

module.exports = function(content) {
  return content.replace(new RegExp(/([\$_\w\.]+\? \.) /.'g'),function(res) {
    let str  = res.replace(/ \? / \..' ');
    let arrs = str.split('. ');
    let strArr = [];
    for(let i = 1; i <= arrs.length; i++) {
      strArr.push(arrs.slice(0,i).join('. ')); 
    }
    let compile = strArr.join('&');
    const done = compile + '&' + str + '. '
    return  done;
  });
};
Copy the code

Class 6: Webpack compilation and optimization

This chapter content

  1. cache-loader
  2. DllPlugin
  3. threadLoader

cache-loader

Cache-loader mainly stores the packed files in a directory on the hard disk, usually in node_modules/. Cache. When you build again, if the file is not modified, the compiled files will be read from the cache, and only the modified files will be compiled. This greatly reduces the compile time. Especially when the project is bigger.

Comparison of data before and after the use of 3342ms -> 2432ms is still more obvious

Cache-loader is only used for Babel, since our ts/ JS is compiled by Babel and we don’t need to cache ts-Loader (we don’t use it either).

config/cacheLoader.js

module.exports = (config, resolve) = > {
  const baseRule = config.module.rule('js').test(/.js|.tsx? $/);
  const babelPath = resolve('babel.js')
  const babelConf = require(babelPath);
  const version = require(resolve('node_modules/@babel/core/package.json')).version
  return () = > {
    baseRule
      .exclude
      .add(filepath= > {
        // Do not cache files under node_modules
        return /node_modules/.test(filepath)
      })
      .end()
      .use('cache-loader')
      .loader('cache-loader')
      .options({
        // Cache location
        cacheDirectory: resolve('node_modules/.cache/babel')}}}Copy the code

DllPlugin

DllPlugin is to isolate and package long-lived third-party packages from the actual project, and then import the packaged DLL packages when we build

I extracted two packages vue and React, and the speed was almost 200ms higher, from 2698ms to 2377ms

Packaging DLL

build/dll.js

const path = require("path");
const dllPath = path.join(process.cwd(), 'dll');
const Config = require('webpack-chain');
const config = new Config();
const webpack = require('webpack')
const rimraf = require('rimraf');
const ora = require('ora')
const chalk = require('chalk')
const BundleAnalyzerPlugin = require('.. /config/BundleAnalyzerPlugin')(config)

BundleAnalyzerPlugin()
config
  .entry('dll')
  .add('vue')
  .add('react')
  .end()
  .set('mode'."production")
  .output
  .path(dllPath)
  .filename('[name].js')
  .library("[name]")
  .end()
  .plugin('DllPlugin')
  .use(webpack.DllPlugin, [{
    name: "[name]".path: path.join(process.cwd(), 'dll'.'manifest.json'),
  }])
  .end()

rimraf.sync(path.join(process.cwd(), 'dll'))
const spinner = ora('Start building the project... ')
spinner.start()

webpack(config.toConfig(), function (err, stats) {
  spinner.stop()
  if (err) throw err
  process.stdout.write(stats.toString({
    colors: true.modules: false.children: false.chunks: false.chunkModules: false
  }) + '\n\n')

  if (stats.hasErrors()) {
    console.log(chalk.red('Build failed \n'))
    process.exit(1)}console.log(chalk.cyan('build complete \ n'))})Copy the code

Merge DLL packages

const webpack = require('webpack')

module.exports = (config, resolve) = > {
  return () = > {
    config.plugin('DllPlugin')
      .use(webpack.DllReferencePlugin, [{
        context: process.cwd(),
        manifest: require(resolve('dll/manifest.json')))}}}]Copy the code

threadLoader

The test effect is worse 😅, the smaller the number of threads, the faster the compilation

config/threadLoader.js

module.exports = (config, resolve) = > {
  const baseRule = config.module.rule('js').test(/.js|.tsx? $/);
  return () = > {
    const useThreads = true;
    if (useThreads) {
      const threadLoaderConfig = baseRule
        .use('thread-loader')
        .loader('thread-loader');
      threadLoaderConfig.options({ workers: 3})}}}Copy the code

Lesson 7: Multi-page configuration

Pay attention to

  • deprecated npm run build & npm run dev & npm run dll
  • to box build & box dev & box dll
  • linkNPM link links the box command globally

This chapter content

  1. use
  2. Convert to scaffolding
  3. Multi-page configuration

use

box build No arguments will compile all pages and empty dist
box dev   The index page is compiled by default
Copy the code

parameter

# index2 specifies the compiled page. Won't empty dist
# report Enables package analysis
box build index2 --report 
box dev index2 --report 
Copy the code

Convert to scaffolding

It is divided into three commands to perform different operations

  • build
  • dev
  • dll

bin/box.js

#! /usr/bin/env node

const chalk = require('chalk')
const program = require('commander')
const packageConfig = require('.. /package.json');
const { cleanArgs } = require('.. /lib')
const path = require('path')
const __name__ = `build,dev,dll`

let boxConf = {}
let lock = false

try {
  boxConf = require(path.join(process.cwd(), 'box.config.js'()}))catch (error) { }

program
  .usage('<command> [options]')
  .version(packageConfig.version)
  .command('build [app-page]')
  .description('Build the development environment')
  .option('-r, --report'.'Package analysis Report')
  .option('-d, --dll'.'Combined differential subcontracting')
  .action(async (name, cmd) => {
    const options = cleanArgs(cmd)
    const args = Object.assign(options, { name }, boxConf)
    if (lock) return
    lock = true;
    if (boxConf.pages) {
      Object.keys(boxConf.pages).forEach(page= > {
        args.name = page;
        require('.. /build/build')(args)
      })
    } else {
      require('.. /build/build')(args)
    }
  })

program
  .usage('<command> [options]')
  .version(packageConfig.version)
  .command('dev [app-page]')
  .description('Build the production environment')
  .option('-d, --dll'.'Combined differential subcontracting')
  .action(async (name, cmd) => {
    const options = cleanArgs(cmd)
    const args = Object.assign(options, { name }, boxConf)
    if (lock) return
    lock = true;
    require('.. /build/dev')(args)
  })

program
  .usage('<command> [options]')
  .version(packageConfig.version)
  .command('dll [app-page]')
  .description('compile difference subcontract')
  .action(async (name, cmd) => {
    const options = cleanArgs(cmd)
    const args = Object.assign(options, { name }, boxConf)
    if (lock) return
    lock = true;
    require('.. /build/dll')(args)
  })

program.parse(process.argv).args && program.parse(process.argv).args[0];
program.commands.forEach(c= > c.on('--help'.() = > console.log()))

if (process.argv[2] && !__name__.includes(process.argv[2]) {console.log()
  console.log(chalk.red('Not found${process.argv[2]}Command `))
  console.log()
  program.help()
}

if(! process.argv[2]) {
  program.help()
}
Copy the code

Multi-page configuration

box.config.js

module.exports = function (config) {
  return {
    entry: 'src/main.js'.// Default entry
    dist: 'dist'.// Default package directory
    publicPath: '/'.port: 8888.pages: {
      index: {
        entry: 'src/main.js'.template: 'public/index.html'.filename: 'index.html',},index2: {
        entry: 'src/main.js'.template: 'public/index2.html'.filename: 'index2.html',}},chainWebpack(config){}}}Copy the code

课时 8: hand write a webpack plug-in

If webpack is treated as a garbage factory, loader is garbage sorting, and takes all garbage to Webpack. Plugin is how to deal with this garbage.

The Webpack plugin is easy to write. You need to know when various hooks fire, and then you can write your logic inside the hooks

  • applyThe function is executed by WebPack when the plugin is called, and you can think of it as a portal
  • compilerExposes hooks related to the entire webPack lifecycle
  • CompilationExposes smaller – grained event hooks related to modules and dependencies

This section profile

  • Implement a CopyPlugin
  • use

Implement a CopyPlugin

Today we will write a copy plug-in that will copy files from the target directory to another directory after the WebPack is built

const fs = require('fs-extra')
const globby = require('globby')

class CopyDirWebpackPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    const opt = this.options
    compiler.plugin('done'.(stats) = > {
      if (process.env.NODE_ENV === 'production') {(async() = > {const toFilesPath = await globby([`${opt.to}/ * * `.'! .git/**'])
          toFilesPath.forEach(filePath= > fs.removeSync(filePath))
          const fromFilesPath = await globby([`${opt.from}/ * * `])
          fromFilesPath.forEach(fromPath= > {
            const cachePath = fromPath
            fromPath = fromPath.replace('dist', opt.to)
            const dirpaths = fromPath.substring(0, fromPath.lastIndexOf('/'))
            fs.mkdirpSync(dirpaths)
            fs.copySync(cachePath, fromPath)
          })
          console.log(Complete copy `${opt.from} to ${opt.to}`}})}) ()); }}module.exports = CopyDirWebpackPlugin
Copy the code

use

Copy the contents from the packaged dist directory to the dist2 directory

const CopyPlugin = require('.. /webapck-plugin-copy');

module.exports = ({ config }) = > {
  return () = > {
    config.plugin('copy-dist')
      .use(CopyPlugin, [{
        from: 'dist'.to: 'dist2'}}}])Copy the code

课时 9: building SSR

SSR is server rendering, the benefits of DOING SSR is to deal with the deficiencies of SPA, such as SEO optimization, server caching and other issues.

Today, we mainly use react SSR to do a simple example, so that we can get a clearer start

This chapter summary

  • Create a box build: SSR
  • Compile the SSR
  • Compile JSX syntax
  • Entry distinguishes server/client
  • Server side rendering
  • summary

Create a box build: SSR

As usual, a box build: SSR command is used to make the program executable

Executing box Build: SSR calls build/ SSR to perform compilation

program
  .usage('<command> [options]')
  .version(packageConfig.version)
  .command('build:ssr [app-page]')
  .description(Server rendering)
  .action(async (name, cmd) => {
    const options = cleanArgs(cmd);
    const args = Object.assign(options, { name }, boxConf);
    if (lock) return;
    lock = true;
    require('.. /build/ssr')(args);
  });
Copy the code

Compile the SSR

No different from other compilations, what is worth living

  • Target is set to umD mode
  • GlobalObject for this
  • Entry changed to SSr.jSX
.libraryTarget('umd')
.globalObject('this')
Copy the code

build/ssr.js

module.exports = function(options) {
  const path = require('path');
  const Config = require('webpack-chain');
  const config = new Config();
  const webpack = require('webpack');
  const rimraf = require('rimraf');
  const ora = require('ora');
  const chalk = require('chalk');
  const PATHS = {
    build: path.join(process.cwd(), 'static'),
    ssrDemo: path.join(process.cwd(), 'src'.'ssr.jsx')};require('.. /config/babelLoader')({ config, tsx: true}) ();require('.. /config/HtmlWebpackPlugin')({
    config,
    options: {
      publicPath: '/'.filename: 'client.ssr.html'
    }
  })();

  config
    .entry('ssr')
    .add(PATHS.ssrDemo)
    .end()
    .set('mode'.'development') // production
    .output.path(PATHS.build)
    .filename('[name].js')
    .libraryTarget('umd')
    .globalObject('this')
    .library('[name]')
    .end();

  rimraf.sync(path.join(process.cwd(), PATHS.build));
  const spinner = ora('Start building the project... ');
  spinner.start();

  webpack(config.toConfig(), function(err, stats) {
    spinner.stop();
    if (err) throw err;
    process.stdout.write(
      stats.toString({
        colors: true.modules: false.children: false.chunks: false.chunkModules: false
      }) + '\n\n'
    );

    if (stats.hasErrors()) {
      console.log(chalk.red('Build failed \n'));
      process.exit(1);
    }
    console.log(chalk.cyan('build complete \ n'));
  });
};
Copy the code

Compile JSX syntax

Since we write in React, we can’t avoid using JSX syntax, so we need to use @babel/preset-react in babel-loader

npm i @babel/preset-react -D
Copy the code

config/babelLoader.js

if (tsx) {
  babelConf.presets.push('@babel/preset-react');
}
Copy the code

Entry distinguishes server/client

Distinguish between server and client rendering separately

const React = require("react");
const ReactDOM = require("react-dom");

const SSR = <div onClick={()= > alert("hello")}>Hello world</div>;

if (typeof document= = ="undefined") {
  console.log('Render on the server')
  module.exports = SSR;
} else {
  console.log('Render on client side')
  const renderMethod = !module.hot ? ReactDOM.render : ReactDOM.hydrate;
  renderMethod(SSR, document.getElementById("app"));
}
Copy the code

Server side rendering

  • Treat the packaged Static folder as a service
  • Visit http://127.0.0.1:8080 to go to the page rendered by the server
  • Execute ssr.js again for event binding
module.exports = function (options) {
  const express = require("express");
  const { renderToString } = require("react-dom/server");
  const chalk = require('chalk')
  
  const SSR = require(".. /static/ssr");
  const port = process.env.PORT || 8080;

  server(port);
  
  function server(port) {
    const app = express();
    app.use(express.static("static"));
    app.get("/".(req, res) = >
      res.status(200).send(renderMarkup(renderToString(SSR)))
    );

    const empty = ' '
    const common = 'App running at: - Local: http://127.0.0.1:${port}\n`
      console.log(chalk.cyan('\n' + empty + common))
    
    app.listen(port, () = > process.send && process.send("online"));
  }
  
  function renderMarkup(html) {
    return ` <! DOCTYPE html> <html> <head> <title>Webpack SSR Demo</title> <meta charset="utf-8" /> </head> <body> <div id="app">${html}</div>
      <script src="./ssr.js"></script>
    </body>
  </html>`; }}Copy the code

summary

SSR is over now, but all the technology that looks great is built up bit by bit, and you can make better frameworks if you understand the principles


The end

I wrote this for more than two weeks. I wrote a little bit every day, and I felt much improved. If you are interested in learning with me, you can join me in the group, where I will organize different topics to learn every day.

The following topics are likely to be:

  • Handwritten vuE-next source code
  • Ts goes from entry to abandonment
  • Node starts to cry

Ha ha, just kidding, it’s like this, about half a month about a topic, if you have a good topic can also discuss together