preface

This long article is to learn cheng Liufeng teacher opened “play webpack” column practice notes, and column is not the same, my actual combat source code is based on Webpack5, its configuration and source code implementation and Webpack4 have many different places, interested students can combine with me in the above released source code warehouse for learning, I believe there will be no small harvest.

After reading this long article, you will learn:

  • Flexible configuration of WebPack according to project requirements.
  • Understand the principles of Tree Shaking, Scope, etc.
  • It can package multi-page application, component library and base library, SSR, etc.
  • Write a maintainable Webpack configuration, control code quality with unit tests, smoke tests, and so on, and use Travis for continuous integration.
  • Know how to optimize build speed and build resource volume.
  • Master webpack packaging principle through source code
  • Write loader and plugin.

I put the source code in my repository, which can be read against the document. In addition, due to the word limit, I can only be divided into two articles. For a better reading experience, you can go to my blog. Source address: play webpack

I met webpack

Configuration file: webpack.config.js

Webpack configuration consists of:

Install webpack:

yarn add webpack webpack-cli -D
Copy the code

Execute webpack from the command line:

./node_modules/.bin/webpack
Copy the code

Run webpack with NPM script:

Principle: Create a soft connection in the node_modules/. Bin directory

yarn run build
Copy the code

Basic usage of Webpack

Entry of core concepts

Specifies the entry to the WebPack package.

Dependency graph (build mechanism) : WebPack treats all resources as modules, starting from the entry file, recursively parses the dependency modules to form a dependency tree, and when the recursion is complete, outputs the built resources.

Method of use

Single entry: The value of entry is a string

module.exports = {
  entry: './src/index.js'};Copy the code

Multiple entry: The value of entry is an object

module.exports = {
  entry: {
    main: './src/index.js',}};Copy the code

Output of core concepts

Tell WebPack where to put the built resources on disk

Method of use

Single entry:

module.exports = {
  output: {
    filename: 'bundle.js'.path: path.resolve(__dirname, '/dist'),}};Copy the code

Multiple entry:

Ensure that file names are unique through placeholders

module.exports = {
  output: {
    filename: '[name].js'.path: path.resolve(__dirname, '/dist'),}};Copy the code

Core concepts loader

Webpack supports only JS and JSON file types by default. You can configure parsing rules for other file types by using Loader, so that WebPack can add other file types to the dependency graph.

Is itself a function that takes the source file as an argument and returns the result of the transformation

Method of use

module.exports = {
  module: {
    rules: [{ test: /\.txt/, use: 'raw-loader'}],}};/ / configuration items
module.exports = {
  module: {
    rules: [{test: /\.txt/,
        use: {
          loader: 'raw-loader'.options: {},},},],},};Copy the code

Plugin for core concepts

Plugin is used for bundle optimization, resource management, and environment variable injection throughout the build process.

Mode of core concepts

Mode specifies the build environment: production/development/none.

Parse ES6 and JSX

Parsing es6

With babel-loader, the configuration file for Babel is babel.config.json

{
  "presets": ["@babel/preset-env"]."plugins": ["@babel/proposal-class-properties"]}Copy the code

Presets are collections of plugins that represent presets that are specific to a particular function.

Install dependencies

yarn add @babel/core @babel/preset-env babel-loader -D
Copy the code

Configure the loader

module.exports = {
  module: {
    rules: [{test: /\.js$/,
        use: {
          loader: 'babel-loader',},},],},};Copy the code

Parsing JSX

Install dependencies

yarn add react react-dom @babel/preset-react
Copy the code
{
  "presets": ["@babel/preset-env"."@babel/preset-react"]}Copy the code

Parsing the CSS/less sass

Parsing the CSS

Css-loader is used to load. CSS files and convert them into CommonJS objects

Style-loader injects the style into the head via the

module.exports = {
  module: {
    rules: [{test: /\.css$/,
        use: ['style-loader'.'css-loader'],},],},};Copy the code

The loader parse rules are from right to left. That is, csS-loader is used to parse files and then the result is handed to style-loader.

Parsing the less

Install dependencies

yarn add less less-loader -D
Copy the code
module.exports = {
  module: {
    rules: [{test: /\.css$/,
        use: ['style-loader'.'css-loader'.'less-loader'],},],},};Copy the code

Parse image and font resources

Using the file – loader

The installation file – loader

yarn add file-loader
Copy the code
module.exports = {
  module: {
    rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
        use: ['file-loader'],},],},};Copy the code

Use url – loader

Small resources can be set to automatically base64

module.exports = {
  module: {
    rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
        use: [{ loader: 'file-loader'.options: { limit: 10240}}],},],},};Copy the code

