webpack4

模块化打包工具的由来:ES Modules存在环境兼容问题、模块文件过多,网络请求频繁,而且所有的前端资源都需要模块化。

打包工具解决的是前端整体的模块化,并不单指JavaScript模块化

webpack

webpack:模块打包器、模块加载器、代码拆分、载入资源模块

webpack的基本使用

  • 先在项目的根目录下执行yarn init -y,创建package.json
  • 安装webpack相关依赖:yarn add webpack webpack-cli --dev
  • 查看webpack版本:yarn webpack --version, 4.43.0
  • 执行yarn webpack进行打包,生成了dist目录,里面有main.js文件
  • 修改index.html中的index.js的路径为dist/main.js,并且去掉script标签的type=module的属性
  • 去package.json的scripts中定义一个build任务:"build": "webpack",以后执行yarn build进行打包

webpack的配置文件

webpack.config.js文件是运行在nodejs文件下的js文件,我们需要按照CommonJS的方式编写代码。这个文件需要导出一个对象,我们完成对应的配置选项。

webpack.config.js文件内容:

const path = require('path')

module.exports = {
  entry: './src/index.js', // 指定打包入口文件,如果是相对路径,前面的点不能少
  output: {
    filename: 'bundle.js', // 输出文件的名称
    path: path.join(__dirname, 'output'), // 输出路径,为绝对路径
  }
}

webpack工作模式

直接执行webpack打包的时候,控制台会有警告:

WARNING in configuration The ‘mode’ option has not been set, webpack will fallback to ‘production’ for this value. Set ‘mode’ option to ‘development’ or ‘production’ to enable defaults for each environment. You can also set it to ‘none’ to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

说没有指定工作模式,默认以生产模式打包,会进行代码的压缩。

我们可以通过cli命令指定工作模式,就是增加一个--mode的参数,属性有三种选择,production、development、none

  • production:生产模式会默认启动优化,优化我们的打包结果
  • development:开发模式,会自动优化打包的速度,添加一些调试过程中的辅助到代码中
  • none:原始状态的打包,不会做任何处理

可以通过yarn webpack --mode development来执行.

此外,还可以在webpack的配置文件中指定工作模式,也就是增加一个mode属性,例如:mode: "development"

webpack资源模块加载

将配置文件中的entry属性的值改为./src/main.css,然后执行打包命令yarn webpack,会报错:

ERROR in ./src/main.css 1:5 Module parse failed: Unexpected token (1:5) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

