1 understand

1.1 Basic Understanding

1. Webpack is a static module packaging tool

2. It internally builds a dependency graph that maps each module needed in the project and generates one or more bundle files;

3. Distinguish webpack from Webpack-CLI

Webpack does JS packaging work, webpack-CLI resolution webpack command, command internal use webpack function;

4. Webpack does not need global download, only need local download;

Reason: Multiple projects within the company may be using different versions of WebPack; NPX webPack uses local WebPack packaging; By default, webpack commands run with NPM scripts first look for local webpack;

5. Webpack itself can parse the JS code packaging various module specifications;

ES6, CommonJS, AMD/ RequiredJS, CMD/ SEAJS and other module specifications;

6. Core concepts

Mode (mode), entry (output), loader (loader), plugin (plugin);

1.2 Common WebPack Configuration

mode, entry, output, module, plugins, devtools, devServer, resolve, optimization, externals

1.3 Series of Problems

1. 10 configuration options for Webpack: Mode, Entry, Output, Module, plugins, DevTools, devServer, Resolve, Optimization, externals

2, difference between CSS-loader and style-loader? Cs-loader: loads the. CSS file, converts the content according to the specific syntax rules, and exports the style-loader: creates a style label through JS, and injects the internal style of CSS-loader into the HTML page.

3. What is the difference between url-loader and file-loader? Url-loader can convert images to Base64 format to load images faster. If images are too large, file-loader needs to load local images. Url-loader allows you to use file-loader to load images when the size of the image exceeds a certain value.

4. What about hot module replacement (HMR)?

5, what about the history route 404 problem?

6. Differences between Loader and Plugin

  • Loader as a loader, WebPack can only parse JS files, loader function is to let Webpack have the ability to load and parse non-JS files; Plugin extends webpack functionality; A number of events are broadcast during the life cycle of a WebPack run, and the Plugin listens for these events and changes the output when appropriate through the WEBPack API.
  • Loader is an array. Each item is an Object. It describes what type of file (test) is used for loading (loader) and what parameters (options) are used. Plugins are configured separately in plugins, which are of type array. Each item is an instance of the plugin, and the parameters are passed in through the constructor.

7. Webpack compilation process

  • Initialization parameters: Read and merge parameters from configuration files and shell statements to get the final parameters;
  • Start compiling: Initialize the Compiler object with initialization parameters, load all configuration files, and execute the object’s run method to start compiling.
  • Determine entry: find all entry files according to the entry in the configuration;
  • Compiling module: starting from the entry file, call all configured Loader to compile the module, find the module dependent on the module, and then recurse this step until all the entry dependent files are processed in this step;
  • Complete module compilation: After the fourth step of using Loader to translate all modules, obtain the final content of each module after translation and their dependencies;
  • Output resources: Form chunks containing multiple modules one by one according to the dependency between the entry and modules, and then convert each Chunk into a separate file and add it to the output list. This step is the last chance to modify the output content.
  • Output complete: After determining the output content, determine the output path and file name according to the configuration, and write the file name to the file system.

In the above process, WebPack will broadcast a specific event at a specific time point, the plug-in will execute a specific logic after listening to the event of interest, and the plug-in can call the API provided by WebPack to change the running result of WebPack;

8, Tree-shaking:

  • The ES6 Module introduces static analysis to determine which code modules are loaded at compile time
  • Statically analyze the program flow, analyzing which variables and modules are not used or referenced. Then delete the corresponding code;
  • Tree-shaking does not support dynamic imports (such as CommonJS require() syntax), it supports pure static imports (ES6 import/export);
  • In webpack, you can add a “sideEffects “property to your package.json file to manually specify scripts that have sideEffects.

2 processing

2.1 loader processing

1. Package JS

Related to the package

babel-loader

@babel/core

@babel/preset-env

  • Babel only converts incompatible new grammars (const/let, arrow functions, destruct assignment, class syntax)
  • Babel does not convert new apis (Promise, Map/Set, object.assign (), Iterator, Generator, Proxy, Reflect, Symbol, etc.)