Use webpack5’s built-in asset

In versions earlier than Webpackage 5, the commonly used Loaders are as follows:

  • Raw-loader processes modules into strings
  • Url-loader can set the specified size of the resource. If the size is smaller than the specified size, the bundle is inlining.
  • File-loader sends files to the output directory

In Webpackage 5, asset Modules replaces the loader above by adding four built-in types:

  • Asset /resource was previously implemented by file-loader
  • Asset /inline was previously implemented by url-loader
  • Asset /source Exports the source code (as a string) of the resource, previously implemented by raw-loader
  • Asset can automatically choose whether to export as a data URI or send a file directly, previously implemented by urL-loader.

When parsing image and font resources, you want to export the resources as data URIs within a limited size, while exceeding the size directly sends the files to the output directory, so use asset:

module.exports = {
  module: {
    rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
        type: 'asset'.parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024.// 10kb},},},],},};Copy the code

Listen for files and hot updates

watch

Listen for file changes, automatically build

{
  "scripts": {
    "watch": "webpack --watch"}}Copy the code

File listening principle analysis:

  • Polling determines whether the last edit time of the file has changed
  • Changes to a file are not immediately reported to listeners, but cached, etcaggregateTimeoutDo not execute the build task until it expires
module.exports = {
  watch: true.// Enable listening
  watchOptions: {
    // The default value is null and the listening folder is ignored to improve performance
    ignored: /node_modules/.// Determine file changes by continuously asking the system whether the specified file has changed, once per second
    poll: 1000.// wait until 300ms after the change
    aggregateTimeout: 300,}};Copy the code

Hot update – WDS

webpack-dev-server:

  • Not refreshing the browser
  • Instead of exporting files, put them in memory (speed of build is a big advantage)
  • Using HotModuleReplacementPlugin

Install dependencies:

yarn add webpack-dev-server -D
Copy the code
module.exports = {
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    historyApiFallback: true.hot: true.open: true.quiet: true.port: 8082,}};Copy the code
{
  "scripts": {
    "serve": "webpack serve"}}Copy the code

Note: If there are multiple entries and only one HtmlWebpackPlugin is configured, multiple chunks will be inserted into the generated HTML and hot updates will not work properly.

Hot update – WDM

Webpack-dev-middleware: This is express middleware that allows WebPack to hand over files to a server, such as the next Express, which gives us more control.

Install dependencies

yarn add express webpack-dev-middleware -D
Copy the code
module.exports = {
  output: {
    publicPath: '/',}};Copy the code
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

const app = express();
const config = require('./webpack.config');
const compiler = webpack(config);

app.use(
  webpackDevMiddleware(compiler, {
    publicPath: config.output.publicPath,
  })
);

app.listen(8081.function () {
  console.log('server is running on port 8081');
});
Copy the code

Start the service Node server.js

Principle of thermal renewal

Concept:

  • Webpack Compiler: Compiles JS into bundles
  • Bundle Server: Provides a service that enables files to be accessed in the browser
  • HMR Server: outputs hot updated files to the HMR Runtime
  • HMR Runtime: this is injected into the bundle and used to update files (using WebSockets to communicate with the server)
  • Bundle.js build artifacts
  1. Bootstrap: The source code is first compiled into bundle.js using the Webpack Compiler, and then submitted to the Bundle Server, where the browser can access the files.
  2. Change phase: WDS will detect whether the last edit time of the file has changed at regular intervals. Once detected, it will be added to the cache list, and so onaggregateTimeoutUpon expiration, the cache list is sent to Webpack Compiler for compilation, the compiled code is sent to HMR Server, and the HRM Server informs HMR runtime change file (transmitted in JSON format). HMR Runtime then updates the response module code.

Strategy: file fingerprint chunkhash/contenthash/hash

File fingerprint: Output file name after packaging, usually used for version management, only updated file content, unupdated file fingerprint does not change, can still use the browser cache.

Common document fingerprints:

  • Hash: Relates to the construction of the entire project. Whenever the project file is changed, the hash value for the entire project will change.
    • The packaging phase has compile and compilation, and when Webpack starts, a compile object is created, and whenever the file changes, the compilation changes, and accordingly, the hash value changes.
    • Page A changes, page B does not change, but the hash also changes.
  • Chunkhash: Related to chunk (entry) packed by Webpack. Different entries will generate different chunkhash.
    • Js generally uses chunkhash.
  • Contenthash: Hash is defined based on the content of the file. Contenthash does not change the content of the file.
    • The CSS generally uses contenthash.

Set the file fingerprint

It can only be used in production environments

Js file fingerprint setting

module.exports = {
  output: {
    filename: '[name]_[chunkhash:8].bundle.js',}};Copy the code

File fingerprint setting for CSS: Extract CSS into a file using mini-CSs-extract-Plugin, then set filename and use [contenthash].

If style-loader is used, the CSS will be injected into the head of the page and the file fingerprint cannot be set.

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

module.exports = {
  module: {
    rules: [{test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],}, {test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'.'less-loader'],},],},plugins: [
    new MiniCssExtractPlugin({
      filename: '[name][contenthash:8].css',})]};Copy the code

Static resource files such as images fingerprint: name of file-loader. Hash is used.

module.exports = {
  module: {
    rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
        use: [
          {
            loader: 'file-loader'.options: {
              name: 'assets/[name][hash:8].[ext]',},},],},},};Copy the code

Split profile

Install webpack – merge:

yarn add webpack-merge -D
Copy the code

Split into three files and place them in the config directory:

  • webpack.common.js
  • webpack.dev.js
  • webpack.prod.js

The configuration is as follows:

const path = require('path');

module.exports = {
  entry: {
    main: './src/index.js'.worker: './src/worker',},output: {
    filename: '[name].js'.path: path.resolve(__dirname, '.. / '.'dist'),
    publicPath: '/',},module: {
    rules: [{test: /\.js$/,
        use: {
          loader: 'babel-loader',},},],},};Copy the code
const { merge } = require('webpack-merge');
const path = require('path');

const common = require('./webpack.common');

module.exports = merge(common, {
  mode: 'development'.devServer: {
    contentBase: path.join(__dirname, '.. /dist'),
    historyApiFallback: true.hot: true.open: false.quiet: true.port: 8082,},module: {
    rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
        use: [
          {
            loader: 'file-loader'.options: {
              name: 'assets/[name].[ext]',}}, {test: /\.css$/,
            use: ['style-loader'.'css-loader'],}, {test: /\.less$/,
            use: ['style-loader'.'css-loader'.'less-loader'],},],},],},});Copy the code
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');