body { | margin: 0 auto; | padding: 0 20px; error Command failed with exit code 2.

因为webpack默认会把文件当做js解析,所以打包css文件时,文件内容不符合JS语法则报错了,报错中提示我们可以寻找正确的loader去解析代码。webpack内部的loader只能解析js,所以我们要手动安装css-loader去处理css代码。

执行命令:yarn add css-loader --dev

然后在webpack的配置文件中增加属性:

module: {
  rules: [
    {
      test: /.css$/,
      use: 'css-loader'
    }
  ]
}

我们增加外部的loader需要在配置文件中增加资源模块module属性,属性值是一个对象,对象中有一个rules数组,数组里每个元素都是一个对象,对象中的test属性是正则式,指明要处理的资源文件,use属性是对该资源进行处理的loader名称。

再次执行打包命令,发现css没有作用,是因为我们使用css-loader只是对css文件进行了打包,但是并没有作用到页面上,接下来还要安装一个style-loader,执行命令:yarn add style-loader --dev

style-loader是将css-loader处理后的结果,通过style的形式追加到页面上

然后将配置文件中的rules对应的处理css资源模块的use属性由'css-loader'改为['style-loader', 'css-loader'],use配置了多个loader,是一个数组,里面的loader从右往左执行,所以要将css-loader写在后面,我们要先用css-loadercss代码转化成js模块,才可以正常打包。

webpack 导入资源模块

虽然webpack的入口文件可以是别的类型文件,但由于前端项目是由JS驱动,所以我们开发时一般将入口文件设置为JS文件,需要用到CSS时,就直接在JS文件中通过import导入即可,如:import './main.css'

webpack建议我们根据代码的需要在JS中动态导入资源文件,因为需要资源的不是应用,而是代码。因为是JavaScript驱动了整个前端应用,这样做的好处是:

  • 逻辑合理,JS确实需要这些资源文件
  • 确保上线资源不缺失,都是必要的

webpack文件资源加载器

安装文件资源加载器:yarn add file-loader --dev,相当于直接拷贝物理文件。不过此时资源文件路径会出现问题,webpack默认认为它打包过后的文件会放在网站的根目录下面,此时需要在配置文件中的output属性中指定publicPath属性值为dist/,即:publicPath: 'dist/',这样在打包时,文件的输出路径前面会拼接上publicPath的值。

webpack URL 加载器

格式:协议 + 媒体类型和编码 + 文件内容

格式: data:[<mediatype>][;base64],<data>

例如:data:text/html;charset=UTF-8,<h1>html content</h1>

  • 先安装url-loader:yarn add url-loader --dev
  • 修改png文件的loader为url-loader
{
  test: /.png$/,
    // use: 'file-loader',
  use: 'url-loader'
}
  • 执行yarn webpack,此时的png文件的URL则为data协议的了。

最佳使用方式:

  • 小文件使用Data URLs,减少请求次数
  • 大文件独立提取存放,提高加载速度

配置方式:

{
  	test: /.png$/,
    // use: 'file-loader',
    use: {
        loader: 'url-loader',
        options: {
          limit: 10 * 1024, // 单位是字节 10KB
        }
    }
}
  • 超过10KB的文件单独提取存放
  • 小于10KB文件转换为Data URLs嵌入代码中

注意:这种方式还是要安装file-loader,因为对超出大小的文件还是会调用file-loader,如果没有file-loader会报错。

webpack 常用加载器分类

  1. 编译转换类,转换为JS代码,如css-loader
  2. 文件操作类,将资源文件拷贝到输出目录,将文件访问路径向外导出,如:file-loader
  3. 代码检查器,统一代码风格,提高代码质量,如:es-loader

webpack 处理ES2015

因为模块打包需要,所以处理importexport,除此之外,并不能转换其他的ES6特性。如果想要处理ES6,需要安装转化ES6的编译型loader,最常用的就是babel-loaderbabel-loader依赖于babel的核心模块,@babel/core@babel/preset-env

  • 执行命令:yarn add babel-loader @babel/core @babel/preset-env --dev
  • 修改js的loader
			{
        test: /.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        },
         exclude: /(node_modules)/, // 这里很重要,千万别忘了,否则会出错的。
      }

注意:

Webpack只是打包工具,

加载器可以用来编译转化代码

webpack的模块加载方式

  1. 遵循ES Modules标准的import声明

  2. 遵循CommonJS标准的require函数。对于ES的默认导出,要通过require('./XXX').default的形式获取

  3. 遵循AMD标准的define函数和require函数

  4. Loader加载的非JavaScript也会触发资源加载

    1. 样式代码中的@import指令和url函数

      @import url(reset.css);
      
      body {
        margin: 0 auto;
        padding: 0 20px;
        max-width: 800px;
        background: url(1.png);
        background-size: cover;
      }
      

      css-loader在处理css代码时,遇到了background属性中的url函数,发现是引入的资源文件是png格式的文件,则将这个资源文件 交给url-loader处理

    2. HTML代码中的图片标签的src属性

      		{
              test: /.html$/,
              use: {
                loader: 'html-loader',
                options: {
                   // html-loader默认只处理页面中的img标签的src属性的资源文件,所以指定其他标签的资源文件也要处理
                  attributes: {
                    list: [
                      {
                        tag: 'img',
                        attribute: 'src',
                        type: 'src'
                      },
                      {
                        tag: 'a',
                        attribute: 'href',
                        type: 'src'
                      }
                    ]
                  }
                }
              }
            }
      

