Webpack 4.0 Gets Into Deep

Keywords: node.js Webpack npm React Vue

webpack4.0

(1) Installation and use of webpack

1. Introduction

webpack is a packaging tool, which handles js files by default, can also handle other types of files with loaders, and can simplify our development process with plug-ins.

2. Configuration environment

First install the preparation environment, node, because webpcak is a node-based packaging tool
Next, install webpack and webpack-cli

npm init -y
npm install webpack webpack-cli -D

3. Command-line packaging

Once installed, we can package it directly from the command line. We can create a new index.js file first, and then at the command line:

npx webpack index.js

After packaging, there will be a default package file, we want to see the effect can create a new index.html to introduce the default package file, and then see the effect.

4. Packaging of scripts

Before that, let's modify the directory to make the structure clearer. We create a new src directory, put index.js in it, then create a new dist directory, and put index.html in it.

4.1 Configure webpack.config.js

Next, create a new webpack.config.js file in the root directory, and configure some packaging parameters in it.

const path = require('path');
module.exports = {
    entry:{
        'main':'./src/index.js'
    },
    output:{
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    }
}

4.2 Configuration package.json

After configuring, let's configure the package.json file to realize the function of script packaging.

{
    "scripts": {
        "bundle": "webpack"
      },
}

At this point, we use npm run bundle directly from the command line. After waiting for the package to be successful, we manually open our index.html file to see the effect. So far, we have completed the installation and use of webpack.

(2) web pack packaged resources (loader)

Last lesson we installed and used webpack. What we dealt with was js files. How did it deal with other types of files? For example, pictures, css, fonts and so on, which requires loaders, that is to say, webpack can recognize js files, but he does not know other files, so he needs an intermediary to tell him how to deal with them.

1. Processing picture resources

First, let's look at the picture file. First we take a picture and put it in src/dog.png. Then we mount it on index.html and write it in index.js.

import dog from './dog.png';
var img  = new Image();
img.src = dog;
var demo = ducument.getElementById('demo');
demo.append(img);

After writing, we will definitely fail to package directly, because webpack does not know this kind of file, so we need to write the configuration file, in webpack.config,js.

module.exports = {
    module:{
        rules:[
            {
                test:/\.(png|jpg|gif)$/,
                use:{
                    loader:'file-loader'
                }
            }
        ]            
    }
}

1.1 file-loader

After writing, we first install file-loader, NPM install file-loader-D, and then pack, npm run bundle, after packaging, and then look at the effect of index.html, you will find that the image has been able to load on the page, we successfully processed the image resources, at this time packaged images. The name is a mess of code. It's too ugly, so we can modify it.

module:{
    rules:[
        {
            test:/\.(jpg|png|gif)$/,
            use:{
                loader:"file-loader",
                options:{
                    name:"[name].[ext]"
                }
            }
        }
    ]
    
}

1.2 url-loader

In addition to using file-loader, we can also use another kind of loader to process images, that is, url-loader. Let's install url-loader-D, and then change the image processing rules to url-loader to see the effect. First, delete the packaged files in dist, then pack them. When we finish, we will find that they are not like F. File-loader packages pictures separately. When we look at the src of its img in the browser, we can see that it is packaged as a base64 file. This is the difference between file-loader and file-loader. Why are there two ways of processing, file-loader and url-loader? This is because the size of the picture is uncertain. If it is very large, it should be processed separately. In this way, the loading of the page does not need to wait until the picture is finished. If it is very small, it is directly included in the packaged file, which will reduce the request to send http.
In fact, url-loader can also be classified and packaged by configuration. It can be set by using limit. If less than a kb, it can be packaged in bundle.js. Otherwise, it can be packaged separately in img and referenced. The configuration is as follows:

options:{
    name:"[name].[ext]",
    outputPath:"img/",
    limit:20400    
}

It will be found that images larger than 20 KB will be packaged separately directly, and those smaller than 20 KB will be packaged in bundle.js in a base64-bit manner.

2. Processing css resources

In the first two lessons, we have been able to deal with js and image resources. Next, we will deal with css, scss and other files.
In fact, the idea is almost the same, they all use the corresponding loader to deal with.
First of all, we will create new style.css and style.scss files and write some styles freely.

//style.css
body{
    background:green
}
//style.scss
body{
    transform:translate(0,0)
}

Then introduce and use it in index.js

import './style.css';

1.1 css/style loader

After the introduction, we need to configure the loader in webpack.config.js

{
    test: /\.css$/,
    use:{
        loader:["css-loader","style-loader"]
    }
}

1.2 scss-loader

