Demo address

This article project code location: source code address

Why do you need a build tool

What does WebPack do?

  • Start the local service at development time
  • Solve the dependency problem of JS and CSS. (Often in the past, CSS didn’t work or a JS variable couldn’t be found because of order issues introduced)
  • Compile ES6, VUE/React, and JSX syntax into browser-aware code
  • Merge, compress and optimize the volume after packaging
  • CSS prefix complement/preprocessor
  • Use ESLint to validate code
  • Unit testing

Webpack configuration component

module.exports = {
  entry: ' '.// Specify the entry file
  output: ' '.// Specify the output directory and output file name
  mode: ' './ / environment
  module: {
    rules: [   / / a loader configuration
      {test: ' '.use: ' '}},plugins: [   // Plug-in configuration
    new xxxPlugin()
  ]
}
Copy the code

Basic common Loader

Why is loader needed?

Webpack native supports only JS and JSON module types, so loader needs to convert files of other types into valid modules and add them to the dependency graph.

Loader itself is a function that takes the source file as an argument and returns the result of the conversion

function loader instructions
Parsing es6 babel-loader Use with. Babelrc
Parsing the vue vue-loader
Parsing the CSS css-loader Use to load the.css file and convert it to a CommonJS object
style-loader Pass styles through<style>The label is inserted into the head
Parsing the less less-loader Convert less to CSS
Parse images and fonts file-loader For processing files (images, fonts)
url-loader It can also handle images and fonts, similar to file-loader, but it can also set smaller resources to automatically transfer to base64. Options: {limit: XXX}

The basic common Plugin

Plug-ins are used for bundle file optimization, resource management, and environment variable injection, and are used throughout the build process

  • Clean-webpack-plugin: Automatically clean up the dist directory before packaging
  • Html-webpack-plugin: Automatically generates HTML and inserts packed JS into it
  • Mini-css-extract-plugin: Extract CSS into a separate file, withstyle-loaderThe functions are mutually exclusive and cannot be used at the same time
  • About code compression:

The built-in Terser-webpack-plugin is enabled by default. Webpack automatically compresses THE compression of JS code and CSS files: Optimize – CSS-assets-webpack-plugin with CSSNano

 plugins: [
   new OptimizeCssAssetsPlugin({
     assetNameRegExp: /\.css$/g,
     cssProcessor: require('cssnano')})]Copy the code

HTML file compression: modify html-webpack-plugin, set compression parameters

plugins: [
  new HtmlWebpackPlugin({
    template: path.join(__dirname, 'src/index.html'),
    filename: 'index.html'.chunks: ['main'.'other'].// Which chunks to include
    inject: true.// Automatically inject chunks into HTML
    minify: { // Compress correlation
      html5: true.collapseWhitespace: true.// Compress whitespace characters
      preserveLineBreaks: false.minifyCSS: true.minifyJS: true.removeComments: true}})]Copy the code

Webpack performance optimization

Development environment performance optimization

Module hot replacement

By default, dev-server will be rebuilt to refresh the browser page whenever a file changes.

Module hot replacement: only repackage the changed modules, partially refresh, preserving the data state (rather than repackage all modules and refresh the page) to speed up the build and make development easier

Through devServer. Hot, its internal dependence webpack. HotModuleReplacementPlugin implementation, HotModuleReplacementPlugin in hot: true automatically was introduced, can not write

  • Style files: You can use the HMR function directly becausestyle-loaderInternally implemented
  • Js file: The HMR function cannot be used by default. You need to modify the JS code to add the code supporting the HMR function
if(module.hot){  // If the HMR function is enabled
	// Listen for changes in the xxx.js file. Once changes occur, other modules do not repackage and execute callback functions
	module.hot.accept('./xxx.js'.function(){ 
    	fn()
    })
}
Copy the code
  • HTML files: There are no hot replacements, nor hot updates, which can be opened by adding the HTML file path to the entry file, but are usually not necessary

Use the source – the map

Since webpacked code is a large JS file after various loaders and plugins have been converted, it cannot be debugged during development. Source-map is a technique that provides a source-to-build-code map that can be used to locate source code when an error is reported. Enabling mode:

module.exports = {
  devtool: 'source-map' 
}
Copy the code

Options: [the inline – | hidden – | eval -] [nosources -] [being – [module -]] source – the map

  • The source – the map: produce.mapFile that provides accurate information about the error code and the source code’s error location
  • Inline: inline, will.mapAs aDataURIEmbedded, not generated separately.mapFile, build faster
  • Eval: inline, usedevalWrap the module code, specifying the corresponding file of the module
  • Cheap: Accurate to rows only, not columns
  • The module: includesloadersourcemap

Recommended combination:

  • Development environment: fast and debug friendly

Eval-source-map (eval is the fastest and source-map debugging is the friendliest)

  • Production environment: Inlining makes code larger, so production environment does not use inlining

1. Consider whether to hide source code? Nosource-source-map — hidden all hidden hidden-source-map — only hidden source code, will prompt error message 2 after build code. Consider whether to be debug-friendly? source-map

Production environment performance optimization

Use file fingerprints for version control and caching

When you set a strong HTTP cache, for example, valid for one day, if you don’t use hash, when the file changes, the file name stays the same, so the client will still use the old cache. If a hash is used, the file name is changed and a new resource is requested, while the unchanged file continues to be cached

  • Hash: buildhash, which changes with each build and is not recommended
  • Chunkhash:webpackpackagedchunkRelevant, differententryIt’s going to be differentchunkhash
  • Contenthash: Defines hash based on the file contentcontenthashUnchanged, recommended incssUse on file

Js file fingerprint setting:

// Set filename for output using [chunkhash]
module.exports = {
  output: {
    filename: '[name][chunkhash:8].js'.path:__dirname+'/dist'}}Copy the code

Fingerprint Settings for CSS files:

Use the MiniCssExtractPlugin to pull CSS out of JS, then use the [contenthash]

plugins: [
  new MiniCssExtractPlugin({
    filename: '[name][contenthash:8].css'})]Copy the code

Add: The difference between Module, chunk, and bundle

  • A file in the source code is a module
  • Chunk: A chunk of an entry file that depends on onechunkCan be interpreted as oneentryCorresponds to achunk
  • Bundle: A packaged resource, usually onechunkThat corresponds to onebundleBut it is also possible to unpack a large one through some plug-inschunkSplit into multiplebundle, such asMiniCssExtractPlugin

tree shaking

Tree shaking: A module may have multiple methods, and as long as one of them is used, the whole file will be sent to the bundle. Tree shaking is simply pouring used methods into the bundle, while unused methods are erased during the Uglify phase.

Use: Webpack by default, set module: false in.babelrc

Tree Shaking is enabled by default in Production Mode. It must be es6 syntax, CJS is not supported

The tree shaking principle

DCE: code that is never used, such as introducing a method but not calling it or if(false){XXX}

Using the features of ES6 module:

  • Can only appear as a statement at the top level of a module
  • importCan only be string constants
  • import bindingisimmutablethe

Static analysis files before packaging, useless code is removed during the UGlify phase

code split

Unpacking a large bundle can be configured in cacheGroups

  • splitChunks
// splitChunks default configuration
optimization: {
    splitChunks: {
      chunks: 'all'.// Both synchronous and asynchronous import
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/.// Matches files in the node_modules directory
          priority: -10   // Priority configuration item
        },
        default: {
          minChunks: 2.// At least 2 times
          priority: -20.// Priority configuration item
          reuseExistingChunk: true}}}}Copy the code

