You haven't mastered the principle of webpack after so long?

Posted by downfall on Sun, 23 Jan 2022 03:48:09 +0100

1, Basic elements

1,Entry/Output

1.1. Single entry configuration

module.exports = {
  entry: './src/index.js', // Packaged entry file
  output: './dist/main.js', // Packaged output
};

1.2. Multi entry configuration

const path = require('path');

module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js',
  },
  output: {,
    filename: '[name].[hash].js', //You can choose to set hash by ensuring that the file name is unique through placeholders
    path: path.join(__dirname, 'dist'),
    // publicPath is used to set the baseUrl for loading static resources. For example, it points to cdn in prod mode and local service in dev mode
    publicPath: process.env.NODE_ENV === 'production' ? `//cdn.xxx.com` : '/',  // 
  },
};

2,Loaders

The Loaders function takes the file type as a parameter and returns the result of the conversion. At present, the two types supported by webpack are JS and JSON, and other types need to be converted

2.1. Universal Loaders

module:{
  rules:[
    {test:/.\(js|jsx|ts|tsx)$/,use:'ts-loader'} // For example, use ts loader
  ]
},

2.2. Inline Loaders

Loaders can also be used directly inline into code:

import 'style-loader!css-loader!less-loader!./style.less';

2.3. Multiple Loaders

The execution order among multiple Loaders is the opposite of the rules configuration, that is, from right to left

2.3.1,Source logic

loader comes in first and comes out last, and the corresponding stack out sequence is from right to left

if (matchResourceData === undefined) {
  for (const loader of loaders) allLoaders.push(loader);
  for (const loader of normalLoaders) allLoaders.push(loader);
} else {
  for (const loader of normalLoaders) allLoaders.push(loader);
  for (const loader of loaders) allLoaders.push(loader); // Push 
}
for (const loader of preLoaders) allLoaders.push(loader); // pre loaders stack
2.3.2 change order

Change the execution order by configuring enforce. Enforce has four enumeration values, and its execution order is pre, normal, inline and post

module:{
  rules:[
     {
        test:/\.less$/,
        loader:'less-loader',
        enforce:'pre' // Pretreatment
    },
    {
        test: /\.less$/,
        loader:'css-loader',
        enforce:'normal' // The default is normal
    },
    {
        test: /\.less$/,
        loader:'style-loader',
        enforce:'post' // Post processing
    },
  ]
},

3,Plugins

Plugins is responsible for optimizing bundle files, resource management and environment variable injection. webpack has many built-in plugins. For example, DefinePlugin global variable injection plug-in, IgnorePlugin exclude file plug-in, ProgressPlugin package progress bar plug-in, etc

plugins: [new HtmlwebpackPlugin({ template: './src/index.html' })];

4,Mode

There are three options for specifying the current build environment: production, development and none. When the mode is production, the built-in optimization plug-ins will be enabled, such as TreeShaking, ScopeHoisting, compression plug-in, etc

module.exports = {
  mode: 'production', // Is written to the environment variable NODE_ENV
};

You can also set it through the webpack cli parameter

webpack --mode=production  

2, Hot renewal

1. Update process

1.1. Startup phase 1 - > 2 - > A - > b

  • Compile JS files into bundles through webpack compile
  • Run the Bundle file on the Bundle Server so that the file can pass through the localhost://xxx visit
  • Then build the output bundle JS file to the browser

1.2. Hot update phase 1 - > 2 - > 3 - > 4

  • Webpack compile compiles JS files into bundles
  • Run the Bundle file on the HMR Server
  • Once the file in the disk is modified, the modified information will be output to the HMR Runtime
  • Then, the HMR Runtime partially updates the file changes

2. Configuration mode

2.1,WDS + HotMoudleReplacementPlugin

2.1.1,WDS(webpack-dev-server)

WDS provides the ability of bundle server. Instead of outputting files, it is placed in memory, that is, the generated bundle JS files can be localhost://xxx At the same time, it provides the liveload capability, so that the browser can refresh automatically