These lodaer s are loaded from the back to the front. css-loader merges multiple CSS files into one. style-loader mounts the merged CSS file on the head of html. First we install it. NPM install css-loader style-loader-D, then npm run bundle packages it, and then open index,html will be Discovering that the page has turned blue indicates that files of this type of CSS can be recognized and packaged by webpack. Let's try the scss file again and configure it in webpack.config.js as well.

{
    test:/\.scss$/,
    use:[
        'style-loader',
        'css-loader',
        'sass-loader'
        ]
}

At this point, we will refer to the style.scss file we just wrote in index.js.

import './style.scss';

Then install NPM install sass-loader-D, and then npm run bundle, you will find an error. This is because we lack the node-sass package. Install NPM install node-sass-D, then continue npm run build package and open it in html, you will find the page turned green, indicating that the sass file can also be installed. Processed by webpack

1.3 postcss

The first is the most basic way to process CSS files. In addition to this, we sometimes need to add vendor prefixes to different CSS attributes. This is achieved by postcss-loader. First, we use some css3 attributes in style.css file.

body{
    transform: translate(0,0);
}

At this point when we come to pack, there will be no manufacturer prefix, which requires us to write some rules.

{
    test:/\.css$/,
    use:['style-loader','css-loader','postcss-loader']

}

After configuring, we need to create a new postcss.config.js to configure postcss.

//postcss.config.js
module.exports = {
    plugins:[require('autoprefixer')]
}

First install postcss-loader, NPM install postcss-loader autoprefixer-D, then npm run build at this time look again, you will find that there is an automatic prefix.

3. Processing font files

So far, we can deal with js, css, picture files. Next, we deal with font files. The fonts we usually use are default fonts. Sometimes we want to use some special fonts. The webpack does not know these files. Let's see how it handles these files. First, let's go down. Load a font, I'm ready, you can download to use it, and then we use it in the style file.

//style.css
font-face {
 font-family: 'fontnameRegular';
 src: url('fontname.ttf');
}


div{
    font-family: fontnameRegular
}

And then refer to it in js

import './style.css';

var demo = document.getElementById('demo');
var div = document.createElement('div');
div.innerHTML = 'I want to learn by myself.';
demo.append(div);

If we pack it at this time, it will definitely fail. Try it, if it fails, how to deal with it?

1.1 file-loader

Configure loader

{
    test:/\.(ttf)$/,
    use:{
        loader:'file-loader'
    }
}

Then try packing, npm run bundle, in the browser to see the effect, the font changed, so that the font file was successfully processed.

(3) web pack automation (plugins)

1. Construction process

All of the above is about processing resources. Next, let's look at how we can simplify the operation process through plugins. First, think about what we have done manually in the packaging and compilation process so far.
Source-Clear-New-Package-Browse-Refresh-Error-Location-Transcoding-Switching

  • 0. The path in the output will automatically create a new dist directory
  • 1. New index. HTML - ----------------------- HTML (template)
  • 2. Introducing packaged files to reference index. HTML - - - HTML
  • 3. clean the last packaged file after modifying the src source code
  • 4. Packing npm run bundle again - ----------------------------------------------- npm run start
  • 5. open index.html in the browser
  • 6. Next time there will be a change to refresh the page - ------------- contentBase:'./dist'
  • 7. Switch to the online environment after code development and reconfigure the relevant parameters - ----------------------------- webpack merge
  • 8. Configure babel to support es6 and react grammars
  • 9. Configuring eslint to support intelligent error reporting

All of these can be automated.

2. New Template Introducing Resources

First, we will deal with 1 and 2 automatic new index.html and introduce packaging resources. This needs to be implemented by plug-in html-webpack-plugin. First, we will install it.

npm install html-webpack-plugin -D

Once installed, it needs to be matched in webpack.config.js.

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

plugins:[new HtmlWebpackPlugin({
    template:'./src/idex.html'
})]
//src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>html Template</title>
</head>
<body>
    <div id="demo"></div>
</body>
</html>

It's a plug-in, so it's written in plugins. Its purpose is to read the template file every time the file is packaged, then insert it into the newly generated index.html, and then import the packaged resources into the file. At this point, npm run bundle found that it did implement new HTML files and introduced packaging resources.

3. Empty packing list

Then we need to solve 3. Auto-empty dist directory, you may want to say, I did not manually clear the dist directory before, after I rebuild the bundle, it can be used directly, then why do you want to clean it? Let's imagine a scenario where when you pack, you change the name of the exit file, and when you pack, you generate different packages. A few more modifications will make the dist directory very bulky, so we need to clear it.
Cleaning up also depends on the plug-in clean-webpack-plugin to install the same

npm install clean-webpack-plugin -D

Then match it up

