Web pack independent packaging and caching

Time:2021-8-24

We will package files through webpack and separate corresponding configuration files according to different environments. However, we can’t be satisfied with packaging files. We should also think about how to package better files. For example, we all know that the browser has strong cache and negotiation cache. If the unchanged files can be effectively cached and the changed files can be updated in time, the performance can be improved.

Suggestion: there are similar instructions on the official website. It is recommended to read it on the official website. The official website provides Chinese switching options. The Chinese online documents have not been updated in time, and some writing methods have changed.

If there is something wrong, please point it out. Thank you. I also try to summarize and write such a question.

Analyze problems

If, we have a momentwebpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
    })
  ],
};

There are suchindex.js

import _ from 'lodash';

const component = function() {
  let el = document.createElement('div');
  el.innerHTML = _. Join (['webpack ',' basic configuration '],' ');
  el.onclick = print;
  return el;
}

const element = component();
document.body.appendChild(element);

implementnpm run buildAfter packaging, there are the following files:

dist
├── app.bundle.js  // 72.2 KiB
└── index.html // 206 bytes

Pay attention to the aboveapp.bundle.jsIt containslodash, ifapp.bundle.jsUpdated. Each time you reload, you need to reload the unchangedlodash, this is obviously a waste of requested resources. Therefore, two things need to be done here
First, separate the packaged files. Second, solve the cache problem

Detach package file

There are three common code separation methods:

  • Entry starting point: manually separate the code using the entry configuration.
  • Prevent duplication: use splitchunksplugin to remove and separate chunks.
  • Dynamic import: it is used to separate the code through the inline function call of the module.

The previous tutorial was throughwebpack.optimize.CommonsChunkPluginTo separate the packaged files, inwebpack4.xAt first, the official removed itcommonchunkPlug in, usingoptimizationProperty for more flexible configuration. To understand the difference between the two, you can refer toWebpack 4: evolution in legato

Next, let’s configure how to separate packaged files:

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  // ...
  entry: {
    common: ['lodash'],
    app: './src/index.js',
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          name: "common",
          chunks: "initial",
          minChunks: 2
        },
      },
    },
  },
  plugins: [
    New cleanwebpackplugin(), // this plug-in will be built many times in the subsequent process
    // ...
  ],
  // ...
}

View Directory:

dist
├── app.bundle.js //  1.69 KiB
├── common.bundle.js // 71.1 KiB 
└── index.html // 246 bytes

As you can see, included incommon.bundle.jsMediumlodashAlready fromapp.bundle.jsFrom their size, compared with the previous ones, it can also prove this.

Now, it’s solvedSeparate packagingProblem, butcacheThe problem has not been solved. From the directory packaged above, we notice that the file name is the same every time. For exampleapp.bundle.js。 In this way, the browser may cache the file. When the file is updated, the build will be executed. It will still generate a file with the same name, and the browser may be lostIt's not updated, the previously cached files are used.

Here’s a point. As for the above-mentioned fact that it has not been updated, my understanding is: if the validity period is unlimited, this may happen, which is related to the server no cache configuration. The solution discussed here is that no matter whether your backend is configured or not, I will generate a new file after changes, so as to ensure the update.

So let’s configure how to generate new files each time we build

[name].[hash].bundle.js

Use hereoutputMediumfilenameField, rename the file to generate a different file name for each build:

module.exports = {
  entry: { /*...*/ },
  output: {
    filename: '[name].[hash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: { /*...*/ },
  plugins: [ /*...*/ ],
};

The packaged files are as follows:

dist
├── app.d58e77cdc316b3ce1854.bundle.js // 1.69 KiB
├── common.d58e77cdc316b3ce1854.bundle.js // 71.1 KiB 
└── index.html // 288 bytes

It seems that this problem has been solved. Yes, it generates a hash, but if we modify itindex.jsIn, perform the build again:

dist
├── app.26935d7e055caac6c663.bundle.js // 1.69 KiB
├── common.26935d7e055caac6c663.bundle.js // 71.1 KiB 
└── index.html // 288 bytes

After observing the packaged files, we found that we only changed themindex.js, after packing,app.jsandcommo.jsThe file names have changed. This is not what we want. We just need to changeapp.jsThe file name has changed, and there is no changecommon.jsThe file name does not change, so you can take advantage of the cache.

[name].[chunkhash].bundle.js

Let’s change the configuration slightly[hash]Change to[chunkhash]According to the contents of the file, a string of hashes is generated( Note that in the development environment, chunkash is generally not used because it will increase the compilation time. Moreover, HMR will not work at this time)

module.exports = {
  // ...
  output: {
    filename: '[name].[chunkhash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  // ...
}

pack:

dist
├── app.6f86a1c3055fcbb2f569.bundle.js
├── common.ed707d7fd801788845a1.bundle.js
└── index.html

At this time, no effect can be seen, so it is necessary to modify itindex.js, packaging:

dist
├── app.afc86616f6e622a167b5.bundle.js
├── common.ed707d7fd801788845a1.bundle.js
└── index.html

Observation, discovery,app.jsThe file name has changed,common.jsThe file name hasn’t changed. It looks like it works.

Situation 1

If, we reverse the order of the files in and out:

module.exports = {
  entry: {
    app: './src/index.js',
    common: ['lodash'],
  },
}

Repackage:

dist
├── app.3c8b22be8868b0feb022.bundle.js
├── common.7310dc2ae1c70f3154f4.bundle.js
└── index.html

It is found that both file names have changed. Pawn!

Situation II

If, on the basis of case 1 (as long as the app entry is in front of the common entry, the effect is the same), inindex.jsIn, introduceprint.js

// index.js
// ...
import print from './print';
// ...

// print.js
import jquery from 'jquery';
export default function printMe() {
  console.log(jquery.name);
}

pack:

dist
├── app.0d03e2f9e94a947297c1.bundle.js
├── common.147b8278aca7b0bd1b4a.bundle.js
└── index.html

Found two file namesalsoEverything has changed. Pawn!

manifest

The above two cases are mainly due to manifest.

My understanding is that during the construction of webpack, the dependency is analyzed through the portal, and a similar interface is generated internallymanifestThis file records the mapping relationship between the module and the file.

In case 1, when the entry order is modified, themanifestThe table will change, and thismanifestWill be packaged into a file, so the first two file names will change;

The original analysis dependency iscommon.js -> index.jsNow becomeindex.js -> common.js

In case 2, the entry order remains unchanged. If you add a dependency or delete a dependency in the file in front of the entry, it will also causemanifestChanges, so the two file names change.

The original analysis dependency isindex.js -> common.jsNow becomeindex.js -> print.js -> common.js

Then what shall I do? There is a way tomanifestJust put forward the document. staywebpack4.xIt may have been throughwebpack.optimize.CommonsChunkPluginExtraction, as mentioned earlier, this plug-in has been killed.webpack4.xIs extracted by the following methods:

module.export = {
  // ...
  optimization: {
    // runtimeChunk: 'single',
    runtimeChunk: {
      name: 'manifest'
    }
    // ...
  }
  // ...
}

tip:

{
  Runtimechunk: 'single' // this is equivalent to the following. So the runtime file you generate is manifest

  runtimeChunk: {
    name: 'runtime'
  }
}

At this time, you go pack and thenSituation 1Situation IIDo it again and find that it will still change. What’s going on?

moduleIds

ExtractedmanifestThe file name will change becausemanifestIt is a mapping table (my immediate), in the form of a key value,[moduleIds] -> 'file'

By default, the number is incrementedmoduleIdssuch as[1] -> index.js [2] -> common.js

Although we extractmanifestHowever, when the order changes, the file corresponding to the number [1] also changes. Therefore, we need to makemoduleIdsAndfileAll fixed·

webpack4.xIt was through,NamedModulesPluginperhapsHashedModuleIdsPluginThe plug-in fixes the ID.

webpack4.xCan passoptimization.moduleIdsSetting, which can be set tonamed hashThe same effect can be achieved.

module.export = {
  // ...
  optimization: {
    runtimeChunk: {
      name: 'manifest'
    },
    moduleIds: 'named',
    // ...
  }
  // ...
}

At this time, operation case 1 and case 2 should meet our expectations.

Complete code

webpack.config.js

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js',
    common: ['lodash'],
  },
  output: {
    filename: '[name].[chunkhash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    // runtimeChunk: 'single',
    runtimeChunk: {
      name: 'manifest'
    },
    moduleIds: 'named',
    splitChunks: {
      // chunks: 'all',
      cacheGroups: {
        commons: {
          name: "common",
          chunks: "initial",
          minChunks: 2
        },
      },
    },
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
    }),
  ],
};

index.js

import _ from 'lodash';
import print from './print';

const component = function() {
  let el = document.createElement('div');
  el.innerHTML = _. Join (['webpack ',' basic configuration 11 '],' ');
  el.onclick = print;
  return el;
}

const element = component();
document.body.appendChild(element);

print.js

import jquery from 'jquery';
export default function printMe() {
  console.log(jquery.name);
}

Reference link

Advanced webpack — caching and independent packaging

Recommended Today

Java Engineer Interview Questions

The content covers: Java, mybatis, zookeeper, Dubbo, elasticsearch, memcached, redis, mysql, spring, spring boot, springcloud, rabbitmq, Kafka, Linux, etcMybatis interview questions1. What is mybatis?1. Mybatis is a semi ORM (object relational mapping) framework. It encapsulates JDBC internally. During development, you only need to pay attention to the SQL statement itself, and you don’t need to […]