// package.json
"scripts":{
  "dev":"webpack-dev-server --open"
}
2.1.2. HotMoudleReplacementPlugin

The HotMoudleReplacementPlugin plug-in provides WDS with the ability of hot update, which comes from its HMR Runtime with the ability to locally update pages. Once the file in the disk is modified, the HMR Server sends the modified js module information to the HMR Runtime

// webpack.dev.js is only used in the development environment
module.exports = {
  mode: 'development',
  plugins: [new webpack.HotModuleReplacementPlugin()],
  devServer: {
    contentBase: './dist', //Service base directory
    hot: true, //Turn on hot update
  },
};
2.1.3 interaction logic

When listening to the file modification, HotMoudleReplacementPlugin will generate a mainifest and update file, in which mainifest describes the changed modules, followed by webpack dev server notifying the client of the update code through websocket, and the client uses jsonp to request the server to obtain the updated code

2.2,WDM(webpack-dev-middleware)

WDM transfers the file output from the webpack to the server, which is suitable for flexible customization scenarios

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

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

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

app.listen(3000, function () {
  console.log('listening on port 3000');
});

3, File fingerprint

The file fingerprint is mainly used for version management, which is expressed in the suffix of the file name after packaging, such as XXX / / xxx_ 51773db. 51773db in JS

1. Three types

type meaning
Hash It is related to the construction of the whole project. As long as the project file is modified, the hash value of the whole project construction will change
Chunkhash Related to the chunk packaged by webpack, different entries will generate different chunkcash values
Contenthash The hash is defined according to the file content. If the file content remains unchanged, the contenthash remains unchanged

2. Common scenes

  • Set the filename of output and use [chunkash]
filename: '[name][chunkhash:8].js';
  • Set the filename of MiniCssExtractPlugin and use [contenthash]
new MiniCssExtractPlugin({
  filename: `[name][contenthash:8].css`,
});
  • Set the name of the file loader and use [hash]
rules: [
  {
    test: /\.(png|svg|jpg|gif)$/,
    use: [
      {
        loader: 'file-loader',
        options: {
          name: 'img/[name][hash:8].[ext]',
        },
      },
    ],
  },
];
// Placeholder explanation: [name]: file name, [ext]: resource suffix

Note: hash is generated by code and path. Therefore, when the same code is packaged and deployed on multiple machines, the hash will be different, resulting in resource loading 404. Generally, it is packaged by one machine, distributed and deployed to different machines

4, SourceMap

1. Open configuration

The development environment is turned on and the online environment is turned off. When troubleshooting online, you can upload the source map to the error monitoring system

module.exports = {
  devtool: 'source-map',
};

2. Type

type explain
cheap-source-map No column number, only line number, fast
cheap-module-source-map The optimized soap source map avoids the mismatch of line numbers of compiled code such as babel
eval Determine the code path through the inline code eval function baseURL
eval-source-map The sourcemap is placed after the eval function
inline-source-map At the end of the packaged code

3. File format

The source string can be restored by using the mappings mapping table, names and sourcesContent

{
  "version": 3, // Source Map version
  "file": "out.js", // Output file (optional)
  "sourceRoot": "", // Source file root directory (optional)
  "sources": ["foo.js", "bar.js"], // Source file list
  "sourcesContent": [null, null], // Source content list (optional, in the same order as the source file list)
  "names": ["src", "maps", "are", "fun"], // List of symbol names used by mappings
  "mappings": "A,AAAB;;ABCDE;" // String with encoded mapping data
}

5, TreeShaking

  • The code will not be executed and cannot be reached
  • The results of code execution are not used
  • The code will only affect dead variables (write only but not read)

TreeShaking will eliminate the above code as obsolete in the uglify stage

When the mode is set to production, it is enabled by default. Through in Set modules:false in babelrc to cancel

TreeShaking uses the characteristics of ES6 module to clear

  • import can only appear as a statement at the top level of the module, and the module name can only be a string constant