plugins:[
    new CleanWebpackPlugin({
        root:path.resolve(__dirname,'dist')
    })
]

This plug-in will be called before packaging and then clear the contents of the dist directory. It achieves our goal.

4. Deserver

Next, let's 4. Enter a command to continuously monitor file changes, rather than duplicate packaging every time you modify the source code. This can be achieved with the help of webpackdevserver. First, download it.

npm install webpack-dev-server -D

Then match it.

devServer:{
    contentBase:'./dist',
    open:true
}

Modify the startup script.

{
    "scripts":{
        "dev":"webpack-dev-server"
    }
}

When we pack it again, npm run dev will find that it opens the browser automatically after packing, and then refreshes the page automatically when the source code is modified.
Why use this server? With server, its protocol becomes http, which helps us avoid cross-domain problems in development.

5. Environment switching

So how do we distinguish between these two environments? Do you change the configuration every time you switch the environment? That's too much trouble. We can write the development environment and the online environment in one file, and then write the common logic in another file. We can call the environment configuration file directly when switching the environment.

First is the development environment.

//webpack.dev.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');


module.exports = {
    mode:"development",
    devtool:"source-map",
    entry:{
        "main":"./src/index.js"
    },
    devServer:{
        contentBase:'./dist',
        open: true,
        port:8080,
        hot:true
    },
    module:{
        rules:[
            {
                test:/\.js$/,
                exclude:"/node_modules/",
                loader:"babel-loader"
            },
            {
                test:/\.(jpg|png|gif)$/,
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',
                        outputPath:'images/',
                        limit:1024
                    }
                }
            },
            {
                test:/\.css$/,
                use:['style-loader','css-loader']
            },
            {
                test:/\.scss$/,
                use:['style-loader',
                {
                    loader:'css-loader',
                    options:{
                        modules:true
                    }
                },

                'sass-loader']
            },
            {
                test:/\.(ttf)$/,
                use:{
                    loader:'file-loader'
                }
            }
        ]

    },
    plugins:[
        new HtmlWebpackPlugin({
            template:'./src/index.html'
        }),
        new CleanWebpackPlugin({
            root:path.resolve(__dirname,'dist')
        }),
        new webpack.HotModuleReplacementPlugin()

    ],
    optimization:{
        usedExports: true 
    },
    output:{
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    }
}

Then there's the online environment.

//webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');


module.exports = {
    mode:"production",
    devtool:"cheap-module-source-map",
    entry:{
        "main":"./src/index.js"
    },

    module:{
        rules:[
            {
                test:/\.js$/,
                exclude:"/node_modules/",
                loader:"babel-loader"
            },
            {
                test:/\.(jpg|png|gif)$/,
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',
                        outputPath:'images/',
                        limit:1024
                    }
                }
            },
            {
                test:/\.css$/,
                use:['style-loader','css-loader']
            },
            {
                test:/\.scss$/,
                use:['style-loader',
                {
                    loader:'css-loader',
                    options:{
                        modules:true
                    }
                },

                'sass-loader']
            },
            {
                test:/\.(ttf)$/,
                use:{
                    loader:'file-loader'
                }
            }
        ]
    },

    plugins:[
        new HtmlWebpackPlugin({
            template:'./src/index.html'
        }),
        new CleanWebpackPlugin({
            root:path.resolve(__dirname,'dist')
        })
    ],

    output:{
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    }
}

Then we can see that there are too many duplicate codes in these two, so we can extract them and put them in a separate file, and then refer to common in dev and prod, respectively.

//wenpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry:{
        "main":"./src/index.js"
    },

    module:{
        rules:[
            {
                test:/\.js$/,
                exclude:"/node_modules/",
                loader:"babel-loader"
            },
            {
                test:/\.(jpg|png|gif)$/,
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',
                        outputPath:'images/',
                        limit:1024
                    }
                }
            },
            {
                test:/\.css$/,
                use:['style-loader','css-loader']
            },
            {
                test:/\.scss$/,
                use:['style-loader',
                {
                    loader:'css-loader',
                    options:{
                        modules:true
                    }
                },

                'sass-loader']
            },
            {
                test:/\.(ttf)$/,
                use:{
                    loader:'file-loader'
                }
            }
        ]

    },

    plugins:[
        new HtmlWebpackPlugin({
            template:'./src/index.html'
        }),
        new CleanWebpackPlugin({
            root:path.resolve(__dirname,'dist')
        })
    ],

    output:{
        filename:'bundle.js',
        path:path.resolve(__dirname,'dist')
    }

}

After pulling out common, we need to use a new plug-in to merge webpack-merge. First, we need to install it.

npm install webpack-merge -D
//dev
const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');


