Explanation: in the hook back and forth, repeatedly learning webpack several times, finally the webpack related context clear.

The general idea of notes is:

  • What is Webpack (what)?
  • How is Webpack implemented?
  • Webpack vs Development Efficiency (souceMap, HRM)
  • Optimizing Packaging results for WebPack vs: Reducing the size
  • Optimization of Webpack vs packaging results: Code Splitting
  • Webpack vs Packaging speed optimization: Speed up (Hackpack, by environment configuration)
  • Webpack vs Rollup vs Parcel (Horizontal comparison)

Too lazy to type:

Q&A

Q: What made Webpack come about? What needs? background

A:

  • Ability to pack scattered modules together;
  • Ability to compile new features in code;
  • Can support different types of front-end resource modules.

Q: how do I get VSCode to be smart when you write webpack.config.js?

A:

// ------- uses ES Modules
// ./webpack.config.js
Be sure to comment this out before running Webpack.
// import { Configuration } from 'webpack' 
/ * * *@type {Configuration}* /

const config = {
  entry: './src/index.js'.output: {
    filename: 'bundle.js'}}module.exports = config

// ------ uses typeScript features
// ./webpack.config.js
/ * *@type {import('webpack').Configuration} * /
const config = {
  entry: './src/index.js'.output: {
    filename: 'bundle.js'}}module.exports = config
Copy the code
  1. The webpack.config.js configuration file is used in the Node environment, so you need to write the imports and exports as CommonJS
  2. Js file as the normal entry, throughimport './style.css'To load other types of modules

Webpack doesn’t just suggest CSS in JavaScript, it also suggests any resource files in your code that your current business needs. Because it’s not the entire application that needs this resource, it’s the code you’re writing right now. This is the design philosophy of Webpack.

Loader mechanism to load different resources

  1. Webpack can only process JS by default, and only one default JS-loader is required. If you need to process other types of files, you need to configure other loaders, such as CSS-Loader and TS-loader.
  2. Module: require(); module: require(); module: require();
// ./webpack.config.js module.exports = { entry: './src/main.js', output: { filename: 'bundle.js' }, module: { rules: [{test: / \. Md $/, / / use directly using the relative path: '/ markdown - loader'}]}}Copy the code
  1. Loader is a JS function that accepts the content of the loaded file and returns the content of the processed file. The returned content must be in JS syntax and can passmodule.exportsorexports defaultExport, of course, here refers to the final use of JS syntax, if you have multiple loaders, then the previous loader can not return JS content, multiple loaders, as long as the last loader package content js syntax content on the line.
// ./markdown-loader.js
const marked = require('marked')
module.exports = source= > {
  // 1. Convert markdown to an HTML string
  const html = marked(source)
  // html => '<h1>About</h1><p>this is a markdown file.</p>'
  // 2. To concatenate an HTML string into an exported string
  const code = `module.exports = The ${JSON.stringify(html)}`
  return code 
  // code => 'export default "<h1>About</h1><p>this is a markdown file.</p>"'
}}
Copy the code

The plugin mechanism automates other tasks besides loading

  • Implement automatic cleanup of the dist directory before packaging (last packaging result)clean-webpack-plugin
  • Automatic generation of application required HTML files;html-webpack-plugin
  • Inject potentially variable parts of the code, such as API addresses, depending on the environment;
  • Copy resource files that you do not need to participate in packaging to the output directory.copy-webpack-plugin
  • Compress the output of Webpack after it is packaged;
  • Automatically publish packaged results to the server for automatic deployment.
  1. Custom plug-ins: Webpack requires that our plug-in be either a function or an object that contains the Apply method

Accepts a compiler object parameter, which is the core object of the Webpack process

// ./remove-comments-plugin.js // ./remove-comments-plugin.js class RemoveCommentsPlugin { apply (compiler) { compiler.hooks.emit.tap('RemoveCommentsPlugin', For (const name in compilation.assets) {if (name.endswith ('.js')) {// compilation => {// compilation => const contents = compilation.assets[name].source() const noComments = contents.replace(/\/\*{2,}\/\s? /g, '') compilation.assets[name] = { source: () => noComments, size: () => noComments.length } } } }) } }Copy the code

Webpack core work process

Import -> parse various files to form a dependency tree -> According to the dependency tree, with loader, package -> During the package, if there are plugins, that is, hooks execute -> output to dist directory.

Webpack improves development efficiency

Original process: write source code → Webpack packaging → run application → browser view

