Objective
Recently, an older company project has been optimized to deal with the main problem is webpack packaging file size too large.
Here's a little bit of experience with webpack packaging optimization.
Mainly divided into the following aspects:
- Remove the configuration from the development environment
- Extracttextplugin: Extracting styles to CSS files
- Webpack-bundle-analyzer:webpack packaging file volume and dependency visualization
- Commonschunkplugin: extracting common Module files
- Extract manifest: Let the extracted public JS hash value do not change
- Compress js,css, picture
- On-demand loading before React-router 4
- On-demand loading of react-router 4
This blog uses the Webpack plugin how to configure can go to see I wrote this blog:
Commonly used solutions for the "Webpack guide 02" webpack
These configurations are not detailed here.
Remove the configuration from the development environment
For example, Webpack in the Devtool to false, do not need to heat load such things only for the development environment.
This is not an optimization, but a mistake.
Things that are useful in a development environment are removed when packaged into a production environment.
Extracttextplugin: Extracting styles to CSS files
Extracts the style to a separate CSS file instead of embedding it in a packaged JS file.
This brings the benefits when the separated CSS and JS can be downloaded in parallel, so that the style and script can be loaded more quickly.
Solution:
Installing Extracttextplugin
npm i --save-dev extract-text-webpack-plugin
Then modify the Webpack.config.js to:
const ExtractTextPlugin = require('extract-text-webpack-plugin');module.exports = { // ... plugins: [ // ... new ExtractTextPlugin({ filename: '[name].[contenthash].css', allChunks: false }), ], module: { rules: [ { test: /\.css$/, exclude: /node_modules/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader?modules', 'postcss-loader'], }), }, { test: /\.css$/, include: /node_modules/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader', 'postcss-loader'], }), }, { test: /\.less$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader?modules', 'less-loader', 'postcss-loader'], }), }, ], },}
After packaging, generate the following files:
Webpack-bundle-analyzer:webpack packaging file volume and dependency visualization
This is not an optimization, but it allows us to see the output file volume and interaction of each package clearly.
Installation:
npm install --save-dev webpack-bundle-analyzer
Then modify the Webpack.config.js:
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');module.exports = merge(common, { // ... plugins: [ new BundleAnalyzerPlugin({ analyzerPort: 8919 }) ],});
A site with a port of 8919 will automatically appear after packaging, with the following site content:
You can see that some of the code in our packaged Main.js comes from modules in the Node_modules folder, some from the code that you wrote, that is, the code in the SRC folder.
In order to describe the convenience later, this figure we directly translate come to call Webpack packing analysis diagram.
Commonschunkplugin: extracting common Module files
The so-called universal module, such as React,react-dom,redux,axios almost every page will be applied to the JS module.
These JS modules are extracted into a file, not only can reduce the size of the main file, in the first download can be downloaded in parallel, improve the load efficiency, more importantly, the code of these files almost does not change, then each package released, will still follow the cache, which improves the load efficiency.
And for those multi-file portals, the application is more effective, because when loading different pages, this part of the code is public and can be applied directly from the cache.
This thing does not need to install, directly modify the Webpack configuration file can:
const webpack = require('webpack');module.exports = { entry: { main: ['babel-polyfill', './src/app.js'], vendor: [ 'react', 'react-dom', 'redux', 'react-router-dom', 'react-redux', 'redux-actions', 'axios' ] }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ names: ['vendor'], minChunks: Infinity, filename: 'common.bundle.[chunkhash].js', }) ]}
The packaged Webpack analysis diagram is:
It is obvious to see that react these modules are packaged into the Common.js.
Extract manifest: Let the extracted public JS hash value do not change
When we understand the hash value in Webpack, we will generally see the configuration of the hash value [hash] and [Chunkhash].
Where the hash is based on the content of each compilation, so every compilation of the file will generate a new hash, it is completely unable to take advantage of the cache.
So here we use [Chunkhash],chunkhash is generated based on content, so if the content doesn't change, the resulting hash value will not change.
Chunkhash is suitable for general situations, however, it is not applicable for the above conditions.
I'm going to change the main file code, and then the Chunkhash values of the two common JS codes are changed, and they don't use the main file.
So I used the text comparison tool to compare their code and found that only one line of code was different:
This is because Webpack has a runtime code with the module identity at execution time.
This code is packaged into the Main.js file when we do not extract the vendor package.
When we extract vendor to Common.js, the script is injected into Common.js, and the script is not in Main.js.
When we extracted the library file into two packages, common1.js and common2.js, we found that the script only appeared in a common1.js, and
That segment of the logo code becomes:
u.src=t.p+""+e+"."+{0:"9237ad6420af10443d7f",1:"be5ff93ec752c5169d4c"}
And then find that the other package header will have this code:
webpackJsonp([1],{2:functio
The code for this run-time script corresponds exactly to the number in the code that the other package started with.
We can extract this part of the code into a separate JS, so that the packaged public JS will not be affected.
We can configure the following:
plugins: [ new webpack.optimize.CommonsChunkPlugin({ names: ['vendor'], minChunks: Infinity, filename: 'common.bundle.[chunkhash].js', }), new webpack.optimize.CommonsChunkPlugin({ names: ['manifest'], filename: 'manifest.bundle.[chunkhash].js', }), new webpack.HashedModuleIdsPlugin() ]
For names, if Chunk is already defined in entry, the chunk file is extracted based on the entry in the entry. If there is no definition, such as mainifest, an empty chunk file is generated to extract all other chunk common code.
And the meaning of our code is to extract the public code that Webpack injected into the package.
Packaged files:
Webpack Packaging Analysis Diagram:
See that piece of green in the picture?
That thing is a packaged manifest file.
After this processing, when we re-modify the code in the main file, the generated public JS Chunkhash will not change, the change is the individual extracted from the manifest.bundle. [Chunkhash].js's Chunkhash.
Compress js,css, picture
This is not actually going to be recorded, because these general items should all be available, but here is a quote in passing.
Compress JS and CSS in one step:
webpack -p
Compression of pictures:
image-webpack-loader
For specific use, see 16th of the common solutions for Webpack.
On-demand loading before React-router 4
If you have used ant Design, you generally know that there is a feature that configures on-demand loading, which is to package only the component code that was used at the end of the package.
For general react components There is also a play that uses react-router to implement on-demand loading.
For each route, the code for the other route is not actually necessary, so when you switch to a route, the first screen loads much faster if you only load the code for that route.
First, configure the output in Webpack
output: { // ... chunkFilename: '[name].[chunkhash:5].chunk.js',},
You then need to change the react-router load to load on demand, for example, for the following code:
import React from 'react';import { createStore } from 'redux';import { Provider } from 'react-redux';import ReactDOM from 'react-dom';import { HashRouter as Router, Route } from 'react-router-dom';import PageMain from './components/pageMain';import PageSearch from './components/pageSearch';import PageReader from './components/pageReader';import reducer from './reducers';const store = createStore(reducer);const App = () => ( <Provider store={store}> <Router> <div> <Route exact path="/" component={PageMain} /> <Route path="/search" component={PageSearch} /> <Route path="/reader/:bookid/:link" component={PageReader} /> </div> </Router> </Provider>);
should read:
Import React from ' React ', import {createstore} from ' redux ', import {Provider} from ' React-redux ', import reactdom from ' React-dom '; import {Hashrouter as Router, Route} from ' React-router-dom '; import reducer from './reducers '; Const STORE = CreateStore (reducer); const Pagemain = (location, callback) + {require.ensure ([], require = {callback (NULL, R Equire ('./components/pagemain '). Default); }, ' Pagemain ');}; Const Pagesearch = (location, callback) = {Require.ensure ([], require + = {callback (null, require ('./component S/pagesearch '). Default); }, ' Pagesearch ');}; Const Pagereader = (location, callback) = {Require.ensure ([], require + = {callback (null, require ('./component S/pagereader '). Default); }, ' Pagereader ');}; Const APP = () = (<provider store={store}> <Router> <div> <route exact path= "/" Getcomponent={pagemain}/> <route path= "/search" Getcomponent={pagesearch}/> <Route path= "/Reader/:bookid/:link "Getcomponent={pagereader}/> </div> </Router> </Provider>);
On-demand loading of react-router 4
The method above applies to React-router 4, because the Getcomponent method has been removed.
Then I refer to the official tutorial method
Here we need to use Webpack, Babel-plugin-syntax-dynamic-import and react-loadable.
Webpack built dynamic loading, but we need to use Babel-plugin-syntax-dynamic-import to avoid doing some extra conversions because of the use of Babel.
So first you need
npm i babel-plugin-syntax-dynamic-import --save-dev
Then join the configuration in. BABELRC:
"plugins": [ "syntax-dynamic-import"]
Next we need to use the react-loadable, which is a high-order component for dynamically loading components.
This is an example of the official web.
import Loadable from 'react-loadable';import Loading from './my-loading-component';const LoadableComponent = Loadable({ loader: () => import('./my-component'), loading: Loading,});export default class App extends React.Component { render() { return <LoadableComponent/>; }}
It is not difficult to use, the Loadable function passes in a Parameter object and returns a render to the component on the interface.
The loader property of this parameter object is the component that needs to be loaded dynamically, and the loading property passes in a component that shows the loading state, and when the dynamic component is not loaded, the loading component is displayed on the interface.
The advantage of using this method in relation to the original is obvious, we are not only able to dynamically load on the route, we dynamically load the component granularity can be more fine, such as a clock component, rather than as before is often a page.
The flexibility to use dynamic loading can perfectly control the size of the loaded JS, so that the first screen load time and other page load times are controlled to a relatively balanced degree.
Here's a point to note, which is usually the problem we often get when using loading components: flicker.
The reason for this is that the loading page appears before the real component is loaded, but the component loads quickly, causing the loading page to appear short, causing flicker.
The workaround is to add a property delay
const LoadableComponent = Loadable({ loader: () => import('./my-component'), loading: Loading, delay: 200});
The loading component will only appear if the load time is greater than 200ms.
There are more ways to play react-loadable: https://www.npmjs.com/package/react-loadable
So now look at our packaging files:
Webpack Packaging Analysis Diagram:
Take a look at the packaging file name above and find that several files that are loaded on demand in this way are named after the numbers, not the component names we expect.
I looked up on GitHub for this project and found that the component-named method that it provided needed to be rendered on the server, and then did not go on.
Anyway, this thing is not very important, so there is no further drill, if there is a good way for the friends of the park, but also hope that in the comments to explain.
Summarize
Generally speaking, the above steps should be able to solve the vast majority of packaging files too large problem.
Of course, because of the differences between the Webpack version and the plugin version, there will be some differences in configuration and gameplay, but the directions described above are no problem, and we believe that the corresponding solution can be found in each version.
If there is any doubt in this article, please do not hesitate to enlighten me.
Webpack Packaging Experience-handling the problem of too large a package file size