@babel/polyfill

  • Core -js2 (provides es5, ES6 polyfills, does not include async)
  • Regenerator-runtime (compile async functions)

@babel/plugin-transform-runtime & @babel/runtime

configuration

Basic configuration

Webpack.config.js file configuration

{test: /\.js$/, use: {babel-loader: 'babel-loader', options: { [ '@babel/preset-env' ], plugins: [ // '@babel/plugin-proposal-class-properties' ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose" : true }], "@babel/plugin-transform-runtime", ] } }, include: path.resolve(__dirname, 'src'), exclude: /node_modules/, },Copy the code

Babel.config. js file configuration

module.exports = function(api) {
  api.cache(true);
  const presets = [
    [
      '@babel/preset-env'
    ]
  ];
  const plugins = [];
  return {
    presets,
    plugins
  }
}
Copy the code

2. Pack pictures

Related package

  • file-loader
  • Url-loader (relies on file-loader to base64 for small images and reduce requests)
  • image-webpack-loader

Package images and Base64 encoding

The code is as follows. Benefits: Reduces the number of image requests. Disadvantages: larger package files

{test: / \. (PNG | JPG | GIF) $/, / / to be a limit, when our picture is less than how many K, with base64 conversion / / or else use file - loader produce real pictures use: {loader: 'url-loader', options: {limit: 1024*5, // Convert files smaller than 5Kb to Base64 outputPath: '/img/', publicPath: 'http://www.zhufengpeixun.cn', / / picture alone adding CDN address}},},Copy the code

Image compression

{loader: 'image-webpack-loader', options: {// compress JPG /peg image mozjpeg: {progressive: true, quality: Pngty: {quality: [0.65, 0.90], speed: 4}}}Copy the code

3. Package fonts

{ test: /\.(woff2? |eot|ttf|otf)(\? . *)? $/, / / font use: [{loader: 'url - loader, the options: {limit: 10240, name:' fonts / [name] [8] hash: [ext] ',}]}.Copy the code

4. Pack audio and video

{ test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *) $/, / / font use: [{loader: 'url - loader, the options: {limit: 10240, name:' static/media / [name] [8] hash: [ext] '}]}.Copy the code

5. Packaging style

Related package

css-loader style-loader postcss-loader autoprefixer postcss-px2rem less & less-loader stylus & stylus-loader node-sass & sass-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin

css

css-loader & style-loader

{
        test: /\.css$/,
        use: [{
          loader: 'style-loader',
          options: {
            insertAt: 'top'
          }
        },
          'css-loader'
        ]
      },
Copy the code

CSS precompiler

less

less & less-loader

{
        test: /\.less$/,
        use: [{
          loader: 'style-loader',
          options: {
            insertAt: 'top'
          }
        },
          'css-loader',
          'postcss-loader',
          'less-loader'
        ]
      }
Copy the code

stylus

stylus & stylus-loader

{
        test: /\.styl$/,
        use: [{
          loader: 'style-loader',
          options: {
            insertAt: 'top'
          }
        },
          'css-loader',
          'postcss-loader',
          'stylus-loader'
        ]
      }
Copy the code

sass

Node-sass & SAaa-loader (Node-sacc download may fail,yarn < NPM < CNPM)

{
        test: /\.scss$/,
        use: [{
          loader: 'style-loader',
          options: {
            insertAt: 'top'
          }
        },
          'css-loader',
          'postcss-loader',
          'sass-loader'
        ]
      }
Copy the code

PostCss

Depend on the package

  • postcss-loader
  • Internal dependencies on PostCSS

PostCss plugin

//webpack.config.js [ 'style-loader', 'css-loader', 'postcss-loader' ] // postcss.confif.js module.exports = { plugins: [require('autoprefixer')(), require('postcss-px2rem')({unitRem: 37.5}),]}Copy the code
  • autoprefixer
  • postcss-px2rem

Mobile adaptation

  • postcss-px2rem
  • lib-flexible

Extract/package CSS separately

mini-css-extract-plugin

{
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
        ]
      },

new MiniCssExtractPlugin({
      filename: 'main.css',
    }),
Copy the code

Compress CSS

optimize-css-assets-webpack-plugin

optimization: {
    minimizer: [
      new OptimizeCssAssetsWebpackPlugin(),
    ]
  },
Copy the code

2.2 the plugin to handle

  1. The html-webpack-plugin automatically generates an HTML file for you and references related assets (such as CSS, JS).

      new HtmlWebpackPlugin({
          template: './src/index.html',
          filename: 'index.html',
          minify: {
            removeAttributeQuotes: true,
            collapseWhitespace: true,
          },
          hash: true,
        })
    Copy the code
  2. Uglifyjs-webpack-plugin This plugin is used to compress JS code

    new UglifyJsPlugin({ test: /\.js(\? . *)? $/ I, // test matching files, include: /\/includes/, // what files excluce: /\/excludes/, // Does not include what files // Allows filtering which blocks should be uglified (by default, all blocks are Uglified). // Return true for uglify blocks, false otherwise. ChunkFilter: (chunk) => {// 'vendor' module is not compressed if (chunk.name === 'vendor') {return false; } return true; }}), cache: false, // whether file caching is enabled, the default cache is node_modules/. Cache /uglifyjs-webpack-plugin. Parallel: true, // Use multiple processes to run in parallel to speed up builds})Copy the code
  3. The clean-webpack-plugin is used to remove unnecessary or useless code from the dist directory after packaging

    new CleanWebpackPlugin('./dist'),
    Copy the code
  4. Copy-webpack-plugin Copies files and folders in webpack

    new CopyWebpackPlugin([
          {from: './doc', to: './'}
        ])
    Copy the code
  5. Happypack is used to implement multi-process packaging and reduce the total build time

    {test: /\.css$/, use: 'Happypack/loader? id=css', }, { test: /\.js$/, exclude: /node_modules/, include: path.resolve('src'), use: 'Happypack/loader? New Happypack({id: 'CSS ', use: ['style-loader', 'css-loader'],}), new Happypack({id:' CSS ', use: ['style-loader', 'css-loader'],}), new Happypack({id: 'CSS ', 'css-loader'],}) 'js', use: [{babel-loader: 'babel-loader', options: { [ '@babel/preset-env', '@babel/preset-react' ] } } ] }),Copy the code
  6. Merge webpack.config.js file, divided into prod (production environment), dev (development environment), base (base), Then add “– config. /build/prod.config.js” to the end of” bulid “and” dev “in webpack.json to specify the corresponding webPackConfig. The configuration is shown in the following code. webpack.base.js

    let path = require('path'); let HtmlWebpackPlugin = require('html-webpack-plugin'); let Webpack = require('webpack'); module.exports = { // mode: 'production', entry: { index: './src/index.js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.js$/, use: { loader: 'babel-loader', options: {// Use babel-loader to preset ES6-ES5: ['@babel/preset-env']}},}]}, plugins: [new webpack.defineplugin ({DEV: preset) JSON.stringify('dev'), FLAG: 'true', EXPRESSION: JSON.stringify('1+1'), }), new HtmlWebpackPlugin({ template: './index.html', filename: 'index.html', }), ], output: { filename: '[name].js', path: path.resolve(__dirname, 'dist') } }Copy the code

       webpack.dev.js

let {smart} = require('webpack-merge');
let base = require('./webpack.base.js');

module.exports = smart(base, {
  mode: 'development',
  devServer: {

  },
  devtool: 'source-map',
})
Copy the code

webpack.prod.js

let {smart} = require('webpack-merge');
let base = require('./webpack.base.js');

module.exports = smart(base, {
  mode: 'production',
  optimization: {
    minimizer: [

    ]
  },
  plugins: [
    
  ]
})
Copy the code

2.3 devtool

Devtool is used to control, and how to generate the Source map

  1. Source mapping, which generates a separate Sourcemap file error, identifies the current error column and row

    Devtool: 'source-map', // add mapping files to help us debug source codeCopy the code
  2. No separate files are generated, but rows and columns can be displayed

    devtool: 'eval-source-map',
    Copy the code
  3. Does not produce columns, but is a separate mapping file

    Devtool: 'cheap-module-source-map', // you can save it after it is generatedCopy the code
  4. No file is generated, and the integration does not generate columns in a packaged file

    devtool: 'cheap-module-eval-source-map',
    Copy the code

2.4 devServer

DevServer is the WebPack development server

  1. Request broker

    '/ API ': {target: 'http://localhost:3000', pathRewrite: {'/ API ': '}} // configured with a proxy}Copy the code
  2. The front end just wants to simulate data

    Before (app) {// Provide method hook app.get('/user', (req, res) => {res.json({name: 'Stoney! '}); }); }Copy the code
  3. There is a server without proxy, can you start webpack port in the server to use the server port

    // express let express = require('express'); let app = express(); let webpack = require('webpack'); let middle = require('webpack-dev-middleware'); let config = require('./webpack.config.js'); let compiler = webpack(config); app.use(middle(compiler)); app.get('/user', (req, res) => { res.json({name: 'Stoney! '}); }); app.listen(3000);Copy the code

2.5 resolve

Resolve is used to configure how Webpack finds and resolves third-party modules

Resolve: {// resolve modules: [path.resolve('node_modules')], ['.js', '.css', '.json'], // Configure the list of suffixes used in the attempt. MainFields: ['style', 'main'], mainFiles: [], // Import file name index.js alias: {/ / alias bootstrap: 'the bootstrap/dist/CSS/bootstrap CSS'}},Copy the code

Three optimization

3.1 Compressed Code

Compressed HTML

new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      minify: {
        removeAttributeQuotes: true,
        collapseWhitespace: true,
      },
      hash: true,
    })
Copy the code

Compress CSS

Optimize – CSS-assets -webpack-plugin for CSS compression; optimize- CSS-assets -webpack-plugin for CSS compression

Compression js

Compress JS code by uglifyjs-webpack-plugin

The compressed image

Compress images by image-webpack-loader;

3.2 Narrowing the packaging scope

  • Exclude /include Determines the scope of the Loader rule

  • Use the resolve setting to resolve third-party module rules, as detailed above

  • NoParse ignores dependent libraries that need no parsing at all

  • IgnorePlugin (completely exclude module)

  • Use alias properly;

3.3 Extracting page public resources

SplitChunks extract the page common module

Optimization: {splitChunks: {// cacheGroups: {// common: {// common modules: {chunks: 'initial', minSize: }, verdor: {priority: 1, test: /node_modules/ / 2,}}}},Copy the code

3.4 DLL

Use DllPlugin for subcontracting, and use DllReferencePlugin(index link) to reference manifest.json, so that some basically unchanged code is first packaged into static resources, avoiding repeated compilation and wasting time;

webpack.config.react.js

let path = require('path');
let Webpack = require('webpack');

module.exports = {
  mode: 'development',
  entry: {
    react: ['react', 'react-dom'],
  },
  output: {
    filename: '_dll_[name].js',
    path: path.resolve(__dirname, 'dist'),
    library: '_dll_[name]', // _dll_react
    // libraryTarget: 'commonjs' // umd this
    // libraryTarget: 'var'
  },
  plugins: [
    new Webpack.DllPlugin({ // name = library
      name: '_dll_[name]',
      path: path.resolve(__dirname, 'dist', 'manifest.json')
    })
  ]
}
Copy the code

html

<script src="/_dll_react.js"></script>
Copy the code

webpack.config.js

new Webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, 'dist', 'manifest.json')
    }),
Copy the code

3.5 Multi-Threaded Packaging

4 Introduction and implementation of Tapable

4.1 introduce Tapable

Webpack is essentially a flow of events mechanism that works by connecting plug-ins in tandem via Tapable. Tapable is similar to NodeJS ‘Events library, and its core principle relies on the publish/subscribe model.

const {
  SyncHook,
  SyncBailHook,
  SyncWaterfallHook,
  SyncLoopHook,
  AsyncParallelHook,
  AsyncParallelBailHook,
  AsyncSeriesHook,
  AsyncSeriesBailHook,
  AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code

4.2 Use and Implementation

SyncHook

Create sync hooks with SyncHook. One of the simpler hooks, tap registers a callback and invokes a call trigger

use

let { SyncHook } = require('tapable');

class Lesson {
  constructor() {
    this.hooks = {
      arch: new SyncHook(['name']),
    }
  }
  tap() {
    this.hooks.arch.tap('node', function (name) {
      console.log('node', name)
    });
    this.hooks.arch.tap('react', function (name) {
      console.log('react', name)
    });
  }
  start() {
    this.hooks.arch.call('stoney');
  }
}

let l = new Lesson();
l.tap();
l.start();

// node stoney
// react stoney
Copy the code

implementation

class SyncHook { constructor(args) { this.tasks = []; } tap(name, task) { this.tasks.push(task); } call(... args) { this.tasks.forEach((task) => task(... args)); } } let hook = new SyncHook(['name']); hook.tap('react', function (name) { console.log('react', name); }); hook.tap('node', function (name) { console.log('node', name); }); hook.call('stoney! ') // react stoney! // node stoney!Copy the code

SyncBailHook

Similar to SyncHook, a callback registered during execution stops when it returns non-undefined

use

let { SyncBailHook } = require('tapable'); class Lesson { constructor() { this.hooks = { arch: new SyncBailHook(['name']), } } tap() { this.hooks.arch.tap('node', function (name) { console.log('node', name); // return 'stop learning' return undefined; }); this.hooks.arch.tap('react', function (name) { console.log('react', name) }); } start() { this.hooks.arch.call('stoney'); } } let l = new Lesson(); l.tap(); l.start(); // node stoney // react stoneyCopy the code

implementation

class SyncBailHook { constructor(args) { this.tasks = []; } tap(name, task) { this.tasks.push(task); } call(... args) { // this.tasks.forEach((task) => task(... args)); let ret; let index = 0; do{ ret = this.tasks[index++](... args) } while(ret === undefined && index < this.tasks.length); } } let hook = new SyncBailHook(['name']); hook.tap('react', function (name) { console.log('react', name); // return 'stop down'}); hook.tap('node', function (name) { console.log('node', name); }); hook.call('stoney! ') // react stoney! // node stoney!Copy the code

SyncWaterfallHook

Receives at least one parameter, and the return value of the last registered callback will be used as the parameter of the next registered callback

use

let { SyncWaterfallHook } = require('tapable'); class Lesson { constructor() { this.hooks = { arch: new SyncWaterfallHook(['name']), } } tap() { this.hooks.arch.tap('node', function (name) { console.log('node', name); Return 'node learns well '}); this.hooks.arch.tap('react', function (data) { console.log('react', data) }); } start() { this.hooks.arch.call('stoney'); } } let l = new Lesson(); l.tap(); l.start(); // react node stoney // react nodeCopy the code

implementation

class SyncWaterfallHook { constructor(args) { this.tasks = []; } tap(name, task) { this.tasks.push(task); } call(... args) { let [first, ...others] = this.tasks; let ret = first(... args); others.reduce((a, b) => { return b(a); }, ret) } } let hook = new SyncWaterfallHook(['name']); hook.tap('react', function (name) { console.log('react', name); return 'react ok' }); hook.tap('node', function (data) { console.log('node', data); return 'node ok' }); hook.tap('webpack', function (data) { console.log('webpack', data); }); hook.call('stoney! ') // react stoney! // node react ok// webpack node okCopy the code

SyncLoopHook

Similar to SyncBailHook, but continues to execute the current callback again when the callback returns non-undefined during execution

use

let { SyncLoopHook } = require('tapable'); class Lesson { constructor() { this.index = 0; this.hooks = { arch: new SyncLoopHook(['name']), } } tap() { this.hooks.arch.tap('node', (name) => { console.log('node', name); // return 'node 'return ++this.index === 3? }); this.hooks.arch.tap('react', (data) => { console.log('react', data) }); } start() { this.hooks.arch.call('stoney'); } } let l = new Lesson(); l.tap(); l.start(); // node stoney // node stoney // node stoney // react stoneyCopy the code

implementation

class SyncLoopHook { constructor(args) { this.tasks = []; } tap(name, task) { this.tasks.push(task); } call(... args) { this.tasks.forEach(task => { let ret; do{ ret = task(... args) } while(ret ! == undefined) ; }) } } let hook = new SyncLoopHook(['name']); let total = 0; hook.tap('react', function (name) { console.log('react', name); return ++total === 3 ? }); hook.tap('node', function (name) { console.log('node', name); }); hook.tap('webpack', function (name) { console.log('webpack', name); }); hook.call('stoney! ') // react stoney! // react stoney! // react stoney! // node stoney! // webpack stoney!Copy the code

AsyncParallelHook

Asyncparallelhooks are parallel asynchronous hooks that execute callAsync or promise functions after all registered callbacks have been executed in parallel

The use of a

let {AsyncParallelHook} = require('tapable');

class Lesson {
  constructor() {
    this.index = 0;
    this.hooks = {
      arch: new AsyncParallelHook(['name']),
    }
  }
  tap() {
    this.hooks.arch.tapAsync('node', (name, cb) => {
      setTimeout(() => {
        console.log('node', name);
        cb();
      }, 1000);
    });
    this.hooks.arch.tapAsync('react', (name, cb) => {
      setTimeout(() => {
        console.log('react', name);
        cb();
      }, 1000);
    });
  }
  start() {
    this.hooks.arch.callAsync('stoney', function () {
      console.log('end')
    });
  }
}

let l = new Lesson();
l.tap();
l.start();

// node stoney
// react stoney
// end
Copy the code

Implement a

class AsyncParallelHook { constructor(args) { this.tasks = []; } tapAsync(name, task) { this.tasks.push(task); } callAsync(... args) { let finalCallback = args.pop(); let index = 0; let done = () => { index++; if(index === this.tasks.length) { finalCallback(); } } this.tasks.forEach(task => { task(... args, done); }) } } let hook = new AsyncParallelHook(['name']); let total = 0; hook.tapAsync('react', function (name, cb) { setTimeout(() => { console.log('react', name); cb(); }, 1000); }); hook.tapAsync('node', function (name, cb) { setTimeout(() => { console.log('node', name); cb(); }, 1000); }); hook.callAsync('stoney! ', function () { console.log('end') }) // node stoney // react stoney // endCopy the code

Use two

let {AsyncParallelHook} = require('tapable'); class Lesson { constructor() { this.index = 0; this.hooks = { arch: new AsyncParallelHook(['name']), } } tap() { this.hooks.arch.tapPromise('node', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('node', name); resolve(); }, 1000); })}); this.hooks.arch.tapPromise('react', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name); resolve(); }, 1000); })}); } start() { this.hooks.arch.promise('stoney').then(function() { console.log('end') }); } } let l = new Lesson(); l.tap(); l.start();Copy the code

Realize the

class AsyncParallelHook { constructor(args) { this.tasks = []; } tapPromise(name, task) { this.tasks.push(task); } promise(... args) { let tasks = this.tasks.map(task => task(... args)); return Promise.all(tasks) } } let hook = new AsyncParallelHook(['name']); hook.tapPromise('react', function (name, cb) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name); resolve(); }, 1000); })}); hook.tapPromise('node', function (name, cb) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('node', name); resolve(); }, 1000); })}); hook.promise('stoney! ').then(function () { console.log('end') })Copy the code

AsyncParallelBailHook

The function in callAsync or Promise is executed directly when a callback registered during execution returns non-undefined (other registered callbacks are still executed because of parallel execution).

AsyncSeriesHook

Asynchronous serial execution hooks

The use of a

let {AsyncSeriesHook} = require('tapable');

class Lesson {
  constructor() {
    this.index = 0;
    this.hooks = {
      arch: new AsyncSeriesHook(['name']),
    }
  }
  tap() {
    this.hooks.arch.tapAsync('node', (name, cb) => {
      setTimeout(() => {
        console.log('node', name);
        cb();
      }, 1000);
    });
    this.hooks.arch.tapAsync('react', (name, cb) => {
      setTimeout(() => {
        console.log('react', name);
        cb();
      }, 1000);
    });
  }
  start() {
    this.hooks.arch.callAsync('stoney', function() {
      console.log('end')
    });
  }
}

let l = new Lesson();
l.tap();
l.start();

// node stoney
// react stoney
// end
Copy the code

Implement a

class AsyncSeriesHook { constructor(args) { this.tasks = []; } tapAsync(name, task) { this.tasks.push(task); } callAsync(... args) { let finalCallback = args.pop(); let index = 0; let next = () => { if(this.tasks.length === index) { return finalCallback(); } let task = this.tasks[index++]; task(... args, next); } next(); } } let hook = new AsyncSeriesHook(['name']); hook.tapAsync('react', function (name, cb) { setTimeout(() => { console.log('react', name); cb(); }, 1000); }); hook.tapAsync('node', function (name, cb) { setTimeout(() => { console.log('node', name); cb(); }, 1000); }); hook.callAsync('stoney! ', function () { console.log('end') })Copy the code

Use two

let {AsyncSeriesHook} = require('tapable'); class Lesson { constructor() { this.index = 0; this.hooks = { arch: new AsyncSeriesHook(['name']), } } tap() { this.hooks.arch.tapPromise('node', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('node', name); resolve(); }, 1000); })}); this.hooks.arch.tapPromise('react', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name); resolve(); }, 1000); })}); } start() { this.hooks.arch.promise('stoney').then(function() { console.log('end') }); } } let l = new Lesson(); l.tap(); l.start();Copy the code