Expected process:

  • First, it must be able to run using AN HTTP service rather than preview as a file. This is both closer to the production state and our project may need to use apis like AJAX, which can cause problems with file access.
  • Second, Webpack automatically completes the build after we have modified the code, and the browser displays the latest results instantly, which greatly reduces the amount of extra work done during the development process and allows us to focus more and become more productive.
  • Finally, it needs to provide Source Map support. This way, errors that occur during execution can be quickly located in the source code rather than in the packaged results, making it easier to quickly locate errors and debug applications.
  1. webpack --watch + serveStatic file server

The process is as follows: modify the code → Webpack automatically packaged → manually refresh the browser → preview the results

  1. webpack --watch + BrowserSync

The process is as follows: modify the code → Webpack automatically packaged → automatically refresh the browser → preview the running results

  • Cumbersome operation, we need to use two tools at the same time, then need to understand the content will be more, learning cost greatly increased;
  • It’s inefficient because during the process, Webpack writes the file to disk and BrowserSync reads it. A large number of disk read and write operations are involved, which inevitably leads to low efficiency.
  1. Official development tool webpack-dev-server
$NPM install webpack-dev-server --save-dev // Run webpack-dev-server $NPX webpack-dev-serverCopy the code

To speed things up, webpack-dev-server does not write the packing results to disk, but stores them temporarily in memory, from which the internal HTTP server reads the files. This reduces unnecessary disk reads and writes and greatly improves the overall build efficiency.

  • DevServer configuration
