从零开始的Webpack原理剖析(八)—— Webpack优化


theme: orange
highlight: gruvbox-dark

前言

这篇文章我们稍微放松下,讲一讲webpack的一些常用的配置和一些优化,对我们的项目有着极大的便利。

缩小查找范围/简写相关配置

先看下常用的配置项的示例:

// webpack.config.js
{
  ...
  // 配置如何查找项目文件中中引入的模块
  resolve: {
    extensions: ['.js', '.json', '.scss', '.css'],
    alias: {
      '@': path.resolve(__dirname, 'src')
    },
    modules: [path.resolve(__dirname, 'my_modules'), 'node_modules'],
    mainFields: ['style', 'main'],
    mainFiles: ['style', 'index']
  },
  // 配置项和resolve是一模一样的,区别就是这个配置项是用来查找loader的
  resolveLoader: {
    ......
  }
  ...
}
  • extensions: 默认配置为['.js', '.json'];在importrequire的时候,文件名不需要加后缀名,webpack会依次按照extensions配置项中的后缀名进行查找尝试。
  • alias: 别名配置项,这个在项目中非常常见,比如可以将我们的模块路径src文件夹,简化成一个别名@,这样在某一个文件中,导入模块就可以写成import xxx from '@/xxxxx'了,非常简便。
  • modules: 默认配置为['node_modules'];即webpack在打包的时候,默认寻找第三方依赖的文件目录,如果我们在加上自己的目录路径,如[path.resolve(__dirname, 'my_modules'), 'node_modules'],那么webpack就会优先在我们自己的目录去寻找依赖包,找不到再去node_modules中寻找。一般这个配置项,只会在之前提到过的,自定义的loaderplugin如果不发布到npm上,而是写在项目内部,则可能会用到这个配置。
  • mainFields: 一般来说我们在引入外部依赖的时候,都会默认按照依赖包的package.json中的main配置的路径,当然这个路径也是可以改的,比如我们在使用import bootstrap from 'bootstrap'的时候,会默认寻找bootstrap包里边package.json文件中main的路径,那么如果我们将配置改为mainFields: ['style', 'main'],那么再import bootstrap from 'bootstrap'的时候,引入的就是package.json文件中style的路径了,找不到style的时候,再去找main
  • mainFiles: 和上边的配置项类似,如果第三方包里边,没有package.json文件,那么就会按照配置的顺序来引入入口文件,比如我们有个第三方包叫my_package,里边只有一个文件叫style.js,那么当我们配置了mainFiles: ['style', 'index']的时候,在项目中import myPackage from 'my_package'的时候,就会默认引入style.js的文件,找不到的话再去找index.js文件。

module配置项优化

// webpack.config.js
{
  ...
  module: {
    // 不需要递归解析的模块
    noParse: /jquery|lodash|chartjs/
    ......
  }
  ...
}
  • module.noParse: 加上此配置项的目的就是告诉webpack,在遇到这些模块的时候,不需要进行递归解析了,因为这些大型的库,没有第三方的依赖,配置完这个字段,如果项目中用到了这些库,可以提高webpack整体的构建速度。

webpack一些优化插件

IgnorePlugin: 可以让webpack忽略某些特定的模块,在打包的时候,不把这些模块打包进最终文件。

一个很经典的例子就是在使用moment库的时候,因为其语言包体积比较大,如果不做任何配置,则会把所有的语言包,都打包进最终的结果;而我们在开发的时候,又用不到那么多的语言包,所以我们不需要把所有的语言包打包进最终文件中。

import moment from 'moment'
import 'moment/locale/zh-cn'
console.log(moment().fromNow());
// webpack.config.js
const webpack = require('webpack')
{
  ...
  plugins: [
    new webpack.IgnorePlugin({
      // 依赖包目录正则匹配
      contextRegExp: /moment$/,
      // 资源文件的正则匹配
      resourceRegExp: /locale/
    })
  ]
  ...
}

配置完后,执行npm run build命令,发现打包结果中,只有我们import的中文包被打包进来了,其他的语言包并没有被打包进来。

speed-measure-webpack-plugin: 耗时分析插件,我们在进行webpack优化的时候,除了一些常见的配置优化,肯定需要一个量化的指标来分析,哪个地方耗时比较久,这样才能够对症下药,进行优化,那么就需要使用到这个插件了。使用起来也很简单

// webpack.config.js
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smw = new SpeedMeasureWebpackPlugin()
// 用smw.srap包裹我们的webpack配置即可
module.exports = smw.srap({
 ...
})

当我们再次执行npm run build命令的时候,就会看到终端中显示了各个插件和loader的耗时了。

image.png

webpack-bundle-analyzer: 生成代码报告分析
配置非常简单,之后执行npm run build命令,就会自动启动一个本地服务器,如下图所示,展示打包结果的文件大小和关系。

// webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = { 
  plugins: [new BundleAnalyzerPlugin()]
}

image.png

webpack构建一个第三方库

在日常工作中,很可能我们需要写一个组件库,或者是一个工具库,发布到npm上,供各个业务线来安装使用,那么我们也可以使用webpack来对这些库进行打包。会用到output中的几个属性,我们一一举例讲解:

libraryTarget设置为var

// webpack.config.js
const path = require('path')
module.exports = {
  entry: {
    main: './src/index.js'
  },
  mode: 'development',
  devtool: false,
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    clean: true,
    // 给这个库起一个导出的名字
    library: 'calculator',
    // 导出的方式,var代表生成一个全局变量
    libraryTarget: 'var'
  }
}
// src/index.js
// 比如我们自己写了一个如下的计算库,里边有两个方法,一个加法一个减法
module.exports = {
  add(a, b) {
    return a + b
  },
  minus(a, b) {
    return a - b
  }
}

我们执行npm run build命令,进行打包,可以在打包结果中看到,在第一行定义了一个全局变量calculator

image.png

然后再最后一行,又将require的结果赋值给了这个全局变量,所以可以直接通过<script>标签引入的方式来使用

image.png

那么如何使用呢?因为是打包一个工具库,所以我们把他当成一个外部工具库来使用就好了,我们可以在打包结果同级新建一个index.html文件,然后把打包结果引入进来,来验证一下,我们的库能不能用。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
<!-- 引入我们打包的库 -->
<script src="./main.js"></script></head>
<body>
  <script>
    // 直接使用calculator,因为库的导出方式是给window全局对象新增了一个属性calculator
    console.log(calculator)
  </script>
</body>
</html>

我们通过浏览器打开index.html文件可以发现,控制台打印出来了两个方法addminus,正是我们库里边提供的2个方法。

libraryTarget设置为commonjs

别的配置项不变,我们把libraryTarget的值改为commonjs,然后再次打包,观察打包结果:

image.png

可以发现,和之前生成全局变量不同,这种方法,是将calculator挂载到了exports对象上,那么如何使用呢?很简单,我们在项目中只需要这样引入并使用:

// 通过require的方式进行引入
const main = require('./main.js')
console.log(main.calculator.add(1, 2))

libraryTarget设置为commonjs2

别的配置项不变,我们把libraryTarget的值改为commonjs2,然后再次打包,观察打包结果,发现别的地方都没变,只是最后把calculator赋值给了module.exports仅此而已,和commonjs可以说是一样的,使用方法也一样

image.png

libraryTarget设置为umd

一说umd,可能懂的都懂了,就是可以兼容各种模式,可以用amd、commonjs、script的方式进行引入,那么打包结果我们也简单的看一下,因为兼容各种模式,所以在打包文件最上边要进行判断,用了那种方式,就用那种方式给calculator进行赋值。

image.png

css相关优化

我们可以单独把css提取出来,因为html文件非常大的时候,加载起来会比较慢,而cssjs加载是可以并行的,所以,我们可以利用插件,将css单独提取出来,同样,修改下配置文件。

// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  entry: {
    main: './src/index.js'
  },
  mode: 'development',
  devtool: false,
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ],
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] }
    ]
  }
}
// src/index.js
import './index.css'

const div = document.createElement('div')
div.innerHTML = '我是一个测试的div'
div.className = 'bg-yellow'
document.body.appendChild(div)
/* src/index.css */
.bg-yellow {
  background-color: orange;
}
div {
  color: red;
}

我们执行npm run build打包命令后可以发现,css文件里边的内容,全部都被打包进了main.js文件中,dist文件夹中,也是只有index.htmlmain.js两个文件被打包出来。

image.png

但,当我们用了抽离css的插件,就可以大大减少main.js文件的体积,优化加载速度,首先要安装这个插件,之后再去增加如下配置。

// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  entry: {
    main: './src/index.js'
  },
  mode: 'development',
  devtool: false,
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    })
  ],
  module: {
    rules: [
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }
    ]
  }
}

再次执行npm run build命令,可以看到,终端中,有3个文件被打包了,而且css文件也被抽离了出来成为了单独的文件,main.js的体积得到了大幅度的减少,从浏览器中打开index.html文件查看结果也是一切正常。

image.png

同样,上边的插件,只是将css抽离出来,但是并没有进行压缩,我们可以用另一个插件对抽离出来的css进行压缩:

// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
  entry: {
    main: './src/index.js'
  },
  mode: 'development',
  devtool: false,
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css'
    }),
    new OptimizeCssAssetsWebpackPlugin()
  ],
  module: {
    rules: [
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }
    ]
  }
}

再次执行npm run build命令,查看打包结果可以发现,css文件被成功压缩了。

结语

关于一些webpack优化,项目中常用的有这些,之后也会对新的知识点及时的进行补充说明。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容