webpack的核心工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnUbVWGT-1645175488344)(/Users/panda/Downloads/webpack.png)]

webpack Loader的工作原理

  • 实现一个markdown的文件加载器。

Loader作为webpack的核心机制,内部的工作原理也非常简单,我们通过开发一个自己的loader,来深入了解loader的工作原理。

我们的需求是实现一个markdown文件的加载器,这个加载器可以在代码当中直接导入markdown文件。markdown文件一般是被转换为html过后再去呈现到页面上的,所以我们导入的markdown文件得到的结果就是转换过后的html字符串。

在项目的根目录下新建一个markdown-loader.js文件,每一个webpackloader都需要去导出一个函数,这个函数就是我们这个loader的对我们所加载到的资源的一个处理过程,它的输入就是我们资源文件的内容,输出就是我们此次加工过后的一个结果。

那我们通过source参数去接收输入,然后通过我们的返回值去输出,我们先尝试打印一下这个source,然后直接去返回一个hello,我们去看一下结果,我们回到webpack配置文件中去添加一个加载新的规则配置,我们匹配到的扩展名就是.md,就是我们刚刚所编写的markdown-loader的模块,我们的use属性不仅仅只可以使用模块的名称,其实对于模块的文件路径也是可以的,这一点其实与node当中的require函数是一样的,所以直接使用相对路径去找到这个markdown-loader,配置好过后,运行打包命令,打包过程当中命令行确实打印出来了我们所导入的markdown的内容,那这也就意味着我们的source确实是所导入的文件内容,但是它同时也爆出一个解析错误,说的是you many need additional load to handle the result of this loader,就是我们还需要一个额外的加载器来去处理我们当前的加载结果,那这究竟是为什么呢?

module.exports = source => {
  console.log(source)
  return 'hello'
}

其实webpack的加载资源的过程有点类似于一个工作管道,你可以在这个过程当中一次去使用多个loader,但是,还要求我们最终这个管道工作过后的结果必须是一段JavaScript代码,因为我们这返回的内容是一个hello,它不是一个标准的JavaScript代码,所以我们这才会出现这样的错误提示,那知道这个错误的原因过后,解决的办法其实也就很明显了,那要么就是我们这个loader的直接去返回一段标准的JavaScript代码,要么就是我们再去找一个合适的加载器,接着去处理我们这里返回的结果。

我们先来尝试第一种办法。回到我们markdown-loader.js的当中,我们将返回的这个内容修改为'console.log("hello")',那这就是一段标准的JavaScript代码,然后我们再一次运行打包,那此时打包过程当中就不再会报错了。

module.exports = source => {
  console.log(source)
  return 'console.log("hello")'
}

接下来我们一起来看一下打包过后的结果究竟是什么样的,我们打开bundle.js当中,然后我们找到最后一个模块,可以看到,webpack打包的时候就是把我们刚刚loader加载过后的结果也就是返回的那个字符串直接拼接到我们这个模块当中了,那这也就解释了刚刚为什么说loader的管道最后必须要去返回JavaScript代码的原因,因为如果说你随便去返回一个内容的话,那放到这里语法就有可能不通过。

那知道了这些过后,我们再回到markdown-loader.js的当中,然后接着去完成我们刚刚的需求,我们先去安装一个markdown解析的模块叫做marked,安装命令为:yarn add marked --dev