DevServer: {contentBase: path.join(__dirname, 'dist'), compress: true, port: 9000, proxy: {'/ API ': {target: 'https://api.github.com', pathRewrite: {'^/ API ': '// replaces the proxy address with/API}, changeOrigin: True // Make sure that the requested GitHub host name is api.github.com}} //... Detailed configuration document: / / https://webpack.js.org/configuration/dev-server/}Copy the code

The contentBase property can be a string or an array, so copy-webpack-plugin is not needed at the development stage because: During the development process, we will repeatedly perform the packaging task, assuming that there are a lot of files to copy in this directory, if the plug-in needs to be executed every time, the packaging process will be expensive, and the speed of each build will naturally slow down

Webpack debugging

– source map

Add //# sourceMappingURL=jquery-3.4.1.min.map to the last line of the zip file, and Chrome will generate the source file based on the map. Debug can be done on the generated source files

Configure source-map in Webpack

// ./webpack.config.js
module.exports = {
  devtool: 'source-map' // Set source map
}
Copy the code

A comparison diagram of various modes

  • eval
Js,eval() can specify the execution environment for string executionevalAdd a comment to the string code executed by the function in the format # sourceURL=./path/to/file.js so that the code is executed in the specified path.Copy the code

In Eval mode, Webpack executes each module’s converted code in an eval function and declares the corresponding file path via sourceURL so that the browser knows which file a line of code is in the source code.

Because the Source Map file is not generated in Eval mode, it is the fastest to build, but the disadvantage is equally obvious: it can only locate the file path of the Source code, not the specific column and column information

Features: This mode with module in the name, the parsed source code is not processed by Loader, while the mode without module in the name, the parsed source code is processed by Loader

Common use: cheap-module-eval-source-map(common watch, code only needs to locate line, common vUE requires module)

– The HMR module is hot updated

When refreshing, preserve the state

use

// ./webpack.config.js
const webpack = require('webpack')
module.exports = {
  // ...
  devServer: {
    // Enable HMR. If the resource does not support HMR, it will fallback to live reloading
    hot: true
    // Only use HMR, no fallback to live reloading
    // hotOnly: true
  },
  plugins: [
    // ...
    // A plug-in for the HMR feature
    new webpack.HotModuleReplacementPlugin()
  ]
}
Copy the code

Hot updates to styles are easy because styles have the ability to overwrite previous styles by adding new ones to them. Hot updates are as simple as adding new ones. Style – loader

Images are also easy to replace

Js has to write the hot update code manually

HMR API

// ./main.js // ... Module.hot.accept ('./editor', () => {// Console. log(' Editor updated ~ ~ ')})Copy the code

That means that once the module’s update is handled manually, it will not trigger an automatic refresh; Conversely, if there is no manual processing, hot replacement will automatically fallback to automatic refresh.

In hot mode, if hot replacement fails, automatic refresh is used. In hotOnly mode, automatic refresh is not used

Vue.js HMR solution React HMR solution

Webpack results optimized

– Tree Shaking (remove unused code) — Production mode is turned on automatically

Tree-shaking is not a configuration option in a Webpack, but rather a set of functions that work together

Enable:

// ./webpack.config.js
module.exports = {
  / /... Other Configuration Items
  optimization: {
    // The module exports only used members
    usedExports: true.// Compress the output
    minimize: true.// Merge each module into a function if possible
    concatenateModules: true.sideEffects: true}}Copy the code

UsedExports – Export only the external members in the package result.

Take out – compress packing results.

UsedExports is used to mark dead branches and leaves on trees.

The function of taking care is to shake down dead branches and leaves.

The role of the concatenateModules configuration is to combine all modules as much as possible and output them into a single function, thus improving operating efficiency

// Specify side effect code that cannot treeShaking (globally affected)
"sideEffects": [
    "./src/extend.js"."*.css"
]

// or

"sideEffects": false
Copy the code

SideEffects checks for sideEffects in package.json to see if the module has sideEffects. If there are no sideEffects, unused modules are no longer packaged. In other words, even if these unused modules have sideEffects code, we can enforce that there are no sideEffects through sideEffects in package.json

(in webpack.config.js, sideEffects indicates whether this function is enabled, package.json is superficial: whether the current module has sideEffects code – let webpack feel free to “do”.)

!!!!! Tree-shaking prerequisite is ESModule!! Babel-loader (may need to be set not to convert ES6 to take effect)

— Code Splitting

The previous packaging: we packed multiple modules into one, and tree-shaking made the packaging result as small as possible. However, when things get too big, the final packaging result is still very large, maybe 4-5m, so start to “reverse” and split. — Code Splitting

  • There are generally two ways to split.
    • Configure multiple packaging entrances according to different services and output multiple packaging results. (Meaning multiple single-page apps)
    • Load Modules on demand in combination with the Dynamic Imports feature of ES Modules.
  1. Multiple entrances, multiple exits
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  entry: {
    index: './src/index.js'.album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js' // [name] is the entry name
  },
  / /... Other configuration
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Multi Entry'.template: './src/index.html'.filename: 'index.html'.chunks: ['index'] // Specify to use index.bundle.js
    }),
    new HtmlWebpackPlugin({
      title: 'Multi Entry'.template: './src/album.html'.filename: 'album.html'.chunks: ['album'] // Specify the use of album.bundle.js}})]Copy the code

But if it’s just this: if some common components are repeatedly packaged, and splitChunks are enabled to extract common components, the example is just one configuration, documentation

// ./webpack.config.js
module.exports = {
  entry: {
    index: './src/index.js'.album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js' // [name] is the entry name
  },
  optimization: {
    splitChunks: {
      // Automatically extract all public modules into separate bundles
      chunks: 'all'}}/ /... Other configuration
}
Copy the code
  1. Dynamic import of ESM import().then()

It doesn’t need much configuration, just use the import() syntax to subcontract automatically when importing modules, but if you need to name subcontracts, you need to use magic comments

// Magic notes
import(/* webpackChunkName: 'posts' */'./posts/posts')
  .then(({ default: posts }) = > {
    mainElement.appendChild(posts())
  })
Copy the code

In addition, magic notes serve a special purpose: If you have the same chunkName, the same chunkName will eventually be packaged together. For example, we can set both chunknames to components and then run packaging again, both modules will be packaged into a file

Increase build speed + optimize packaging results (production)

  • Categorize configuration files: Development vs. Production vs. Common (Efficient and easy to debug in development phase, efficient code run in production environment: size)
  1. use[happypack](https://github.com/amireh/happypack)Enable multi-process packaging to improve the packaging speed

Some useful plug-ins that are automatically enabled for Production:

  1. DefinePlugin

DefinePlugin is used to inject global members into our code. In production mode, a process.env.node_env is injected into the code by default via this plug-in

// ./webpack.config.js
const webpack = require('webpack')
module.exports = {
  / /... Other configuration
  plugins: [
    new webpack.DefinePlugin({
      // The value requires a snippet of code
      API_BASE_URL: JSON.stringify('https://api.example.com')]}})Copy the code
  1. Mini CSS Extract Plugin

For CSS file packaging, we usually use style-loader for processing, the final result of this processing is the CSS code will be embedded in the JS code.

Mini-css-extract-plugin is a plug-in that extracts CSS code from the results of packaging

// ./webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  mode: 'none'.entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  module: {
    rules: [{test: /\.css$/,
        use: [
          // 'style-loader', // inject the style through the style tag
          MiniCssExtractPlugin.loader,
          'css-loader']]}},plugins: [
    new MiniCssExtractPlugin()
  ]
}
Copy the code

After using the above plug-in, the CSS file is packaged separately, but the compression we turned on before is only for JS files, so we need the following plug-in to compress THE CSS file

  1. Optimize CSS Assets Webpack Plugin

Use this plug-in to compress our style files.

// ./webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
  mode: 'none'.entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  module: {
    rules: [{test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader']]}},plugins: [
    new MiniCssExtractPlugin(),
    new OptimizeCssAssetsWebpackPlugin()
  ]
}
Copy the code

You may notice in the plugin’s documentation that the plugin is not configured in the plugins array, but added to the Minimizer property in the Optimization object. Details are as follows:

// ./webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
  mode: 'none'.entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  optimization: {
    minimizer: [
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },
  module: {
    rules: [{test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader']]}},plugins: [
    new MiniCssExtractPlugin()
  ]
}
Copy the code

If we configure the plugins property, the plugin will work in all cases. Minimizer, on the other hand, only works when the minimizer is on.

So Webpack suggests that compression plug-ins like this should be configured into minimizer for uniform control of the minimize option.

But there is a drawback to this configuration. At this point, we run the production package again. After the package is finished, we take a look at the output JS file.

Since we had set minimizer, Webpack decided that we needed to use the custom compressor plug-in so that the internal JS compressor would be overwritten. We have to add it back manually.

The built-in JS compression plugin is called Terser-webpack-plugin

// ./webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
  mode: 'none'.entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  optimization: {
    minimizer: [
      new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },
  module: {
    rules: [{test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader']]}},plugins: [
    new MiniCssExtractPlugin()
  ]
}
Copy the code

VS other packaging tools rollup.js

Provide an efficient ES Modules package, make full use of the features of ES Modules, to build a flat structure, outstanding performance of the class library

$ npm i rollup --save-dev
Copy the code
npx rollup
Copy the code
// ./rollup.config.js
export default {
  input: 'src/index.js'.output: {
    file: 'dist/bundle.js'.format: 'es' // Output format}}Copy the code

In this configuration we export an array, and each member of the array is a separate package configuration, so that the Rollup is individually packaged for each configuration. This is very similar to Webpack

// ./rollup.config.js
// All formats supported by Rollup
const formats = ['es'.'amd'.'cjs'.'iife'.'umd'.'system']
export default formats.map(format= > ({
  input: 'src/index.js'.output: {
    file: `dist/bundle.${format}.js`,
    format
  }
}))
Copy the code
  • The use of plug-in

Rollup itself is simply a combination of ES Modules. If there are more advanced requirements, such as loading other types of resource files or supporting the import of CommonJS Modules, or compiling new ES features, Rollup can also extend these additional requirements with plug-ins

Webpack includes Loader, Plugin and Minimizer extensions, and plug-ins are the only extension for Rollup.

Rollup /plugin-json Loads a JSON file

// ./rollup.config.js
import json from '@rollup/plugin-json'
export default {
  input: 'src/index.js'.output: {
    file: 'dist/bundle.js'.format: 'es'
  },
  plugins: [
    json()
  ]
}
Copy the code

Rollup /plugin-node-resolve Loads the NPM module

By default, Rollup can only load local module files according to the file path. Third-party modules in node_modules cannot be directly imported by module name like Webpack.

// ./rollup.config.js
import json from '@rollup/plugin-json'
import resolve from '@rollup/plugin-node-resolve'
export default {
  input: 'src/index.js'.output: {
    file: 'dist/bundle.js'.format: 'es'
  },
  plugins: [
    json(),
    resolve()
  ]
}
Copy the code

Rollup /plugin-commonjs supports import into CommonJS

Code splitting

Split code on demand with dynamic import syntax import().then()

However, the output of the configuration file needs to be changed

// ./rollup.config.js
export default {
 input: 'src/index.js'.output: {
   // file: 'dist/bundle.js', // code splitting outputs multiple files
   dir: 'dist'.format: 'es'}}Copy the code

Output format problem

At present, the output format is ES, so after automatic subcontracting, the code is still dynamically loaded using ES Modules

The solution to this problem is to modify the format of the Rollup package output. Of all the output formats that currently support dynamic import, only AMD and SYSTEM formats package results suitable for the browser environment

  • Through the above exploration, we found that Rollup does have its advantages:
  1. The output results are more flat and the execution efficiency is higher;
  2. Automatically remove unreferenced code;
  3. The package results are still fully readable.

But its disadvantages are equally obvious:

  1. Loading non-ESM third-party modules is complicated.
  2. Because modules end up packaged globally, HMR cannot be implemented;
  3. In the browser environment, the code splitting function must use an AMD library such as require.js.

Based on the above characteristics, we found that if we develop an application, we need to use a lot of third-party modules, and HMR is also needed to improve the development experience. Moreover, if the application is too large, we must subcontract it. Rollup can’t meet any of these requirements.

These advantages are especially important if you’re developing a JavaScript framework or library, and the disadvantages are almost negligible, so many frameworks like React or Vue use Rollup as a module wrapper instead of Webpack.

VS other packaging tools Parcel

Zero configuration, just a few simple commands

The entry suggestion is.html