Writing in the front

We deal with Webpack every day and know it is a module and packaging tool, so what are the alternatives and why did we choose Webpack? Webpack is extended based on the AST syntax tree, so how to manipulate ast? What does the WebPack workflow look like? I’m going to expand it

What tools are similar to WebPack, and why do you choose WebPack

grunt/gulp/rollup/webpack

  • grunt
Introduction: High degree of automation, for repeated tasks: compression, compilation, unit testing, linting; It uses the idea of configuration. Disadvantages: 1. Too many configuration items 2. Different plug-ins may have their own extension fields 3. Therefore, the cost of learning will be very high, and you need to understand the configuration rules and cooperation modes of various plug-ins when using themCopy the code
  • gulp
1. Stream packaging tool based on Node.js 2. Automatic packaging tool based on task flow 3. The entire development process is built through TaskCopy the code
  • rollup
Rollup is a modular tool for ES6. It takes advantage of ES6 module design and tree-shaking to generate simpler code. Rollup is typically used for development libraries and WebPack 3 is used for development projects. The code is based on ES6 modules and is used when you want the code to be used directly by others. Not recommended: when you need to handle a lot of static resources, need to split code, or build a project that introduces a lot of CommonJS module dependenciesCopy the code
  • webpack
  1. Is a modular management tool and packaging tool. Through loader conversion, resources in any form can be regarded as modules, such as CommonJS modules, AMD modules, ES6 modules, CSS, images, etc., that is, loose modules can be packaged into front-end resources that meet the deployment of production environment according to dependencies and rules
  2. Modules loaded on demand can be partitioned and asynchronously loaded when needed
  3. Webpack is defined as a module packer, while gulp and Grunt are build tools.

The difference between loder and plugin

Simple distinction

  • Loader means loader. Webpack will be all files as modules, but Webpack native only can parse JS files, with loader can convert all other files into JS files, so all files can be parsed through Webpack. Loader gives WebPack the ability to load non-JS files.
  • Plugins are plug-ins that extend the functionality of WebPack. Because many events are broadcast during the WebPack run cycle, the Plugin only needs to listen for these events and, when appropriate, operate on the AST through the API provided by WebPack.

Take a closer look at the WebPack build process

Start by drawing a concept map

Note: Throughout the webPack build process, WebPack will broadcast specific events at specific times, the plug-in executes specific logic after listening for the corresponding events, and the plug-in can call the API provided by WebPack to change the results of the WebPack run.

Do you know the classification and execution order of loader?

Category: inline-loader pre-loader post-loader normal-loader

Layout: post-loader1 Post-loader2 Inline-loader1 Inline-loader2 Normal-loader1 normal-loader2 pre-loader1 pre-loader2

But the order of execution looks something like this. Let me just draw a little diagram

Hence the concept of loader.pitch where loaders are executed in reverse order from bottom to top and right to left. But essentially the normal order of execution.