const devConfig = {
    mode:"development",
    devtool:"source-map",
    devServer:{
        contentBase:'./dist',
        open: true,
        port:8080,
        hot:true
    },
    
    plugins:[
        new webpack.HotModuleReplacementPlugin()
    ],
    optimization:{
        usedExports: true 
    }
}

module.exports = merge(commonConfig,devConfig)
//prod

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js')

 const prodConfig = {
    mode:"production",
    devtool:"cheap-module-source-map"
}
module.exports = merge(commonConfig,prodConfig)

At this point, there is a bunch of configuration files in the root directory, which is too messy, so put them in the build folder, and then modify the script path.

  "scripts": {
    "bundle": "webpack",
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js"
  },

6. Configure babel

6.1 Supports ES6 grammar

Using es6 code to write business, this needs to use babel for transcoding, let's write it.
First, to use babel, you need to install it first

npm install -D babel-loader @babel/core

Then, configure loader

module.exports = {
    module:{
        rules:[
            {
                test:/\.js$/,
                exclude:"/node_modules/",
                loader:"babel-loader",
                options:{
                    presets:['@babel/preset-env']
                }
            }
        ]
    }
}

After configuring the loader, we also need to install a module, because the loader only links babel with webpack, and the specific conversion work is done by the following / preset-env module. Sometimes the converted content is not supported by some low-level browsers, so we need to merge with him here. Allow use / polyfill module

npm isntall @babel/preset-env -D
npm install @babel/polyfill --save

After that, let's write some es6 code to try it out.

//index.js
import '@babel/polyfill'
const arr = [
    new Promise(() => {}),
    new Promise(() => {})
];

arr.map(item => {
    console.log(item);
});

Then you will see that the code is packaged into es5 code that can be recognized by low-level browsers, but let's consider that we only use a few es6 grammars, but when he recognizes, he converts all the features, which is not necessary. In addition, some browsers support es6 by default. Code, can be used without conversion, can be used without conversion, ah, how to solve these two points? We can add some configuration to it.

options:{
        presets:[['@babel/preset-env',{
            targets:{
                chrome:"67"
            },
            useBuiltIns:"usage"
        }]]
    }

To sum up:
1. babel-loader@babel/core: With the necessary environment of babel, loader is responsible for linking Babel to web pack
2.@babel/preset-env: Module for actual code conversion
3.@babel/polyfill: Converted code is also unrecognizable in some low-level browsers and compatible with this module
4.targets:{chrome:"67"}: browser support does not need to be converted
5.useBuiltIns:"usage": Convert only the es6 code features used

So far we can write any es6 code, but babel itself has a lot of configuration, if all written in webpack.config.js, it will be very bloated, so we can take it out and write it in. babelrc alone.

//.babelrc
{
    "presets":[
        [
            "@babel/preset-env",
            {
                "targets":{
                    "chrome":"67"
                },
                "useBuiltIns":"usage"
            }
        ]
    ]
}

Let's try packing again. npm run bundle found no problem

6.2 Support react grammar

Finally, how to use the framework to write source code?
First, let's install the framework.

npm install react react-dom -D

Then write the configuration.

//.babelrc
{
    "presets":[
        [
            "@babel/preset-env",
            {
                "targets":{
                    "chrome":"67"
                },
                "useBuiltIns":"usage"
            }
        ],
        "@babel/preset-react"
    ]
}

Write a little react code

import React ,{ Component }from 'react';
import ReactDom from 'react-dom';

class App extends Component{
    render() {
        return <div>hello world</div>
    }
}

ReactDom.render(<App />,document.getElementById('demo'));

Then run npm run dev to see the react code.

6.3 Support vue grammar

Similarly, install the framework first

npm install vue

Then install loader

npm install -D vue-loader vue-template-compiler

Prepare a loader

module:{
  rules:[
    {
      test: /\.vue$/,
      loader: 'vue-loader'
    }
    ]
},
plugons:[
    new VueLoaderPlugin()
]

Write point vue code

//index.vue
<template>
    <div class="rest">{{info}}</div>
</template>


<script>
module.exports = {
    data(){
        return {
            info:'hello'
        }
    }
}

</script>


<style lang="stylus" scoped>
.test
  color:red
</style>

Mounted on the page

//index.js
import Vue from 'vue';
import App from './index.vue';

const root = document.getElementById('root');

new Vue({
    render:(h) => h(App)

}).$mount(root)

7. Configuring Eslint

In the project, in order to unify the coding specification, we can use some tools to help us constrain, eslink is the most commonly used specification tool, let's use it for a while.

npm instll eslint -D
npm install babel-eslint -D
npm install eslint-loader -D
npx eslint --init

