likes
comments
collection
share

“手把手” 优化 Webpack 配置 (一)

作者站长头像
站长
· 阅读数 15

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

1、缩小范围

extensions

指定extensions之后在使用exportrequire的时候,就不用加扩展名,会从extensions配置中依次进行匹配的。

resolve: { 
    extensions: [".js",".jsx",".json",".css"] 
}

alias

配置别名,可以加快webpack查找的速度

  • 每当引入bootstrap模块的时候,它会直接引入bootstrap,而不需要从node_modules文件夹中按模块的查找规则查找
  const bootstrap = path.resolve(__dirname,'node_modules/bootstrap/dist/css/bootstrap.css')
  resolve: {
    // 配置模块别名
    alias: {
      bootstrap
    },
  },

mudules

作用: 比如在项目中直接使用import react from 'react' ,这种的其路径查找规则,就是在这里可以进行配置。

如果可以确定项目内所有的第三方依赖模块都是在项目根目录下的 node_modules ,则可以如下配置。

resolve: {
    modules: [path.resolve(__dirname, 'node_modules')], 
}

如果有其他路径下的模块,也可以直接加入到数组中来

resolve: {
    modules: [path.resolve(__dirname, 'xxxx')], 
}

mainFields

配置package.json中的文件入口字段。

默认情况下package.json 文件则按照文件中 main 字段的文件名来查找文件

   mainFields: ['xxx', 'main'],

mainFiles

作用:当前目录下面没有package.json的时候,会默认加载的文件,可以用这个配置进行修改。

resolve: { 
    mainFiles: ['index'], // 你可以添加其他默认使用的文件名 
}

resolveLoader

resolve.resolveLoader用于配置解析 loader 时的 resolve 配置,默认的配置:

  resolveLoader: {
    modules: ['node_modules'],
    extensions: ['.js', '.json'],
    mainFields: ['loader', 'main']
  },

2、noParse

作用:module.noParse可以配置那些模块文件不需要进行解析

既,没有依赖的第三方库,可以配置这个字段来提高构建速度。例如下面的lodash

“手把手” 优化 Webpack 配置 (一)

  module: {
    noParse: /jquery|lodash/
  },

使用 noParse 进行忽略的模块文件中不能使用 import、require、define 等导入机制

3、ignorePlugin

作用:ignorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去。

这里以moment这个库为例,

index.js

import moment from 'moment';
import 'moment/locale/zh-cn' // 添加忽略配置之后,需要单独引入中文语言包
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));

  plugins: [
    new webpack.IgnorePlugin({
      contextRegExp: /moment$/,     // 模块名
      resourceRegExp: /^\.\/locale/ // 模块下面的目录
    }),
  ]

之前: “手把手” 优化 Webpack 配置 (一)

之后: “手把手” 优化 Webpack 配置 (一)

4、费时分析

作用:可查看webpack构建过程中的耗时情况。

“手把手” 优化 Webpack 配置 (一)

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
module.exports = smw.wrap({
    // webpack 配置信息
});

5、webpack-bundle-analyzer

“手把手” 优化 Webpack 配置 (一)

yarn add webpack-bundle-analyzer -D
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

6、webpack 打包库

webpack不仅仅可以用来打包项目,还可以用来打包类库。

  • output.library 配置导出库的名称
  • output.libraryExport 配置要导出的模块中哪些子模块需要被导出。 它只有在 output.libraryTarget 被设置成 commonjs 或者 commonjs2 时使用才有意义
  • output.libraryTarget 配置以何种方式导出库,是字符串的枚举类型,支持以下配置
libraryTarget使用者的引入方式使用者提供给被使用者的模块的方式
var只能以script标签的形式引入我们的库只能以全局变量的形式提供这些被依赖的模块
commonjs只能按照commonjs的规范引入我们的库被依赖模块需要按照commonjs规范引入
commonjs2只能按照commonjs2的规范引入我们的库被依赖模块需要按照commonjs2规范引入
amd只能按amd规范引入被依赖的模块需要按照amd规范引入
this
window
global
umd可以用script、commonjs、amd引入按对应的方式引入