const common = require('./webpack.common');

module.exports = merge(common, {
  mode: 'production'.output: {
    filename: '[name]_[chunkhash:8].bundle.js'.path: path.resolve(__dirname, '.. / '.'dist'),
    // publicPath: '/',
  },
  module: {
    rules: [{test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf)$/,
        use: [
          {
            loader: 'file-loader'.options: {
              name: 'assets/[name]_[hash:8].[ext]',},},],}, {test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],}, {test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'.'less-loader'],},],},plugins: [
    new MiniCssExtractPlugin({
      filename: '[name]_[contenthash:8].css',})]});Copy the code

HTML, CSS, and Javascript code compression

Webpack turns on compression of Javascript code by default.

Compress CSS

Use csS-minimizer-webpack-plugin to be more precise in source maps and Assets than optimizer-CSS-assets-webpack-plugin, allowing caching and parallelism.

Install dependencies:

yarn add css-minimizer-webpack-plugin -D
Copy the code

Configuration:

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true./ / '... 'can inherit the default compression configuration
    minimizer: [new CssMinimizerPlugin(), '... '],}};Copy the code

Compressed HTML

The installation relies on the HTml-webpack-plugin, and compressed HTML is enabled by default in production. Automatically inserts built artifacts such as bundle.js, xx.css into the generated HTML. If there are multiple entries and only one HtmlWebpackPlugin is specified, all will be inserted into that HTML.

yarn add html-webpack-plugin -D
Copy the code
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '.. / '.'public/index.html'),})]};Copy the code

Webpack advanced usage

Automatic cleanup of build artifacts

  1. Use NPM scripts to clean up the build directory:

rm -rf ./dist && webpack

  1. In Webpackage 5, the Output configuration provides the clean parameter, which is a Boolean type that, if true, clears the last build before it builds.

The PostCSS plugin autoprefixer is used to automatically complete the browser vendor prefix

Install dependencies:

yarn add postcss-loader autoprefixer -D
Copy the code
module.exports = {
  module: {
    rules: [{test: /\.less$/,
        use: [
          'style-loader'.'css-loader'.'less-loader',
          {
            loader: 'postcss-loader'.options: {
              postcssOptions: {
                plugins: [require('autoprefixer'],},},},],},},};Copy the code

Configure Autoprefixer in package.json:

{
  "browserslist": ["1%" >."last 2 versions"."not ie <= 10"]}Copy the code

Px is automatically converted to REM

Px2rem-loader is used to automatically convert PX into REM, and with the Lib-Flexible library of Handamoy, font size of the root element can be calculated during rendering, so as to achieve self-adaptation on the mobile end.

Install dependencies:

yarn add px2rem-loader -D
yarn add lib-flexible -S
Copy the code
module.exports = {
  module: {
    rules: [{test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'.'less-loader',
          {
            loader: 'postcss-loader'.options: {
              postcssOptions: {
                plugins: [require('autoprefixer'],},},}, {loader: 'px2rem-loader'.options: {
              remUnit: 75.remPrecision: 8,},},],},},};Copy the code

Since the current configuration does not support static resource inlining, the use of lib-flexible is covered in the next section.

Static resources are inlined

Meaning of resource inlining

Code level:

  • Initialization script for the page frame
  • Report related information (CSS and JS are loaded successfully)
  • CSS inlining prevents page flickering and is better for the first screen load (come back with HTML)

Request level:

Reduce the number of HTTP network requests

  • Small inline images or font (url – loader |type: "asset")

Ejs is the default ejS template engine, because html-webpack-plugin is used.

Modify the WebPack configuration:

module.exports = {
  module: {
    rules: [{resourceQuery: /raw/,
        type: 'asset/source',},],},};Copy the code

Inline resources to index.ejs:

<! DOCTYPEhtml>
<html lang="en">
  <head><%= require('./meta.html? raw') %><title>How to play the webpack</title>
    <script>< % =require('.. /node_modules/lib-flexible/flexible? raw') % ></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
Copy the code

Multipage application packaging common solution

Install dependencies:

yarn add glob -D
Copy the code
// Configure multi-page packing. The idea is to use glob to parse out the corresponding entry file, and then set the corresponding entry and HtmlWebpackPlugin
function setMpa() {
  const entry = {};
  const htmlWebpackPlugins = [];

  const pagePaths = glob.sync(path.join(__dirname, '.. /src/mpa/**/index.js'));

  pagePaths.forEach((pagePath) = > {
    const name = pagePath.match(/src\/mpa\/(.*)\/index\.js/) [1];

    entry[name] = pagePath;
    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({
        filename: `${name}.html`.chunks: [name],
        template: path.join(__dirname, '.. / '.`src/mpa/${name}/index.html`),}));return name;
  });
}
Copy the code

Use the source map

Key words:

  • Eval: Wrap module code in eval
  • Source map: generate.map files (separate from source files)
  • Cheap: Does not contain column information
  • Inline: embed.map as DataURI instead of generating a separate.map file (which causes the source file to be too large)
  • Module: Source map containing the Loader

Pay attention to the point

  • For performance reasons, it is recommended not to use source Map in production for best packaging performance.
  • The development environment is on, the online environment is off
    • If you want to use it, you can use the Source Map type that does not analyze the business logic.
    • You can upload the Source Map to the error monitoring system when troubleshooting problems online.
    • Production environment:devtool: source-map;Have a high-quality Source map
    • Recommended development environment:devtool: eval-cheap-module-source-map

Extract page common resources

Install the react/React-DOM base bundle using the CDN.

  • Use htML-webpack-externals-plugin to separate the base library.
  • Use the SplitChunkPlugin, which has been built in since WebPack4.

Separate react/react-dom base library

Install dependencies:

yarn add html-webpack-externals-plugin -D
Copy the code
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackExternalsPlugin({
      externals: [{module: 'react'.entry: 'https://now8.gtimg.com/now/lib/16.8.6/react.min.js'.global: 'React'}, {module: 'react-dom'.entry: 'https://now8.gtimg.com/now/lib/16.8.6/react-dom.min.js'.global: 'ReactDOM',},],}),],};Copy the code

Entry uses the address of the CDN, and then adds the react/react-dom library to the index.ejs:

<! DOCTYPEhtml>
<html lang="en">
  <head><%= require('./meta.html? raw') %><title>How to play the webpack</title>
    <script>< % =require('.. /node_modules/lib-flexible/flexible? raw') % ></script>
  </head>
  <body>
    <div id="root"></div>

    <script src="https://now8.gtimg.com/now/lib/16.8.6/react.min.js"></script>
    <script src="https://now8.gtimg.com/now/lib/16.8.6/react-dom.min.js"></script>
  </body>
</html>
Copy the code

Chunk Parameter Description

  • Async Decouple libraries introduced asynchronously (default)
  • Initial separates libraries that are introduced synchronously
  • All decouples all imported libraries (recommended)

Such as:

modulex.exports = {
  optimization: {
    splitChunks: {
      chunk: 'async'.// Only asynchronously imported libraries are analyzed, and if they meet the set criteria, they are split into a separate package, i.e., subcontracting,}}};Copy the code

SplitChunksPlugin is used to separate the base package

Test: matches the packets to be separated. React and React-DOM are separated into vendors packages.

MinChunks: Sets the minimum number of references to 2.

MinSize: size of separated package volume.

module.exports = {
  optimization: {
    cacheGroups: {
      minSize: 0.commons: {
        test: /(react|react-dom)/,
        name: 'vendors'.chunks: 'all'.minChunks: 2.// Remove libraries that have two or more page references}},}};Copy the code

Vendors Chunk is then introduced in HtmlWebpackPlugin:

modulex.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      chunks: ['vendors'].template: path.join(__dirname, '.. / '.'public/index.ejs'),})]};Copy the code

Tree Shaking

Tree shaker: Erase useless code

  • The code must be es6
  • Tree Shaking will fail if it has side effects

DCE (Elimination)

Mode: Production Tree shaking is enabled by default

  • Code will not be executed, not reachable
  • The results of code execution are not used
  • Code only affects dead variables (write, not read)
if (false) {
  / / inaccessible
}

function getSex() {
  return 'male';
}

getSex(); // The result of the code execution will not be used

var name = 'ywhoo'; // Write without reading
Copy the code

The principle of

Using the features of ES6 module:

  • An import can only occur at the top level of a module
  • The module name for import must be a string constant
  • Import binding is immutable

During the compile phase (static analysis), the code used is identified, the unused code is marked, and the tagged code is removed during the Uglify phase.

Scope principle analysis

Symptom: Built code has a lot of closure code

Question:

  • Bundle volume increases
  • Function scope increases, memory overhead increases

How it works: Put all module code in a function scope in reference order, and then rename some variables appropriately to prevent variable name conflicts.

Implementation: Scope can reduce function declaration code and memory overhead

Use: mode to enable production by default

  • Must be ES6 syntax.

Code splitting and dynamic import

Code splitting was introduced earlier, using splitChunks to separate the base package from the common functions.

Code splitting implications: For large Web applications, it’s not efficient to have all the code in one file, especially if certain blocks of code are used at specific times. One feature of WebPack is to break up your code base into chunks and load them as needed, rather than loading them all at once.

Scenarios used:

  • Remove the same code block to a shared block
  • Scripts are lazily loaded (loaded on demand), making the initial download smaller

Lazy loading of JS scripts:

  • CommonJS: require.ensure
  • ES6: Dynamic import (Babel conversion required)

How it works: Use jSONP at load time to create a script tag to dynamically import the script.

Implement an on-demand component import that asynchronously loads the code contained in getComponent when the button is clicked:

btn.addEventListener('click'.function () {
  getComponent().then((comp) = > {
    document.body.appendChild(comp);
  });
});

async function getComponent() {
  const { default: _} =await import('lodash');

  const ele = document.createElement('div');
  ele.innerHTML = _.join(
    ['hello'.'webpack'.'I'm dynamically importing generated code'].' '
  );

  return ele;
}
Copy the code

Problems encountered

Babelrc: async: async: async: async: async: async: async: async: async: async: async: async: async: async

{
  "presets": [["@babel/preset-env",
      {
        "targets": {
          "esmodules": true}}]]}Copy the code

Json is used in autoprefixer configuration of postcss. HMR cannot be used properly if this is configured:

{
  "browserslist": ["1%" >."last 2 versions"."not ie <= 10"]}Copy the code

The workaround is to add the Target configuration to the WebPack configuration:

modulex.exports = {
  target: 'web'};Copy the code

Use ESLint in WebPack

Good industry ESLint specification practices:

  • Reality: eslint – config – reality, eslint – config – reality – base
  • alloyteam: eslint-config-alloy
  • ivweb: eslint-config-ivweb
  • umijs: fabric

Specifies the esLint specification for the team:

  • Don’t duplicate wheels, based on ESLint: Recommend configuration and improved
  • Enable all rules that help you find code errors
  • Helps keep the team’s code style uniform, not limit the development experience (ESLint usually checks for possible problems, while Prettier usually standardizes code style)

Eslint fall to the ground

  • Integration with CI/CD systems
  • Integration with WebPack (esLint fails without a build)

Practice for installing ESLint and Airbnb:

yarn add eslint eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y eslint-config-airbnb -D
Copy the code

Install eslint – loader:

yarn add eslint-loader babel-eslint -D
Copy the code

Solution 1: Webpack and CI/CD integration

Add Lint before build in the CI section and allow subsequent processes to be executed after Lint passes.

Added preCOMMIT hooks to the local development phase

Install the husky

yarn add husky -D
Copy the code

Added NPM script to incrementally check for modified files from Lint-staged:

{
  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "*.{js}": ["eslint --fix"."git add"]}}Copy the code

To avoid bypassing git precommit hooks, you need to add a Lint step to the CI step.

Solution 2: WebPack integrates with ESLint

With eslint-Loader, the JS specification is checked at build time, suitable for new projects, and all JS files are checked by default.

module.exports = {
  rules: [{test: /\.jsx? $/,
      use: ['babel-loader'.'eslint-loader'],},]};Copy the code
module.exports = {
  parser: 'babel-eslint'.extends: 'airbnb'.env: {
    browser: true.node: true,},rules: {
    'comma-dangle': 'off'.'no-console': 'off'.'jsx-quotes': 'off'.'jsx-a11y/click-events-have-key-events': 'off'.'jsx-a11y/no-static-element-interactions': 'off',}};Copy the code

Webpack packages components and base libraries

Rollup is better for packaging components and libraries; it’s much purer.

Webpack can be used to package applications as well as JS libraries.

Implement a large integer plus library packaging:

  • You need to package the compressed and uncompressed versions
  • Support AMD/CJS/ESM module introduction

Expose the library:

  • Library: Specifies a global variable for the library
  • LibraryTarget: Support library introduction method

Here is the implementation. I put the source code in the lib/big-number repository:

// Large integer addition
/** ** add from the ones bit@export
 * @param {*} a string
 * @param {*} b string
 */
export default function add(a, b) {
  let i = a.length - 1;
  let j = b.length - 1;
  let res = ' ';
  let carry = 0; / / carry

  while (i >= 0 || j >= 0) {
    let x = 0;
    let y = 0;
    let sum = 0;

    if (i >= 0) {
      x = +a[i];
      i -= 1;
    }

    if (j >= 0) {
      y = +b[j];
      j -= 1;
    }

    sum = x + y + carry;

    if (sum >= 10) {
      carry = 1;
      sum -= 10;
    } else {
      carry = 0;
    }

    res = sum + res;
  }

  if (carry) {
    res = carry + res;
  }

  return res;
}
Copy the code

Webpack configuration:

const path = require('path');
const TerserWebpackPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production'.entry: {
    'big-number': path.join(__dirname, './src/index.js'),
    'big-number.min': path.join(__dirname, './src/index.js'),},output: {
    filename: '[name].js'.path: path.resolve(__dirname, './dist'),
    library: 'bigNumber'.libraryTarget: 'umd'.clean: true,},optimization: {
    // minimize: false,
    // Webpack5 uses the terser-webpack-plugin to compress the code by default, which is used for customization here
    minimizer: [
      // Only files ending in.min.js are compressed
      new TerserWebpackPlugin({
        test: /\.min\.js$/i,}),],},};Copy the code

Once packaged, write an entry file for the component library, specified in package.json, such as main.js, where different versions are specified for different environment variables:

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./dist/big-number.min.js');
} else {
  module.exports = require('./dist/big-number.js');
}
Copy the code