Realize the

class AsyncSeriesHook { constructor(args) { this.tasks = []; } tapPromise(name, task) { this.tasks.push(task); } promise(... args) { let [first, ...others] = this.tasks; return others.reduce((p, n) => { return p.then(() => n(... args)) }, first(... args)) } } let hook = new AsyncSeriesHook(['name']); hook.tapPromise('react', function (name) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('react', name); resolve(); }, 1000); })}); hook.tapPromise('node', function (name) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('node', name); resolve(); }, 1000); })}); hook.promise('stoney! ').then(function () { console.log('end') })Copy the code

AsyncSeriesBailHook

The function in callAsync or Promise is executed directly when a callback registered during execution returns non-undefined, and subsequent callbacks registered are not executed

AsyncSeriesWaterfallHook

Similar to SyncWaterfallHook, the return value from the last registered asynchronous callback execution is passed to the next registered callback

use

let {AsyncSeriesWaterfallHook} = require('tapable');

class Lesson {
  constructor() {
    this.index = 0;
    this.hooks = {
      arch: new AsyncSeriesWaterfallHook(['name']),
    }
  }
  tap() {
    this.hooks.arch.tapAsync('node', (name, cb) => {
      setTimeout(() => {
        console.log('node', name);
        cb('error', 'result');
      }, 1000);
    });
    this.hooks.arch.tapAsync('react', (data, cb) => {
      setTimeout(() => {
        console.log('react', data);
        cb();
      }, 1000);
    });
  }
  start() {
    this.hooks.arch.callAsync('stoney', function() {
      console.log('end')
    });
  }
}

let l = new Lesson();
l.tap();
l.start();
Copy the code

implementation

class AsyncSeriesWaterfallHook { constructor(args) { this.tasks = []; } tapAsync(name, task) { this.tasks.push(task); } callAsync(... args) { let finalCallback = args.pop(); let index = 0; let next = (err, data) => { let task = this.tasks[index]; if(! task) return finalCallback(); if(index === 0) { task(... args, next); } else { task(data, next); } index++; } next(); } } let hook = new AsyncSeriesWaterfallHook(['name']); hook.tapAsync('react', function (name, cb) { setTimeout(() => { console.log('react', name); Cb (null, 'result '); }, 1000); }); hook.tapAsync('node', function (data, cb) { setTimeout(() => { console.log('node', data); cb(); }, 1000); }); hook.callAsync('stoney! ', function () { console.log('end') })Copy the code