安装完成过后,我们再回到代码当中去导入这个模块。然后在我们的加载器当中去使用这个模块,去解析来自参数当中的这个source,我们的返回值就是一段html字符串,也就是转换过后的结果,但是如果直接返回这个html的话,那就会面临刚刚同样的问题,正确的做法就是把这段html变成一段JavaScript代码,其实我们希望是把这一段html作为我们当前这个模块导出的字符串,也就是我们希望通过export导出这样一个字符串,但是如果说我们只是简单的拼接的话,那我们html当中存在的换行符还有它内部的一些引号,拼接到一起就有可能造成语法上的错误,所以说这里我使用一个小技巧,就是通过JSON.stringify先将这个字符串转换为一个标准的JavaScript格式字符串,那此时内部的引号以及换方符都会被转义过来,然后我们再参与拼接,那这样的话就不会有问题了,我们再次运行打包,看一下打包的结果,那此时我们所看到的结果就是我们所需要的了,当然了,除了module.exports这种方式有外,webpack的还允许我们在返回的代码当中直接去使用ESModule的方式去导出.

const marked = require('marked')

module.exports = source => {
  // console.log(source)
  // return 'console.log("hello")'
  const html = marked(source)
  console.log(html)
  // 两种导出方式:
  // return `module.exports=${JSON.stringify(html)}`
  return `export default ${JSON.stringify(html)}`
}

通过第一种方式解决了我们刚刚所看到的那样一个错误,我们再来尝试一下刚刚所说的第二种方法,那就是在我们markdown-loader的当中去返回一个html的字符串,然后我们交给下一个loader处理这个html的字符串,我们直接去返回marked的解析过后的html,然后我们再去安装一个用于去处理html加载的loader,叫做html-loader,完成过后,我们回到配置文件当中,我们把use属性修改为一个数组,那这样的话我们的loader工作过程当中就会依次去使用多个loader,那不过这里需要注意,就是它的执行顺序是从数组的后面往前面,那也就是说我们应该把先执行的loader放到后面,后执行的loader放到前面。

const marked = require('marked')

module.exports = source => {
  // console.log(source)
  // return 'console.log("hello")'
  const html = marked(source)
  console.log(html)
  return html
}
module: {
    rules: [
      {
        test: /.md$/,
        use: ['html-loader', './markdown-loader.js']
      }
    ]
  }

回到命令行进行打包,此时我们打包的结果仍然是可以的,我们marked处理完的结果是一个html的字符串,然后这个html字符串交给了下一个loader,也就是html-loader,那这个loader又把它转换成了一个导出这个字符串的一个JavaScript代码,那这样的话我们webpack再去打包的时候就可以正常工作了。

那通过以上的这些尝试我们就发现了loader它内部的一个工作原理其实非常简单,就是一个从输入到输出之间的一个转换,那除此之外,我们还了解了loader,它实际上是一种管道的概念,我们可以将我们此次的这个loader的结果交给下一个loader去处理,然后我们通过多个loader去完成一个功能,那例如我们之前所使用的css-loaderstyle-loader之间的一个配合,包括我们后面还会使用到的,像sass或者less这种loader他们也需要去配合我们刚才所说到的这两种loader,这就是loader的工作管道这样一个特性。

webpack插件机制

插件机制的是webpack一个核心特性,目的是为了增强webpack自动化方面的能力。

Loader专注实现资源模块的加载,从而去实现整体项目的打包。

