webpack基础

167 阅读8分钟

技能点

1658395484947.png

打包流程图

graph TD
入口index.js --> 寻找依赖模块eg:elementUi 
                寻找依赖模块eg:elementUi --> chunk块
入口index.js --> 寻找依赖模块eg:less
                寻找依赖模块eg:less --> chunk块
                chunk块  --> less打包成css
                chunk块  --> js打包成js
                less打包成css --> 最后打包成一个bundle
                js打包成js --> 最后打包成一个bundle

webpack 5个核心概念

graph TD
webpack --> entry
webpack --> output
webpack --> Loader
webpack --> plugin
webpack --> mode

1.webpack基础知识点

1.build打包时相关说明

插件下载webpack和webpack-cli

"scripts": {
    //表示webpack会以./src/index.js为入口开始打包 打包后输出到./build/build.js,整体打包环境为development
    "build":"webpack ./src/index.js -o ./build/build.js --mode=development"
  },

2.loader处理图片相关问题

//处理图片资源
//问题:处理不了html中的img
{
    test:/\.(jpg|png|gif)$/,
    //使用一个loader
    //下载 url-laoder file-loader
    loader:'url-loader',
    options:{
        //图片小于8kb,就会被base64处理
        //优点:减少请求数量(减轻服务器压力)
        //缺点:图片体积会更大(文件请求速度更慢)
        limit:8*1024,
        //问题:因为url-loader默认使用es6模块化解析,而html-loader引入图片是common.js
        //解析式会出问题:[object Module]
        //解决:关闭url-loader的es6模块化,使用common.js
        esModule:false,
        //给图片重命名
        //[hash:10]取图片hash的前10位
        //[ext]去文件原来扩展名
        name:'[hash:10].[ext]',
        outputPath:'imgs',//表示输出到imgs目录下
    }
}
//解决方式:
{
    test:/\.html$/,
    //处理html文件的img图片(负责引入img,从而被url-laoder进行处理)
    loader:'html-loader'
}

3.打包其他资源

//打包其他资源(除了css/js/html 等资源 以外的资源)
{
    //排除css/js/html等资源
    exclude:/\.(css|js|html)$/,
    laoder:'file-loader'
}

4.webpack-dev-server

//开发服务器 devServer 用来自动化(自动编译,自动打开浏览器,自动刷新浏览器)
//特点:指挥在内存中打包,不会有任何输出
//配置1
devServer:{
    contentBase:path.resolve(__dirname,'build'),//项目构建后的路径
    compress:true,//启动zip压缩
    port:3000,
    open:true,//自动打开浏览器
}

//配置2
devServer: {
    static: {
      //给出静态资源文件的路径
      directory: path.join(__dirname, "dist"),
    },
    compress: true, //启动压缩
    port: 9000, //端口号
    hot: true, //热启动
  },

5.构建环境的介绍