Now that the large integer add library is developed, publish it to the NPM repository, assuming you have logged in to the NPM account with NPM login, and then publish.

Note: If you use taobao image, you need to switch back to the official image.

It can be installed and used after a successful release:

import bigNumber from 'yw-big-number';

console.log(bigNumber('999'.'1')); / / 1000
Copy the code

Webpack implements SSR packaging

Why is server-side rendering needed and what are its advantages?

In the process of parsing HTML, if external JS and CSS are encountered, they need to be parsed later. Of course, browsers will pre-request resources, and non-critical resources will not block THE parsing of HTML. During the parsing process, the page will be blank. After the parsing is completed, the page starts to display. At this time, the LOADING may be the only way to display the real content after loading. If there are image resources, the image is still invisible and the page can be interactive only after loading is completed.

It can be found that the page has gone through a series of steps when loading before it is truly displayed in front of the user. The advantage of server rendering is that static resources and data are taken together with THE HTML, and the browser directly parses the HTML, and the JS script can be fully interactive after it is completed. It has the following advantages:

  • Reduce white screen time
    • All static resources, such as templates, are stored on the server
    • Intranet machines pull data faster
    • One HTML returns all the data
  • Friendly to SEO

Bottom line: The core of server-side rendering is request reduction.

Code implementation ideas

  1. configurationwebpack.ssr.jsExport the client code to the UMD specification
  2. The server code is implemented using Express, importing the exported client code, converting it into a string by ReactDOMServer’s renderToString method, putting it into the root node in the template, and then registering the route and enabling the listening port service.

The problem

  1. performnode server/index.jsWhen submitted to theself is not definedSince the server does not have the self global variable, add the following judgment at the top of the execution:
