The writer is from the Wall Street View technology team – Flower Shorts

A few days ago webpack author Tobias Koppers released a new article webPack 4.0 to 4.16: Did You Know? To summarize what changes and optimizations have been made since the release of WebPack 4. He said he was working on WebPack 5.

3. I’m sorry, what is blocking you? We are already working on webpack 5, so your stack might be outdated soon…

Translated into Chinese:

The new version of WebPack has improved a lot, and WebPack 4 has been released for more than five months, so I should have no problems, and I can feel at ease to upgrade according to the upgrade guide written by others. The reason I was so slow to upgrade was because I was scared of webpack 3 last year. It will completely fail the CommonsChunkPlugin in cases of code splitting. It took a while to repair it, and there were no tears.

So THIS time I waited almost half a year to upgrade to Webpack 4, but I still ran into a lot of problems! There are many problems left over from the past that have not been well resolved. But the main problem is the lack of its documentation, has been abolished things such as commonsChunkPlugin still appear everywhere in the official documentation, many important things are overwritten, or even not written, users need to look at the source code to solve.

As in v4.16.0 version also abolished the optimization. The occurrenceOrder, optimization. The namedChunks, optimization. HashedModuleIds, optimization. NamedMo The “dules” configuration items are replaced with “optimization.moduleids” and “optimization.chunkids”, but none of them are in the documentation, so it will have no effect if you follow the documentation in the new version.

The most recent and complete documentation is the webpackoptions. json configuration for his project. It is highly recommended to look at this configuration item if it is not clear, because it is guaranteed to be synchronized with the latest code.

So much teasing, let’s get down to business. Due to the length of this hand touch hand, so disassembled into two articles:

  • Part 1 — is the ordinary inwebpack 3Based on the upgrade, what operations to do and what pits encountered
  • Next — is inwebpack 4How to pack and unpack properly, and how to maximize uselong term caching

This article does not teach webPack configuration by hand, so it will not cover too many basic configuration issues. For example, how to handle CSS files, how to configure webpack-dev-server, the difference between file-loader and url-loader, etc., are recommended to see the official documentation or surviveJS tutorials. Or I recommend another introduction to wbepack that has been synchronized to the webpack4 portal.

Upgrade article

preface

I always believe that imitation and reference are the most efficient ways to learn something new. So I recommend borrowing some mature WebPack configurations. For example, if your project is based on the React ecosystem, use create-React -app. Download NPM Run eject to see its detailed webpack configuration. Vue cli does not support eject, and uses webpack-chain to configure, so it may not be convenient to reference, mainly configure the address. You can use the vue-element-admin configuration directly if you don’t like it. Or if you want to do it yourself, you can use webPack’s official examples to assemble your configuration.

Upgrade webpack

After upgrading webpack to 4 first, running Webpack — XXX directly won’t work because the new version takes the command line stuff apart and wraps it into WebPack-CLI. The following error is reported:

The CLI moved into a separate package: webpack-cli. Please install webpack-cli in addition to webpack itself to use the CLI.

All you need to install NPM install webpack-cli -d -s. You can also install it globally.

Also, the new webPack requires a minimum support version of Node.js of 6.11.5. Don’t forget to upgrade. If you still need to maintain old projects, you can use NVM to do node versioning.

Upgrade all dependencies

Because Webpack4 has changed its hook API, all loaders and plugins need to be updated to fit.

You can use the command line NPM outdated to list all packages that can be updated. So you don’t have to go through NPM one by one to find the corresponding available version.

Update devDependencies to add new dependencies.

Changes brought about

In fact, this upgrade brought a lot of changes, but most of them were not needed for ordinary users. For example, SideEffects, Module Type’s Introduction and WebAssembly Support Introduced by this upgrade were basically not needed at ordinary times. We focus on changes that affect us the most, such as: Optimization.splitchunks instead of CommonsChunkPlugin(more on that in the next article) and Better defaults-mode for Better Defaults are some of the things you need to pay a little attention to.

If you want to learn more about Tree Shaking and SideEffects visible at the end of the article read more. In the figure above, refer to Webpack 4 for advancement

The default configuration

Webpack 4 introduced the concept of zero configuration, stimulated by Parcel. Whatever the effect, the change is laudable.