node中的this是当前模块的导出对象module.exports 也等于exports

假设现在我们要使用webpack打包一个库, 采用var的形式。

vue.js

module.exports = {
  ref() {
    console.log('ref')
  },
  reactive() {
    console.log('reactive')
  },
}

配置文件

  entry: {
    main: './src/vue.js',
  },
  output: {
    path: path.resolve('dist'),
    filename: '[name].js',
    library: 'Vue',   // 导出库的名字
    libraryTarget: 'var'  // 相当于,全局声明一个变量 calculator
  },

打包效果如下 “手把手” 优化 Webpack 配置 (一)

再演示一下采用commonjs的形式进行打包

更改配置

  output: {
    // .....
    libraryTarget: 'commonjs'  // 相当于,全局声明一个变量 calculator
  },

dist目录下面新建test.js

const main = require('./main')

main.Vue.ref()

执行效果

“手把手” 优化 Webpack 配置 (一)

7、提取css

yarn add css-loader mini-css-extract-plugin -D

index.js

import moment from 'moment';
import 'moment/locale/zh-cn'

import './a.css'
import './b.css'

a.css

#app {
  background: red;
}

b.css

#app {
  background: red;
}

webpack.config.js

  const miniCssExtractPlugin = require('mini-css-extract-plugin')
  
  output: {
    path: path.resolve('dist'),
    filename: '[name].js',
    publicPath: '/'
  },
   module: {
    rules: [
      {
        test: /\.css$/,
        use: [miniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new miniCssExtractPlugin({
      filename: '[name].css'
    })
  ]

“手把手” 优化 Webpack 配置 (一)

  plugins: [
    new miniCssExtractPlugin({
      filename: 'css/[name].css'  // 这样可以指定目录
    })
  ]

如下:

“手把手” 优化 Webpack 配置 (一)

8、压缩 Js Css Html

css

yarn add -D css-minimizer-webpack-plugin

github

还是刚才的那两个css文件

a.css

#app {
  background: red;
}

b.css

#app {
  background: red;
}

.logo {
  color: pink;
}

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  optimization: {
    minimizer: [
      // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
      // `...`,
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

“手把手” 优化 Webpack 配置 (一)

js

yarn add -D terser-webpack-plugin

webpack.config.js

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
  plugins: [
    new htmlWebpackPlugin({
      template: './index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ]
};

文档

9、CDN

  • qiniu
  • CDN 又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。
  • public-path
  • external-remotes-plugin

“手把手” 优化 Webpack 配置 (一)

缓存策略

  • HTML文件不缓存,放在自己的服务器上,关闭自己服务器的缓存,静态资源的URL变成指向CDN服务器的地址
  • 静态的JavaScriptCSS、图片等文件开启CDN和缓存,并且文件名带上HASH值 (也就是webpack打包产生的hash值)
  • 为了并行加载不阻塞,把不同的静态资源分配到不同的CDN服务器上

域名限制

  • 同一时刻针对同一个域名的资源并行请求是有限制
  • 可以把这些静态资源分散到不同的 CDN 服务上去
  • 多个域名后会增加域名解析时间
  • 可以通过在 HTML HEAD 标签中 加入<link rel="dns-prefetch" href="http://img.baidu.cn">去预解析域名,以降低域名解析带来的延迟

文件指纹

  • 打包后输出的文件名后缀
  • hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。

指纹占位符

占位符名称含义
ext资源后缀名
name文件名称
path文件的相对路径
folder文件所在的文件夹
hash每次webpack构建时生成一个唯一的hash值(常用)
chunkhash根据chunk生成hash值,来源于同一个chunk,则hash值就一样(常用)
contenthash根据内容生成hash值,文件内容相同hash值就相同(常用)

hash

hash是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash

webpack.config.js

  output: {
    path: path.resolve('dist'),
    filename: '[name].[hash].js',
    publicPath: '/'
  },
  plugins: [
    new miniCssExtractPlugin({
      filename: 'css/[name].[hash].css'
    })
  ]

打包结果如下:

“手把手” 优化 Webpack 配置 (一)

chunkhash

采用hash计算的话,每一次构建后生成的哈希值都不一样,如果文件内容没有任何变化(则hash值也是不会变的,但是一般重新打包项目下总是会有文件内容发生改变的)。这样子是没办法实现缓存效果,我们需要换另一种哈希值计算方式,即chunkhash

chunkhashhash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。我们在生产环境里把一些公共库程序入口文件区分开,单独打包构建,接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。 index.js

import moment from 'moment';
import 'moment/locale/zh-cn'

import './a.css'
import './b.css'
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));

a.css

#app {
  background: red;
}

b.css

#app {
  background: green;
}

.logo {
  color: pink;
}

webpack.config.js

  mode: "production",
  entry: {
    main: './src/index.js',
    vender: ['lodash']
  },
  output: {
    path: path.resolve('dist'),
    filename: '[name].[chunkhash].js',
    publicPath: '/'
  },
  plugins: [
    new miniCssExtractPlugin({
      filename: 'css/[name].[chunkhash].css'
    })
  ]

js产出了两个文件,css一个 ,因为css是在入口文件中引入的。 vender被单独打成了一个文件 “手把手” 优化 Webpack 配置 (一)

我们修改入口文件index.js, 之后重新打包再进行观察。

公共库的hash并没有变只是cssjs文件的hash发生了变化。这样我们就看出了前两种hash之间的区别。 “手把手” 优化 Webpack 配置 (一)

contenthash

chunkhash的例子,我们可以看到由于a.cssindex.js引用了,所以共用相同的chunkhash值。但是这样子有个问题,如果index.js更改了代码,css文件就算内容没有任何改变,由于是该模块发生了改变,导致css文件会重复构建。缓也会失效。

这个时候,我们可以使用extra-text-webpack-plugin里的contenthash值,保证即使css文件所处的模块里就算其他文件内容改变,只要css文件内容不变,那么不会重复构建。

webpack.config.js

  new miniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css'
  })