Now you have a configuration file. eslint.js, but for some errors that cannot be displayed directly in sublimt, we can use eslint-loader to configure, and then let's configure it.

//.eslintrc
module.exports = {
    "parser":"babel-eslint"
}
rules:{}
//dev
devServer:{
    overlay:true
}
//common
module:{
    rules:[
        {
            test:/\.js$/,
            use:["babel-loader","eslint-loader"]
        }
    ]
}

At this point, any error will pop up on the browser.
Now let's take a look at all the manual operations. Now we have become automatic operations, which greatly simplifies the development of tedious.

(4) Web pack improves performance

1. Performance analysis

The next question we need to consider is performance. What performance improvements can we make in the development process above?

  • 1. When the source code is modified, do not refresh the whole page, just refresh the modified part (HML)
  • 2. When the source code is wrong, it can quickly locate the wrong position, not to the character, as long as it is precise to which line. (sourcemap)
  • 3.

2.HMR (thermal replacement)

2.1 cssHMR

First, 1. Partial refresh replacement code, which can be implemented using HML. Its name is hot replacement. When we open HML, we only refresh the changed code. Let's take an example.

//index.js
import './style.css'
var div = document.createElement('div');
div.innerHTML = "I want to learn by myself.";
var p = document.createElement('p');
p.innerHTML = "51zxw";

var demo = document.getElementById('demo');
demo.append(div);
demo.append(p);
//style.css
div{
    color:green;
}
p{
    color:red
}

When npm run dev opens the server, we will find that the content is rendered on the page. When we change the color of div, the page will refresh the whole page. But in fact, the color of p has not changed. It has also been reloaded once. If only these two styles are used, it's good to say that once the styles increase, this is absolutely true. Great waste of resources, so we just need to refresh the div style instead of the whole style. We need to match it.

const webpack = require('webpack');

devServer:{
    contentBase:'./dist',
    open:true,
    port:8080,
    hot:true
},
plugins:[
    new HtmlWebpackPlugin({
        template:'./src/index.html'
    }),
    new CleanWebpackPlugin({
        root:path.rasolve(__dirnam,'dist')
    }),
    new webpack.HotModuleReplacement()    
]

At this point, we restart the server, and when we change the div color, we will find that it will not refresh the whole page, but only replace the modified code.

2.2jsHMR

The above is a hot replacement for css code. Actually, js code can also be used. Let's give an example.

//counter.js
function counter(){
    var div = document.createElement('div');
    div.setAttribute('id','counter');
    div.innerHTML = 1;
    div.onclick = function(){
        div.innerHTML = parseInt(div.innerHTML,10)+1;
    }
    document.body.appendChild(div)
}
export default counter;
//number.js
function number(){
    var div = document.createElement('div');
    div.setAttribute('id','number');
    div.innerHTML = 3;
    document.body.appendChild(div);
}
export default number;
//index.js
import counter from './counter.js';
import number from './number.js';

counter()
number()

When HML is not opened, the page loads for the first time, showing p and Div. When modifying the content of div, the content of p does not change, but it is refreshed.
When opening HML, we can specify that only refresh a's content

//index.js
import createDiv from './a.js';
import createP from './b.js';
createDiv();
createP();

if(module.hot){
    module.hot.accept('./a.js',function(){
        var div = document.getElementById('d');
        document.body.remove(div)
        createDiv()
    })
}

So far, the hot replacement of HTML in js code has been realized.

3. sourceMap (source mapping)

Then we need to locate the error code, which is located by using soucemap, but it is opened by default in the development environment, so we turn it off first.

//webpack.config.js
    module.exports = {
        devtool:"none"
    }

Then start the server, and we deliberately correct the code.

console.lo('51zxw')

Then when we click on the link to the wrong code, we jump directly into the package file instead of the source code, so we hope to locate it in the source code. Let's modify the configuration again.

module.exports = {
    devtool:"source-map"
}

Then restart the server and click again to locate the specific error source code. At this time, we will find that the dist directory disappeared. In fact, webpack put the dist directory directly in memory in order to speed up packaging. We will learn from npm run bundle again, and then the dist directory appears again. But there is also an additional map file, which is the corresponding file of soucre-map. He will drunk the package resources and source relationship into a corresponding file. Now that we know how to use it, let's talk about what parameters devtool has and what each means.
inline-source-map: Put the corresponding file map directly in the packaged file
cheap-source-map: Locates only the wrong line, not the specific error character, and does not handle the errors caused by the introduced third-party packages.
module-source-map: Errors that deal with both business code and third-party packages
eval-source-map: the fastest but incomplete error message
In development environments, devtool:cheap-module-eval-source-map is commonly used
In production environments, devtool:cheap-module-source-map is commonly used