1658458947548.png

  • 开发模式中
    代码自动编译,自动打开浏览器,自动刷新,让代码本地调试提高开发效率
  • 生产模式中(让代码要快,平稳
    (1) css-->js做分离
    (2) 代码的压缩
    (3) 样式和JS兼容性问题

6.对css进行剥离 mini-css-extract-plugin

7. css兼容性处理

//webpack.config.js中
//postcss-loader 默认是生产环境的设置
//如果在development模式下生效 则需设置环境变量 process.enb.NODE_ENV = 'development'

// 兼容性postcss-loader 要放在less-loader上面  注意顺序
module:{
    rules:[
        {
            test:/\.css$/,
            use:[
                MiniCssExtractPlugin.loader,//剥离css时使用的loader
                'css-loader',
                //1.css兼容性处理:postcss --> postcss-loader  postcss-preset-env
                //2.postcss-preset-env帮postcss找到packge.json中browserslist里面的配置,
                //通过配置加载指定的css兼容性样式
                
                //使用loader的默认配置直接这样写
                //'postcss-loader'
                //需要修改loader配置则需要下面方式 以对象格式
                {
                    loader:'postcss-loader',
                    options:{
                        ident:'postcss',
                        plugins:()=>{
                            //postcss插件
                            require('postcss-preset-env')()
                        }
                    }
                },
                'less-loader'
            ]
        }
    ]
}

//在package.json中
{
   "browserslist":{
       //开发模式下 需要在webpack.config.js中设置环境变量
       // process.env.NODE_ENV = 'development'
       "development":[
           "last 1 chrome version",//表示兼容最近的chrome版本
           "last 1 firefox version",//表示兼容最近的firefox版本
           "last 1 safari version",//表示兼容最近的safari版本
       ],
       //生产环境下
       // process.env.NODE_ENV = 'production'
       "production":[
           ">0.2%",//表示兼容大于99.8%的浏览器
           "not dead",//不要已经死的浏览器  eg:ie10
           "not op_mini all" //表示不要 op_mini 这种浏览器
       ]
   }
}

8.压缩css(optimize-css-assets-webpack-plugin)

9.语法检查 eslint

//使用airbnb模式 
//需要安装 
//yarn add eslint eslint-loader -D
//yarn add eslint-config-airbnb-base eslint-plugin-import -D

//在webpack.config.js中
rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/, // 对node_modules不检查
    enforce:'pre',//表示优先执行
    loader: "eslint-loader",
    options: {
      // 自动修复eslint错误
      fix: true,
    },
  },
],
//在 .eslintrc.js或packge.json中
"eslintConfig": {
    "extends":"airbnb-base"
  }

10.js兼容性处理

注意:使用corejs后就不需要使用第二种@babel/polyfill 了

//使用 1,3两步即下面的配置  做兼容性处理比较好
rules: [
  //js兼容性处理 babel-loader @babel/core  @babel/preset-env
  //! 1.基本js的兼容性处理 @babel/preset-env  (问题:只能转换基本语法,如promise不能转换)
  //! 2.全部js的兼容性处理 @babel/polyfill (问题:我只需要解决部分兼容性处理,但是将所有兼容性代码全部引入,体积太大)
  //!3.需要按需加载 做兼容性处理  -->  corejS  (用的多)
  // 安装 > yarn add core-js -D
  {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: "babel-loader",
    options: {
      //指示babel做怎样的兼容性处理
      presets: [
        [
          "@babel/preset-env",
          //使用core.js
          {
            //按需加载
            useBuiltIns: "usage",
            //指定core-js版本
            corejs: {
              version: 3,
            },
            //指定兼容性做到那个版本浏览器
            targets: {
              chrome: "60",
              firefox: "60",
              ie: "9",
              safari: "10",
              edge: "17",
            },
          },
        ],
      ],
    },
  },
],

//@babel/polyfill的使用方式  在index.js中直接引入即可
import "@babel/polyfill";
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log("zhixing");
    resolve();
  }, 1000);
});

11.html和js的压缩

1.js压缩只需要设置 mode:'production'//生产模式
2.js压缩2(有兼容性,但是多进程快)

const WebpackParallelUglifyPlugin = require("webpack-parallel-uglify-plugin");
plugins:[
    new WebpackParallelUglifyPlugin({
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        warnings: false,
        compress: {
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        },
      },
    }),
]

3.html压缩

plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "./public/index.html"),
      //html压缩加以下配置
      minify:{
          collapseWhitespace:true,//移除空格
          removeComments:true//移除注释
      }
    })
],

12.生产环境基本配置

注意:以下几条

  1. 先执行eslint-loader再执行babel-loader
graph TD
生产环境配置总览 --> css处理 
生产环境配置总览 --> js处理
生产环境配置总览 --> 图片处理
生产环境配置总览 --> html处理
生产环境配置总览 --> 其他文件处理
graph TD
css处理 --> .css
css处理 --> .less
css处理 --> .scss
css处理 --> postcss-loader兼容性处理
css处理 --> 压缩css
graph TD
js处理 --> eslint检查eslint-loader(用的比较多airbnb检查)
js处理 --> js兼容性处理balbel-loader
js处理 --> js压缩(设置mode:production)
graph TD
其他文件处理 --> 使用exclude排除不需要处理的文件
其他文件处理 --> 使用file-loader