if (typeof self === 'undefined') {
  global.self = {};
}
Copy the code
  1. The packaged components need to be written in a compatible way. For example, the server module uses commonJS, and the client writing group needs to follow the CommonJS specification as well.

  2. Change the FETCH or Ajax request method to isomorphic-fetch or AXIos.

  3. Style issues (NodeJS can’t parse CSS)

    1. Server packaging ignores CSS parsing through ignore-loader
    2. Replace style-loader with isomorphic-style-loader (CSS module)

Using the packaged browser-side HTML as a template, set placeholders to dynamically insert components:

const template = fs.readFileSync(
  path.join(__dirname, '.. /dist/index.html'),
  'utf-8'
);

const useTemplate = (html) = > template.replace('<! --HTML_PLACEHOLDER-->', html);

app.get('/app'.(req, res) = > {
  const html = useTemplate(renderToString(App));

  res.status(200).send(html);
});
Copy the code

There will be a blank screen after implementation.

  1. How to process the first screen data?

After the server retrieves the data, the placeholder is replaced.

Optimize the display log of commands at build time

Use friendly-errors-webpack-plugin to provide friendly build information prompts.

Build exception and interrupt handling

Actively catch and handle build errors:

  • Compiler triggers the done hook after each build
  • Process. exit actively handles build errors
