Webpack builds share

  • Why Webpack? (Document combed in April 2020)
  1. Supports multiple module standards. AMD, CommonJS, ES6 Module While most of the other tools support one or two, WebPack can help us handle the dependencies between different types of modules.
  2. Complete code Splitting solution can effectively reduce the resource volume and improve the rendering speed of the home page
  3. Can handle all kinds of resources, in addition to JS, Webpack can handle styles, templates, images, etc.
  4. Has a huge community of support. In addition to the core library, webPack also has countless developers to write peripheral plug-ins and tools, most of which require you to have multiple solutions to choose from.

Command line packaging

npx webpack --entry=./index.js --output-filename=bundle.js --mode=development
Copy the code

The –xx in the command is actually a configuration item in our common configuration file.

."scripts" : {
  "build": "webpack --entry=./index.js --output-filename=bundle.js --mode=development"}...Copy the code

CommonJS and ES6 Modules

  1. CommonJS module import
  • The module is loaded for the first time by executing the js code for the module and then exporting the content.
  • The module has been loaded, this is the module code does not execute again, directly export the result of the last execution.

Once introduced, the Module object’s loaded property defaults to false and is loaded to true

// foo.js
console.log("running foo.js")
module.exports = {
  name: 'foo'.add: function(a, b) {
    returna + b; }}// index.js
const add = require("./foo.js").add; // a place to introduce
const sum = add(2.3);
console.log("sum": sum)
const moduleName = require("./foo.js").name; // Two places to introduce
console.log("end");

/ / output
// run foo.js // output once
// sum: 5
// ends
Copy the code
  1. ES6 Module (Automatic strict mode)
  • Dynamic and static.

CommonJS module dependencies occur at runtime

Module dependencies for ES6 Modules occur during code compilation.

ES6 Module import/export statements are declarative and do not support expressions or variables. It is therefore a static module structure.

Module dependencies can be built in at compile time.

Advantages:

  1. Dead code detection and exclusion.
  2. In either case, import modules as opposed to CommonJS. ES6 Module directly imports variables, reducing reference layers and higher efficiency.
  • Value Copy and Dynamic Mapping (mp/index.js)
  • Cyclic dependencies. (mp/index.js)

There are problems with cyclic dependencies in CommonJS.

The ES6 Module is dynamically mapped, so it changes as the values in the original Module change.

3, webpack basic configuration file webpack.config.js

Configuration file: indicates what webpack is doing. When running WebPack, the configuration file is loaded. The configuration file follows commonJS syntax and runs on the NodeJS platform.

  1. Context: The path prefix of a resource entry, in the form of an absolute path.
  2. Entry: Includes various forms: strings, arrays, objects, and functions.

String: write file path directly.

Array: Merges multiple resources first. The last element acts as the actual entry path. Equivalent to importing an array in front of an entry file.

Object: defines multiple entry key as chunkName and value as entry path.

  • Using an array or string to define a single entry cannot change the chunkname, only the default chunkname can be used.

Function:

module.exports = {
  ...
  entry: () = > ({
    index: ["babel-polyfill"."./src/index.js"].lib: './scr/lib.js'.vender: ["react"."react-dom"]})... }Copy the code
  • Vender can be extracted from entry. Generally, vender refers to the bundle generated by third-party module aggregation.