4.TreeShaking

We've learned how to package different resources and automate development processes, and we've also done some performance optimization, but there are still many things to optimize. For example, we configure "useBuiltIns" and "usage" before, which is to make babel transcode only the es6 features we use instead of the es6 features we use when transcoding. All the es6 features are transcoded, so if we introduce a module in our own js code, but only use one of the exported content, then other content will be packaged. Is there any way that we can pack what we use and remove all the unused ones? This is TreeShaking, which implements that all the exported content that is not referenced by es module is not loaded.

export const add = function(a,b){
    console.log(a+b)
}

export const minus = function(a,b){
    console.log(a-b)
}
//index.js
import { add } from './math.js';

add(1,2)

When we pack, both methods are packaged into bundle.js. Let's configure them.

optimization:{
    usedExports:true
}
{
    "sideEffects":false
}

The online environment basically defaults to treeshaking, and the development environment uses optionization to trigger.

5. CodeSplitting

Then let's look at code segmentation. Sometimes our code includes both business logic written by ourselves and third-party packages introduced. When users load, if they pack two files in one file, the download speed will be very slow, so we need to split the code and load two comparisons. Small files are faster than loading a larger file. So how do you implement code splitting?
First, let's use an external library loadsh

npm install loadsh -D
//index.js
import _ from 'loadsh';
console.log(_.join(['a','b'],'***'))

Then configure common to implement code splitting

optimization:{
        splitChunks:{
            chunks:"all"
        }
    },

At this point, the loadsh and business code are packaged separately

6. Lazy Loading

Let's take another look at lazy loading, loading modules asynchronously through import, that is, loading only when you need to use this module, for example.

function getComponent(){
    return import('loadsh');
    var element = document.createElement('div');
    element.innerHTML = _.join(['a','b'],'***');
    rerun element;
}
document.addEventListener('click',()=>{
    getComponent().then(element => {
        document.body.appendChild(element)
    })
})

7. PreFech (pre-request)

Let's look at prefech again. Asynchronous code downloads automatically when the network is idle, for example.

//index.js

document.addEventListener('click',()=>{
    import(/*webpackPrefetch:true*/'/.click.js').then(
    ({default:func}) => {
        func()
    })
})
//click.js

function handleClick(){
    const ele = document.createElement('div')
    ele.innerHTML = "hello";
    document.body.append(ele)
}

export default handleClick;

8. caching

caching, when packaging, if there is no change in the contents of the package, then the name of the two packages is the same, which makes the browser in the request, if you see the same file will not re-send the request, so you can use contenthash to modify each time to generate a new file name. Avoid.

output:{
    filename:'[name].[contenthash].js'
}

9. Shimming (gasket)

6.shimming: Gaskets, a compatible scheme, have many behaviors called gaskets, which mainly make up for some shortcomings, such as globalizing variables.

const webpack = require('webpack');

plugins:[
    new webpack.ProvidePlugin({
            $:'jquery'
        })
]

global variable

module.exports = (env) => {
    if(env && env.production){
        return merge(commonConfig,prodConfig);
    }else{
        return merge(commonConfig,devConfig);
    }
}

10.DllPlugins

When packaging, we will inevitably introduce third-party packages. When repeated packaging, these unchanged modules will be repackaged. In order to speed up the packaging time, we can save it at the first packaging time and call it directly at the later packaging time. This is done with the help of Dllplugin. To achieve.
First of all, let's introduce some content.

import React, { Component } from 'react';
import ReactDom from 'react-dom';

class App extends Component{
    render() {
        return <div>hello world</div>
    }
}

ReactDom.render(<App />,document.getElementById('demo'));

Then create a new webpack.dll.js

const path = require('path');
const webpack = require('webpack');



module.exports = {
    mode:'development',
    entry:{
        vendors:['react','react-dom']
    },
    output:{
        filename:'[name].dll.js',
        path:path.resolve(__dirname,'../dll'),
        library:'[name]'

    },
    plugins:[
        new webpack.DllPlugin({
            name:'[name]',
            path:path.resolve(__dirname,'../dll/[name].manifest.json'),

        })
    ]

}

First, package once to generate the package file corresponding to the module and the json corresponding file

{
    "scripts":{
        "build:dll": "webpack --config ./build/webpack.dll.js"
    }
}

Next we need to introduce this file in index.html.

//common.js
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
plugins:[
    new AddAssetHtmlWebpackPlugin({
            filepath:path.resolve(__dirname,'../dll/vendors.dll.js')
        })
]

After successful introduction, check in commonjs for packaged content in vendors.manifest.json when packaging. If so, use it directly in global variables, instead of looking for node_modules and packaging them.