Plugin解决除了资源加载以外的其他的一些自动化工作:

  • 自动在打包之前去清除dist目录

    • 安装:yarn add clean-webpack-plugin --dev

    • webpack.config.js:

      const {CleanWebpackPlugin} = require('clean-webpack-plugin')
      plugins: [
        new CleanWebpackPlugin()
      ]
      
  • 自动生成HTML插件

    • 安装:yarn add html-webpack-plugin --dev

      const HtmlWebpackPlugin = require('html-webpack-plugin')
      plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin()
      ]
      
    • 自动生成HTML文件到dist目录中,根目录下的index.html则不再需要了

    • HTML中自动注入了bundle.js的引用到HTML文件中

    • 增加配置参数生成HTML文件:

      new HtmlWebpackPlugin({
        title: 'Webpack Plugin Sample',
        meta: {
          viewport: 'width=device-width'
        }
      })
      
    • 通过模板文件生成HTML文件, webpack.config.js中指定HtmlWebpackPlugin的template参数

      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>webpack</title>
      </head>
      <body>
        <div class="container">
          <h1><%= htmlWebpackPlugin.options.title %></h1>
        </div>
      </body>
      </html>
      
      plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
          title: 'Webpack Plugin Sample',
          meta: {
            viewport: 'width=device-width'
          },
          template: './src/index.html'
        })
      ]
      
    • 生成多个HTML页面

       plugins: [
          new CleanWebpackPlugin(),
          // 用于生成index.html 
          new HtmlWebpackPlugin({
            title: 'Webpack Plugin Sample',
            meta: {
              viewport: 'width=device-width'
            },
            template: './src/index.html'
          }),
          // 用于生成about.html 
          new HtmlWebpackPlugin({
            filename: 'about.html'
          })
        ]
      
  • 拷贝那些不需要参与打包的资源文件到输出目录

    • 安装:yarn add copy-webpack-plugin --dev

      const CopyWebpackPlugin = require('copy-webpack-plugin')
      
      new CopyWebpackPlugin({
        patterns: ['public']
      })
      
  • 压缩我们打包结果输出的代码

webpack开发插件

相比于Loader,Plugin拥有更宽的能力范围,Plugin通过钩子机制实现。

Webpack要求插件必须是一个函数或者是一个包含apply方法的对象。通过在生命周期的钩子中挂载函数实现扩展。

//此插件实现清除打包生成的js文件中的注释
class MyPlugin {
  apply (compiler) {
    console.log('MyPlugin 启动')
    compiler.hooks.emit.tap('MyPlugin', compilation => {
      // compilation 可以理解为此次打包的上下文
      for (const name in compilation.assets) {
        // console.log(name) // 文件名
        console.log(compilation.assets[name].source())
        if(name.endsWith('.js')) {
          const contents = compilation.assets[name].source()
          const withoutComments = contents.replace(/\/\*\*+\//g, '')
          compilation.assets[name] = {
            source: () => withoutComments,
            size: () => withoutComments.length
          }
        }
      }
    })
  }
}
plugins: [
  new MyPlugin()
]

webpack开发体验问题

  • 自动进行编译:执行yarn webpack --watch会监视文件的变化自动进行打包
  • 自动刷新浏览器:webpack-dev-server,安装:yarn add webpack-dev-server,执行:yarn webpack-dev-server --open

webpack dev server 静态资源访问

Dev Server默认只会serve打包输出文件,只要是webpack打包输出的文件都会被访问到,其他静态资源也需要被server

devServer: {
    contentBase: './public'
  }

contentBase额外为开发服务器指定查找资源目录

webpack Dev server代理API

webpack-dev-server支持配置代理

  devServer: {
    contentBase: './public',
    proxy: {
      '/api': {// 以/api开头的地址都会被代理到接口当中
        // http://localhost:8080/api/users -> https://api.github.com/api/users
        target: 'https://api.github.com',
        // http://localhost:8080/api/users -> https://api.github.com/users
        pathRewrite: {
          '^/api': ''
        },
        // 不能使用localhost:8080作为请求GitHub的主机名
        changeOrigin: true, // 以实际代理的主机名去请求
      }
    }
  }	

Source Map

运行代码与源代码之间完全不同,如果需要调试应用,错误信息无法定位,调试和报错都是基于运行代码,SourceMap 就是解决这种问题的最好办法。

Source Map解决了源代码与运行代码不一致所产生的问题.

Webpack 支持sourceMap 12种不同的方式,每种方式的效率和效果各不相同。效果最好的速度最慢,速度最快的效果最差

eval函数可以运行字符串当中的js代码:eval("console.log(123)")

当DevTool的值为eval,打包后的报错信息只有源代码文件名称,没有行列信息

每个关键词的特点组合:

