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

  1. Package files as needed
  2. Distinguish the online environment from the development environment configuration
  3. Webpack and Code Splitting
  4. SplitChunksPlugin Details about configuration parameters
  5. Lazy loading/chunk
  6. Package analysis process
  7. Webpack and browser caching issues
  8. CSS segmentation
  9. Browser cache
  10. Shimming
  11. The environment variable

The specific application

  1. 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
  2. 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
  3. Code splitting, synchronous loading and asynchronous loading configuration
  4. SplitChunksPlugin Common configurations
  5. Lazy loading examples with chunk introduction
  6. Simple packing analysis, preloading,prefetching
  7. Instead of obfuscating and packaging CSS, create a CSS folder in the dist directory and package it
  8. Js file cache problem in browser, package solution
  9. Shimming role
  10. 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.