The prefixes post, inline, normal, and pre are attributes that control the execution order.

 module: {
        rules: [{enforce: "pre".//post/inline/normal/pre
            test: /\.jsx? $/,
            use: {
                loader: "babel-loader".options: {
                    presets: ["@babel/preset-env"]}},include: path.join(__dirname, "src"),
            exclude: /node_modules/,}},Copy the code

Implement the loader idea

Understanding the principles of AST compilation and parsing was mentioned in this article and we’ll review it again today.

In fact, loader is to operate the AST syntax tree to modify the corresponding function, you can see the above article for detailed interpretation

const babel = require("@babel/core");
function loader(source, inputSourceMap,data) {
    const options = {
        presets: [@babel/preset-env],
        inputSourceMap: inputSourceMap,
        sourceMaps: true.filename: this.request.split("!") [1].split("/").pop(),
    };
    let {code, map, ast } = babel.transform(source, options);
    return this.callback(null, code, map, ast)
}
module.exports = loader;
Copy the code

The idea of implementing plugin

As mentioned above, WebPack broadcasts certain events at certain times, the plugin executes certain logic after listening for the events of interest, and the plugin can call the API provided by WebPack to change the running results of WebPack

  1. Common objects that can be loaded for plug-ins
An object that can be loaded with plug-ins hook
The Compiler (key) run,compile, compilation, make, emit, done
Compilation buildModule, normalModuleLoader, finishModules, seal,optimiz, after-seal
Module Factory beforeResolver,afterResolver,module,parser
Module
Parser program, statement, call, expression
Template hash,bootstrap, localVars, render
  1. Ideas for creating plug-ins
Create a js named function on prototype that defines an apply method that specifies an event hook bound to WebPack itself to handle the specific data function inside WebPack and call the webPack callback when it's doneCopy the code
  1. compiler & compilation
  • Compiler is the complete configuration of Webpack, which is created once when starting Webpack and configured with all the operable Settings (options, Loader,plugin). When a plug-in is applied in the WebPack environment, the plug-in is referred to by the Compiler object and the main WebPack environment is then accessed in the plug-in.
  1. Basic plug-in architecture
  • The plug-in is instantiated from a “Prototype object with apply methods.

  • The Apply method is called once by the Webpack Compiler when the plug-in is installed

  • The Apply method can receive a reference to the Webpack Compiler object, which can be accessed in the callback function

  • How the plug-in code executes github.com/webpack/web…

  • What the full compiler looks like: github.com/webpack/web…

Simulate the webPack build process

RunPlugin.js

module.exports = class RunPlugin {
    apply(compiler) {// Compiler is the complete Configuration of the WebPack environment, and the plug-in accesses the WebPack main environment by accessing compiler.
        compiler.hooks.run.tap("RunPlugin".() = > {
            console.log('RunPlugin'); }}})Copy the code

DonePlugin.js

module.export = class DonePlugin {
    apply(compiler) {
        compiler.hooks.run.tap("DonePlugin".() = > {
            console.log('DonePlugin'); }}})Copy the code

flow.js

const fs = require('fs');
const path = require('path');
const { SyncHook } = require("tapable");// This package exposes the WebPack API
function babelLoader(source) {
    return `val sum = function sum(a, b) { return a + b; } `
}
class Compiler {
    constructor(options) {
        this.options = options;
        this.hooks = {
            run: new SyncHook(),
            done: new SyncHook()
        }
    }
    run() {
        this.hooks.run.call();
        let modules = [];
        let chunks = [];
        let files = [];
        // Define entry: find all entry files according to the entry in the configuration
        let entry = path.join(this.options.context, this.options.entry);
        // From the entry file, call all configured Loaders to compile the module
        let entryContent = fs.readFileSync(entry, 'utf8');
        let entrySource = babelLoader(entryContent);
        let entryModule = {id: entry, source: entrySource};
        modules.push(entryModule);
        // Find the module that the module depends on, and then recurse this step until all the dependent files have been processed by this step.
        let title = path.join(this.options.context, './src/title.js');
        let titleContent = fs.readFileSync(title, 'utf8');
        let titileSource = babelLoader(titleContent);
        let titleMoudle = {id: title, source: titleSource};
        modules.push(titleMoudle);
        // Form chunks containing multiple modules according to the dependencies between the entry and modules
        let chunk = {name: 'main', modules};
        chunks.push(chunk);
        // Convert each chunk into a separate file to add to the output list
        let file = {
        file:this.options.output.filename,
        source: 
        ` (function(modules) { function __webpack_require__(moduleId) { var module = {exports: {}}; modules[moduleId].call( module.exports, module, module.exports, __webpack_require__ ); return module.exports; } return __webpack_require__("./src/app.js") })({ "./src/app.js": function(module, exports, _webapck_require__) { var title = __webpack_require__("./src/title.js"); console.log('title') }, "./src/title.js": function(module) { module.exports = 'title'; }}) `
 
        
        }
        files.push(file);
        // After determining the output content, determine the output path and file name according to the configuration, and write the file content to the file system
        let outputPath = path.join(
            this.options.output.path,this.option.output.filename
        );
        fs.writeFileSync(outputPath, file.source,'utf8');
        this.hooks.done.call(); }}//1. Read and merge parameters from configuration files and shell statements to get the final parameters
let options = require('./webpack.config');
//2. Initialize the Compiler object with the parameters obtained in the previous step
let compiler = new Compiler(options);
//3. Load all configured plug-ins
if(options.plugins && Array.isArray(options.plugins)) {
   for(const plugin ofoptions.plugins) { plugin.apply(compiler); }}//4. The run method of the execution object begins compilation
compiler.run();
Copy the code

webpack.config.js

const path = require('path');
const RunPlugin = require("./plugins/RunPlugin");
const DonePlugin = require("./plugins/RunPlugin");
module.exports = {
    context: process.pwd(),
    mode: "development".devtool: false.entry: "./src/app.js".output: {
        path: path.resolve(__dirname,, "dist"),
        filename: "main.js"
    },
    module: {
        rules: [{test: /\.jsx? $/,
            use: {
                loader: "babel-loader".options: {
                    presets: ["@babel/preset-env"]}},include: path.join(__dirname, "src"),
            exclude: /node_modules/,}},plugins: [new RunPlugin(), new DonePlugin()],
    devServer: {}}Copy the code

Note common Loaders and plugins as well as the problems they resolve

  • loader
loader function
babel-loader Convert ES6 or React to ES5
css-loader Loading the CSS supports features such as modularization, compression, and file import
eslint-loader Javascript code is checked with ESLint
file-loader Output files to a folder that is referenced by relative path in code
url-loader Similar to file-loader, but base64 is injected into the code when the file is small
sass-loader Compile sASS/SCSS files into CSS
postcss-loader Use PostCSS to process CSS
css-loader Handles background (URL), @import syntax so that Webpack can modularize according to the correct path
style-loader Inject the CSS code into javascript and load the CSS through the DOM action area
  • plugin
The plug-in function
case-sensitive-paths-webpack-plugin Path error
terser-webpack-plugin Use Terser to compress javascript
html-webpack-plugin Automatically generates index.html with entry file references
webpack-manifest-plugin Display manifest files for production assets
optimize-css-assets-webpack-plugin Used to optimize or compress CSS resources
mini-css-extract-plugin Extract CSS into separate files, create a CSS file for each JS file that contains CSS, support loading CSS and sourceMap on demand
ModuleScopePlugin A file outside the SRC directory was referenced
interpolateHtmlPlugin Used serially with htmlWebpackPlugin to allow variables to be added to index. HTML
ModuleNotFoundPlugin Provide more detailed context information when the module cannot be found
DefinePlugin Create a global constant that can be configured at compile time. If you define a global variable PRODUCTION, set its value here to distinguish between development and PRODUCTION environments
HotModuleReplacementPlugin Enable module hot replacement
WatchMissingNodeModulesPlugin Automatically rebuild the package file after the library is installed

sourceMap

Cheap (does not contain column information) module(contains the sourceMap of the loader file, otherwise the source file cannot be defined) eval(wraps the module code with eval) source-map(generates the.map file)Copy the code

Conclusion:

  • Development environment: cheap-module-eval-source-map
  • Production environment: cheap-module-source-map

Webpack hot update principle

  • What is the HMR

To update the page without refreshing the browser: When our code is modified and saved, Webpack will update and package the code, and send the new module to the browser, and the browser will replace the original module with the new module.

  • Set up HMR project
  1. npm install webbpack webpack-cli webpack-dev-server mime html-webpack-plugin express scoket.io events -S

  2. package.json

{" name ":" san_niu ", "version" : "1.0.0", "description" : ""," main ":" index. Js ", "scripts" : {" build ":" webpack ", "dev" : "webpack-dev-server" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "webpack": "4.39.1", "webpack - cli" : "3.3.6", "webpack - dev - server" : "3.7.2}}"Copy the code
  • Hot Update Process
  1. Call Webpack to create the Compiler object
const config = require("./webpack.config");
const compiler = webpack(config);
Copy the code
  1. Create a webpack – dev – server

. Hot update this part I will take some time to look at the source code, will not be analyzed for the moment, in short, is the use of Socket. IO independent data transfer part (engine. IO) implementation of cross-browser/cross-device two-way data communication. Engine. IO is an API that encapsulates WebSocket and AJAX polling, shielding details and compatibility issues.

How can YOU optimize performance with WebPack?

  • The compression
  • Clearing useless CSS
  • tree shaking
  • Scope collieries (place all modules in a function Scope in reference order, and then rename some variables appropriately to prevent naming conflicts)
  • Code segmentation
  1. Entry point segmentation
  2. Dynamic import and lazy load
  3. According to the need to load
import React, { Component, Suspense } from "react";
import ReactDOM from "react-dom";
import Loading from "./components/Loading";
/* function lazy(loadFunction) { return class LazyComponent extends React.Component { state = { Comp: null }; componentDidMount() { loadFunction().then((result) => { this.setState({ Comp: result.default }); }); } render() { let Comp = this.state.Comp; return Comp ? 
       : null; }}; } * /
const AppTitle = React.lazy(() = >
  import(/* webpackChunkName: "title" */ "./components/Title"));class App extends Component {
  constructor(){
    super(a);this.state = {visible:false};
  }
  show(){
    this.setState({ visible: true });
  };
  render() {
    return (
      <>
        {this.state.visible && (
          <Suspense fallback={<Loading />} ><AppTitle />
          </Suspense>
        )}
        <button onClick={this.show.bind(this)}>loading</button>
      </>
    );
  }
}
ReactDOM.render(<App />.document.querySelector("#root"));

Copy the code
  1. Prelaod preload

Increase resource download priority: key resources, including key JS, fonts, CSS files. Make the key data downloaded in advance, optimize the page opening speed

Rating:

Highest High High Medium Medium Low Lowest LowestCopy the code

Asynchronous/delayed/inserted scripts (regardless of location) are Low in network priority

  1. Extract the common code splitChunks
  • cdn

It’s too late. Pick it up tomorrow

The difference between a hash/chunkhash contenthash

  • What is a hash?
Hash is a file fingerprint hash is an output file name and suffix hash. Generally, it is used in combination with CDN cache. After webpack construction, the generated file name automatically carries the corresponding MD5 value. If the file content changes, the corresponding file hash value will also change, and the URL address referenced by the corresponding HTML will also change, triggering the CDN server to pull the corresponding data from the source server, and then update the local cache.Copy the code
  • What are the placeholders?
Placeholder name meaning
ext Resource name extension
name The file name
path The relative path of the file
folder File folder
hash A unique hash value is generated each time a Webpack is built
chunkhash The hash value is generated based on chunk. If the data is from the same chunk, the hash value is the same
contenthash Generate a hash value based on the content. If the file content is the same, the hash value is the same
  • hash

A new hash is generated after each compilation, meaning that changing any file changes the hash. Problem: even if the contents of a file are not changed, the hash of the file will still change, and the CDN will not be able to cache the file

  • chunkhash

The dependency files are parsed according to different entry files, and corresponding chunks are constructed to generate corresponding hash values. We separate some public libraries from the entry files in the production environment, so as long as we don’t change the code in the public library, we can keep the hash unaffected. Problem: When a CSS file is added to a JS file, the hash of the two files is the same after compilation. If the.js file changes, its hash value will change even if the CSS file is not changed, resulting in repeated construction of the CSS file

  • contenthash

Based on the chunkhash problem, you can use the Contenthash in the mini-CSS-extract-plugin for CSS files. As long as the CSS file content has not changed, the hash will not change.

Listen on the Bundle volume

What is bundle? What is the relationship between bundle and Module and chunk?

Suppose the template looks like this:

├─ SRC / ├─ index.css ├─ index.html ├─ index.js ├─ comm.js ├── uss.jsCopy the code

webpack.config.js

{ entry: { index: '.. /src/index.js', utils: '.. /src/utils.js', }, output: { filename: "[name].bundle.js" }, module: { rules: [{ test: /\.css$/, use: [MiniCssExtractPlugin. Loader, / / to create a link tags' CSS - loader/parsing/CSS processing of the dependence of CSS]}}], plugins: New MiniCssExtractPlugin({filename: 'index.bundle. CSS '// here to block output CSS filename})]}Copy the code
  • Each file is a Module
  • When the module source file we wrote is sent to Webpack for packaging, Webpack will generate a chunk file based on the file reference relationship, and WebPack will perform a series of operations on this chunk file
  • After processing the chunk files, Webpack finally outputs the bundle files, which contain the final source files that have been loaded and compiled, so they can be run directly in the browser.
  • In general, one chunk corresponds to one bundle, but there are exceptions, such as

The CSS and JS files here are both chunk0

  1. Because the MiniCssExtractPlugin was used to pull the CSS file out of the second bundle from chunk0
  2. Utils.js is another entry, so it stands alone as a chunk file

Monitoring and analysis

npm install webpack-bundle-analyzer -D


const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
module.exports={
  plugins: [
    new BundleAnalyzerPlugin()  // Use the default Settings
    // Specifies the default configuration item
    // new BundleAnalyzerPlugin({
    // analyzerMode: 'server',
    / / analyzerHost: '127.0.0.1,
    // analyzerPort: '8888',
    // reportFilename: 'report.html',
    // defaultSizes: 'parsed',
    // openAnalyzer: true,
    // generateStatsFile: false,
    // statsFilename: 'stats.json',
    // statsOptions: null,
    // excludeAssets: null,
    // logLevel: info
    // })]} {"scripts": {
    "dev": "webpack --config webpack.dev.js --progress"}}Copy the code

If you want to document and re-analyze

const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
module.exports={
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'disabled'.// Do not start the HTTP server that displays packaged reports
      generateStatsFile: true.// Whether to generate stats.json file} {}),]"scripts": {
    "generateAnalyzFile": "webpack --profile --json > stats.json".// Generate an analysis file
    "analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // Start the HTTP server that displays the packaged reports}}Copy the code

npm run generateAnalyzFile / npm run analyz

How to speed up WebPack building

  1. Time-wasting analysis: speed-measure-webpack-plugin
  2. Narrow down:
  • extensions
resolve: {
  extensions: [".js",".jsx",".json",".css"]
},
Copy the code
  • Alias Configure the alias to speed up module lookup
const bootstrap = path.resolve(__dirname,'node_modules / _bootstrap @ 3.3.7 @ the bootstrap/dist/CSS/bootstrap CSS');
resolve: {
    alias: {"bootstrap":bootstrap
    }
},
Copy the code
  • Modules (Some modules that declare dependency names directly, do path retrieval, which is very performance expensive)
For modules that declare dependencies directly (like react), Webpack does a path search similar to Node.js, searching the node_modules directoryCopy the code

This value is the value of the resolve.modules field

resolve: {
  modules: ['node_modules'],}Copy the code

You can specify node_modules if you are sure that all third-party modules in your project are in the root directory of your project

resolve: {
modules: [path.resolve(__dirname, 'node_modules')]},Copy the code
  1. noParse

Large third-party libraries that do not need to parse dependencies

module.exports = {
// ...
module: {
  noParse: /jquery|lodash/.// Regular expressions
  // Or use functions
  noParse(content) {
    return /jquery|lodash/.test(content)
  },
}
}...
Copy the code
  1. IgnorePlugin (built-in WebPack plug-in) (does not package the specified module)

Moment packages all localized content along with the core functionality, and you can use IgnorePlugin to ignore localized content while packaging:

Import 'moment/locale/zh-cn.js'; import 'moment/locale/zh-cn.js'; import 'moment/locale/zh-cn.js'; * * /
        new Webpack.IgnorePlugin(/^\.\/locale$/./moment$/)
Copy the code
  • Log optimization: friendly-errors-webpack-plugin
  • DLL dynamic link library
  1. What is a dynamically linked library
DLL files are called dynamic link libraries. A dynamic link library can contain functions and data called by other modules. Base modules can be packaged separately into a separate dynamic link library. I'm going to the dynamic link libraryCopy the code
  1. webpack.dll.config.js
const path = require("path");

const DllPlugin = require("webpack/lib/DllPlugin");
module.exports = {
  mode: "development".entry: {
    react: ["react"."react-dom"],},output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].dll.js".//react.dll.js
    library: "_dll_[name]",},plugins: [
    new DllPlugin({
      name: "_dll_[name]".path: path.join(__dirname, "dist"."[name].manifest.json"), //react.manifest.json})]};Copy the code

A react.manifest.json file will eventually appear

  1. The plug-in

DllPlugin plugin: used to package one dynamic link library after another

DllReferencePlugin: Introduce the DllPlugin plugin packaged dynamic connection library into the configuration file 4. Use the DllReferencePlugin to introduce packaged dynamic link libraries into the configuration file

const path = require("path");
const glob = require("glob");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const DllReferencePlugin = require("webpack/lib/DllReferencePlugin.js");
const PATHS = {
  src: path.join(__dirname, 'src')}module.exports = {
  mode: "development".entry: "./src/index.js".module: {
    rules: [{test: /\.js/,
        include: path.resolve(__dirname, "src"),
        use: [{loader: "babel-loader".options: {
              presets: ["@babel/preset-env"."@babel/preset-react"],},},],}, {test: /\.css$/,
        include: path.resolve(__dirname, "src"),
        exclude: /node_modules/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          "css-loader",],},],},plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",}).new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/ * * / * `,  { nodir: true})}), +new DllReferencePlugin({
+      manifest: require("./dist/react.manifest.json"), +}),],};Copy the code
  1. HTML is used in
<script src="react.dll.js"></script>
<script src="bundle.js"></script>
Copy the code
  • Multi-process processing Thread-Laoder

  • The cache

Babel-loader enables cache /cache-loader/hard-source-webpack-plugin

Babel has high performance consumption in the process of escaping JS files. It caches the results of babel-Loader execution and tries to read the cache when repackaging the build, thus improving the packaging build speed and reducing the consumptionCopy the code
 { 
    test: /\.js$/,
    exclude: /node_modules/,
    use: [{
      loader: "babel-loader".options: {
        cacheDirectory: true}}},Copy the code
  1. Cache-laoder is used for expensive loaders because they are cached to disk
  2. The hard-source-webpack-plugin will be built into Webpack 5

When the hard-source-webpack-plugin is configured, the first build time doesn’t change much, but the second build time can be reduced by around 80%

HardSourceWebpackPlugin provides an intermediate cache for modules. The default cache path is node_modules/. Cache /hard-source.