//common.js
plugins:[
    new webpack.DllReferencePlugin({
            manifest:path.resolve(__dirname,'../dll/vendors.manifest.json')
        })
]

You'll find that packaging really speeds up. If we need to introduce multiple modules in a large project, it's too cumbersome to duplicate the configuration, so sit down and insert automatically.

//common

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const devConfig = require('./webpack.dev.js');
const prodConfig = require('./webpack.prod.js');
const merge = require('webpack-merge');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const fs = require('fs');



const plugins = [
    new HtmlWebpackPlugin({
            template:"./src/index.html"
        }),
    new CleanWebpackPlugin({
            root:path.resolve(__dirname,'../dist')
        }),
    new webpack.ProvidePlugin({
            $:'jquery'
        })
]

const files = fs.readdirSync(path.resolve(__dirname,'../dll'));
files.forEach(file => {
    if(/.*\.dll.js/.test(file)){
        plugins.push(new AddAssetHtmlWebpackPlugin({
            filepath:path.resolve(__dirname,'../dll',file)
        }))
    }

    if(/.*\.manifest.json/.test(file)){
        plugins.push(new webpack.DllReferencePlugin({
            manifest:path.resolve(__dirname,'../dll',file)
        }))
    }
})



// module.exports = {
const commonConfig = {
    entry:{
        "main":"./src/index.js"
    },
    module:{
        rules:[
            {
                test:/\.js$/,
                exclude: /node_modules/,
                use:[
                    "babel-loader",
                    // "eslint-loader",
                    // {
                    //     loader:"imports-loader?this=>window"
                    // }
                ]
            },
            {
                test:/\.(png|jpg|gif)$/,
                use:{
                    loader:"url-loader"
                }
            },

            {
                test:/\.(ttf|eot|svg)$/,
                use:{
                    loader:"file-loader"
                }
            }
        ]
    },
    plugins,

    optimization:{
        usedExports:true,
        splitChunks:false
    },

    output:{
        path:path.resolve(__dirname,'../dist')
    }

}

module.exports = (env) => {
    if(env && env.production){
        return merge(commonConfig,prodConfig);
    }else{
        return merge(commonConfig,devConfig);
    }
}

1. Use the latest version of node
2.loader and plugin work on the required modules, such as third-party modules, as far as possible without checking.
3.resolve

resolve:{
    extends:['js','jsx'],
    alias:{
        child:path.resolve(__diename,'/')
    }
}

4.DllPlugins
5. Controlling Packet Size
6. Rational use of sourcemap
7. Analysis with stats.json
8. Memory Compilation in Development Environment
9. Eliminate unused plug-ins in the development environment

Summarize the performance improvements:
1.treeshaking: Shake off unused content in the introduction module

//dev
optimization:{
    usedExports:true
}
//package
{
    "sideEffects":false
}

2. Codsplitting: Block packing code

optimization:{
        splitChunks:{
            chunks:"all",
            minSize:30000,
            minChunks:1,
            name:true,
            cacheGroup:{
                vendors:{
                    vendors:{
                        test:/[\\/]node_modules[\\/]/,
                        priority:-10,
                        filename:'vendors.js'
                    }
                },
                default:{
                    priority:-20,
                    reuseExistingChunk:true
                    filename:"common.js"
                }
            }
        }
    },
npm install mini-css-extract-plugin -D//split
npm install optimize-css-assets-webpack-plugin -D//compress
//prod
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module:{
        rules:[
            {
                test:/\.css$/,
                use:[
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    "postcss-loader"
                    ]
            },

            {
                test:/\.scss$/,
                use:[
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    "sass-loader"
                       ]
            }

        ]
    },
    optimization:{
        minimizer:[new OptimizeCssAssetsWebpackPlugin({})]
    },
    plugins:[
        new MiniCssExtractPlugin({
            filename:'[name].css',
            chunkFilename:'[name].chunk.css'
        })
    ]
//Be careful to avoid conflicts with optimization s, and take out the css processing in common and attach it to the prod

3.lazy-loading: lazy loading, loading modules only when called

function getComponent(){
    return import('loadsh');
    var element = document.createElement('div');
    element.innerHTML = _.join(['a','b'],'***');
    rerun element;
}
document.addEventListener('click',()=>{
    getComponent().then(element => {
        document.body.appendChild(element)
    })
})

4.preFech: Asynchronous code downloads automatically when the network is idle

document.addEventListener('click',()=>{
    import(/*webpackPrefetch:true*/'/.click.js').then(
    ({default:func}) => {
        func()
    })
})

5.caching: huancun

output:{
    filename:'[name].[contenthash].js'
}