In default Settings

  • willnode_mudulesThe modules in the folder are packed into a folder calledvendorsbundle
  • All modules that reference more than twice are assigned todefault bundleMedium, yespriorityTo set the priority.

DLL

Separate third-party libraries and business base packages into a single file, only for the first time, or when you need to update dependencies, and then you can only type your own source code each time, speeding up the build.

Method: The DLLPlugin is used for subcontracting, and the DllReferencePlugin references manifest.json

Subcontracting requires separate configuration files:

// webpack.dll.js
module.exports={
  entry: {
    lib: [
      'lodash'.'jquery']},output: {
    filename: '[name]_[chunkhash].dll.js'.path: path.join(__dirname, 'build/lib'),
    library: '[name]' // Global variable name exposed after packaging
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]'.// In manifest.json, the name must be the same as ouput.library
      path: path.join(__dirname, 'build/lib/manifest.json'),}})]Copy the code

Add commands to package.json to package DLLS separately:

"scripts": {
    "dll": "webpack --config webpack.dll.js"
  },
Copy the code

Manifest.json is referenced using the DllReferencePlugin to tell WebPack what dynamic link libraries are used without packing them

// webpack.prod.js
new webpack.DllReferencePlugin({
  manifest: require('./build/lib/manifest.json')}),Copy the code