wenpack优化配置

graph TD
wenpack优化流程 --> 开发环境性能优化
开发环境性能优化 -->构建速度优化
构建速度优化 --> HMR
开发环境性能优化 -->代码调试优化
代码调试优化 --> source-map
wenpack优化流程 --> 生产环境性能优化
生产环境性能优化 -->构建速度优化.
构建速度优化. --> oneOf
构建速度优化. --> babel缓存
构建速度优化. --> 多进程打包
生产环境性能优化 -->代码运行性能优化
代码运行性能优化 ---> 文件缓存contenthash
代码运行性能优化 ---> treeshaking
代码运行性能优化 ---> 代码分割
代码运行性能优化 ---> 懒加载/预加载
代码运行性能优化 ---> pwa
代码运行性能优化 ---> externals
代码运行性能优化 ---> dll
  1. HRM功能js需要手动加支持HRM功能的代码
    注意:HRM功能只能对非入口js文件
/*
HRM: hot-module-replacement 热模块替换 
作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)极大提升构建速度
使用方式:在devServer开启HRM功能
1.样式文件:可以使用HRM功能,因为style-loader内部实现了(所以开发模式使用style-loader,生产模式使用css分割loader)
2.js文件:默认不能使用HRM功能 --> 需要修改js,添加支持HRM功能的代码
3.HTML文件:默认不能使用HRM功能,同时会导致问题:html文件不能热更新了 -(一般html不考虑做HRM功能,因为只有一个文件)
*/
devServer: {
    //开启HRM功能
    hot: true,
},

//js文件支持HRM功能
//eg:建一个print.js文件导出print函数
//在index.js中
import print from './print'
if(module.hot){
    //一旦module.hot为true 说明开启了HRM功能,--> 让HRM功能代码生效
    module.hot.accept('./print.js',()=>{
        //方法会监听print.js文件的变化,一旦变化,其他模块不会重新打包
        //会执行回调函数
        print();
    })
}

2. source-map(提供后见后代码映射关系,构建代码出错,通过映射可以找到源代码错误)

//内联和外部的区别:外部生成了文件,内联没有。内联构建速度更快
//0. source-map //外部 (错误代码的准确信息,源代码的位置)
1.inline-source-map //嵌到js中  内联(只生成一个内联的source-map)(错误代码的准确信息,源代码的位置)
2.hidden-source-map //外部 (错误代码的错误原因,不提示源代码的位置)
3.eval-source-map //内联(每个文件都生成对应的source-map,都在eval)
4.nosources--source-map //外部 (错误代码的准确信息,但没有任何源代码的位置)
5.cleap-source-map //外部 (错误信息只能精确到行)
6.cleap-module-source-map //外部

1658740297389.png

  • 速度快:eval>inline>cheap>eval-cheap-source-map>eval-source-map
  • 调试友好:source-map>cleap-module-source-map
  • 开发环境:速度快,调试友好 综上所述用 eval-source-map
  • 生产环境:源代码隐藏,内联代码体积增大所以要排除综上所述用 source-map

3.oneOf优化

module: {
    rules: [
      {
        test: /\.js$/,
        enforce:'pre',//表示优先执行
        loader:'eslint-loader',
      },
      //使用oneOf以下loader只会匹配一个
      //注意:不能有两个配置处理同一种类型文件(如下babel-loader和eslint-loader都有处理.js文件)
      //所以把同时处理.js文件的 eslint-loader提出去先执行,再执行oneOf里面的类型文件
      oneOf:[
          {
            test: /\.css$/,
            use: [MiniCssExtractPlugin.loader, "css-loader"],
          },
          {
            test: /\.less$/,
            use: [MiniCssExtractPlugin.loader, "css-loader",'less-loader'],
          },
          {
            test: /\.js$/,
            loader:'babel-loader',
          },
      ]
    ],
  },