  • eval- 是否使用eval执行代码模块
  • cheap- Source map是否包含行信息
  • module-是否能够得到Loader处理之前的源代码
  • inline- SourceMap 不是物理文件,而是以URL形式嵌入到代码中
  • hidden- 看不到SourceMap文件,但确实是生成了该文件
  • nosources- 没有源代码,但是有行列信息。为了在生产模式下保护源代码不被暴露

开发模式推荐使用:eval-cheap-module-source-map,因为:

  • 代码每行不会太长,没有列也没问题
  • 代码经过Loader转换后的差异较大
  • 首次打包速度慢无所谓,重新打包相对较快

生产模式推荐使用:none,原因

  • Source Map会暴露源代码
  • 调试是开发阶段的事情
  • 对代码实在没有信心可以使用nosources-source-map

webpack HMR

HMR(Hot Module Replacement) 模块热替换,应用运行过程中,实时替换某个模块,应用运行状态不受影响。

webpack-dev-server自动刷新导致的页面状态丢失。我们希望在页面不刷新的前提下,模块也可以即使更新。热替换只将修改的模块实时替换至应用中。

HMR是webpack中最强大的功能之一,极大程度的提高了开发者的工作效率。

HMR已经集成在了webpack-dev-server中,运行webpack-dev-server --hot,也可以通过配置文件开启.

Webpack中的HMR并不是对所有文件开箱即用,样式文件支持热更新,脚本文件需要手动处理模块热替换逻辑。而通过脚手架创建的项目内部都集成了HMR方案。

HMR注意事项:

  • 处理HMR的代码报错会导致自动刷新
  • 没启动HMR的情况下,HMR API报错
  • 代码中多了很多与业务无关的代码

webpack生产环境优化

我们在开发环境中,更注重开发效率。

模式(mode)

webpack建议我们为不同的环境创建不同的配置,两种方案:

  • 配置文件根据环境不同导出不同配置
  • 一个环境对应一个配置文件

Webpack.common.js

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

module.exports = {
  entry: './src/main.js',
  output: {
    filename: `bundle.js`
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: `index.html`
    })
  ]
}

Webpack.dev.js

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

module.export = merge(common, {
  mode: 'development',
})

Webpack.prod.js

const common = require('./webpack.common')
const merge = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
        patterns: ['public']
    })
  ]
})

Package.json

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

执行打包命令:yarn build

webpack优化配置

  • DefinePlugin 为代码注入全局成员,这个内置插件默认就会启动,往每个代码中注入一个全局变量process.env.NODE_ENV

    const webpack = require('webpack')
    
    plugins: [
        new HtmlWebpackPlugin({
          filename: `index.html`
        }),
        new webpack.DefinePlugin({
          API_BASE_URL: JSON.stringify('http://api.example.com')
        })
      ]
    
  • Tree-Shaking 摇掉代码中未引用到的代码(dead-code),这个功能在生产模式下自动被开启。Tree-Shaking并不是webpack中的某一个配置选项,而是一组功能搭配使用后的效果。

    optimization: {
        usedExports: true,
        minimize: true
      }
    
  • 合并模块函数 concatenateModules, 又被成为Scope Hoisting,作用域提升

    optimization: {
        usedExports: true,
        minimize: true,
        concatenateModules: true
      }
    
  • 很多资料中说如果使用Babel-Loader,会导致Tree-Shaking失效,因为Tree-Shaking前提是ES Modules,由Webpack打包的代码必须使用ESM,为了转化ES中的新特性,会使用babel处理新特性,就有可能将ESM转化CommonJS,而我们使用的@babel/preset-env这个插件集合就会转化ESM为CommonJS,所以Tree-Shaking会不生效。但是在最新版babel-loader关闭了转换ESM的插件,所以使用babel-loader不会导致Tree-Shaking失效

  • sideEffects 副作用,指的是模块执行时除了导出成员之外所做的事情,sideEffects一般用于npm包标记是否有副作用。如果没有副作用,则没有用到的模块则不会被打包。

    在webpack.config.js中开启这个功能:

    optimization: {
        usedExports: true,
        minimize: true,
        concatenateModules: true,
        sideEffects: true
      }
    

    在package.json里面增加一个属性sideEffects,值为false,表示没有副作用,没有用到的代码则不进行打包。确保你的代码真的没有副作用,否则在webpack打包时就会误删掉有副作用的代码,比如说在原型上添加方法,则是副作用代码;还有CSS代码也属于副作用代码。

    "sideEffects": false
    

    也可以忽略掉有副作用的代码:

    "sizeEffects": [
      "./src/extend.js",
      "*.css"
    ]
    