Using addAssetHtmlWebpackPlugin put the DLL resources in HTML

// webpack.prod.js
new addAssetHtmlWebpackPlugin([
  {
    filepath: path.resolve(__dirname, './build/lib/*.dll.js'),
    outputPath: 'static'.// The output path after copying *.dll. Js, relative to the HTML file
    publicPath: 'static'}])Copy the code

Demo address: Webpack practice – DLL -plugin branch

Comparison before and after use:

usedllpluginBefore, the base library hitmain.jsAnd accounted for160kbAfter use:main.jsonly1.23 KB

Difference between splitChunks and DLLS

  • splitChunksIs unpacking at build time,dllBuild the base library in advance, so when you pack it, you don’t need to build the base librarydllsplitChunksA bit faster
  • dllYou need to configure one morewebpack.dll.config.jsAnd oncedllThe dependencies in the update, have to go twice to pack, thansplitChunksSome trouble
  • It is recommended to usesplitChunksTo extract commonality between pagesjsFile.DllPluginUsed for the separation of base packages (framework packages, business packages).

Multi-process packaging

Use thread-Loader to enable multi-process packaging and speed up packaging! Note: it takes around 600ms to start a process, interprocess communication is also expensive, and it is not worth the cost to start multiple processes on a small project, so use multiple processes only when the project is large and the packaging takes a long time.

module: {
  rules: [{test: /.js$/, 
      use: [
        {
          loader: 'thread-loader'.options: {
            workers: 2 // Start two processes}}, {loader: 'babel-loader'.options: {
            presets: ['@babel/preset-env'].cacheDirectory: true}}]},]}Copy the code

Multi-page packaging common configuration

Multi-page packaging requires multiple entry files, and multiple HTMLWebpackplugins generate multiple HTML. It is impossible to write many entries and HtmlWebpackPlugin by hand

Solution: Dynamically get entry and set the number of HTML-webpack-plugin

// Core method
const setMPA = () = > {
  const entry = {};
  const htmlWebpackPlugins = [];

  const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
  entryFiles.forEach(entryFile= > {
    const match = entryFile.match(/src\/(.*)\/index\.js/);
    const pageName = match && match[1];

    entry[pageName] = entryFile;
    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({ 
        template: path.join(__dirname, `src/${pageName}/index.html`),
        filename: `${pageName}.html`.chunks: [pageName], // Which chunks to include
        inject: true.// Automatically inject chunks into HTML
        minify: {
          html5: true.collapseWhitespace: true.preserveLineBreaks: false.minifyCSS: true.minifyJS: true.removeComments: false}}})))return {
    entry,
    htmlWebpackPlugins
  }
}

Copy the code

See the WebPack practice-MPA-Build branch for additional configurations