结果如下 “手把手” 优化 Webpack 配置 (一) 现在我们尝试修改index.js文件 index.js

import moment from 'moment';
import 'moment/locale/zh-cn'

import './a.css'
import './b.css'
console.log('修改 index.js文件啦啦啦啦啦啦啦')  //更改
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));

发现只有main.js文件的hash值变了。 “手把手” 优化 Webpack 配置 (一)

理解各种hash

“手把手” 优化 Webpack 配置 (一)

这里借用crypto这个库

模拟hash模式

function createHash() {
  return require('crypto').createHash('md5');
}

let entry1 = 'require depModule1';//模块entry1
let entry2 = 'require depModule2';//模块entry2

let depModule1 = 'depModule1';//模块depModule1
let depModule2 = 'depModule2';//模块depModule2

//如果都使用hash的话,因为这是工程级别的,即每次修改任何一个文件,所有文件名的hash至都将改变。
//所以一旦修改了任何一个文件,整个项目的文件缓存都将失效
let hash = createHash()
  .update(entry1)
  .update(entry2)
  .update(depModule1)
  .update(depModule2)
  .digest('hex');
console.log('hash', hash)

结果如下,没改变内容,多次打包hash值是不变的。 “手把手” 优化 Webpack 配置 (一) 假设改变其中的一个文件内容, 最终的hash值会改变。

let entry1 = 'require depModule1---------';//模块entry1
let entry2 = 'require depModule2';//模块entry2

let depModule1 = 'depModule1';//模块depModule1
let depModule2 = 'depModule2';//模块depModule2

“手把手” 优化 Webpack 配置 (一)

chunkhash, 假设模块2 是公共库的入口。

let entry1 = 'require depModule1';//模块entry1
let entry2 = 'require depModule2';//模块entry2   假设模块2 是公共库的入口

let depModule1 = 'depModule1';//模块depModule1
let depModule2 = 'depModule2';//模块depModule2

//chunkhash根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。
//在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,
//接着我们采用chunkhash的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响
let entry1ChunkHash = createHash()
  .update(entry1)
  .update(depModule1).digest('hex');;