6.shimming: Gaskets, a compatible scheme that globalizes variables

const webpack = require('webpack');

plugins:[
    new webpack.ProvidePlugin({
            $:'jquery'
        })
]

global variable

module.exports = (env) => {
    if(env && env.production){
        return merge(commonConfig,prodConfig);
    }else{
        return merge(commonConfig,devConfig);
    }
}

(5) Exploration of the underlying web pack

1. Realize loader by oneself

loader is used to deal with all kinds of resources. It's essentially a function. Let's write about it.

//webpack.config.js
const path = require('path');

module.exports = {
    mode:'development',
    entry:{
        main:"./src/index.js"
    },
    resolveLoader:{
        modules:['node_modules','./loaders']
    },
    module:{
        rules:[
            {
            test:/\.js$/,
            use:{
                loader:'my-loader.js',
                options:{
                    name:'kk'
                }
            }

        }

        ]
    },
    output:{
        filename:'[name].js',
        path:path.resolve(__dirname,'dist')

    }

}
//index.js
console.log('hello world');
//my-loader.js
//npm install loader-utils -D

const loaderUtils = require('loader-utils');

module.exports = function(source){
    //1. Direct processing of parameters
    const options = loaderUtils.getOptions(this);
    // return source.replace('lee',options.name);

    //2.callback processing parameters
    // const result = source.replace('lee',options.name);
    // this.callback(null,result);

    //3. Write asynchronous code
    const callback = this.async();
    setTimeout(()=>{
        const result = source.replace('dell',options.name);
        callback(null,result)
    },1000)

}

2. Implement plugin by yourself

plugin is used to help us simplify some of the operating procedures. It's essentially a class. Let's write about it.

//webpack.config.js
const path = require('path');
const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin.js');




module.exports = {
    mode:'development',
    entry:{
        main:"./src/index.js"
    },
    resolveLoader:{
        modules:['node_modules','./loaders']
    },
    plugins:[
        new CopyrightWebpackPlugin({
            name:'dell'
        })
    ],
    output:{
        filename:'[name].js',
        path:path.resolve(__dirname,'dist')

    }
}
//plugins/copyright-webpack-plugin.js



class CopyrightWebpackPlugin{
    constructor(options){
        cosnole.log(options)
    }
    apply(compiler){
        compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin',(compiltion,cb)=>{
            compiltions.assets['kk.txt'] = {
                source:function(){
                    return 'kk'
                },
                size:function(){
                    return 2;
                }
                
                };
                cb();
        })
    }
    

}
module.exports = CopyrightWebpackPlugin;

3. Realize bundller by oneself

bundller is essentially looking for layer-by-layer references and then converting code to execute them. Let's write



const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const babel = require('@babel/core');



//Analysis of a module
const moduleAnalyser = (filename) => {
    //1. Read files
    const content = fs.readFileSync(filename,'utf-8'); 
    //2. Obtaining an abstract grammar tree
    const ast = parser.parse(content,{
        sourceType:'module'
    });
    //3. Find out the relative/absolute address of the module introduced in the first module, and translate the code.
    const dependencies = {};
    traverse(ast,{
        ImportDeclaration({ node }){
            const dirname = path.dirname(filename);
            const newFile = './' + path.join(dirname,node.source.value);
            dependencies[node.source.value] = newFile;
        }
    });
    const { code } = babel.transformFromAst(ast,null,{
        presets:['@babel/preset-env']
    });
    return {
        filename,
        dependencies,
        code
    }
    
    
}


//4. Traversing through all modules
const makeDenpendencesGraph = (entry) => {
    const entryModule = moduleAnalyser(entry);
    const graphArray = [ entryModule ];
    for(let i = 0; i<graphArray.length; i++){
        const item  = graphArray[i];
        const { dependencies } = item;
        if(dependencies){
            for(let j in dependencies){
                graphArray.push(
                    moduleAnalyser(dependencies[j])
                    );
            }
        }
    }
    const graph = {};
    graphArray.forEach(item => {
        graph[item.filename] = {
            dependencies:item.dependencies,
            code:item.code
        }

    });
    return graph;
}


//5. Generate packaged code
const generateCode = (entry) => {
    const graph = JSON.stringify(makeDenpendencesGraph(entry))
    return `
        (function(graph){
            function require(module){
                function localRequire(relativePath){
                    return require(graph[module].dependencies[relativePath]);
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code);
                })(localRequire,exports,graph[module].code)    
                return exports;
            };

            require('${entry}')

        })('${graph}')
    `;
}
const code = generateCode('./src/index.js');

console.log(code)

Posted by yumico23 on Tue, 20 Aug 2019 22:07:36 -0700