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

  • 相关阅读:
    js 格式化相关的时间
    JCE无限制权限策略文件
    Java设计模式之《观察者模式》及应用场景
    Idea for Mac 过期 IntelliJ IDEA 2017 完美注册方法(附idea for Mac破解方法)
    Macbook系统环境安装wget的2个方法
    Mac上brew&thrift安装 以及在thrift架构下,自己新作了maven的小例 Demo
    idea编译器光标变为insert状态
    配置自己的Maven方式并使用Maven 运行项目Idea的maven的项目
    SourceTree 如何下载git 管理的代码-如何创建分支,删除分支,提交代码,回退代码
    ultraEdit MAC 破解方法
  • 原文地址:https://www.cnblogs.com/qfstudy/p/10775561.html
Copyright © 2011-2022 走看看