module.exports = {
  plugins: [
    function () {
      // This points to compiler
      this.hooks.done.tap('done'.(stats) = > {
        if (
          stats.compilation.errors &&
          stats.compilation.errors.length &&
          process.argv.indexOf('--watch') = = = -1
        ) {
          process.exit(1); // Throw an exception and the terminal knows the build failed}}); },]};Copy the code

Write a maintainable WebPack build configuration

This section will write a maintainable WebPack build configuration library based on the previous configuration, which follows the writing specifications of the full library, including development specifications, smoke testing, unit testing, continuous integration, and so on, and will be published to the NPM community.

Build the significance of configuration abstraction into NPM packages

  • generality
    • Developers don’t have to worry about build configurations
    • Unify team build scripts
  • maintainability
    • Build a properly configured split
    • README documents and ChangeLog documents
  • The quality of
    • Smoke testing, unit testing, test coverage
    • Continuous integration

Build configuration management options

  • Manage the build of different environments through multiple configuration files, controlled by the webpack –config parameter
    • Basic configuration webpack.common.js
    • Development environment webpack.dev.js
    • Production environment Webpack.prod.js
    • SSR environment webpack. SSR. Js
  • Design the build configuration as a repository for unified management
    • Specifications: Git commit logging, README, Eslint specifications
    • Quality: Smoke testing, unit testing, test coverage, and CI

Merge the configuration using webpack-merge.

Functional module design and directory structure

Developed using the ESLint specification

Since this is the base library development, only the eslint-config-Airbnb-base version of Airbnb is needed.

Install dependencies:

yarn add eslint babel-eslint eslint-config-airbnb-base -D
Copy the code

Configuration. The eslintrc. Js:

module.exports = {
  parser: 'babel-eslint'.extends: 'airbnb-base'.env: {
    browser: true.node: true,},rules: {
    'comma-dangle': 'off'.'no-console': 'off'.'jsx-quotes': 'off'.'global-require': 'off'.'import/extensions': 'off'.'jsx-a11y/click-events-have-key-events': 'off'.'jsx-a11y/no-static-element-interactions': 'off'.'no-restricted-globals': 'off',}};Copy the code

Add esLint checks to scripts:

{
  "scripts": {
    "eslint": "eslint config --fix"}}Copy the code

Introduction and practical application of smoke testing

Smoke testing refers to the testing of the submitted software prior to detailed and in-depth testing. The purpose of this pre-testing is to expose serious problems such as the failure of basic functions of the software to be redistributed.

Smoke test execution:

  • Determine whether the build was successful
  • Whether the build artifacts have content
    • Whether there are static resource files such as JS and CSS
    • Whether there is an HTML file

Dependencies required by the installation:

yarn add rimraf webpack mocha assert glob-all
Copy the code

Write test cases that determine the success of the build:

const rimraf = require('rimraf');
const webpack = require('webpack');
const Mocha = require('mocha');
const path = require('path');

const mocha = new Mocha({
  timeout: '10000'});// Delete the old build artifacts
// process.chdir(); // Change the working directory

rimraf('.. /.. /dist'.() = > {
  const prodConfig = require('.. /.. /config/webpack.prod');

  webpack(prodConfig, (err, stats) = > {
    if (err) {
      console.error(err);
      process.exit(2);
    }
    console.log(
      stats.toString({
        colors: true.modules: false.children: false,}));console.log('webpack build succeeded, begin to test.');

    mocha.addFile(path.join(__dirname, './html-test.js'));
    mocha.addFile(path.join(__dirname, './js-css-test.js'));

    mocha.run();
  });
});
Copy the code

It is necessary to pay attention to whether the path is correct. HTML, JS and CSS test cases will not post codes. If you are interested, you can check them in my warehouse code.

Unit testing and test coverage

Simple test framework: Mocha/AVA, you need to install additional library assertion: chai/should js/expect/better – assert integration framework, out of the box: Jasmine/Jest (React)

Using Mocha + Chai, the main testing apis:

  • Describe a file that needs to be tested
  • It multiple test cases in a file
  • Expect assertion

Execute the test command:

mocha add.test.js
Copy the code

Unit testing

Writing unit test cases:

Mocha looks for test/index.js by default.

describe('webpack config test.'.() = > {
  require('./unit/webpack-base.test');
});
Copy the code
const assert = require('assert');

describe('webpack.common.js test case.'.() = > {
  const baseConfig = require('.. /.. /config/webpack.common');

  it('entry'.() = > {
    // Test whether the file path of the entry is correct
    assert.strictEqual(
      baseConfig.entry.main,
      '/Users/yewei/Project/source-code-realize/play-webpack/lib/yw-build-webpack/src/index.jsx'
    );
  });
});
Copy the code

The test passed as follows:

Test coverage

Installation of Istanbul.

After installation, modify the test:unit command:

{
  "scripts": {
    "test:unit": "istanbul cover mocha"}}Copy the code

Note: The test object code cannot have es6+ syntax code, otherwise the test coverage data will not be collected.

The result of executing YARN test:unit is as follows, and the coverage directory is generated in the root directory to store the code coverage results:

Continuous integration and Travis CI

Role of Continuous integration:

  • Find errors quickly
  • Prevent branches from straying too far from the trunk

The idea: Code must pass automated testing before it can be integrated into the trunk. If you have one error, you can’t integrate.

Github’s most popular CI’s:

Access to the Travis CI:

  1. Travis click login
  2. Activate projects that require continuous integration
  3. Add.travis. Yml to the project root directory

Create a new project on Github, and then upload the code under yw-build-Webpack to the repository by performing the following steps:

#Go to yw-build-webpack and initialize Git
git init
git add .
git commit -m "xxx"
#Add the remote repository
git remote add origin https://github.com/weiTimes/yw-build-webpack.git
#Push the code
git push -u origin master
Copy the code

Add. Travis. Yml:

language: node_js # language

sudo: false

node_js:
  - 12.161.

cache: # save cache
  - npm
  - yarn

before_install: # install dependencies
  - npm install -g yarn
  - yarn

scripts: # Execute test
  - yarn test
Copy the code

When code is priced up, a walk triggers a build task.

Publish build packages to the NPM community

Release NPM

Add a user: NPM adduser

Upgraded version

  • Upgrade patch version: NPM Version Patch
  • Upgrade minor version: NPM Version Minor
  • Major version: NPM Version Major

Release: NPM publish

Go to the root directory of the project you want to publish, then log in to NPM and publish:

npm login
npm publish
Copy the code

To release a patch, perform the following steps:

git add .
git commit -m "doc: udpate reamde"
npm version patch
git push -u origin master
npm publish
Copy the code

Git commit specification and Changelog generation

Advantages of a good Git commit specification:

  • Accelerate the process of code review
  • Generate changelog based on git commit metadata
  • Convenient for subsequent maintenance

Angular Git Commit specification:

Added preCOMMIT hooks to the local development phase

Add dependencies:

yarn add conventional-changelog-cli @commitlint/{config-conventional,cli}
Copy the code

Refer to Git submission specifications

Changlog generated

After committing according to the specification, it is easy to generate Changelog

Semantic version

Open source project version information amway

  • It usually consists of three members: X.Y.Z
  • The version is strictly incremented: 16.2.0 -> 16.3.0 -> 16.3.1
  • For major releases, alpha (internal), beta (external small range), RC (public beta) and other prior releases 16.2.0-RC.123 can be released

Follow the Semver specification:

  • Avoid circular dependencies
  • Reducing dependency conflicts

Specification format:

  • Major version number: Incompatible API changes were made
  • Minor version: Added the backward compatibility function
  • Revision number: Backward-compatible issue fixes

conclusion

Here’s what you should have learned by now:

  • Flexible configuration of WebPack according to project requirements.
  • Understand the principles of Tree Shaking, Scope, etc.
  • It can package multi-page application, component library and base library, SSR, etc.
  • Write a maintainable Webpack configuration, control code quality with unit tests, smoke tests, and so on, and use Travis for continuous integration.

The second part will cover webPack performance optimization, packaging principles, and writing loaders and plugins.