Recently, there’s a new Fastpack to keep an eye on.

Anyway, what does webPack default do for us?

In development mode, NamedChunksPlugin and NamedModulesPlugin are enabled by default to facilitate debugging, providing more complete error information and faster recompilation speed.

module.exports = {
+ mode: 'development'
- devtool: 'eval',
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.NamedChunksPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
-]
}
Copy the code

In production mode, the code will be automatically divided, compressed, and optimized with minimal configuration due to splitChunks and disarrays, and the Webpack will also help you Scope and tree-shaking automatically.

module.exports = {
+ mode: 'production',
- plugins: [
- new UglifyJsPlugin(/* ... * /),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
- new webpack.optimize.ModuleConcatenationPlugin(),
- new webpack.NoEmitOnErrorsPlugin()
-]
}
Copy the code

Webpack has always been criticized for its extremely high configuration threshold, which is extremely complex and tedious, making it easy for people to start and give up. However, its up-and-comers such as Rollup and Parcel have greatly optimized the configuration process to be ready out of the box. So WebPack 4 has learned a lot from this experience to improve its configuration efficiency. May the world no longer need webpack configuration engineers.

html-webpack-plugin

Using the latest version of the HTMl-webpack-plugin you may also encounter the following errors:

throw new Error('Cyclic dependency' + nodeRep)

This bug is caused by circular reference dependencies and can be ignored if you do not have this problem.

The current solution can use the Alpha version, NPM I –save-dev html-webpack-plugin@next

Or add chunksSortMode: ‘None’.

But a closer look at the documentation shows that setting chunksSortMode: ‘None’ is problematic.

Allows to control how chunks should be sorted before they are included to the HTML.

This property determines the order in which chunks are loaded. If set to None, the order in which chunks are loaded on the page is not guaranteed and styles may be overwritten. For example, I changed the style of a third-party library, element-UI, in app.css to override it by loading order, but since I set it to None, the package would look like this:

<link href="/app.8945fbfc.css" rel="stylesheet">
<link href="/chunk-elementUI.2db88087.css" rel="stylesheet">
Copy the code

App.css is loaded first, and the style overrides written before will be invalid unless you overwrite it with important or some other CSS weight, which is obviously not reasonable. Vue-cli also happens to have this related issue, yuxi hack it without using @Next version. If you are interested, you can study it by yourself. I directly use @Next version in the project. I didn’t encounter any other issues (other than incompatible Webpack prefetch/ Preload related issues). You can do either. You can choose.

The configuration of the other HTMl-webpack-plugin is the same as before.

mini-css-extract-plugin

With extract – text – webpack – the plugin

The extract-text-webpack-plugin that we’ve been using for a long time has done its job, thanks to webPack4’s improved CSS module support and tweaks to the way CSS files are extracted. Will give way to the Mini-CSS-extract-plugin.

The way of use is also very simple, you can look at the document copy.

The biggest difference between the extract-text-webpack-plugin and the extract-text-webpack-plugin is that when code spliting, the CSS of each JS chunk bundle will be written inline and split into separate CSS files.

CSS was originally inline in js files like this:

The biggest advantage of unpacking CSS is that changes to JS and CSS do not affect each other. For example, if I change the JS file, it will not invalidate the CSS file cache. And now it automatically works with the optimization.splitchunks configuration to split CSS files. For example, if I configure element-UI as a separate bundle, it will automatically package its styles into a separate CSS file. It will not pack all third-party CSS into a dozens or even hundreds of KB app.xxx. CSS file by default.

Compression and optimization

After packing the CSS and looking at the source code, we found that it didn’t help us compress the code, so we need to use the optimization-CSS-assets-webpack-plugin, which not only compresses the CSS but also optimizes your code.

/ / configuration
optimization: {
  minimizer: [new OptimizeCSSAssetsPlugin()];
}
Copy the code

As shown in the test case above, since the optimization-csS-assets-webpack-plugin plugin uses CSsnano by default for CSS optimization, Margin: 10px 20px 10px 20px margin: 10px 20px 10px 20px =>margin:10px 20px; . It also greatly reduces the file size of your CSS. See the documentation for more details on optimizations.

contenthash

However, there is a special requirement to use the MiniCssExtractPlugin. In the default document, it is configured like this:

new MiniCssExtractPlugin({
  // Options similar to the same options in webpackOptions.output
  // both options are optional
  filename: devMode ? "[name].css" : "[name].[hash].css".chunkFilename: devMode ? "[id].css" : "[id].[hash].css"
});
Copy the code

To be brief: filename is a generated filename that is introduced in your entry file, and chunkname is a file that is not introduced in the entry file, but is introduced by loading (asynchronous) modules on demand.

After the use of copy as above code found that the situation is wrong! Every time a xx.js file is changed, its CSS is unchanged, but the file hash is still changed. A closer comparison revealed that hash was to blame. 6. F3bfa3af. CSS = > 6.40 bc56f6. The CSS

But I wrote it according to the official document! Why is there a problem! Later in the document at the bottom of the very bottom sent such a paragraph of words!

For long term caching use filename: [contenthash].css. Optionally add [name].

Maintainers is also used in the Maintainers section. Wouldn’t it be better to have a default configuration message? Some enthusiasts have already opened a PR that defaults to contenthash. Chunkhash => Contenthash related issue.

This is really quite excessive, if you are not careful, you will invalidate your CSS file cache. And many users don’t care about the changes in the dist folder that they end up packing when they make code changes. So the problem may have been there forever. What a waste of resources! People are hard! It’s not unreasonable to find WebPack difficult to use.

A sidebar: The MiniCssExtractPlugin isn’t perfect either. It now defaults to splitting each bundle’s CSS into a single CSS file, separate from the JS file. But that creates a new problem. For example, IF I have a page that has only one line of CSS, it will be split into a single CSS file and require an additional HTTP request, which makes no sense. Therefore, I proposed an issue to the official, calling for adding a minSize. When the CSS content is smaller than this size, it is still inlined into the JS file, expecting the official to add this function.

Here’s a quick explanation of the differences between the hashes:

  • hash

The hash is related to each build, and without any changes, it will compile the same hash every time, but when you change anything, its hash will change.

Simply put, you change anything and hash will be different from the last time.

  • chunkhash

Chunkhash is computed based on the contents of each module file including its dependencies, so changes to one file will only affect its own hash, not other files.

  • contenthash

It appears mainly to solve, so that CSS files are not affected by JS files. For example, foo.css is referenced by foo.js, so they share the same chunkhash value. If foo.js changes the code, the CSS file will change its hash, even if the content of the file does not change.

At this point we can use contenthash to ensure that even if anything changes in the CSS file’s module, as long as the CSS file’s contents remain unchanged, its hash will not change.

Contenthash is simply a hash generated by moduleId + Content.

Thermal renewal rate

In fact, I am more concerned with the local development hot update speed than the webPack online packaging speed, after all, this is the real thing that every programmer deals with every day, packaging is usually thrown to CI automatic execution, and the average project is not packed many times a day.

Webpack 4 always say oneself to make better use of the cache can improve the compilation speed, but the measured found that there is a promotion, but when you have a project, after routing more lazy loading page, after 50 +, slow hot update will be very obvious, the problem of previous article also mentioned the problem, thought that a new version of the commission to solve this problem, but did not.

However, you should first exclude your hot update slow is not, such as:

  • Not using proper Devtool souce map
  • Not properly usedexclude/includeThe example that does not need to be processed is processednode_modules
  • Do not compress code in a development environmentUglifyJs, extract CSS, Babel Polyfill, compute file hash and other unnecessary operations

The old plan

The earliest solution is not to use routing lazy load in the development environment, only use online environment. Encapsulate a _import function to distinguish lazy loading from environment changes.

Development environment:

module.exports = file= > require("@/views/" + file + ".vue").default;
Copy the code

Generation environment:

module.exports = file= >() = >import("@/views/" + file + ".vue");
Copy the code

However, due to the implementation mechanism of Webpack import, there are certain side effects. This will result in all.vue files under @/views/ being packaged. Whether or not you are referenced by dependencies, you will probably never need js code. Related issue

The new solution is still the same: only use route lazy loading in build mode, not local development. But in a way that doesn’t have any side effects.

A new scheme

Use plugins babel-plugin-dynamic-import-node with Babel. It does only one thing: convert all import() to require(), so that all asynchronous components are introduced synchronously with the plugin, and the BABEL_ENV bebel environment variable is used to make it only available in the development environment. Convert all import() to require() in the development environment. This solution solves the problem of repeated packaging, and is also less intrusive to the code. When you usually write routes, you just need to follow the lazy loading mode of the official document route, and leave the rest to Babel. Just remove it from Babel’s plugins.

Specific code:

Start by adding BABEL_ENV to package.json

"dev": "BABEL_ENV=development webpack-dev-server XXXX"
Copy the code

Then in. Babelrc you can only add the babel-plugin-dynamic-import-node plugins and make them work only in development mode.

{
  "env": {
    "development": {
      "plugins": ["dynamic-import-node"]}}}Copy the code

Then you’re done. Just write the route as usual. The document

 { path: '/login'.component: (a)= > import('@/views/login/index')}
Copy the code

This will greatly speed up your hot updates. Basic 200 plus pages can also be completed in 2000ms hot and new, basically do no sense of refresh. Of course, your project itself is small and not many pages, there is no need to do this. Think about it when your page changes faster than you can write code.

Packing speed

Webpack 4 has been tested in projects to improve packaging speed by 20% to 30% in general.

This article won’t go into too much depth on this topic, but check out the Slack team’s article on the speed of packaging optimization, nuggets and translation.

Here are a few tips to help you speed up your Webpack.

First you need to know where you are currently packing slowly.

We can use the speed-measure-webpack-plugin, which can monitor the time of each webpack operation. The diagram below:

You can see that most of the packaging time is spent in Uglifyjs compression code. Similar to the previous promotion hot update entry point, check the correct source map, exclude/include, and so on.

When using the new UglifyJsPlugin, remember to add cache: true and parall: true to speed up the compression of code. For more configurations, refer to the documentation or vue-CLI configuration.

Another reason compilation is slow is because of third-party libraries. Echarts and Element-UI, for example, are huge, and echarts packs 775KB. So if you want to make compilation much faster, you can externalize these third-party libraries, import them as scripts, or package them as DLLS. It has been tested that packages as large as Echarts can save anywhere from tens to tens of seconds.

There are also libraries that can execute Webpack in parallel: Parallel-Webpack, happypack.

Upgrade, by the waynodeThere could be surprises. Shortly before theCIThe node version inside depends on the6.9.2= >8.11.3, the packaging speed directly increased by more than a minute.

In short, I think the packaging time can be controlled in the same range, there is no need to optimize too much. You may have done a lot of research, changed a bunch of parameters and found that it actually increased the cost of maintenance by a few seconds. Upgrade Node, upgrade Webpack, and upgrade the hardware of your build environment.

For example, the CI of our company uses the common 8-core 16G machine of Tencent Cloud. This project is also a huge background management project of 200+ pages, which references many third-party libraries, but does not use happypack or DLL, but only the latest version of WebPack4, [email protected]. The compilation speed was stable at over two minutes, and there was no need to optimize.

Tree-Shaking

This is not a new concept that was introduced in WebPack 4, it was first introduced and implemented by Rollup and later implemented in WebPack 2, In Webpack 4, we just added JSON Tree Shaking and sideEffects to make Shaking better.

It is important to note that webpack is tree-shaking by default, but it may not work in your projects because of Babel.

Since Tree Shaking is based on static detection of ES6 modules to find unused code, if you use Babel, for example: Babel-preset -env, which packages modules as CommonJS by default, makes Tree Shaking invalid.

It actually supports modular processing itself after WebPack 2. So just make Babel not transform modules. The configuration is as follows:

// .babelrc
{
  "presets": [["env", {
      modules: false. }}]]Copy the code

By the way, please don’t use babel-preset- ESxxxx again, please use babel-preset-env, see you in this article, babel-preset-2015.

The next part

  • Using webpack4 with proper posture

Develop reading

  • Getting started with Webpack 4 and single-page applications
  • How do I use sideEffects in Webpack?
  • Your tree-shaking is not good for eggs
  • A joke on webpack documentation
  • Tree-Shaking Performance Optimization Practices – Principles
  • Goodbye, Babel – preset – 2015