console.log('entry1ChunkHash', entry1ChunkHash);

let entry2ChunkHash = createHash()
  .update(entry2)
  .update(depModule2).digest('hex');;
console.log('entry2ChunkHash', entry2ChunkHash);

“手把手” 优化 Webpack 配置 (一)

下面我们修改entry1里面的内容

let entry1 = 'require depModule1%%%%%%%%%%%%%%';//模块entry1    // 改变
let entry2 = 'require depModule2';//模块entry2   假设模块2 是公共库的入口

let depModule1 = 'depModule1';//模块depModule1
let depModule2 = 'depModule2';//模块depModule2

可以看出entry2ChunkHash的值并没有发生变化。

“手把手” 优化 Webpack 配置 (一)

contenthash 实验

let entry1File = entry1 + depModule1;
let entry1ContentHash = createHash()
  .update(entry1File).digest('hex');;
console.log('entry1ContentHash', entry1ContentHash);

let entry2File = entry2 + depModule2;
let entry2ContentHash = createHash()
  .update(entry2File).digest('hex');;
console.log('entry2ContentHash', entry2ContentHash);

“手把手” 优化 Webpack 配置 (一)

改变entry1里面的内容之后也是只影响entry1ContentHash的结果。

最后在假设这两个entry里面的内容都是一样的,会输出怎样的结果呢?

let entry1 = 'require';//模块entry1
let entry2 = 'require';//模块entry2   假设模块2 是公共库的入口

let depModule1 = 'depModule';//模块depModule1
let depModule2 = 'depModule';//模块depModule2

“手把手” 优化 Webpack 配置 (一)

10、moduleIds & chunkIds的优化

  • module: 每一个文件其实都可以看成一个 module
  • chunk: webpack打包最终生成的代码块,代码块会生成文件,一个文件对应一个chunk
  • 在webpack5之前,没有从entry打包的chunk文件,都会以1、2、3...的文件命名方式输出,删除某些些文件可能会导致缓存失效
  • 在生产模式下,默认启用这些功能chunkIds: "deterministic", moduleIds: "deterministic",此算法采用确定性的方式将短数字 ID(3 或 4 个字符)短hash值分配给 modules 和 chunks
  • chunkId设置为deterministic,则outputchunkFilename里的[name]会被替换成确定性短数字ID
  • 虽然chunkId不变(不管值是deterministic | natural | named),但更改chunk内容,chunkhash还是会改变的
可选值含义示例
natural按使用顺序的数字ID1
named方便调试的高可读性idsrc_two_js.js
deterministic根据模块名称生成简短的hash值915
size根据模块大小生成的数字id0

index.js

import moment from 'moment';
import 'moment/locale/zh-cn'

import './a.css'
import './b.css'
console.log('修改 index.js文件啦啦啦啦啦啦啦')
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'));

import('./a')
import('./b')
import('./c')

a.js

console.log('a')

b.js

console.log('b')

c.js

console.log('c')

webpack.config.js

  optimization: {
    moduleIds: 'natural',
    chunkIds: 'natural'
  },

删除前 “手把手” 优化 Webpack 配置 (一)

在上面的基础上我们删除import('./b') 之后在进行打包。

import('./a')
// import('./b')
import('./c')

删除后

“手把手” 优化 Webpack 配置 (一)

看结果,这样就会让原来4.05eac48c15ada5d0fcc0.js文件的缓存失效了,本来只是减少了一个chunk,但是因为少了一个chunk,所以会使最终的文件名发生变化。

我们再继续将其设置成deterministic,还是进行上面的两个操作,并进行对比 webpack.config.js

  optimization: {
    moduleIds: 'deterministic',
    chunkIds: 'deterministic'
  },

删除前

“手把手” 优化 Webpack 配置 (一) 删除后

“手把手” 优化 Webpack 配置 (一)

这样即使删除了一个chunk但是它不会让原来的chunk名字发生改变,所以使用这种配置,会提高缓存的命中率。 “手把手” 优化 Webpack 配置 (一)

转载自:https://juejin.cn/post/7140663872347865095
评论
请登录