Code Splitting 代码分包/代码分割

webpack的一个弊端:所有的代码都会被打包到一起,如果应用复杂,bundle会非常大。而并不是每个模块在启动时都是必要的,所以需要分包、按需加载。物极必反,资源太大了不行,太碎了也不行。太大了会影响加载速度;太碎了会导致请求次数过多,因为在目前主流的HTTP1.1有很多缺陷,如同域并行请求限制、每次请求都会有一定的延迟,请求的Header浪费带宽流量。所以模块打包时有必要的。

目前的webpack分包方式有两种:

  • 多入口打包:适用于多页应用程序,一个页面对应一个打包入口,公共部分单独抽取。

    entry: {
      index: './src/index.js',
        album: './src/album.js'
    },
    output: {
        filename: '[name].bundle.js'
    },
    // 每个打包入口形成一个独立的chunk
    plugins: [
        new HtmlWebpackPlugin({
          title: 'Multi Entry',
          template: './src/index.html',
          filename: 'index.html',
          chunks: ['index']
        }),
        new HtmlWebpackPlugin({
          title: 'Nulti Entry',
          template: './src/album.html',
          filename: 'album.html',
          chunks: ['album']
        })
      ],
    // 不同的打包入口肯定会有公共模块,我们需要提取公共模块:
      optimization: {
        splitChunks: {
          chunks: 'all'
        }
      }
    
  • 动态导入:需要用到某个模块时,再加载这个模块,动态导入的模块会被自动分包。通过动态导入生成的文件只是一个序号,可以使用魔法注释指定分包产生bundle的名称。相同的chunk名会被打包到一起。

    import(/* webpackChunkName: 'posts' */'./post/posts').then({default: posts}) => {
      mainElement.appendChild(posts())
    }
    

MiniCssExtractPlugin可以提取CSS到单个文件

当css代码超过150kb左右才建议使用。

const MiniCssExtracPlugin = require('mini-css-extract-plugin')

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        // 'style-loader',
        MiniCssExtracPlugin.loader,
        'css-loader'
      ]
    }
  ]
},

OptimizeCssAssetsWebpackPlugin 压缩输出的CSS文件

webpack仅支持对js的压缩,其他文件的压缩需要使用插件。

可以使用 optimize-css-assets-webpack-plugin压缩CSS代码。放到minimizer中,在生产模式下就会自动压缩

optimization: {
  minimizer: [
    new TerseWebpackPlugin(), // 指定了minimizer说明要自定义压缩器,所以要把JS的压缩器指指明,否则无法压缩
    new OptimizeCssAssetWebpackPlugin()
  ]
}

输出文件名hash

生产模式下,文件名使用Hash

  • 项目级别的hash

    output: {
          filename: '[name]-[hash].bundle.js'
      },
    
  • chunk级别的hash

    output: {
          filename: '[name]-[chunkhash].bundle.js'
      },
    
  • 文件级别的hash,:8是指定hash长度 (推荐)

    output: {
          filename: '[name]-[contenthash:8].bundle.js'
      },
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值