The import module is statically loaded and obtains variable references, that is, when the module changes internally, the imported variables will also change. Therefore, import cannot appear in statements such as conditions and functions (similar to export), while require in commonjs obtains the cache of the module

  • import binding is immutable

6, Module mechanism

After the webpack is packaged, a layer of package will be added to the module, and the import will be converted into__ webpack_require

1. Anonymous closure

After the webpack is packaged, it is an anonymous closure. The received parameter modules is an array, and each item is a module initialization function. Pass__ webpack_require loads the module and returns modules exports,

Each module member of modules uses__ webpack_require__ installedModules is the cache of the loaded module. If it is already loaded__ webpack_require__ If it is loaded, it does not need to be loaded again.

2,ScopeHoisting

There are a large number of closure codes in the built code, resulting in the increase of function scopes created at runtime and large memory overhead. ScopeHoisting puts the codes of all modules in one function scope according to the reference order, and then renames some variables appropriately to prevent variable name conflicts, so as to reduce the function declaration code and memory overhead

7, SSR

The core of SEO friendly server rendering SSR is to reduce requests, so as to reduce white screen time. The implementation principle is: the server renders the React component into a string through the renderToString method of React DOM / server, and returns the template corresponding to the route. The assisted client generates components for the server through packaging

renderToString carries the data reactive attribute, which can be used with hydrate. It will reuse the previous nodes and only bind events, so as to optimize the first rendering speed. A similar method is rendertostatic markup

1. Compatibility issues

1.1. Browser global variables

  • node.js has no document and window, so it needs to be adapted through the packaging environment

In the react ssr application, reading document and window can be done in useEffect or componentDidMount. When nodejs is rendered, these executions will be skipped to avoid error reporting

  • Replace fetch and xhr with isomorphic fetch or axios

1.2 style problems

  • node.js cannot parse css. Ignore loader can be used to ignore css parsing

For the antd component library, set style to false in Babel plugin import

  • Replace style loader with isomorphic style loader

2. Cooperation at both ends

Use the packaged HTML as the template, and replace the placeholder after the server obtains the data

<body>
  <div id="root">
    <!--HTML_PLACEHOLDER-->
  </div>
  <!--INITIAL_DATA_PLACEHOLDER-->
</body>

8, Common optimization measures

1. Code compression

1.1 compression of JS files

  • Built in uglifyjs webpack plugin

  • Commons chunkplugin extracts the common modules in chunks to reduce the total volume

1.2 compression of CSS files

  • Use optimize CSS assets webpack plugin and cssnano

  • Extract text webpack plugin separates css from the product.

1.3. html file compression

html webpack plugin is usually used to define html templates. You can also set the compress minify parameter (set true automatically in production mode)

1.4 picture compression

Using image webpack loader

2. Auto clean build directory

Use CleanWebpackPlugin to automatically clean the output directory specified by output

3. Static resource inline

The first screen rendering style should be inline or styled components. Resource inlining can reduce the number of requests, avoid the flashing of the first screen page, conduct relevant reporting and management, and initialize scripts

3.1 code level

  • Raw loader: js/html inline
  • Style loader: css inline

3.2. Request level

  • URL loader: small picture or font inline

  • File loader: it can parse the url import path in the project, modify the file reference path after packaging, and point to the output file.

4. Base library separation

4.1,HtmlWebpackExternalsPlugin

Pass the base package through cdn instead of compressing it into the bundle

plugins: [
  new HtmlWebpackExternalsPlugin({
    externals: [
      {
        module: 'react',
        entry: '//11.url.cn/now/lib/15.1.0/react-with-addons.min.js?_bid=3123',
        global: 'React',
      },
    ],
  }),
];

4.2,SplitChunksPlugin

You can separate public scripts, base packages, and page public files

