takeaway
After learning the first three sections, in fact, we will have some packaging of file resources, but these are far from enough in large projects, so what problems will we encounter in the usual configuration?
What can I learn
By studying in this section, you can learn
- Package files as needed
- Distinguish the online environment from the development environment configuration
- Webpack and Code Splitting
- SplitChunksPlugin Details about configuration parameters
- Lazy loading/chunk
- Package analysis process
- Webpack and browser caching issues
- CSS segmentation
- Browser cache
- Shimming
- The environment variable
The specific application
- Package the imported code as needed according to import, avoiding the whole package of the form file. What do you package when I import it
- Configure the configuration of online and development environment according to their own requirements, split the common configuration code, and use custom commands to package the code in one click
- Code splitting, synchronous loading and asynchronous loading configuration
- SplitChunksPlugin Common configurations
- Lazy loading examples with chunk introduction
- Simple packing analysis, preloading,prefetching
- Instead of obfuscating and packaging CSS, create a CSS folder in the dist directory and package it
- Js file cache problem in browser, package solution
- Shimming role
- Configuration and use of environment variables
Tree Shaking
To recap, if we set “useBuiltIns”: “usage” in preset-env, it’s actually ok not to introduce Babel /polyfill. Since we’re using useBuiltIns, it will automatically import for us, so we can just write es6 syntax for this section.
Create a new math.js, then we’ll introduce it in M.js, modify the packaging configuration file yourself, and if you don’t already click 3 minutes to learn about Webapck
export const add = (a, b) => {
return a + b
}
export const minus = (a, b) => {
return a - b
}
// m.js
import { add } from './math'
console.log(add(1, 3))
Copy the code
At this point we did, but in the package file, I completely packaged my Math file. In this case, I only introduced the Add method, so I expect it to only package the files I introduced. So you can do the following configuration in package.json.
"sideEffects": fasle
Copy the code
It is important to note that this environment is useful only online, because it is easy to debug during development.
Distinguish the online environment from the development environment configuration
Why are we doing this
Every time before packaging (online, development) code, we have to constantly modify the files in webpack.config.js, such as Modo, plug-ins, etc., this operation is very troublesome, and we can not 100% guarantee that we will not change the wrong files, after all, the impact of changing the wrong files is very big. Let’s take a look at how to differentiate online and development environment configurations.
Split dev and prod files
We had a webpack.config.js, we renamed it webpack.dev.js, and then made a copy and renamed it webpack.prod.js. Then update the following two files as needed.
// webpack.dev.js
const path = require('path'); // Import path from nodejs const htmlPlugin = require('html-webpack-plugin'); Const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const webpack = require('webpack'// exports of webpack plugins module.exports = {mode:'development',
devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/m.js',
},
devServer: {
contentBase: './dist'// Start the server with webpack. The root directory is the packaged dist folder open:trueNPM run start automatically opens browser proxy: {// Configure proxy'/api': 'http://localhost:3000'}, port: 8080, // configure port number hot:true// enable hot update //hotOnly:true}, module: {// module package configuration //... }, plugins: [new htmlPlugin({template:'./index.html'}), new CleanWebpackPlugin (), new webpack. HotModuleReplacementPlugin () / / introduce plug-in], the output: {publicPath:'/',
filename: 'dist.js'Path: path.resolve(__dirname,'dist'}} // webpack.prod.js const path = require()'path'); // Import path from nodejs const htmlPlugin = require('html-webpack-plugin'); Const cleanPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map',
entry: {
main: './src/m.js'}, module: {// module package configuration //... }, plugins: [ new htmlPlugin({ template:'./index.html'
}),
new cleanPlugin(['dist']),
],
output: {
publicPath: '/',
filename: 'dist.js'Path: path.resolve(__dirname,'dist'), // package to dist folder}}Copy the code
Ok, the file is split and we will change the scripts in package.json at this point
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js"."build": "webpack --config webpack.prod.js"
},
Copy the code
Restart the service and the package file runs normally. So we can distinguish them, but there is an obvious problem that these two files have too much overlap. If I add a common code in the future, both files will have to be added and deleted. This would make my maintenance more expensive and still increase the chance of errors, so it would be necessary to merge the configuration files.
Merge common configuration files
Start by downloading Webapck-Merge, which will help you merge webPack configurations
npm install webpack-merge -D
Copy the code
Create webpack.common.js to merge the code
- Like entry, extract it out.
- Module, extract.
- Plugins have two common plug-ins, which are presented.
- Output as well as output
// webpack.common.js
const path = require('path'); // Import path from nodejs const htmlPlugin = require('html-webpack-plugin'); Const cleanPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/m.js'}, module: {// module package configuration //... }, plugins: [new htmlPlugin({template:'./index.html'
}),
new cleanPlugin(['dist']),
],
output: {
publicPath: '/',
filename: 'dist.js'Path: path.resolve(__dirname,'dist'}} // webpc.dev. js const webpack = require()'webpack'Const webpackMerge = require()'webpack-merge')
const commonConfig = require('./webpack.common')
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist'// Start the server with webpack. The root directory is the packaged dist folder open:trueNPM run start automatically opens browser proxy: {// Configure proxy'/api': 'http://localhost:3000'}, port: 8080, // configure port number hot:true// enable hot update //hotOnly:true}, plugins: [new webpack HotModuleReplacementPlugin () / / introduce plug-in]} module. Exports = webpackMerge (commonConfig, devConfig) // webpack.prod.js const webapckMerge = require('webpack-merge')
const commonConfig = require('./webpack.common')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports = webapckMerge(commonConfig, prodConfig)
Copy the code
NPM run dev, ok nice.
Code Splitting
Code splitting, I’ll just give you an example.
Lodash.js is often used when using large frameworks such as Vue, assuming that we download and use the changes normally.
file | The size of the |
---|---|
lodash.js | 1MB |
axin.js | 1MB |
// axin.js
import _ form 'lodash'/ / use lodashCopy the code
In this case, assuming that our code is not compressed, our code will reach the size of 2MB, if the user opens our web page, we will first load the 2MB file, so that the user experience is very bad. It would be much better if we could achieve the following results.
// lo.js
import _ form 'lodash'Window._ = _ // axin.js // use lodash.jsCopy the code
Js supports parallel loading, not necessarily faster than 2m, but at least can be optimized a lot, what is the biggest benefit? If we only modify the content of axin.js, then our lo. Js does not need to be changed. There will be caching in the browser, and the desired effect will be significantly improved.
So how do we configure it in WebPack? Find webpack.com mon. Js
optimization: {
splitChunks: {
chunks: 'all'}},Copy the code
In particular, Webpack and Code Splitting are not related. By default, webpack will help us download a function, which we only need to configure.
So this is synchronous loading, sometimes our files come back asynchronously, and that’s the way it works. I don’t do a lot of demonstrations. If you’re interested, you can try it yourself.
SplitChunksPlugin
Just to be clear, the plugin still doesn’t avoid writing an asynchronous loading method to use the component.
function getComponent() {return import('lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin'.'hello'].The '-')
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
Copy the code
// {default:} assigns the loaded value to _
Whether loading synchronously or asynchronously, we split the code. Let’s start by downloading an official plugin for dynamic import.
Babeljs. IO /docs/en/nex…
npm install --save-dev @babel/plugin-syntax-dynamic-import
// .babelrc
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
//package.json
"dev-build": "webpack --config webpack.dev.js".Copy the code
Webpack-dev-server writes files to memory that we can’t see, so add a command NPM run dev-build to pack the code.
This generates a 0.dist. Js for me
We can name them using annotations before importing them
function getComponent() {return import(/* webpackChunkName: "loadash"* /'lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin'.'hello'].The '-')
return element
})
}
Copy the code
Vendors ~lodash.dist. Js are generated
Because there are a lot of Settings here, let’s briefly explain the configuration items. The following are the configuration items. If your splitChunks do not have any configuration items, they will be used as configuration items.
optimization: {
splitChunks: {
chunks: 'async'MinRemainingSize: 0, maxSize: 0, minRemainingSize: 0, maxSize: 0 // maxAsyncRequests: 6 can be used at least once, and maxInitialRequests can be loaded with up to 6 modules simultaneously: 4, // The entry file will also be split, but the maximum number of entries beyond 4 will not be split:'~', // Concatenators for names and groups, VENDORS ~ loDash cacheGroups: {// Split groups defaultVendors: {// Default groupstest: /[\\/]node_modules[\\/]/, // If it is node_modules we go to defaultVendors group priority: -10, // priority, and the following default meet the conditions of packaging in the high priority // filename:'vendor.js'}, default: {minChunks: 2, priority: -20, reuseExistingChunk:true// if a is referenced before, it will not package a into common.js.'common.js'You can name it}}}},Copy the code
Lazy Loading
Lazy loading
Let’s improve the asynchronous code a little bit
function getComponent() {return import(/* webpackChunkName: "loadsh"* /'lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin'.'hello'].The '-')
return element
})
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
Copy the code
We load a loadsh function asynchronously and bind a click event to the page. Only when we listen for the click event will we go back and call getComponent, which then imports the loadsh function.
The effect is that we start loading a main.js file on the page, and then clicking on the page loads a loadsh function, and some method of this function is called and we implement a string concatenation process to render the file on the page.
With the import method, the file is loaded asynchronously only if it is accessed and then executed. And we’ll load faster.
Of course, you can also use the popular ASYNc method in ES7 to handle this time, making your code more straightforward.
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName: "loadsh"* /'lodash')
const element = document.createElement('div')
element.innerHTML = _.join(['jsxin'.'hello'].The '-')
return element
}
Copy the code
chunk
We’ve used chunk many times before, so what exactly is our chunk?
In the JS code package, we will split into multiple JS files, so each JS file, we call it a chunk.
Package analysis, preloading, prefetching
Packaging analysis
Take a look at the official WebPack analysis tool
If you analyze our packaged code, first you need to put –profile –json > stats.json in your packaged command
"dev-build": "webpack --profile --json > stats.json --config webpack.dev.js".Copy the code
What he means is to put my packaging process in a stats.json file.
He will write down our whole packaging process, which is time-consuming, what resources are packed, how many modules and chunks are there, etc. You can use official tools to translate it for you. Here you can understand, I will not do more introduction, you can try to package.
preloading
Before we do that, let’s take a look at our original code.
document.addEventListener('click', () => {
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
})
Copy the code
This is the standard way we write it, isn’t there any room for optimization in this way?
Once compiled we open F12 and press Command + Shift + P to type coverage
We click on Show Coverage, and a record button appears on the left, and we see that our main.js is only 75% code used.
Why is that? Because we use it in page loading, we don’t use it
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
Copy the code
This code is only used when clicked, so this is not the way webPack recommends writing code.
We can write this part of the code like this, creating a new click.js
// click.js
export default function addComponent () {
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
}
// com.js
document.addEventListener('click', () => {
import('./click.js').then(({ default: _ }) => {
_()
})
})
Copy the code
That would give him 79% usage, which would save us the first screen load time.
prefetching
A web page, we first initialize the cover letter, we do not load the login modal box, first load the home page of other logic, such as loading is completed, the bandwidth is released, we secretly load login modal dialog, in this way, not only satisfy the needs of my home page load faster, and meet the needs of login to load faster.
This scheme is a practical example of combining preloading and prefetching.
You can declare prefetching configuration before importing
import(/* webpackPrefetch: true* /'./click.js')
Copy the code
At this point, after he has finished loading our core code, he will secretly load click.js
CSS code splitting
scenario
We generate the following code
import '.. /statics/style/index.css'
console.log(123)
Copy the code
Now I want my index. CSS not to generate CSS code directly to my page, but to create a new folder under DIST and put CSS in it. So what should we do with this operation?
The plugin is introduced
The official plug-in: webpack.js.org/plugins/min…
Special note: Suitable for use in online environments because updates are not automatically refreshed
Let’s start by installing the plug-in
npm install --save-dev mini-css-extract-plugin
Copy the code
Then use it in the online environment.
The plug-in configuration
// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [new MiniCssExtractPlugin()],
Copy the code
Then the style-loader we used before will not work, he provided us with a loader, we will replace the style-loader with his loader, and then we need to distinguish the CSS from the development environment.
// webpack.prod.js
module: {
rules: [{
test: / \. CSS $/, / / test file is the use of CSS end: [MiniCssExtractPlugin loader,'css-loader'] {},test: / \. SCSS $/, / / test file is SCSS end use: [MiniCssExtractPlugin loader, {loader:'css-loader', options: {importLoaders: 2, // To import SCSS files, also go to the following two loader // modules:true}},'sass-loader'.'postcss-loader']]}}Copy the code
Then let’s package up a line of code and try it out. The main.css file is generated in our code.
Configuration items
Let’s take a quick look at his configuration items
plugins: [new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'})].Copy the code
If the style is referenced directly, it will go filename, and the indirect is Chunkfilename
We might as well try some more.
// index.css
.avatar{
width: 100px;
height: 100px;
}
// index1.css
.avatar{
display: block;
}
// style.css
import '.. /statics/style/index.css'
import '.. /statics/style/index1.css'
console.log(123)
Copy the code
We’ll pack again and you’ll notice that it automatically merged the two style files into main.css for me.
Avatar {width: 100px; height: 100px; } .avatar{ display: block; } / *# sourceMappingURL=main.css.map*/
Copy the code
Ok, so what if we still want to compress this CSS?
optimize-css-assets-webpack-plugin
// install
npm install optimize-css-assets-webpack-plugin -D
// prod
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
Copy the code
Then in packaging, he not only compressed our code, but merged our code together.
Avatar {width:100px; height:100px; display:block}Copy the code
Ok, other more advanced usage, please refer to the official documentation, here on the one hand is to experience with you, on the other hand is to recommend commonly used plug-ins.
Browser cache
When we load a web page, we might first load an index. HTML and two JS files. The next time you visit the web page, the browser already has two js files in the cache, and the files in the cache will be read first.
You can either change the name of the file or force the refresh, but you can’t force the user to refresh the page. So we can ignore the debugging process and reconfigure the output
// cache.sj
import _ from 'lodash'
let str = _.join(['j'.'s'.'x'.'i'.'n'].The '-')
console.log(str)
output: {
filename: '[name].[contenthash].js',}Copy the code
Contenthash is a hash value that we generate based on the content, so as long as your content doesn’t change, we don’t have to reload the JS.
What have we changed? We’ll modify our own logical source code, but you won’t change the third party code for node_modules, so these things are definitely readable in the browser cache to make the site load faster.
Shimming
Webpack is module-based, meaning that the code in one module is not found in another.
// jq.js
import $ from 'jquery'
import { jqui } from './jq.ui'
jqui()
$('body').append('<div>axin</div>')
// jq.ui.js
export function jqui(){
$('body').css('background'.'red')
}
// $ is not defined
Copy the code
Webpack comes with a plugin, so let’s see what it does.
new webpack.ProvidePlugin({
$: 'jquery'
})
Copy the code
It will check which files you use the $character, and if so, whether you have jquery in them, and if not, it will automatically do it for you. Nice.
Remember we used a Babel/Polyfill? If you don’t have a promise, he will help you make it happen.
The environment variable
We previously merged common in the dev/prod environment, so let’s remerge our package configuration files using an environment variable, starting with the code.
Const webapckMerge = require(// dev/prod)'webpack-merge')
const commonConfig = require('./webpack.common')
module.exports = prodConfig
module.exports = devConfig
// webpack.common.js
const webapckMerge = require('webpack-merge')
const prodConfig = require('./webpack.prod')
const devConfig = require('./webpack.dev') const commonConfig = ... Module. exports = (env) => {if(env && env.production){
return webapckMerge(prodConfig, commonConfig)
}
return webapckMerge(devConfig, commonConfig)
}
Copy the code
Here we export a function directly and receive an env, so we’ll do this in the package script
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config webpack.common.js"."dev": "webpack-dev-server --config webpack.common.js"."build": "webpack --env.production --config webpack.common.js"
},
Copy the code
If env is configured, we will package webpack.common.js, and then decide to package it in different configurations.
Ok, let’s stop here, the knowledge point is the summary, if you also want to learn Webpack with me, let’s see section 5.