4.缓存优化

  • babel缓存优化(设置cacheDirectory:true
    优点:让第二次打包构建速度更快
    假设有100个文件 babel做翻译时只有一个文件变,那其中99个文件应该保持不变直接使用缓存
module:{
    rules:[
        {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          //指示babel做怎样的兼容性处理
          presets: [
            [
              "@babel/preset-env",
              //使用core.js
              {
                //按需加载
                useBuiltIns: "usage",
                //指定core-js版本
                corejs: {
                  version: 3,
                },
                //指定兼容性做到那个版本浏览器
                targets: {
                  chrome: "60",
                  ie: "9",
                },
              },
            ],
          ],
          //开启babel缓存
          //第二次构建时,会读取之前的缓存
          cacheDirectory:true
        },
      },
    ]
}
  • 文件资源缓存(contenthash让代码上线运行缓存更好用
    (1)通过hash值设置不同文件名来处理文件缓存
    hash:每次webpack构建时都会生成唯一的hash值
    问题:因为js和css同时使用一个hash值,如果从新打包会导致所有缓存失效
    chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
    问题:js和css hash值还是一样的
    contenthash:根据文件内容生成hash值,不同文件hash值一定不一样
    优点:比如css和js只有改变的文件才会生成新的hash值文件名
//1.通过给打包后文件设置hash值来处理缓存问题
output:{
    //filename:'bundle.[hash:10].js',
    filename:'bundle.[contenthash:10].js',
    path:resolve(__dirname,'./src/index.js')
}
plugins:[
    //对css文件取hash值处理缓存
    new MiniCssExtractPlugin({
        //filename:'css/index.[hash:10].css'
        filename:'css/index.[contenthash:10].css'
    })
]

14.treeshaking(去除业务中没有使用的代码)

  • 前提:
- 使用esModule模块化
- 设置mode:profuction
  • 在package.json中设置
//"sideEffexts":false 所有代码都可以进行tree shaking
//问题:可能会把css / @babel/polyfill(没有用到的副作用文件)干掉
//"sideEffexts":["*.css","*.less"]表示css和less文件不会进行treeshaking (文件不会被干掉)
{
    "sideEffexts":["*.css"]
}

15.代码分割(code split)

//第一种 多入口方式
module.exports = {
    entry: {
        index: path.join(__dirname, "./src/index.js"),
        tree: path.join(__dirname, "./src/tree.js"),
    },
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: "js/[name].[contenthash:10].js",
    },
}

//第二种 使用splitChunks (工程化里面有详细配置)
optimization:{
    splitChunks:{
        chunks: "all",
    }
}

// 通过js代码,让某个文件被单独打包成一个chunk
//import 动态导入语法:能让某个文件单独打包
import("./tree")
  .then((result) => {
    console.log("文件加载成功" + result);
  })
  .catch(() => {
    console.log("文件加载失败");
  });

16.js文件懒加载和预加载

//通过import异步方式 点击或某个动作的时候再加载此文件
//1.懒加载 当文件需要使用时才加载
//2.预加载(兼容性差,慎用) prefetch 会在使用之前,提前加载js文件 (等其他资源加载完毕,浏览器空闲了再偷偷加载)
//3.正常加载 可认为并行加载 (同一时间加载多个文件)
document.onclick = function (){
    import(/*webpackChunkName:'test',webpackPrefetch:true*/"./tree").then(({ mul }) => {
      console.log("文件加载成功" + mul(3, 12));
    });
}

17.PWA渐进式网络开发应用程序(让文件离线也能访问)

注意:使用es6语法时要用babel-loader处理

/*
PWA:渐进式网络开发应用程序`(离线也能访问)
  workbox --> yarn add workbox-webpack-plugin -D

  问题:eslint 不认识 window navigator全局变量
  解决:需要修改package.json中.eslintConfig配置
  "env":{
    "browser":true//支持浏览器全局变量  要支持node则设置node:true
   }
*/

//1.在webpack.config.js中
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
module.exports = {
    plugins:[
        //帮我们生成一个serviceWorker配置文件
        new WorkboxWebpackPlugin.GenerateSW({
          clientsClaim: true, //帮助serviceWorker快速启动
          skipWaiting: true, //删除旧的 serviceWorker
        }),
    ]
}