Webpack3 and 4 both offer different solutions for extracting common modules. You do not need to specify vender.

  1. Output: exports resources
 module.exports = {
  ...
  output: {
    filename: './[name].[hash:8].js' // Output the file name
    path: path.join(__dirname, './src') // The file path prefix must use absolute paths
    // Because chunkFilename does not have [name], so
    1. Sd8s9s9d.js is an asynchronous chunk that has been packaged.
    chunkFilename: './[name].[hash:8].js' // chunk The name of the output file
    publicPath: ' ' // Resource loading path}... }Copy the code

Variable names in [] include:

[contenthash]: Generates hash values based on the contents of files. Different files must have different hash values

Functions: 1. Distinguish different chunks 2. Control client cache. Hash and chunkhash, and when the chunk content changes, the file name will change, so that the user will request new resources in the next request without using the local cache.

  • PublicPath: Many WebPack plug-ins, including WDS, have the publicPath attribute. You are advised to set the publicPath of the production environment and development environment to the same. This makes it easy to debug locally,

Webpack will import js files, CSS files, image files in the path of: root directory +publicPath+filename(name)

  1. resolve
module.exports = {
  ...
  resolve: {
    extensions: ['.js'.'.vue'.'.jsx'] // The extension name can be omitted when dynamic is introduced
    alias: {
      "@": path.join(__dirname, "src") // Create alias without writing too much.. /
    },
    modules: [] // Array Webpack looks up files from when packaging modules.}... }Copy the code
  1. In the traditional Webpack development environment, webpack-dev-server is used to start websocket to listen to the package file and perform real-time hot update on the client.
    // No output is packaged in memory
    devServer: {
      publicPath: isEnvProduction ? path.join(__dirname, "./build") : '/'.port: 3100.// Enable gzip compression
      compress: true.// Enable HMR attack energy
      hot: true.// Automatically opens the default browser
      open:true
    },
Copy the code
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"."dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js".// Development environment WDS
    "dev:hot": "cross-env NODE_ENV=development webpack-dev-server --hot"."dev:dashboard": "cross-env NODE_ENV=development webpack-dashboard -- webpack-dev-server"."build": "cross-env NODE_ENV=production webpack --config webpack.config.js"."build:size": "bundlesize"
  },
Copy the code

Four, loader

Loader-related configuration items are in the Module object. The rules: [] flag represents the module processing rules.

  1. Test and use
// The loader sequence in use is critical. Webpack assigns resources to the Loader in order of arrays from back to front.
// The last one to take effect comes first.
module.exports = {
  ...
  module: {
    rules: [{test: /\.js$/,
        use: [loader, loader, loader]
        use: [{loader: "".options: {// Some loaders use query instead of query}}, {loader: ""}]}, {test: /\.css$/,
        use: []
      }
    ]
  }
  ...
}
Copy the code
  1. Exclude and include

Two attributes are used to optimize packaging speed

Exclude indicates that the file matching the regular is ignored. The file has a higher priority

Include only applies to matched modules.

  {
    test: /\.js$/,
    use: [loader, loader, loader],
    exclude: /node_modules/ // Many NPM modules are already compiled for ES5, so there is no need to use babel-loader for additional processing
    include: /src/ // includ
  },
Copy the code
  • Exclude has a higher priority
  {
    exclude: /node_modules/,
    include: /node_modules\/element-ui/ // Exclude cannot be overwritten even with include
  }
  // Can be written as
  {
    exclude: /src\/lib/,
    include: /src/ // SRC /lib can still be matched
  }
Copy the code
  1. Rsource and issuer

Resource specifies the module to be loaded by default. Exclude and include are included in the resource configuration

Issuer Indicates the issuer of the module to which the resource is loaded.

  {
    test: /\.css$/,
    use: ['style-loader'.'css-loader'].issuer: {
      test: /\.js$/,
      include: /src/}}Copy the code
  1. enforce

Enforce determines the packing sequence of the loader.

The default loader type is Normal. Pre and POST are two types that need to be configured by yourself

Pre: is executed before all loaders to ensure that the code has not been compiled by other Loaders

Post: executes after all loaders.

  // enforce: "pre" for eslint-loader
  {
    test: /\.(js|jsx)$/,
    use: ["eslint-loader"].enforce: "pre"
  }
Copy the code
  1. oneOf

Only one loader in oneOf will match. Note: You cannot have two configurations that handle the same type of file. If js files need to be processed by both babel-loader and eslint-loader, one of them needs to be extracted.

  module.exports = {
    module: {
      rules: {{test: /.js$/,
          loader: "eslint-loader".enforce: "pre"
        }
        oneOf: [{test: /.js$/,
            loader: "babel-loader"}]}}}Copy the code
  1. Commonly used loader

Webpack itself does not have a preprocessor, it only knows javascript, for other types of resources to use the corresponding precompiler to escape, into the form that Webpack can receive.

  • babel-loader

It is used to process the ES6 + code specification and compile it to ES5, because babel-loader needs to exclude node_modules, otherwise Babel will compile all of the modules, which seriously affects the packaging speed.

Babel-loader v8.x.x: Webpack works with Babel modules

@babel/core v7.x.x: Babel compiler core module

@babel/preset-env v7.x.x: Official Babel preset, which can be used to add the required configuration and compile ES6 code according to the user setting target

    {
      test: /.(jsx|js)$/,
      use:[
        {
          loader: "babel-loader".options: {
            cacheDirectory: true // Enable caching mechanism to repackage unchanged modules to prevent double compilation
            // A string path can be accepted as the cache path
            // If true, the path is node_modules/. Cache /babel-loader
            / / the demo}}}]Copy the code
  • .babelrc

The configuration items required by the.babelrc file are presets and plugins.

(1) Common presets:

  • @babel/preset-env
// is an intelligent preset that lets you use the latest JavaScript without micromanaging the syntactic conversions (and optional browser polyfill) required for your target environment. This makes your life easier and JavaScript packages smaller! Such as babel-loader + @babel/preset-env can convert the arrow function
/ / address: https://www.babeljs.cn/docs/babel-preset-env
["@babel/preset-env", {"modules": false,}],Copy the code
  • @babel/polyfill

I just need to introduce.

All compatibility issues can be resolved, and introducing all compatibility code will result in too much packaged code.

  • core-js

Solve all the introduction of compatibility processing, reduce the code problem set

You don’t have to introduce @babel/polyfill

  ["@babel/preset-env", {"modules": false.// Load on demand does not fully introduce compatibility
    "useBuiltIns": "usage".// Specify the corejs version
    "corejs": {
      "version": 3
    },
    // Specify compatible browser versions
    "targets": {
      "chrome": "60"."firefox": "60"."ie": "9"/ / etc.}}].Copy the code
  • @babel/preset-react
{
  / / including
// @babel/plugin-syntax-jsx
// @babel/plugin-transform-react-jsx
// @babel/plugin-transform-react-display-name

// Development environment configuration
// @babel/plugin-transform-react-jsx-self
// @babel/plugin-transform-react-jsx-source
  "presets": [
    "@babel/preset-react"]}Copy the code
  • @babel/preset-flow
  • @babel/preset-typescript

(2) Common plugins:

  • react-hot-loader
  • @babel/plugin-transform-runtime: dependency @babel/runtime needs to be installed at the same time

Function:

  1. Improve code reuse and reduce the size of compiled code.
  2. Prevent contamination global scope. (Enable corejs configuration)

Babel-polyfill pollutes the global space by adding global variables such as promises.

  • @babel/plugin-proposal-decorators

Class decorator: must be used with @babel/plugin-proposal-class-properties colleagues,

What is a class decorator? Javascript @istestable (true) class MyClass {} // Common @hot@withrouter @connect

@babel/plugin-proposal-class-properties decorators @ Babel /plugin-proposal-class-properties decorators @ Babel /plugin-proposal-class-properties [["@babel/plugin-proposal-decorators", // decorator {"legacy": True // True must be used in loose mode}], ["@babel/plugin-proposal-class-properties", {"loose": True // Babel is compiled using assignment expressions for class attributes instead of Object.defineProperty (which is more concise)}]]}Copy the code

Legacy If set to true, loose mode must be on

Javascript // loose true uses the assignment expression class Bork {static a = ‘foo’; static b;

x = 'bar';
y;
Copy the code

} // loose false var Bork = function Bork() { babelHelpers.classCallCheck(this, Bork); Object.defineProperty(this, “x”, { configurable: true, enumerable: true, writable: true, value: ‘bar’ }); Object.defineProperty(this, “y”, { configurable: true, enumerable: true, writable: true, value: void 0 }); };

Object.defineProperty(Bork, “a”, { configurable: true, enumerable: true, writable: true, value: ‘foo’ }); Object.defineProperty(Bork, “b”, { configurable: true, enumerable: true, writable: true, value: void 0 });

-@babel /plugin-syntax-dynamic-import supports dynamic import in code for example: route lazy loading, package generating asynchronous chunks, implementing splitChunks' 'javascript import()Copy the code
  • Overview:
{
  "presets": [["@babel/preset-env", {"modules": false}]."@babel/preset-react"]."plugins": [
    "react-hot-loader/babel"./ / hot update
    "@babel/plugin-transform-runtime"["@babel/plugin-proposal-decorators"./ / a decorator
      {
        "legacy": true}], ["@babel/plugin-proposal-class-properties",
      {
        "loose": true}]."@babel/plugin-syntax-dynamic-import".// Dynamic import support
    ["import", {
      "libraryName": "antd"."libraryDirectory": "es"."style": true // 'style: true' will load less files
    }] / / configuration antd]}Copy the code

5.2 style – loader CSS – loader

Css-loader: handling CSS style syntax, loading syntax (@import, URL ())

Style-loader: Inserts the style into the page. To be used in conjunction with csS-Loader and after CSS-Loader processing.

Style precompilation predicts downloading the corresponding loader

  • less: less less-loader
  • sass: sass-loader node-sass

5.3 ts – loader

Used to compile TS code

5.4 the file – loader

Used to package file type resources. And return publicPath

npm i file-loader --save

// You can use js to load images
{
  test: /\.(png|jpg|gif)$/,
  use: "file-loader"
}
import packageImage from 'big.png';
console.log(packageImage) // c6f482a9a192005e......... png
Copy the code
  • PublicPath: Resource import path.

The packaged image here is the name that JS will spell into the packaged image from behind publicPath.

  • File-loader can specify the path

Introducing rules:

PublicPath + path(loader support can be specified) + name

    output: {
      publicPath: path.join(__dirname, "build")},module: {
      rules: [{test: /\.(png|jpg|gif)$/,
          use: {
            loader: "file-loader".options: {
              name: "static/image/[name].[hash:8].[ext]".// name Specifies the path of the file name
              path: ' ' // Specify the file packaging path
              publicPath: 'static/image' // The calculated path of the resource will overwrite the path of the output}}}]}Copy the code

5.5 the url – loader

If the value of file-loader is greater than the limit value, file-loader returns the same publicPath.

If the value is less than limit, the base64 file is returned. This can effectively reduce the cost of requests

  • Url-loader cannot be configured with path. Therefore, the output path of file resource packaging greater than limit must be written in name.

5.6 antd

Antd internal style uses less preparation compiler

npm i antd less less-loader

Therefore, WebPack needs to configure the corresponding Less Loader

// .babelrc 
 ["import", {
   "libraryName": "antd"."libraryDirectory": "es"."style": "css" // 'style: true' will load less files
 }] / / configuration antd
// webpack.config.js
use: [
  "style-loader"."css-loader",
  {
    loader: "less-loader".options: {
      javascriptEnabled: true // Must be added}}]Copy the code

5.7 Loader Principle Analysis

Custom loader:

5.7.1 Basic implementation

Creating a Loader > NPM init -Y > Installing a custom Loader > Packing WebPack > Packing Result Loading result of the Loader

5.7.2 cache

When input and other dependencies do not change, the Loader uses the cache directly. Instead of repackaging, webpack controls this. Cacheable.

  module.exports = function() {
    if (this.cacheable) {
      this.cacheable() // Webpack built-in methods}}Copy the code

5.7.3 access options

npm i loader-utils

Get the Options object using loaderUtils

module.exports = function(content) {
  const options = loaderUtils.getOptions(this) | | {}console.log("options", options)
}
Copy the code

5. Code sharding

One of the most important aspects of implementing high-performance applications is to make sure that users load only the necessary resources at a time, and that resources with low priorities are loaded asynchronously. Webpack’s Code Splitting can split code into specific forms that users can load on demand.

  1. CommonWebpackPlugin(Webpack 4 below)

It can extract parts of multiple chunks, such as third-party libraries.

The benefits of this include:

1. Reduce repeated packaging in the development process and improve the development speed

2. Reduce the total resource volume

3. Sharded code makes more efficient use of client caching

module.exports = {
  ...
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: 'common'.// Specifies the name of the public chunk
      filename: 'common.js'.// The extracted resource name
      chunks: ["a"."b"].// Set the extraction range
      minChunks: 3.// At least three modules reference this module
      // import from "./utils"
      // The Infinity code is infinitely high and all modules will not be extracted
    })
    // Manifest extracts the Webpack runtime
    // To use the long-acting caching strategy, the package generates a manifest.js file. The changes in app.js affect the manifest.js file, which is a very small file. The previously extracted vendor and hash will not change.
    new webpack.optimize.CommonChunkPlugin({
      name: 'manifest'.// The extract of the manifest must come after, otherwise WebPack will not extract the module properly})]. }Copy the code

Disadvantages:

1. One CommonsChunkPlugin can only extract one vendor. If you want to extract more than one vendor, you need to configure multiple plug-ins and add repeated codes.

Manifest.js actually loads one more resource, affecting performance

The dependency relationship in the original Chunk will be destroyed when the 3.CommonsChunkPlugin extracts the common module.

  1. optimization.splitChunks(Wepback 4)

Webpack4 is recommended to replace CommonsChunkPlugin and has an id (0,1,2…) on each chunk. An asynchronous chunk has an ID instead of a name.

module.exports = {
  mode: 'production'.optimization: {
    splitChunks: {
      chunks: 'all' // If you specify all, it applies to all chunks. Otherwise, it applies only to asynchronous chunks by default.
      // Easy to extract all js file public module.}}}Copy the code

SplitChunks default extraction conditions:

1. After extracting, chunk is stored in node_modules directory.

2. After extraction, the JS file is larger than 30K, and the CSS file is larger than 50K. If it is too small, the optimization effect is not obvious

3. Load on demand in asynchronous chunks is less than or equal to 5. Each request consumes resources. If an NPM package is only needed in asynchronous chunks, the first screen does not need to be imported.

The number of resources to be extracted in parallel requests is less than or equal to the first load. 3. The default value is lower because the first screen requires higher performance.

  • Configuration:
splitChunks: {
  chunks: "async".// The asynchronous chunk is extracted by default
  minSize: { // Minimum volume after extraction
    javascript: 30000.style: 50000
  },
  maxSize: 0.minChunks: 1.// The minimum number of entries
  maxAsyncRequests: 5.// Maximum number of asynchronous chunk loaded resources
  maxInitialRequests: 3 // Maximum number of resources loaded on the first screen
  automaticNamedelimiter: '~'.// Name the split symbol
  name: true.// means that splitChunks are named for newly generated chunks based on cacheGroups and scope,
  // Delimit with automaticNamedelimiter
  cacheGroups: { // rules for separating chunks that you want to disable to false. The default is venders and default.
    vendor: { // Extract all node_modules modules,
      test: /[\\/]nodeModules[\\/]/,
      priority: -10 / / priority
    },
    default: { // applies to modules that are referenced multiple times
      minChunks: 2.// Minimum number of references
      priority: -20./ / priority
      reuseExestingChunks: true}}}Copy the code
  1. Asynchronous resource loading (asynchronous chunks)

3.1 The main official recommendation is the import() function. Unlike es6’s import function, modules loaded with import() and their dependencies are loaded asynchronously and return a Promise object.

3.2 Asynchronous Chunk has no name so use the chunk ID to define asynchronous chunkName in output.chunkfilename (see demo)

Sixth, style processing

Legacy issue: We used style-loader to generate style tags and insert them into HTML, not output CSS files separately. In general, in a production environment, we want styles to be introduced in separate CSS files using the link tag, not in the style tag, because the file is easier for clients to cache. (Only for the development environment)

Things to do:

  1. CSS is not placed in JS to prevent large JS files
  2. CSS extraction and compression
  3. CSS is introduced with compatibility in JS files
  1. Webpack (pre-4.0)

npm i extract-text-webpack-plugin --save

This plug-in is no longer recommended in webpack4

In the code [name] refers to chunkName

Fallback: Refers to the loader used when the plug-in cannot extract the style.

Use: use CSS-loader to precompile the style file before using plug-in extraction.

const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
module.exports = {
  module: {
    rules: [{test: /\.css$/,
        use: new ExtractTextWebpackPlugin({
          fallback: "style-loader".use: "css-loader"}})},plugins: [
    new ExtractTextWebpackPlugin("[name].css")]}Copy the code
  1. mini-css-extract-plugin

The mini-CSs-extract-Plugin has been officially recommended since WebPack 4.0. This is an upgraded version of the extract-Text-webpack-plugin with better performance and features than the latter.

(Previous webpack versions are not available).

Asynchronously loading CSS is supported. When loading CSS references in asynchronous chunks, in previous versions they were loaded synchronously with the link tag.

For example, suppose a.js loads B.js from import() and b.cs from b.js. Then the previous version using the plug-in will load the CSS synchronously. The mini-CSs-extract-plugin is packaged separately with a 0.css style file, which is introduced via the A.js dynamic insert link tag. (Here you can modify the development environment style package configuration to demonstrate)

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports ={
  module: {
    rules: [{test: /\.css$/,
        use: [
          loader: MiniCssExtractPlugin.loader,
          option: {
            publicPath: '.. /.. / '}]}]}plugins: [
    new MiniCssExtractPlugin({
      filename: 'static/css/[name].[contenthash:8].css'.chunkFilename: 'static/css/[id].[contenthash:8].css']}}),Copy the code

1) Fallback is not required

2)publicPath refers to the resource import path. Due to the asynchronous style introduced in JS, it needs to fall back two levels to the upper file.

3) You also need to configure the asynchronous resource name. The resource name is the same as the chunk name.

  • Note: In the development environment we are more concerned with packaging speed, so style extraction plugins are not applicable and style tags are inserted directly using style-loader.
  1. postcss-loader

CSS is a tool that uses plug-ins to convert CSS. There are many great plug-ins, such as Prefixer, CSSNext and cssModules. These features are implemented by the corresponding PostCSS plug-in. The use of PostCSS needs to be combined with Webpack.

1)Autoprefixer: Autoprefixer will prefix you with feature and attribute data based on the current browser support.

No browser differences have been fully compliant with W3C standards for css2.1 properties display, position, etc. Autoprefixer does not prefix them

The cSS3 property transform is prefixed, where –webkit is a chrome and Safari prefix, and — MS is an Ie prefix

module.exports = {
  module: {
    rules: [{test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            option: {
              publicPath: '.. /.. / '}}, {loader: "css-loader"
          },
          {
            loader: "postcss-loader".options: {
              ident: "postcss".plugins: () = > {
                // Help PostCSS find Browserslist in package.json: set CSS compatibility by configuring Browserslist
                require("postcss-preset-env"}}}]}}// package.json
{
  "browserslist": {
    "development": [
      "last 1 chrome version" // Compatible with a recent version of Chrome
      "last 1 firefox version" // Compatible with a recent Version of Firefox
      "last 1 safari version" // Compatible with a recent version of Safari
    ]
    "production": [
      "0.2%" >.// Greater than 99.8% of browsers
      "not dead".// Incompatible dead browsers are not put into use with IE10
      "not op_mini all" // Do not use all browsers of op_mini]}}Reference address: / / https://github.com/browserslist/browserslist
Copy the code
.app {
  display: none;
  position: relative;
  transform: rotate(45deg);
}
// -->> convert to
.app {
  display: none;
  position: relative;
  -webkit-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  transform: rotate(45deg);
}
Copy the code

2)Stylelint: CSS quality detection tool, like ESLint, you can add rules to it to unify the project code style.

3)CSS Module: no need to install additional modules, just need to csS-loader options: {

modules: true

}

4)CSS Next(interested to know): useful place is to use var() and calc() to calculate CSS property values, there are also @apply such as the application of large section of rule writing, can also take this to understand some new CSS draft features

:root {
  --fontSize: 1rem;
  --mainColor: #12345678;
  --centered: {
    display: flex;
    align-items: center;
    justify-content: center;
  }
}
body {
  color: var(--mainColor);
  font-size: var(--fontSize);
  line-height: calc(var(--fontSize) * 1.5);
  padding: calc((var(--fontSize) / 2) + 1px);
}
.centered {
  @apply --centered;
}

// -->> compile to
body {
  color: rgba(18.52.86.0.47059);
  font-size: 16px;
  font-size: 1rem;
  line-height: 24px;
  line-height: 1.5rem;
  padding: calc(0.5rem + 1px);
}
.centered {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
    -ms-flex-align: center;
      align-items: center;
  -webkit-box-pack: center;
    -ms-flex-pack: center;
      justify-content: center;
}
Copy the code

7. Production environment configuration

How to make users load resources faster in production environment, how to design into resource compression, how to add environment variables to optimize packaging, and how to maximize the use of resource cache. including

  1. Use of environment variables
  2. SourceMap use
  3. Resources compression
  4. Optimize hash and cache
  5. Dynamic HTML Generation
  1. Mode Configures environment variables

Webpackage 4 adds a mode configuration that accepts “production” and “development”.

The WebPack that does this automatically adds many configuration items suitable for production environments in order to reduce the complexity of manual configuration into more semantic configuration.

Webpack encourages less configuration to be written, not more.

  • If Mode: “production” is enabled, webpack automatically defines process.env.node_env = “production”; You can also manually set the environment variable, and then configure mode according to the environment variable.
Mode options describe The characteristics of
development Process.env.node_env is set to bit development and NamedChunksPlugin and NamedModulesPlugin are enabled will
production NODE_ENV for production, enabling flagDependencyUsagePlugin FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmmitOnErrorPlugin OccurrenceOrderPlugin, SideEffectsFlagPlugin and UglifyJsPlugin An environment that optimizes code to run online
  1. source map

Webpack’s compilation of source code can change the structure, location, and even file location of the code. So the source map is generated at each step of the process, and the file is passed along step by step until the final map file is generated. This file is suffixed with.map.

module.exports = {
  ...
    devtool: "source-map". }// When the bundle is generated, a comment is added to the file name to identify the map file.
// When we open developer tools, the map file is loaded simultaneously
// Map files are large, but browsers won't load them unless you open developer tools.
// Therefore, it has little impact on users.
// cheap: contains no column information
// module: simplifies loader sourcemap, supports Babel precompilation
// eval: Improve continuous build efficiency
Copy the code

There are many abbreviated versions of source :cheap-source-map, eval-source-map

Cheap-module-eval-source-map is recommended for use in development environments as a trade-off between speed of packaging and source information retrieval.

  • Security issues: The problem with Source-Map is that anyone can access the project’s source code through developer tools, which is a security issue.

There are two solutions for this WebPAC: 1. Hidden-source-map 2. Nosource-source-map.

Hidden-source-map: Webpack still packs out all map files, but the corresponding map file reference is not added to the JS file. So if you open the browser developer tools, you won’t see the map file, and the browser won’t be able to parse the bundle. If you want to trace the source code, you need to use some third-party services. As the Sentry. docs.sentry.io/

Nosource-source-map: You can hide the source code of the file, but you can still see the number of lines in the console panel. For finding errors can be basically satisfied, the security is higher than source-map.

In addition, you can use nginx to whitelist. Map files on the Intranet, so that the. Map files cannot be seen on the client.

  1. Resources compression

To publish resources to an online environment, we usually do code compression, or Uglify. The compressed code is basically very unreadable, reducing the packaging volume and improving the code security to a certain extent.

Terser-webpack-plugin is recommended

npm i terser-webpack-plugin

const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  ...
  mode: "production".// If production mode is configured, default compression mode is enabled
  optimization: {
    minimize: true.// You can override the default mode manually
    minimizer: [
      // Compress js to remove dead code
      new TerserPlugin({
        // To match.map
        test: /\.js(\? . *)? $/i,
        exclude: /\/excludes/.// Exclude certain directories
        // include: // applies to certain directories
        // cache // Whether to cache
        // parallel: Whether to allow simultaneous compression of multiple processes. Pass in a number to specify the number of processes
        // sourceMap: 
        // terserOption: {} You can set whether to rename variables, compatible with IE8, etc}}),]}... }Copy the code

Optimize – CSS -assets-webpack-plugin is recommended

npm i optimize-css-assets-webpack-plugin

The previous mini-CSS-extract-plugin extracted the styles and then compressed them using the optimized-CSS-assets-webpack-plugin, which is essentially cssnano

const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  ...
  mode: "production".// If production mode is configured, default compression mode is enabled
  optimization: {
    minimize: true.// You can override the default mode manually
    minimizer: [
      / / compress CSS
      // progecss
        new OptimizeCSSAssetsPlugin({
          // Compress the matched resources
          assetNameRegExp: /\.css$/g.// Compress the processor used by CSS. The default is CSsnano
          cssProcessor: require("cssnano"),
          // Processor configuration
          cssProcessorOptions: {
            preset: ['default', { discardComments: { removeAll: true}}]}})}),]}... }Copy the code
  1. The cache

Resources that the browser has acquired are cached, and the browser repeatedly uses the local cache to respond before they expire. When a resource is updated and we want all users to load the new resource, the best way to do this is to change the resource URL to force users to load the updated resource.

4.1 resources hash

  • Hash For example, [hash:10].js and [hash:10].css. Prevents the client from modifying the code if the browser does not request resources from the server again due to strong caching.
  • Chunkhash generates hash based on chunk, but some CSS is introduced in JS, so if you change the code of the JS file, the corresponding CSS file will also be repackaged, and the browser will reload the resource, so the browser cache is not properly utilized
  • [contenthash] Generates hash values based on files. Different files must have different hash values.

4.2 Babel Packaging cache is to optimize packaging time.

  1. Dynamic output HTML

Using the HTML-webpack-plugin (see demo)

  new HtmlWebpackPlugin({
    title: "My webpack".template: 'public/index.html'.inject: true.// Compress the HTML code configuration
    minify: {
      // Remove whitespace
      collapseWhitespace: true.// Remove comments
      removeComments: true
    }
    // favicon: "./favicon.ico"
  }),
Copy the code

Github.com/jantimon/ht…

  1. Bundle volume monitoring and analysis

To ensure a good user experience, bundle volumes can be continuously monitored to prevent unnecessary redundant modules from being added. Vs Code uses Import Cost to monitor the size of imported modules in real time.

The Webpack plugin webpack-bundle-Analyzer generates a map of the bundle’s module composition. The volume of each module is clear, and the resource volume is automatically monitored.

  const BundleAnalyzer = require("webpack-bundle-analyzer");
module.exports = {
  ...
  mode: "production".// If production mode is configured, default compression mode is enabled
  plugins: [
    new BundleAnalyzer()
  ]
  ...
}
Copy the code

We can also use this as part of an automated test to ensure that output resources are posted knowingly if they are overrun.

Package optimization and development environment configuration

8.1 HappyPack

8.2 noParse

8.3 the Tree Shaking

8.4 webpack – dashboard

8.5 HMR

  1. HappyPack

npm i happypack

This packaging can significantly reduce the packaging time:

(1) Obtain the package entry from the configuration file entry

(2) Match loader rules and compile the entry module

(3) The search relies on the recursive process of repeating step 2 for the new module

Webpack requires a step by step, single-threaded migration. A module may depend on several modules, which have no dependencies, and WebPack can only be escaped one by one. Happypack can start multiple threads and escape different modules to speed up local packaging.

const HappyPack = require("happypack")
module.exports = {
  // All loader configurations are written to the plug-in with the corresponding ID.module: {
    rules: [{test: /\.js$/,
        loader: 'happypack/loader? id=js'}},plugins: [
    new HappyPack({
      id: 'js'.loader: 'babel-loader'.options: {} // babel-options })]. }Copy the code
  1. noParse

Some libraries that we don’t want WebPack to parse can be ignored in module.noparse.

module.exports = {
  ...
  module: {
    noParse: /lodash/}... }Copy the code

Webpack will still pack LoDash, but it won’t do any parsing.

  1. tree shaking

Tree shaking does not work in development environments, but compression in production removes “dead code”.


// index.js
import { foo } from './utils.js';
foo();

// utils.js
export function foo() {
  console.log("foo");
} 
export function bar() {
  console.log("bar");
}

// package.json
"sideEffects": false
"sideEffects": ["*.css"."*.less"] // Mark some files not shaking by tree
/* All code has no side effects, tree shaking is possible: CSS, @babel, etc. */
Copy the code

Some library interfaces are exported using CommonJS for compatibility. Even though only one interface is referenced, the whole library is loaded. The size of the packaged bundles is not reduced because tree shaking only works for ES6 Modules. But the library is exported using CommonJS. So tree Shaking doesn’t work. Some NPM packages provide two export forms: ES6 Module and commonJS. If possible, use the exported modules of ES6.

[@babel/preset-env,{modules: false}] disables Babel’s module dependency resolution, otherwise tree shaking receives code converted to commonJS won’t work.

  • Treeshaking itself cannot remove dead code. Just mark dead code and remove it with the Terser-webpack-plugin.
  1. webpack-dashboard

Windows10 can only view the main log, do not support mouse scrolling (demo)

plugins:[
  // Development environment: The console has several panels to display different aspects of information. Log: webpack logs.
   // modules Specifies the module that participates in packaging. Problems: Errors and warnings during the packaging process
   isEnvDevelopment && new DashboardPlugin(),
]
Copy the code
  1. HMR hot replacement

Webpack-dev-server automatically rebuilds as soon as it detects a code change, and then initiates a web refresh. This is commonly called live-reload. Hot replacement takes the live-reload count a step further, allowing code to get the latest changes without refreshing the page, and we don’t even need to re-request to see what happens. Hot Module Replacement, HMR.

Webpack itself provides the HMR plug-in, but there are a number of unexpected issues that can occur during the trigger process, causing the module update application to behave differently from the normal load.

// WebPack has a built-in HMR solution
module.exports = {
  // All loader configurations are written to the plug-in with the corresponding ID.devServer: {...hot: true
  },
  plugins: [
    // Enable devServer HMR
    isEnvDevelopment && new webpack.HotModuleReplacementPlugin()
  ]
  ...
}
Copy the code

But HMR has many problems of its own, so many third-party solutions have sprung up in the community. For example, react-hot-loader handles hot updates for the React component.

npm i react-hot-loader

Let’s start by looking at which upper-layer configurations need to be changed:

// .babelrc 
"plugins": ["react-hot-loader/babel"]

// Make sure the react-hot-loader is loaded before react and react-dom
// webpack.config.js
resolve: {
  alias: {
    "react-dom": "@hot-loader/react-dom"}},entry: [
  isEnvDevelopment && 'react-hot-loader/patch'."./index.js".// paths.AppPaths
]
// The entry file is hot wrapped around the entire outer component
import {hot} from "react-hot-loader/root"
@hot
class App extends Component {
  render() {
    return (
      <div></div>)}}export default App;
// scirpt
"webpack-dev-server --hot"
Copy the code

At this point, a HRM is basically achieved. But there is still a drawback when we make changes in asyncChunk that we can’t hotreplace. Therefore, hot substitution in code splitting should be solved. If asynchronous hot substitution is needed, the chunk components of asynchronous chunk should be passed into the HOT function to ensure correct loading.

HMR principle:

  1. WDS maintains a WebPackSocket between the browser and WDS. When the local resource changes, WDS pushes the update event to the browser and brings the hash built this time for the client to compare with the last resource. Hash comparison prevents redundant updates.
  2. When the client knows the difference between the new build and the previous build, it issues a request like WDS to change the list of files, that is, which files have changed. Hto-update.json see demo.
  3. With the chunk that needs to be updated, the Hash client can proceed to send a request to WDS for the updated increment hot-update.js

PS:

  1. create-react-app
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw), // React-app internal plugin replaces %PUBLIC_URL% with the file in the public directory
Copy the code
  1. SizePlugins

By calculating the volume difference between two packages, the volume change can be detected as soon as possible.