splitChunks:{
  chunks:'async',// async: separation of asynchronously imported libraries (default) initial: separation of synchronously imported libraries all: separation of all imported libraries (recommended)
  ...
  cacheGroups:{
    // 1. Common script separation
    vendors:{
      test:/[\\/]node_modules[\\/]/,
      priority:-10
    },
    // 2. Base package separation
    commons:{
      test:/(react|react-dom)/,
      name:'vendors',
      chunks:'all'
    },
    // 3. Page public file separation
    commons:{
      name:'commons',
      chunks:'all',
      minChunks:2
    }
  }
}

4.3 subcontracting

plugins: [
  // Subcontracting using DLLPlugin
  new webpack.DLLPlugin({
    name: '[name]',
    path: './build/library/[name].json',
  }),
  // DllReferencePlugin to manifest JSON reference
  new webpack.DllReferencePlugin({
    manifest: require('./build/library/manifest.json'),
  }),
];

5. Multi process and multi instance construction

Multi process and multi instance construction, in other words: each webpack parses a module and assigns it and its dependencies to worker threads, such as HappyPack and ThreadLoader

6. Cache

  • Enable cache: Babel loader, terser webpack plugin
  • Use cache loader, hard source webpack plugin

7. Narrow the building target and reduce the scope of file search

  • Reasonably configure the test of the loader and use include to narrow the file processing range of the loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/, // The tail supplementary $sign indicates tail matching
        use: ['babel-loader?cacheDirectory'], // Babel loader enables caching through the cacheDirectory option
        include: path.resolve(__dirname, 'src'), // Only the code in the src directory is processed, which greatly improves the compilation speed. (if there are uncompiled libraries under node_modules, it is not recommended to open them here)
      },
    ],
  },
};
  • Optimize the resolve configuration:
module.exports = {
  resolve: {
    modules: [path.resolve(__dirname, 'node_modules')], // The absolute path is used to indicate the storage location of the third-party module to reduce the search steps
    extensions: ['.js', '.json'], // extensions should be minimized to reduce the number of file searches
    noParse: [/\.min\.js$/], // noParse can ignore the dependency resolution of the module. The min.js file is generally packaged
  },
};

9, Maintainable webpack build configuration

1. Multiple profiles manage webpack configurations for different environments

1.1. Merge configuration through webpack merge

merge = require('webpack-merge');
module.exports = merge(baseConfig, devConfig);

2. Analysis of Web pack construction

2.1 log analysis

In package Add stats to the build statistics field of the JSON file

"scripts":{
  "build:stats":"webpack --env production --json > stats.json"
}

2.2 speed analysis

Use speedmeasure webpack plugin to analyze the total packaging time and the time consumption of each plug-in and loader

const speedMeasureWebpackPlugin = require("speed-measure-webpack-plugin")
const smp = new speedMeasureWebpackPlugin()
const webpackConfig = smp.wrap({
  plugins:[
    new MyPlugin()
    ...
  ]
})

2.3 volume analysis

Use bundleAnalyzerPlugin to analyze the file size of the dependent third-party module and the component code size in the business. After construction, it will be displayed on port 8888

const bundleAnalyzerPlugin = require('webpack-bundle-analyzer');
module.exports = {
  plugins: [
    new bundleAnalyzerPlugin({
      analyzerMode: 'server',
      analyzerHost: 'localhost',
      analyzerPort: 8888, // Port number
      reportFilename: 'report.html',
      defaultSizes: 'parsed',
      openAnalyzer: true,
      generateStatsFile: false, // Output to static file
      statsFilename: 'stats.json',
      statsOptions: null,
      logLevel: 'info',
    }),
  ],
};

2.4 compile time progress analysis

Using ProgressPlugin to analyze compilation progress and module processing details

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.ProgressPlugin({
      activeModules: false,
      entries: true,
      handler(percentage, message, ...args) {
        // Print real-time processing information
        console.info(percentage, message, ...args);
      },
      modules: true,
      modulesCount: 5000,
      profile: false,
      dependencies: true, // Displays the dependency count message in progress
      dependenciesCount: 10000,
      percentBy: null,
    }),
  ],
};

Topics: Javascript