//在入口的js文件中
// 注册serviceWorker
//处理兼容性
if('serviceWorker' in navigator){//判断'serviceWorker' 在没在navigator上
  window.addEventListener('load',()=>{//等全局资源加载好  再去注册serviceWorker
    navigator.serviceWorker.register('/service-worker.js').then(()=>{
      console.log('sw注册成功')
    }).catch(()={
      console.log('sw注册失败') 
    })
  })
}

1658808696146.png 1658808777522.png

1658808871407.png

18.多进程打包

进程启动大概为600ms。进程通信也有开销。只有工作消耗事件比较长 才需要多进程打包

//1.安装 yarn add thread-loader -D
//thread-loader  要给babel-laoder用时 为以下配置
//当给其他loader类型文件用时,同理放在其他loader左面,最后执行
//在webpack.config.js中
rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
        //开启多进程打包
      {
        loader: "thread-loader",
        options: {
          workers: 2, //进程为2
        },
      },
      {
        loader: "babel-loader",
        options: {},
      },
    ],
  },
],

19.externals(防止将某些包打包到bundle中) 不需要打包

注意:当忽略掉某些包后 要用cdn或本地方式通过script标签将这些忽略的包引入在index.html中

//webpack.config.js
module.exports = {
    externals:{
        //拒绝Jquery被打包进来
        jquery:'jQuery'
    }
}

20.dll(动态连接库) 需要打包一次

  • 作用:和externals差不多提取要打包的库,防止重复打包
  1. 建一个webpack.dll.js (npm 打包执行一次此文件)
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');

// dll文件存放的目录
const dllPath = 'public/vendor';

module.exports = {
    entry: {
        // 需要提取的库文件
        vendor: [
            'vue',
            'vue-router',
            'vuex',
            'axios',
            'vue-gemini-scrollbar',
            'element-ui',
            'less'
        ],
        chartVendor: ['p-charts', 'less']
    },
    output: {
        path: path.join(__dirname, dllPath),
        filename: '[name].dll.js',
        // vendor.dll.js中暴露出的全局变量名
        // 保持与 webpack.DllPlugin 中名称一致
        library: '[name]_[hash]' //打包库暴露出去的内容叫什么名字
    },
    plugins: [
        // 清除之前的dll文件
        new CleanWebpackPlugin(['*.*'], {
            root: path.join(__dirname, dllPath)
        }),
        // 设置环境变量
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: 'production'
            }
        }),
        // manifest.json 描述动态链接库包含了哪些内容
        new webpack.DllPlugin({
            path: path.join(__dirname, dllPath, '[name]-manifest.json'),
            // 保持与 output.library 中名称一致
            name: '[name]_[hash]',
            context: process.cwd()
        })
    ]
};

执行webpack.dll.js打包后的文件目录结构
1658821211471.png

  1. 在webpack.config.js中
//1.安装 yarn add add-asset-html-webpack-plugin -D
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
    plugins:[
        //告诉webpack哪些库不参与打包,同时使用时名称也得变
        new webpack.DllReferencePlugin({
            context: process.cwd(),
            //manifest: require('./public/vendor/vendor-manifest.json')
            manifest:path.resolve(__dirname,'./public/vendor/vendor-manifest.json')
        }),
        //告诉webpack哪些库不参与打包,同时使用时名称也得变
        new webpack.DllReferencePlugin({
            context: process.cwd(),
            //manifest: require('./public/vendor/chartVendor-manifest.json')
            manifest:path.resolve(__dirname,'./public/vendor/chartVendor-manifest.json')
        }),
        //将某个文件打包输出去,并在html中自动引入该资源
        new AddAssetHtmlPlugin({
            // dll文件位置
            //filepath: path.resolve(__dirname, './public/vendor/*.js'),
            filepath: path.resolve(__dirname, "./public/vendor/vendor.dll.js"),
            // dll 引用路径
            publicPath: './vendor',
            // dll最终输出的目录
            outputPath: './vendor'
        }),
    ]
}

webpack配置详解

1.entry

