zoukankan      html  css  js  c++  java
  • 一行一行手敲webpack4配置

    代码:github


    一、webpack4--基本配置

    这一部分通过webpack的基本配置,使用loader对图片和样式进行打包,从而了解webpack4简单的用方法,保证自己能够配置正确,提升学习动力。

    1.初始化配置

    mkdir webpack4
    cd webpack4
    mkdir demo1
    cd demo1
    npm init -y 或 npm init
    

    目录结构

    webpack4
    ├── webpack4/demo1
    │   └── webpack4/demo1/package.json
    └── webpack4/README.md
    
    
    • 安装webpack
      npm install webpack --save-dev

    • 安装指定版本webapck
      npm install --save-dev webpack@<version>

    • webpack 4+ 版本,还需要安装webpack-cli
      npm install webpack-cli --save-dev

    • npx webpack -v:查看webpack版本

    • npx webpack-cli -v:查看webpack-cli版本

    推荐本地安装webpack和webpack-cli
    写这篇博客的时候webpack最新版本为:4.30.0,也是这篇学习webpack4使用的版本

    在demo1目录下新建src目录,在src目录下新建index.js

    mkdir src 
    cd src
    touch index.js
    

    demo1目录结构

    demo1
    ├── demo1/package.json
    ├── demo1/package-lock.json
    └── demo1/src
        └── demo1/src/index.js
    
    

    在index.js中加写代码,例如:

    //index.js
    
    let demo='webpack4'
    console.log(demo)
    

    webpack4可以零配置打包,webpack4会默认对src目录下的index.js文件打包。
    现在运行npx webapck,可以在demo1目录下看到dist目录,dist目录下有一个main.js文件,这就是打包后的文件,打开查找可以看到 console.log(demo),说明index.js被打包到main.js中。

    2.webpack4的简单配置

    demo1目录下新建webpack配置文件webpack.config.js

    配置webpack--webpack.config.js
    const path = require('path')
    
    module.exports={
      //mode development: 开发环境 production:生产环境
      mode: 'development', 
      //entry 入口文件配置  
      entry: {
        index: './src/index.js'
      },
      //打包完成后文件输出位置配置
      output: {
        //filename 设置打包后文件的名字
        //如果不设置filename,则文件的名字跟入口文件路径的属性名一样
        filename: 'bundle.js',
        //path 设置打包完成后文件输出路径
        path: path.resolve(__dirname,'dist')
      }
    }
    

    运行npx webpack命令
    npx webpack等价于npx webpack --config webpack.config.js

    webapck配置文件命名为webpack.config.js时可以省略--config *.js,直接执行npx webpack即可,否则执行npx webpack --config 配置文件名

    看到dist目录下有bundle.js,说明webpack配置正确。

    package.json中配置'script'

    "scripts": {
        "build": "webpack"
      }
    

    添加"build": "webpack",运行npm run build效果等价于执行npx webpack命令。

    配置webpack.config.js的modoule对象

    loader的用法
    file-loader的使用

    安装file-loader
    npm i file-loader --save-dev

    webpack.config.js
    const path = require('path')
    
    module.exports={
      //mode development: 开发环境 production:生产环境
      mode: 'development', 
      //entry 入口文件配置  
      entry: {
        index: './src/index.js'
      },
      //打包完成后文件输出位置配置
      output: {
        //filename 设置打包后文件的名字
        //如果不设置filename,则文件的名字跟入口文件路径的属性名一样
        filename: 'bundle.js',
        //path 设置打包完成后文件输出路径
        path: path.resolve(__dirname,'dist')
      },
      module: {
        rules:[
          {
            test: /.(png|jpg|gif)$/,
            use: {
              loader: 'file-loader',
              options: {
                name: '[name].[ext]', //对打包后的图片命名
                outputPath: 'images/' //打包后图片输出的位置 distimages
              }
            }
          }
        ]
      }
    }
    

    在src目录下新建images文件夹,存放图片

    修改index.js
    //index.js
    
    //import导入图片
    import image from './images/11.png'
    
    let img=new Image()
    img.src=image
    document.body.append(img)
    

    运行npm run build后的目录结构如下

    demo1
    ├── demo1/dist
    │   ├── demo1/dist/bundle.js
    │   ├── demo1/dist/images
    │   │   └── demo1/dist/images/11.png
    │   └── demo1/dist/index.html
    ├── demo1/package.json
    ├── demo1/package-lock.json
    ├── demo1/src
    │   ├── demo1/src/images
    │   │   └── demo1/src/images/11.png
    │   └── demo1/src/index.js
    └── demo1/webpack.config.js
    
    

    在dist目录下出现了images目录和图片,创建index.html,引入js文件,在浏览器中打开就可以看到图片。

    url-loader的使用

    url-loader安装
    npm i url-loader -D

    url-loader的作用跟'file-loader'的作用很类似

    webpack.config.js

      module: {
        rules:[
         {
            test: /.(png|jpg|gif)$/,
            use: {
              loader: 'url-loader',
              options: {
                name: '[name].[ext]', //对打包后的图片命名
                outputPath: 'images/', //打包后图片放的位置 distimages
                limit: 20480
                //1024 == 1kb  
                //小于20kb时打包成base64编码的图片否则单独打包成图片
              }
            }
          }
        ]
      }
    }
    

    limit属性:当图片大小大于属性值时打包成图片输出到images目录下,否则打包成base64编码的图片注入bundle.js中

    因为base64编码的图片导致打包文件变大,所以图片比较小时打包成base64编码的图片,图片比较大时单独打包成一张图片。

    cssscss的打包

    安装相应的loader
    npm i css-loader style-loader -D
    npm i node-sass sass-loader -D
    npm i postcss-loader -D
    npm i autoprefixer -D

    postcss-loaderautoprefixer配合使用可以在打包过程中自动添加前缀

    在demo1根目录下新建postcss.config.js,配置如下

    //postcss.config.js
    module.exports={
      plugins: [
        require('autoprefixer')
      ]
    }
    
    在webpack.config.js文件的`module.rules'数组中添加配置
    module:{
      rules:[
        {
          test: /.css$/,
           use:[
             'style-loader',
             'css-loader',
             'postcss-loader'  
             //加前缀  npm i autoprefixer -D
             //在项目根目录下配置postcss.config.js文件
           ]
         },
         {
            test: /.scss$/,
            use:[
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 2,
                  //importLoaders
                  //用于配置css-loader作用于@import的资源之前有多少个loader先作用于@import的资源
                }
              },
              'postcss-loader',
              'sass-loader'
            ]
          }
      ]
    }
    

    demo1src下新建css文件夹,在css文件夹下新建style.cssindex.scss文件。

    index.scss

    body{
      border: 1px solid red;
       300px;
      height: 300px;
      img{
         100px;
        height: 100px;
        border-radius: 10%;
        transform: translate(100px,100px);
      }
    }
    

    style.css

    body{
      border-radius: 10%;
    }
    

    index.js

    //index.js
    
    import image from './images/11.png'
    import './style.css'
    import './index.scss'
    
    let img=new Image()
    img.src=image
    document.body.append(img)
    

    运行npm run build,在dist目录下新建index.html,引入js文件,在浏览器中打开就可以看到效果,说明打包成功。

    css模块化

    css模块化,避免页面样式之间相互影响
    webpack.config.js中的css-loader添加modules: true

    //webpack.config.js
    
    module:{
      rules: [
          {
            test: /.css$/,
            use:[
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  modules: true
                }
              },
              'postcss-loader'  
              //加前缀  npm i autoprefixer -D
              //在项目根目录下配置postcss.config.js文件
            ]
          },
          {
            test: /.scss$/,
            use:[
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 2,
                  //importLoaders
                  //用于配置css-loader作用于@import的资源之前有多少个loader先作用于@import的资源
                  modules: true //加载css模块化打包,避免样式文件之间相互影响
                }
              },
              'postcss-loader',
              'sass-loader'
            ]
          }
      ]
    }
    

    修改index.js
    .img是类名需要在样式文件中提前写好样式

    //index.js
    
    import image from './images/11.png'
    import style from './css/style.css'
    // import style from './css/index.scss'
    
    let img=new Image()
    img.src=image
    
    //style.img .img是scss文件中写好的类名
    img.classList.add(style.img)
    
    document.body.append(img)
    

    index.scss

    body{
      border: 1px solid red;
       300px;
      height: 300px;
      img{
         100px;
        height: 100px;
        border-radius: 10%;
        transform: translate(100px,100px);
      }
      .img{
        border: 10px solid royalblue;
      }
    }
    

    style.css

    body{
      border-radius: 10%;
    }
    body .img{
      border: 10px solid yellow;
    }
    

    结果

    可以看到添加了一个class,类名是一串比较复杂的字符串,从而避免这个样式对别的元素产生影响。


    二、进一步配置webpack4,使自己在学习webpack4的时候更方便

    这一部分主要是学会使用html-webpack-pluginclean-webpack-plugin插件,主要是学会配置devServer以及使用webpack的热模块替换功能。

    首先,在webpack4目录下新建demo2文件夹将demo1目录下的所有东西复制到demo2

    在上一部分我们都是手动在dist目录下创建index.html引入js文件查看打包结果,这样会很麻烦。我们可以使用html-webpack-plugin来自动生产index.html,并且能够自动引入打包好的文件,直接打开生产的html就可以看到打包结构。

    1.html-webpack-plugin的使用

    安装
    npm i html-webpack-plugin -D

    webpack.config.js中配置plugins配置项

    const path = require('path')
    const htmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports={
      //mode development: 开发环境 production:生产环境
      mode: 'development', 
      //entry 入口文件配置  
      entry: {
        index: './src/index.js'
      },
      //打包完成后文件输出位置配置
      output: {
        //filename 设置打包后文件的名字
        //如果不设置filename,则文件的名字跟入口文件路径的属性名一样
        filename: 'bundle.js',
        //path 设置打包完成后文件输出路径
        path: path.resolve(__dirname,'dist')
      },
      module: { },
      plugins: [
        new htmlWebpackPlugin({
          template: './index.html'
        })
      ]
    }
    

    在demo2目录下新建index.html作为模板

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>模板</title>
    </head>
    <body>
      <div id="root"></div>
    <script type="text/javascript" src="bundle.js"></script></body>
    </html>
    

    运行npm run build,可以看到dist目录下自动生产index.html,并且还自动引入js文件

    2.clean-webpack-plugin的使用

    每次打包生成的dist目录,如果改一次代码,都得要删除一次dist目录,这样很麻烦,可以通过clean-webpack-plugin在每次打包的前自动清空dist目录。

    安装
    npm i clean-webpack-plugin -D

    webpack.config.jsplugins中配置如下

    const path = require('path')
    const htmlWebpackPlugin = require('html-webpack-plugin')
    const cleanWebpackPlugin = require('clean-webpack-plugin')
    
    module.exports={
      //mode development: 开发环境 production:生产环境
      mode: 'development', 
      //entry 入口文件配置  
      entry: {
        index: './src/index.js'
      },
      //打包完成后文件输出位置配置
      output: {
        //filename 设置打包后文件的名字
        //如果不设置filename,则文件的名字跟入口文件路径的属性名一样
        filename: 'bundle.js',
        //path 设置打包完成后文件输出路径
        path: path.resolve(__dirname,'dist')
      },
      module: { },
      plugins: [
        new htmlWebpackPlugin({
          template: './index.html'
        }),
       new cleanWebpackPlugin()
      ]
    }
    

    运行npm run build,可以自己测试,每次打包前都会把dist目录下的文件删掉。

    3.entryoutput多入口配置

    module.exports={
      //mode development: 开发环境 production:生产环境
      mode: 'development', 
      //entry 入口文件配置  
      entry: {
        index: './src/index.js',
        main: './src/index.js'
      },
      //打包完成后文件输出位置配置
      output: {
        //filename 设置打包后文件的名字
        //如果不设置filename,则文件的名字跟入口文件路径的属性名一样
        // 占位符
        filename: '[name].js',
        //path 设置打包完成后文件输出路径
        path: path.resolve(__dirname,'dist')
      },
    }
    

    当有多入口的时候,需要修改filename的属性值为'[name].js'

    运行npm run build,就会在dist目录下生产index.jsmain.js

    4.配置devtool

    devtool决定源代码与打包后的代码之间的映射关系,方便对代码进行调试。

    开发环境推荐: cheap-module-eval-source-map
    生产环境推荐: cheap-module-source-map

    devtool具体内容请查阅:文档:devtool

    module.exports={
      devtool: 'cheap-module-eval-source-map',
      //开发环境推荐: cheap-module-eval-source-map
      //生产环境推荐: cheap-module-source-map
    }
    

    5.配置devServer

    文档:devServer

    安装webpack-dev-server
    npm i webpack-dev-server -D

    在webpack.config.js中添加以下内容
    module.exports={
      devServer: {
        contentBase: './dist',
        // open: true, //自动打开浏览器
        // port: 8080, //默认8080
      }
    }
    

    修改package.jsonscript,添加 "start": "webpack-dev-server"

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

    执行npm run start后打开浏览器就可以看到效果,当我们修改代码的时候页面就会重新刷新。

    有时我们希望页面只更新我们修改的那一部分就可以了,而并不是刷新页面,所以需要启用webpack的热模块替换功能。

    6.启用webpack的热模块替换功能

    首先修改index.js
    import './css/style.css'
    
    var btn = document.createElement('button')
    btn.innerHTML='新增'
    document.body.appendChild(btn)
    
    btn.onclick=function(){
      var div=document.createElement('div')
      div.innerHTML='items'
      document.body.appendChild(div)
    }
    
    修改style.css,删掉index.scss
    //style.css
    body{
      background: yellow;
    }
    div:nth-of-type(odd){
      background: chartreuse;
      font-size: 18px;
    }
    
    在webpack.config.js中

    引入webpack:const webpack=require('webpack')
    添加内容如下:

    const webpack=require('webpack')
    module.exports={
      plugins: [
        new webpack.HotModuleReplacementPlugin() //启用HMR
      ],
      devServer: {
        contentBase: './dist',
        // open: true, //自动打开浏览器
        // port: 8080,
        hot: true, //启用webpack的热模块替换功能
        hotOnly: true 
        //devServer.hot在没有页面刷新的情况下启用热模块替换作为构建失败时的后备
      }
    }
    

    hot:true启用HotModuleReplacementPlugin(HMR)

    执行npm run start,在浏览器打开以后,修改div的背景颜色,只有改变的地方才发生变化,但是页面并没有刷新。

    在demo2的src目录下新建number.js

    number.js
    var number=function(){
      var div=document.createElement('div')
      div.setAttribute("id","number")
      div.innerHTML=103
      document.body.appendChild(div)
    }
    
    export default number
    

    修改index.js

    import number from './number'
    number()
    

    运行npm run start,在浏览器中打开看结果,然后在number.js中修改内容,但是页面并没有显示修改后的内容

    这是因为在引入js文件的时候,热模块替换的实现方式有点区别。

    js要达到热模块替换的效果,得要if(module.hot){}这一部分代码,否则就算改了代码,页面不刷新,修改的地方在页面上页面变化。

    css样式因为css-loader已经实现if(module.hot){}这一部分,所以不需要单独实现这一部分。

    再次修改index.js

    import number from './number'
    number()
    
    if(module.hot){
      module.hot.accept('./number.js',function(){
        number()
        document.body.removeChild(document.getElementById('number'))
      })
    }
    

    运行npm run start,在浏览器中打开看结果,然后在number.js中修改内容,发现页面显示修改后的内容


    三、使用Babel处理js文件

    Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。

    Babel总共分为三个阶段:解析(parse),转换(transform),生成(generate)。

    Babel本身不具有任何转化功能,它把转化的功能都分解到一个个plugin里面。因此当我们不配置任何插件时,经过Babel输出的代码和输入是相同的。

    Babel插件的使用

    1. 将插件的名字增加到配置文件中:项目根目录下创建.babelrc配置文件或是webapck.config.js中配置,一般都是在.babelrc中配置。

    2. 使用 npm install xxx 进行安装

    Babel的配置文件是.babelrc,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。

    该文件用来设置转码规则和插件,基本格式如下。

    {
      "presets": [],
      "plugins": []
    }
    
    

    Babel简单介绍

    preset

    preset(预设)就是一系列插件的集合
    @babel/preset-env包含所有ES6转译为ES5的插件集合

    core-js

    转换一些内置类(Promise, Symbols等等)和静态方法(Array.from等)。

    @babel/core

    是作为Babel的核心存在,Babel的核心api都在这个模块里面。

    babel-loader

    babel-loader在webpack中使用,是webpack和Babel之间的通讯桥梁

    @babel/polyfill介绍

    @babel/preset-env默认只转译js语法,而不转译新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转译。这时就必须使用@babel/polyfill(内部集成了core-jsregenerator)。

    使用时,在所有代码运行之前增加import "@babel/polyfill"

    或者是在webpack.config.js入口配置

    module.exports = {
      entry: ["@babel/polyfill", "./app/js"],
    }
    

    因此必须把@babel/polyfill作为dependencies而不是devDependencies

    @babel/polyfill主要有两个缺点:

    1.使用@babel/polyfill需要做些额外配置,实现打包的时候按需引入,否则会把@babel/polyfill全部注入代码中会导致打出来的包非常大。

    2.@babel/polyfill会污染全局变量。

    Babel7的一个重大变化就是npm package 名称的变化,把所有babel-*重命名为@babel/*,例如:

    • babel-polyfill重命名为@babel/polyfill
    • babel-preset-env重命名为@babel/preset-env

    Babel在webpack中的用法

    首先实现对ES6语法的转译

    在webpack4目录下新建demo3文件夹,将demo2目录下的所有东西复制到demo3中

    安装babel-loader、 @babel/core、@babel/preset-env

    • npm i babel-loader -D
    • npm i @babel/core -D
    • npm i @babel/preset-env -D

    babel-loader@8需要安装@babel/core7.x版本。

    在webpack.config.js配置

    module.exports={
      module: {
        rules:[
          {
            test: /.js$/,
            exclude: /node_modules/,
            use:{
              loader: 'babel-loader',         
              options:{
                presets: [
                  ["@babel/preset-env",{
                    //targets:表示编译出的代码想要支持的浏览器版本
                    targets: {
                      chrome: "67"                 
                    }
                  }]
                ]
              }
            }
          }
        ]
      }
    }
    

    执行npm run buildnpx webpack就可以看到dist目录下的打包文件,但是只是将ES6的语法进行转译,并没有对ES6新API进行转译,所以我们需要配置@babel/polyfill解决这个问题。

    安装@babel/polyfill
    npm i @babel/polyfill --save

    index.js中引入@babel/polyfill

    index.js

    //index.js
    
    import '@babel/polyfill'
    
    let arr=[
      new Promise(()=>{}),
      new Promise(()=>{}),
      2
    ]
    
    arr.map((item)=>{
      console.log(item)
    })
    

    引入@babel/polyfill前,main.js的大小为29.5KB

    引入@babel/polyfill后,main.js的大小为1MB

    注意:以上对比都是在没有targets这个选项的情况下,因为有些浏览器几乎都支持ES6,在这种情况下,@babel/preset-env将不会对代码进行处理。

    这是因为把@babel/polyfill对所有API的实现都注入到打包文件中,但是里面很多的API我们在代码中并没有用到,所以需要修改配置,按需引入对应的API。

    修改webpack.config.js配置

    添加"useBuiltIns": "usage"以后,需要安装core-js@2,并且添加"corejs": 2配置项,这时配置选项比较多,需要在项目根目录下新建.babelrc文件,在这个文件中配置。

    .babelrc配置如下:

    • "useBuiltIns"属性值为"usage"时,会自动引入@babel/polyfill,必须保证已经安装了@babel/polyfill

    • "useBuiltIns"属性值为"usage"时,需要添加"corejs": 2配置项,否则报错,需要安装core-js

    首先删掉index.js中的import '@babel/polyfill'

    安装core-js
    npm i --save core-js@2npm i --save core-js@3

    {
      "presets": [["@babel/preset-env",{
        "useBuiltIns": "usage", //不需要把polly都打包到代码中,根据代码按需转译
        // core-js@3和core-js@2二选一
        //"corejs": 3,  //npm i --save core-js@3
        "corejs": 2  //npm i --save core-js@2
      }]]
    }
    

    修改webpack.config.js,删除options对象

    module.exports={
      module: {
        rules:[
          {
            test: /.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
          }
        ]
      }
    }
    

    执行npm run build,打包后的文件大小为165KB

    但是,在开发类库或是第三方模块时不适合使用@babel/polyfill,所以接下来使用@babel/plugin-transform-runtime来解决这个问题。

    @babel/plugin-transform-runtime、@babel/runtime和@babel/runtime-corejs2的用法

    @babel/runtime-corejs2:是一个包含Babel modular runtime helpersregenerator-runtime以及core-js的库。

    @babel/runtime:是一个包含Babel modular runtime helpersregenerator-runtime的库。

    在配置项中corejs属性值为默认为false,如果需要将PromiseAPI进行转译,则需要设置属性值为2时,并且安装@babel/runtime-corejs2

    安装:

    • npm i @babel/plugin-transform-runtime -D
    • npm i --save @babel/runtime
    • npm i --save @babel/runtime-corejs2

    修改.babelrc文件

    {
      "plugins": [
        ["@babel/plugin-transform-runtime",{
          "helpers": true,
          "regenerator": true,
          "useESModules": false,
          "corejs": 2
        }]
      ]
    }
    

    我们把presets配置项去掉了,然后npm run build打包,打开打包后的main.js查看,虽然把转译了Promise,但是ES6新语法并没被转译,例如:let没有被转译为var

    所以还是需要配置presets,因为"@babel/preset-env"包含了对所有ES6语法转译为ES5插件。

    再次修改.babelrc文件

    {
     "presets": ["@babel/preset-env"],
      "plugins": [
        ["@babel/plugin-transform-runtime",{
          "helpers": true,
          "regenerator": true,
          "useESModules": false,
          "corejs": 2
        }]
      ]
    }
    

    添加presets配置项,然后npm run build打包,打开打包后的main.js查看,可以看到let和箭头函数都被转译为ES5语法了。 


    四、Tree Shaking使用

    首先,在webpack4目录下新建demo4文件夹,将demo3目录下的所有东西复制到demo4中

    Tree Shaking可以用来剔除JavaScript中用不上的死代码。它依赖静态的ES6模块化语法,例如通过importexport导入导出。

    需要注意的是要让Tree Shaking正常工作的前提是JavaScript代码必须采用ES6模块化语法,因为ES6模块化语法是静态的,这让Webpack可以简单的分析出哪些export的被import过了。

    接下来配置WebpackTree Shaking生效

    webpack4默认保留ES6模块化语句,并没有通过Babel将其转换
    修改.babelrc文件为如下:

    //.babelrc
    
    {
       "presets": [["@babel/preset-env",{
          "useBuiltIns": "usage",
          "corejs": 2,
          "modules":false //关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法
          //默认是auto,取值还可以是 amd, umd, systemjs, commonjs,auto等
       }]]
    }
    

    修改webapck.config.js,添加

    optimization: {
      usedExports: true
    }
    

    module.exports{}

    module.exports={
     mode: 'development',
      optimization: {
      //开发坏境使用tree shaking时加usedExports: true
        usedExports: true 
      },
    }
    

    还需通过package.json"sideEffects"属性来告诉webpack哪些模块是可以忽略掉,如果没有则设置为false,来告知webpack,它可以安全地删除未用到的export

    修改package.json

    {
      "name": "your-project",
      "sideEffects": false
    }
    

    在demo4下的src新建math.js

    index.js

    //tree shaking import export
    import {cube} from './math.js'
    
    let component = () => {
      let element = document.createElement('pre')
      element.innerHTML = [
        'Hello webpack!',
        '2 cubed is equal to ' + cube(2)
      ].join('
    
    ');
      console.log(cube)
      
      return element;
    }
    document.body.appendChild(component());
    

    math.js

    export let square= (x) => {
      console.log(x)
      return x * x;
    }
    
    export let cube = (x) => {
      console.log(x)
      return x * x * x;
    }
    

    运行npm run build,然后打开打包后的js文件:main.js找到下面这段文字

    /*!*********************!*
       !*** ./src/math.js ***!
       *********************/
     /*! exports provided: square, cube */
     /*! exports used: cube */
     /***/
    

    从上面这段文字可以看出Tree Shaking生效了,但是在开发环境下,并没有把没有用的代码删掉,因为 环境下还需要对代码进行调试。

    我们已经找出需要删除的“未引用代码(dead code)”,然而,不仅仅是要找出,还要删除它们。为此,我们需要将mode配置选项设置为production,将optimization对象删掉,修改devtool配置选项

    webpack.config.js

    module.exports = {
      mode: 'production',
      devtool: 'cheap-module-source-map'
    }
    

    运行npm run build,查看打包结果就可以看到没有用的代码被删掉了。


    五、DevelomentProduction不同环境的配置

    在webpack4下新建demo5,将demo4下的所有文件复制到demo5中

    因为在不同的环境下,webpack的配置稍微有点区别,如果我们需要在不同的换将下切换,就得修改webpack配置,这是很麻烦而且还容易改错,所以我们需要把配置文件进行拆分。

    在项目根目录下新建build文件夹,然后在build文件夹中新建webpack.dev.jswebpack.prod.jswebpack.base.js三个文件

    webpack.dev.js:是开发环境
    webpack.prod.js:是生产环境
    webpack.base.js:是开发环境和生产环境都用到的配置

    这几个文件之间的结合靠'webpack-merge'这个插件。

    安装
    npm i webpack-merge -D

    webpack.dev.js

    //webpack.dev.js
    
    const webpack=require('webpack')
    const merge = require('webpack-merge')
    const baseConfig=require('./webpack.base')
    
    const devConfig={
      mode: 'development', 
      devtool: 'cheap-module-eval-source-map',
      plugins: [
        new webpack.HotModuleReplacementPlugin()
      ],
      optimization: {
        usedExports: true
      },
      devServer: {
        contentBase: './dist',
        // open: true, //自动打开浏览器
        // port: 8080,
        hot: true, //启用webpack的热模块替换功能
        //hotOnly: true 
        //devServer.hot在没有页面刷新的情况下启用热模块替换作为构建失败时的后备
      }
    }
    
    module.exports=merge(baseConfig,devConfig)
    

    webapck.prod.js

    //webapck.prod.js
    
    const merge = require('webpack-merge')
    const baseConfig=require('./webpack.base')
    
    const prodConfig={
      mode: 'production', 
      devtool: 'cheap-module-source-map'
    }
    
    module.exports=merge(baseConfig,prodConfig)
    

    但是这两个文件还有大量重复的代码,新建webpack.base.js

    //webpack.base.js
    
    const path = require('path')
    const htmlWebpackPlugin = require('html-webpack-plugin')
    const cleanWebpackPlugin = require('clean-webpack-plugin')
    
    module.exports={
      entry: {
        main: './src/index.js'
      },
      output: {
        filename: '[name].js',
        path: path.resolve(__dirname,'dist')
      },
      module: {
        rules:[
          {
            test: /.(png|jpg|gif)$/,
            use: {
              loader: 'url-loader',
              options: {
                name: '[name].[ext]', 
                outputPath: 'images/', 
                limit: 2048           
              }
            }
          },
          {
            test: /.css$/,
            use:[
              'style-loader',
              'css-loader',
              'postcss-loader' 
            ]
          },
          {
            test: /.scss$/,
            use:[
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 2,
                  modules: true 
                }
              },
              'sass-loader',
              'postcss-loader'
            ]
          },
          {
            test: /.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
          }
        ]
      },
      plugins: [
        new htmlWebpackPlugin({
          template: './index.html'
        }),
        new cleanWebpackPlugin(),
      ]
    }
    

    修改package.jsonscript:

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

    开发环境:运行npm run dev,打开浏览器访问http://localhost:8080/就可以看到结果
    生产环境:运行npm run build


    六、SplitChunksPlugin插件的用法

    1.SplitChunksPlugin插件介绍

    webpack 4移除CommonsChunkPlugin,取而代之的是SplitChunksPlugin。下面介绍SplitChunksPlugin的用法。

    webpack4目录下新建demo6目录,将demo5目录下的所有文件都复制到demo6中。

    安装 lodash

    npm i lodash --save

    package.json添加

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

    修改index.js

    import _ from 'lodash'
    console.log(_.join(['lodash', 'babel', 'webpack'], '-'))
    

    运行npm run dev-build后,demo6目录结构如下

    demo6
    ├── demo6/build
    │   ├── demo6/build/webpack.base.js
    │   ├── demo6/build/webpack.dev.js
    │   └── demo6/build/webpack.prod.js
    ├── demo6/dist
    │   ├── demo6/dist/index.html
    │   └── demo6/dist/main.js
    ├── demo6/index.html
    ├── demo6/package.json
    ├── demo6/package-lock.json
    ├── demo6/postcss.config.js
    └── demo6/src
        └── demo6/src/index.js
    

    dist目录下只有main.js一个js文件,lodash库也一起打包到main.js里面,这种情况下会导致文件变大,导致页面加载速度变慢,我们需要把第三库或是需要单独打包的代码给分割出来。

    这时需要使用webpack4自带的插件SplitChunksPlugin,默认情况下它将只会影响按需加载的代码块

    webpack.config.js添加optimization.splitChunks.chunks

    optimization: {
       splitChunks: {
        //chunks: all, async, initial.
        //async针对异步加载的chunk做切割,initial针对初始chunk,all针对所有chunk。
         chunks: 'async'
       }
    }
    

    运行npm run dev-build后,打包后的代码并没有分割。

    修改optimization.splitChunks.chunksall

    optimization: {
       splitChunks: {
        //chunks: all, async, initial.
        //async针对异步加载的chunk做切割,initial针对初始chunk,all针对所有chunk。
         chunks: 'all'
       }
    }
    

    运行npm run dev-build后,demo6目录结构如下

    demo6
    ├── demo6/build
    │   ├── demo6/build/webpack.base.js
    │   ├── demo6/build/webpack.dev.js
    │   └── demo6/build/webpack.prod.js
    ├── demo6/dist
    │   ├── demo6/dist/index.html
    │   ├── demo6/dist/main.js
    │   └── demo6/dist/vendors~main.js
    ├── demo6/index.html
    ├── demo6/package.json
    ├── demo6/package-lock.json
    ├── demo6/postcss.config.js
    └── demo6/src
        └── demo6/src/index.js
    

    可以看到dist目录下多了vendors~main.js文件,说明SplitChunksPlugin插件生效了

    接下来先看optimization.splitChunks的默认配置

    module.exports = {
      //...
      optimization: {
        splitChunks: {
          chunks: 'async',
          minSize: 30000,
          maxSize: 0,
          minChunks: 1,
          maxAsyncRequests: 5,
          maxInitialRequests: 3,
          automaticNameDelimiter: '~',
          name: true,
          cacheGroups: {
            vendors: {
              test: /[\/]node_modules[\/]/,
              priority: -10
            },
            default: {
              minChunks: 2,
              priority: -20,
              reuseExistingChunk: true
            }
          }
        }
      }
    }
    
    • chunks: 表示将对哪些块进行优化,可选async, initial, all,async对异步加载的模块进行分割,initial对初始模块,all对所有模块

    • minSize: 加载的模块不小于30kb才进行分割

    • minChunks: 生成的chunk,共享该模块的chunk必须不小于1时才分割

    • maxAsyncRequests:按需加载时的最大并行请求数

    • maxInitialRequests:入口处的最大并行请求数

    • automaticNameDelimiter:默认情况下,webpack将使用块的名称和名称生成名称(例如vendors~main.js)。此选项指定用于生成的名称的分隔符

    • name:生成chunk的名字,如果设成true,将根据模块和缓存组配置结合生成名称

    • cacheGroups: 缓存组可以继承和/或覆盖任何选项splitChunks.*; 但是test,priority并且reuseExistingChunk只能在高速缓存组级别配置。要禁用任何默认缓存组,请将其设置为false。

    • test:控制此缓存组选择的模块

    • priority:模块可以属于多个缓存组,模块则归于缓存组priority高的

    • reuseExistingChunk: 如果当前块包含已拆分的模块,则将重用它而不是生成新的块。

    从以上可以看出来,默认的配置只对异步加载的模块有效

    修改index.js,异步加载lodash

    function getComponent(){
      return import('lodash').then(({default: _})=>{
        var element=document.createElement('div')
        element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-')
        return element
      })
    }
    
    getComponent().then(element=>{
      document.body.appendChild(element)
    })
    

    这时运行npm run dev-build会报错,需要下载安装 @babel/plugin-syntax-dynamic-import
    npm i @babel/plugin-syntax-dynamic-import -D

    在.babelrc中添加

     "plugins": ["@babel/plugin-syntax-dynamic-import"] 
    

    再次运行npm run dev-build,此时打包成功,目录结构如下

    demo6
    ├── demo6/build
    │   ├── demo6/build/webpack.base.js
    │   ├── demo6/build/webpack.dev.js
    │   └── demo6/build/webpack.prod.js
    ├── demo6/dist
    │   ├── demo6/dist/0.js
    │   ├── demo6/dist/index.html
    │   └── demo6/dist/main.js
    ├── demo6/index.html
    ├── demo6/package.json
    ├── demo6/package-lock.json
    ├── demo6/postcss.config.js
    └── demo6/src
        └── demo6/src/index.js
    

    可以看到dist目录下0.js,就是对lodash打包后的文件,有时我们希望能够改变0.js的名字

    修改index.js,添加/* webpackChunkName:"lodash" */

    function getComponent(){
      return import(/* webpackChunkName:"lodash" */'lodash').then(({default: _})=>{
        var element=document.createElement('div')
        element.innerHTML=_.join(['lodash', 'babel', 'webpack'], '-')
        return element
      })
    }
    
    getComponent().then(element=>{
      document.body.appendChild(element)
    })
    

    运行npm run dev-build,发现0.js变为vendors~lodash.js

    也可以通过设置optimization.splitChunks.cacheGroups.vendors.name来修改打包后的文件名字

    修改optimization.splitChunks配置

    optimization: {
        splitChunks: {
          chunks: 'all',
          minSize: 30000,
          minChunks: 1,
          maxAsyncRequests: 5,
          maxInitialRequests: 3,
          automaticNameDelimiter: '~',
          name: true,
          cacheGroups: {
            vendors: {
              test: /[\/]node_modules[\/]/,
              priority: -10,
              name: 'vendors'
            },
            default: {
              minChunks: 1,
              priority: -20,
              reuseExistingChunk: true
            }
          }
        }
      },
    

    运行npm run dev-build,发现打包后的文件名字变为vendors.js

    以上就是SplitChunksPlugin的简单用法

    详情请访问官网


    七、css代码的分割

    MiniCssExtractPlugin插件将CSS提取到单独的文件中。它为每个包含CSS的JS文件创建一个CSS文件,在webpack 4才能使用

    在webpack目录下新建demo7,将demo6下的所有文件都复制到demo7中,进入demo7

    安装
    npm install --save-dev mini-css-extract-plugin

    官网提示,这个插件应该在生产环境中使用,所以修改webpack的相关配置。

    首先将webpack.base.js中对css和scss处理的loader配置分别复制粘贴(在webpack.base.js中删掉这一部分)到webpack.prod.jswebpack.dev.js中。

    webpack.prod.js中引入
    const miniCssExtractPlugin = require('mini-css-extract-plugin')
    修改后的相关配置如下:

    webpack.base.js
    const path = require('path')
    const htmlWebpackPlugin = require('html-webpack-plugin')
    const cleanWebpackPlugin = require('clean-webpack-plugin')
    
    module.exports = {
      entry: {
        main: './src/index.js' //对应filename
        //入口文件引入的模块,分割打包的名字对应chunkFilename
      },
      output: {
        filename: '[name].js', 
        chunkFilename: '[name].chunk.js',
        path: path.resolve(__dirname, '../dist')
      },
      module: {
        rules: [
          {
            test: /.(png|jpg|gif)$/,
            use: {
              loader: 'url-loader',
              options: {
                name: '[name].[ext]',
                outputPath: 'images/',
                limit: 2048
              }
            }
          },
          {
            test: /.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
          }
        ]
      },
    
      plugins: [
        new htmlWebpackPlugin({
          template: './index.html'
        }),
        new cleanWebpackPlugin(),
      ],
      optimization: {
        usedExports: true,
        splitChunks: {
          //chunks: all, async, initial.
          //async 只对异步分割有效
          // initial 同步
          //all 要配置cacheGroups
          chunks: 'all',
          // 引入的包或是库大于30kb才对代码进行分割
          minSize: 30000,
    
          maxSize: 0, //没多大意义
          minChunks: 1, //当一个模块至少引入多少次时才会进行代码分割
          maxAsyncRequests: 5, //同时加载的模块数最多是5个
          maxInitialRequests: 3,
          automaticNameDelimiter: '~', //打包后的文件名字之间的连接符
          name: true,
          cacheGroups: { //缓存组
            // vendors: false,
            vendors: { 
              //同步 检查是否在node_modules里面
              test: /[\/]node_modules[\/]/,
              priority: -10, //优先级
              name: 'vendors'
            },
            // default: false
            default: {
              minChunks: 1,
              priority: -20,
              reuseExistingChunk: true, //如果模块已经打包过了就引用之前打包好的模块
              // filename: 'common.js'
            }
          }
        }
      }
    }
    
    webpack.prod.js
    const merge = require('webpack-merge')
    const baseConfig=require('./webpack.base')
    const miniCssExtractPlugin = require('mini-css-extract-plugin')
    
    const prodConfig={
      mode: 'production', 
      devtool: 'cheap-module-source-map',
      module: {
        rules: [
          {
            test: /.css$/,
            use: [
              miniCssExtractPlugin.loader,
              'css-loader',
              'postcss-loader'
            ]
          },
          {
            test: /.scss$/,
            use: [
              miniCssExtractPlugin.loader,
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 2,
                  modules: true
                }
              },
              'postcss-loader',
              'sass-loader'
            ]
          },
        ]
      },
     
      plugins: [
        new miniCssExtractPlugin({
          filename: '[name].css',
          chunkFilename: '[name].chunk.css'
        })
      ]
    }
    
    module.exports=merge(baseConfig,prodConfig)
    
    webapck.dev.js
    const webpack=require('webpack')
    const merge = require('webpack-merge')
    const baseConfig=require('./webpack.base')
    
    const devConfig={
      mode: 'development', 
      // devtool: 'cheap-module-eval-source-map',
      module: {
        rules: [
          {
            test: /.css$/,
            use: [
              'style-loader',
              'css-loader',
              'postcss-loader'
            ]
          },
          {
            test: /.scss$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 2,
                  modules: true
                }
              },
              'sass-loader',
              'postcss-loader'
            ]
          },
        ]
      },
      plugins: [
        new webpack.HotModuleReplacementPlugin()
      ],
      
      devServer: {
        contentBase: './dist',
        // open: true, //自动打开浏览器
        // port: 8080,
        hot: true, //启用webpack的热模块替换功能
        //hotOnly: true 
        //devServer.hot在没有页面刷新的情况下启用热模块替换作为构建失败时的后备
      }
    }
    
    module.exports=merge(baseConfig,devConfig)
    

    需要注意的一点就是,在前面的时候,我们配置tree shaking的时候,在package.json添加了sideEffects配置项,需要修改这个配置项为

    "sideEffects": [
        "*.css"
      ],
    

    否则通过import './*.css'方式引入的css文件会被删除掉。

    运行npm run build,我们就可以看到dist目录下,css文件被单独分割出来了。

    官网还提供一个插件可以对css文件进行压缩

    安装
    npm i optimize-css-assets-webpack-plugin -D

    修改webpack.prod.js
    //引入
    const optimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
    //添加
    optimization: {
        minimizer: [new optimizeCSSAssetsPlugin({})],
      },
    

    再次运行npm run build,打开打包后的css文件,就发现css文件被压缩了。

    更具体的使用方法请看官网


    代码都已经上传到github:github:webpack4

  • 相关阅读:
    POJ 3630 Phone List/POJ 1056 【字典树】
    HDU 1074 Doing Homework【状态压缩DP】
    POJ 1077 Eight【八数码问题】
    状态压缩 POJ 1185 炮兵阵地【状态压缩DP】
    POJ 1806 Manhattan 2025
    POJ 3667 Hotel【经典的线段树】
    状态压缩 POJ 3254 Corn Fields【dp 状态压缩】
    ZOJ 3468 Dice War【PD求概率】
    POJ 2479 Maximum sum【求两个不重叠的连续子串的最大和】
    POJ 3735 Training little cats【矩阵的快速求幂】
  • 原文地址:https://www.cnblogs.com/qfstudy/p/10775561.html
Copyright © 2011-2022 走看看