//单入口 默认打包文件名 main.js
entry:path.join(__dirname, "./src/index.js")
//多入口
entry:{
    index:path.join(__dirname, "./src/index.js"),
    add:path.join(__dirname, "./src/index.js"),
    indexMore:['jquery','vue','lodash'],//表示将多个文件打包成一个入口
}

2.output

output:{
    filename:'js/[name].js',//文件名称(指定名称+目录)
    path:path.resolve(__dirname,'build'),//输出文件公共目录
    //所有资源引入公共路径前缀 --> 'imgs/a.jpg' -->转成 '/imgs/a.jpg'
    publicPath:'/',
    chunkFilename:'js/[name]_chunk.js',//非入口chunk的名称
    library:'[name]',//暴露打包库文件的名称供使用
    //libraryTarget:'window',//变量名添加到window下 browser(浏览器)
    //libraryTarget:'global',//变量名添加到global下 node端
    libraryTarget:'commonjs',//表示后面以commonjs语法引入
}

3.module

module:{
    rules:[
        {
            test:/\.css$/,
            //多个loader使用use
            use:['style-loader','css-laoder']
        },
        {
            test:/\.js$/,
            
            //排除node_modules下的js文件
            exclude:/node_modules/,
            //只检查src下的js文件
            include:path.resolve(__dirname,'src'),
            enforce:'pre',//pre优先执行  post延后执行  不写中间执行
            //单个laoder使用laoder
            laoder:'eslint-loader'
            options:{}
        },
        {
            //只会生效一个
            oneOf:[]
        }
    ]
}

4.resolve 解析模块规则

resolve:{
    //配置解析模块路径别名:优点简写路径 缺点写路径没有提示
    alias:{
        _c:path.resolve(__dirname,'src/commonent'),
        _a:''
    },
    //配置省略文件路径后缀名的规则
    //不写文件名后缀时 先找.js再找.json没有再找.css
    //缺点:文件名如果一致就会有问题
    extensions:['.js','.json','.css'],
    //告诉webapck解析模块去找那个目录
    modules:[path.resolve(__dirname,'../node_modules','node_modules')]
}

5.devServer

devserver:{
    //运行代码目录
    //contenBase:path.resolve(__dirname,"build"),
    //或 运行代码目录
    static:{
        directory:path.resolve(__dirname,"build")
    },
    //监视ContentBase目录下所有文件,一旦文件变化就会reload
    watchContentBase:{
        ignored:/node_modules/,//不监视node_modules
    },
    compress:true,//启动压缩
    port:3000,//启动端口号
    host:'localhsot',//指定域名
    open:true,//打开浏览器
    hot:true,//开启HMR
    //不需要启动服务器日志信息
    clientLogLevel:'none',
    quiet:true,//除了一些基本信息,其他内容不显示
    overlay:false,//如果出错不要全屏提示
    //服务器代理  --> 解决开发环境跨域问题
    proxy:{
        //一旦devServer(5000)服务器收到/api/xx/的请求,就会把请求转发到另外一个服务器(3000)
        '/api':{
            target:'http://localhost:3000',
            //发送请求时,请求路径从写 将/api/xx 转换成 /xx (去掉/api)
            pathRewrite:{
                '^/api':''
            }
        }
    }
}

6.optimization

1658977129008.png 1658977253423.png

optimization: {
    splitChunks: {
      chunks: 'all'
      // 默认值,可以不写~
    },
    // 将当前模块的记录其他模块的 hash 单独打包为一个文件 runtime
    // 解决:修改 a 文件导致 b 文件的 contenthash 变化
    runtimeChunk: {
      name: entrypoint => `runtime-${entrypoint.name}`
    },
    minimizer: [
      // 配置生产环境的压缩方案:js 和 css
      new TerserWebpackPlugin({
        // 开启缓存
        cache: true,
        // 开启多进程打包
        parallel: true,
        // 启动 source-map
        sourceMap: true
      })
    ]
  }

webpack5与webpack4的区别

  1. webpack5会默认入口和输出目录 entryoutput可以不写
  2. webpack5会自动treeshaking功能更强大,打包的包更小
  3. webpack5中commonJS也能进行 treeshaking
  4. 更多详情见