快捷搜索:
来自 新京葡娱乐场网址 2019-12-01 13:58 的文章
当前位置: 67677新澳门手机版 > 新京葡娱乐场网址 > 正文

带您用合理的架子使用webpack4,配置最棒实施

手摸手,带你用合理的姿势使用webpack4(下)

2018/08/26 · 基础技术 · webpack

原文出处: 华尔街见闻技术团队 - 花裤衩   

推荐先阅读 webpack 入门教程之后再来阅读本文。

  • Webpack 4 和单页应用入门
  • 手摸手,带你用合理的姿势使用 webpack4 (上)

本文为手摸手使用 webpack4(下),主要分为两部分:

  • 怎么合理的运用浏览器缓存
  • 怎么构建可靠的持久化缓存

Webpack 4 配置最佳实践

2018/06/22 · JavaScript · webpack

原文出处: Zindex   

Webpack 4 发布已经有一段时间了。Webpack 的版本号已经来到了 4.12.x。但因为 Webpack 官方还没有完成迁移指南,在文档层面上还有所欠缺,大部分人对升级 Webpack 还是一头雾水。

不过 Webpack 的开发团队已经写了一些零散的文章,官网上也有了新版配置的文档。社区中一些开发者也已经成功试水,升级到了 Webpack 4,并且总结成了博客。所以我也终于去了解了 Webpack 4 的具体情况。以下就是我对迁移到 Webpack 4 的一些经验。

本文的重点在:

  • Webpack 4 在配置上带来了哪些便利?要迁移需要修改配置文件的哪些内容?
  • 之前的 Webpack 配置最佳实践在 Webpack 4 这个版本,还适用吗?

默认分包策略

webpack 4 最大的改动就是废除了 CommonsChunkPlugin 引入了 optimization.splitChunks

webpack 4 的Code Splitting 它最大的特点就是配置简单,如果你的 modeproduction,那么 webpack 4 就会自动开启 Code Splitting

图片 1

以下内容都会以 vue-element-admin 为例子。 在线
bundle-report

如上图所示,在没配置任何东西的情况下,webpack 4 就智能的帮你做了代码分包。入口文件依赖的文件都被打包进了app.js,那些大于 30kb 的第三方包,如:echartsxlsxdropzone等都被单独打包成了一个个独立 bundle。

它内置的代码分割策略是这样的:

  • 新的 chunk 是否被共享或者是来自 node_modules 的模块
  • 新的 chunk 体积在压缩之前是否大于 30kb
  • 按需加载 chunk 的并发请求数量小于等于 5 个
  • 页面初始加载时的并发请求数量小于等于 3 个

图片 2

但有一些小的组件,如上图:vue-count-to 在未压缩的情况下只有 5kb,虽然它被两个页面共用了,但 webpack 4 默认的情况下还是会将它和那些懒加载的页面代码打包到一起,并不会单独将它拆成一个独立的 bundle。(虽然被共用了,但因为体积没有大于 30kb)

你可能会觉得 webpack 默认策略是不是有问题,我一个组件被多个页面,你每个页面都将这个组件打包进去了,岂不是会重复打包很多次这个组件?就拿vue-count-to来举例,你可以把共用两次以上的组件或者代码单独抽出来打包成一个 bundle,但你不要忘了vue-count-to未压缩的情况下就只有 5kb,gizp 压缩完可能只有 1.5kb 左右,你为了共用这 1.5kb 的代码,却要额外花费一次 http 请求的时间损耗,得不偿失。我个人认为 webpack 目前默认的打包规则是一个比较合理的策略了。

但有些场景下这些规则可能就显得不怎么合理了。比如我有一个管理后台,它大部分的页面都是表单和 Table,我使用了一个第三方 table 组件,几乎后台每个页面都需要它,但它的体积也就 15kb,不具备单独拆包的标准,它就这样被打包到每个页面的 bundle 中了,这就很浪费资源了。这种情况下建议把大部分页面能共用的组件单独抽出来,合并成一个component-vendor.js的包(后面会介绍)。

优化没有银弹,不同的业务,优化的侧重点是不同的。个人认为 webpack 4 默认拆包已经做得不错了,对于大部分简单的应用来说已经够用了。但作为一个通用打包工具,它是不可能满足所有的业务形态和场景的,所以接下来就需要我们自己稍微做一些优化了。

Webpack 4 之前的 Webpack 最佳实践

这里以 Vue 官方的 Webpack 模板 vuejs-templates/webpack 为例,说说 Webpack 4 之前,社区里比较成熟的 Webpack 配置文件是怎样组织的。

优化分包策略

就拿 vue-element-admin 来说,它是一个基于 Element-UI 的管理后台,所以它会用到如 echartsxlsxdropzone等各种第三方插件,同时又由于是管理后台,所以本身自己也会写很多共用组件,比如各种封装好的搜索查询组件,共用的业务模块等等,如果按照默认的拆包规则,结果就不怎么完美了。

如第一张图所示,由于element-uientry入口文件中被引入并且被大量页面共用,所以它默认会被打包到 app.js 之中。这样做是不合理的,因为app.js里还含有你的router 路由声明store 全局状态utils 公共函数icons 图标等等这些全局共用的东西。

但除了element-ui,其它这些又是平时开发中经常会修改的东西,比如我新增了一个全局功能函数,utils文件就会发生改变,或者我修改一个路由的 path,router文件就变了,这些都会导致app.js的 hash 发生改变:app.1.js => app.2.js。但由于 element-uivue/react等也被打包在其中,虽然你没改变它们,但它们的缓存也会随着app.xxx.js变化而失效了,这就非常不合理的。所以我们需要自己来优化一下缓存策略。

我们现在的策略是按照体积大小、共用率、更新频率重新划分我们的包,使其尽可能的利用浏览器缓存。

图片 3

我们根据上表来重新划分我们的代码就变成了这样。

  • 基础类库 chunk-libs

它是构成我们项目必不可少的一些基础类库,比如 vue vue-router vuex axios 这种标准的全家桶,它们的升级频率都不高,但每个页面都需要它们。(一些全局被共用的,体积不大的第三方库也可以放在其中:比如 nprogress、js-cookie、clipboard 等)

  • UI 组件库

理论上 UI 组件库也可以放入 libs 中,但这里单独拿出来的原因是: 它实在是比较大,不管是 Element-UI还是Ant Design gizp 压缩完都可能要 200kb 左右,它可能比 libs 里面所有的库加起来还要大不少,而且 UI 组件库的更新频率也相对的比 libs 要更高一点。我们不时的会升级 UI 组件库来解决一些现有的 bugs 或使用它的一些新功能。所以建议将 UI 组件库也单独拆成一个包。

  • 自定义组件/函数 chunk-commons

这里的 commons 主要分为 必要非必要

必要组件是指那些项目里必须加载它们才能正常运行的组件或者函数。比如你的路由表、全局 state、全局侧边栏/Header/Footer 等组件、自定义 Svg 图标等等。这些其实就是你在入口文件中依赖的东西,它们都会默认打包到app.js中。

非必要组件是指被大部分页面使用,但在入口文件 entry 中未被引入的模块。比如:一个管理后台,你封装了很多 select 或者 table 组件,由于它们的体积不会很大,它们都会被默认打包到到每一个懒加载页面的 chunk 中,这样会造成不少的浪费。你有十个页面引用了它,就会包重复打包十次。所以应该将那些被大量共用的组件单独打包成chunk-commons

不过还是要结合具体情况来看。一般情况下,你也可以将那些非必要组件函数也在入口文件 entry 中引入,和必要组件函数一同打包到app.js之中也是没什么问题的。

  • 低频组件

低频组件和上面的共用组件 chunk-commons 最大的区别是,它们只会在一些特定业务场景下使用,比如富文本编辑器、js-xlsx前端 excel 处理库等。一般这些库都是第三方的且大于 30kb,所以 webpack 4 会默认打包成一个独立的 bundle。也无需特别处理。小于 30kb 的情况下会被打包到具体使用它的页面 bundle 中。

  • 业务代码

这部分就是我们平时经常写的业务代码。一般都是按照页面的划分来打包,比如在 vue 中,使用路由懒加载的方式加载页面 component: () => import('./Foo.vue') webpack 默认会将它打包成一个独立的 bundle。

完整配置代码:

splitChunks: { chunks: "all", cacheGroups: { libs: { name: "chunk-libs", test: /[/]node_modules[/]/, priority: 10, chunks: "initial" // 只打包初始时依赖的第三方 }, elementUI: { name: "chunk-elementUI", // 单独将 elementUI 拆包 priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app test: /[/]node_modules[/]element-ui[/]/ }, commons: { name: "chunk-comomns", test: resolve("src/components"), // 可自定义拓展你的规则 minChunks: 2, // 最小共用次数 priority: 5, reuseExistingChunk: true } } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
splitChunks: {
  chunks: "all",
  cacheGroups: {
    libs: {
      name: "chunk-libs",
      test: /[/]node_modules[/]/,
      priority: 10,
      chunks: "initial" // 只打包初始时依赖的第三方
    },
    elementUI: {
      name: "chunk-elementUI", // 单独将 elementUI 拆包
      priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
      test: /[/]node_modules[/]element-ui[/]/
    },
    commons: {
      name: "chunk-comomns",
      test: resolve("src/components"), // 可自定义拓展你的规则
      minChunks: 2, // 最小共用次数
      priority: 5,
      reuseExistingChunk: true
    }
  }
};

图片 4

上图就是最终拆包结果概要,你可以 点我点我点我,在线查看拆包结果。

这样就能尽可能的利用了浏览器缓存。当然这种优化还是需要因项目而异的。比如上图中的共用组件 chunk-commons,可能打包出来发现特别大,包含了很多组件,但又不是每一个页面或者大部分页面需要它。很可能出现这种状况:A 页面只需要 chunk-commons里面的 A 组件,
但却要下载整个chunk-commons.js,这时候就需要考虑一下,目前的拆包策略是否合理,是否还需要chunk-commons?还是将这些组件打包到各自的 bundle 中?还是调整一下 minChunks: 2( 最小共用次数)?或者修改一下它的拆包规则?

// 或者你可以把策略改为只提取那些你注册在全局的组件。 - test: resolve("src/components") test: resolve("src/components/global_components") //你注册全局组件的目录

1
2
3
4
// 或者你可以把策略改为只提取那些你注册在全局的组件。
 
- test: resolve("src/components")
test: resolve("src/components/global_components") //你注册全局组件的目录

区分开发和生产环境

大致的目录结构是这样的:

build config src

1
2
3
build
config
src

在 build 目录下有四个 webpack 的配置。分别是:

  • webpack.base.conf.js
  • webpack.dev.conf.js
  • webpack.prod.conf.js
  • webpack.test.conf.js

这分别对应开发、生产和测试环境的配置。其中 webpack.base.conf.js 是一些公共的配置项。我们使用 webpack-merge 把这些公共配置项和环境特定的配置项 merge 起来,成为一个完整的配置项。比如 webpack.dev.conf.js 中:

'use strict' const merge = require('webpack-merge') const baseWebpackConfig = require('./webpack.base.conf') const devWebpackConfig = merge(baseWebpackConfig, { ... })

1
2
3
4
5
6
7
'use strict'
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
 
const devWebpackConfig = merge(baseWebpackConfig, {
   ...
})

这三个环境不仅有一部分配置不同,更关键的是,每个配置中用 webpack.DefinePlugin 向代码注入了 NODE_ENV 这个环境变量。

这个变量在不同环境下有不同的值,比如 dev 环境下就是 development。这些环境变量的值是在 config 文件夹下的配置文件中定义的。Webpack 首先从配置文件中读取这个值,然后注入。比如这样:

build/webpack.dev.js

plugins: [ new webpack.DefinePlugin({ 'process.env': require('../config/dev.env.js') }), ]

1
2
3
4
5
plugins: [
  new webpack.DefinePlugin({
    'process.env': require('../config/dev.env.js')
  }),
]

config/dev.env.js

module.exports ={ NODE_ENV: '"development"' }

1
2
3
module.exports ={
  NODE_ENV: '"development"'
}

至于不同环境下环境变量具体的值,比如开发环境是 development,生产环境是 production,其实是大家约定俗成的。

框架、库的作者,或者是我们的业务代码里,都会有一些根据环境做判断,执行不同逻辑的代码,比如这样:

if (process.env.NODE_ENV !== 'production') { console.warn("error!") }

1
2
3
if (process.env.NODE_ENV !== 'production') {
  console.warn("error!")
}

这些代码会在代码压缩的时候被预执行一次,然后如果条件表达式的值是 true,那这个 true 分支里的内容就被移除了。这是一种编译时的死代码优化。这种区分不同的环境,并给环境变量设置不同的值的实践,让我们开启了编译时按环境对代码进行针对性优化的可能。

博弈

其实优化就是一个博弈的过程,是让 a bundle 大一点还是 b? 是让首次加载快一点还是让 cache 的利用率高一点? 但有一点要切记,拆包的时候不要过分的追求颗粒化,什么都单独的打成一个 bundle,不然你一个页面可能需要加载十几个.js文件,如果你还不是HTTP/2的情况下,请求的阻塞还是很明显的(受限于浏览器并发请求数)。所以还是那句话资源的加载策略并没什么完全的方案,都需要结合自己的项目找到最合适的拆包策略。

比如支持HTTP/2的情况下,你可以使用 webpack4.15.0 新增的 maxSize,它能将你的chunkminSize的范围内更加合理的拆分,这样可以更好地利用HTTP/2来进行长缓存(在HTTP/2的情况下,缓存策略就和之前又不太一样了)。


Code Splitting && Long-term caching

Code Splitting 一般需要做这些事情:

  • 为 Vendor 单独打包(Vendor 指第三方的库或者公共的基础组件,因为 Vendor 的变化比较少,单独打包利于缓存)
  • 为 Manifest (Webpack 的 Runtime 代码)单独打包
  • 为不同入口的公共业务代码打包(同理,也是为了缓存和加载速度)
  • 为异步加载的代码打一个公共的包

Code Splitting 一般是通过配置 CommonsChunkPlugin 来完成的。一个典型的配置如下,分别为 vendor、manifest 和 vendor-async 配置了 CommonsChunkPlugin。

new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { return ( module.resource && /.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }), new webpack.optimize.CommonsChunkPlugin({ name: 'app', async: 'vendor-async', children: true, minChunks: 3 }),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks (module) {
        return (
          module.resource &&
          /.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
 
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
 
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),

CommonsChunkPlugin 的特点就是配置比较难懂,大家的配置往往是复制过来的,这些代码基本上成了模板代码(boilerplate)。如果 Code Splitting 的要求简单倒好,如果有比较特殊的要求,比如把不同入口的 vendor 打不同的包,那就很难配置了。总的来说配置 Code Splitting 是一个比较痛苦的事情。

而 Long-term caching 策略是这样的:给静态文件一个很长的缓存过期时间,比如一年。然后在给文件名里加上一个 hash,每次构建时,当文件内容改变时,文件名中的 hash 也会改变。浏览器在根据文件名作为文件的标识,所以当 hash 改变时,浏览器就会重新加载这个文件。

Webpack 的 Output 选项中可以配置文件名的 hash,比如这样:

output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') },

1
2
3
4
5
output: {
  path: config.build.assetsRoot,
  filename: utils.assetsPath('js/[name].[chunkhash].js'),
  chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},

Long term caching

持久化缓存其实是一个老生常谈的问题,前端发展到现在,缓存方案已经很成熟了。简单原理:

  • 针对 html 文件:不开启缓存,把 html 放到自己的服务器上,关闭服务器的缓存
  • 针对静态的 js,css,图片等文件:开启 cdn 和缓存,将静态资源上传到 cdn 服务商,我们可以对资源开启长期缓存,因为每个资源的路径都是独一无二的,所以不会导致资源被覆盖,保证线上用户访问的稳定性。
  • 每次发布更新的时候,先将静态资源(js, css, img) 传到 cdn 服务上,然后再上传 html 文件,这样既保证了老用户能否正常访问,又能让新用户看到新的页面。

相关文章 大公司里怎样开发和部署前端代码?

所以我们现在要做的就是要让 webpack 给静态资源生产一个可靠的 hash,让它能自动在合适的时候更新资源的 hash,
并且保证 hash 值的唯一性,即为每个打包后的资源生成一个独一无二的 hash 值,只要打包内容不一样,那么 hash 值就不一样。

其实 webpack 4 在持久化缓存这一块已经做得非常的不错了,但还是有一些欠缺,下面我们将要从这几个方面讨论这个问题。

  • RuntimeChunk(manifest)
  • Module vs Chunk
  • HashedModuleIdsPlugin
  • NamedChunksPlugin

Webpack 4 下的最佳实践

RuntimeChunk(manifest)

webpack 4 提供了 runtimeChunk 能让我们方便的提取 manifest,以前我们需要这样配置

new webpack.optimize.CommonsChunkPlugin({ name: "manifest", minChunks: Infinity });

1
2
3
4
new webpack.optimize.CommonsChunkPlugin({
  name: "manifest",
  minChunks: Infinity
});

现在只要一行配置就可以了

{ runtimeChunk: true; }

1
2
3
{
  runtimeChunk: true;
}

它的作用是将包含chunks 映射关系的 list单独从 app.js里提取出来,因为每一个 chunk 的 id 基本都是基于内容 hash 出来的,所以你每次改动都会影响它,如果不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。

单独抽离 runtimeChunk 之后,每次打包都会生成一个runtimeChunk.xxx.js。(默认叫这名字,可自行修改)

图片 5

优化

其实我们发现打包生成的 runtime.js非常的小,gzip 之后一般只有几 kb,但这个文件又经常会改变,我们每次都需要重新请求它,它的 http 耗时远大于它的执行时间了,所以建议不要将它单独拆包,而是将它内联到我们的 index.html 之中(index.html 本来每次打包都会变)。

这里我选用了 script-ext-html-webpack-plugin,主要是因为它还支持preloadprefetch,正好需要就不想再多引用一个插件了,你完全可以使用 inline-manifest-webpack-plugin或者 assets-webpack-plugin等来实现相同的效果。

const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin"); // 注意一定要在HtmlWebpackPlugin之后引用 // inline 的name 和你 runtimeChunk 的 name保持一致 new ScriptExtHtmlWebpackPlugin({ //`runtime` must same as runtimeChunk name. default is `runtime` inline: /runtime..*.js$/ });

1
2
3
4
5
6
7
8
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
 
// 注意一定要在HtmlWebpackPlugin之后引用
// inline 的name 和你 runtimeChunk 的 name保持一致
new ScriptExtHtmlWebpackPlugin({
  //`runtime` must same as runtimeChunk name. default is `runtime`
  inline: /runtime..*.js$/
});

Webpack 4 的变与不变

Webpack 4 这个版本的 API 有一些 breaking change,但不代表说这个版本就发生了翻天覆地的变化。其实变化的点只有几个。而且只要你仔细了解了这些变化,你一定会拍手叫好。

迁移到 Webpack 4 也只需要检查一下 checklist,看看这些点是否都覆盖到了,就可以了。

Module vs Chunk

我们经常看到xxxModuleIdsPluginxxxChunksPlugin,所以在 webpack 中 modulechunk到底是一个怎么样的关系呢?

  • chunk: 是指代码中引用的文件(如:js、css、图片等)会根据配置合并为一个或多个包,我们称一个包为 chunk。
  • module: 是指将代码按照功能拆分,分解成离散功能块。拆分后的代码块就叫做 module。可以简单的理解为一个 export/import 就是一个 module。

每个 chunk 包可含多个 module。 比如:

//9.xxxxxxxxx.js //chunk id为 9 ,包含了Vc2m和JFUb两个module (window.webpackJsonp = window.webpackJsonp || []).push([ [9], { Vc2m: function(e, t, l) {}, JFUb: function(e, t, l) {} } ]);

1
2
3
4
5
6
7
8
9
10
//9.xxxxxxxxx.js
 
//chunk id为 9 ,包含了Vc2m和JFUb两个module
(window.webpackJsonp = window.webpackJsonp || []).push([
  [9],
  {
    Vc2m: function(e, t, l) {},
    JFUb: function(e, t, l) {}
  }
]);

一个module还能跨chunk引用另一个module,比如我想在app.js里面需要引用 chunkId13的模块2700可以这样引用:

return n.e(13).then(n.bind(null, "27OO"));

1
return n.e(13).then(n.bind(null, "27OO"));

开发和生产环境的区分

Webpack 4 引入了 mode 这个选项。这个选项的值可以是 development 或者 production。

设置了 mode 之后会把 process.env.NODE_ENV 也设置为 development 或者 production。然后在 production 模式下,会默认开启 UglifyJsPlugin 等等一堆插件。

Webpack 4 支持零配置使用,可以从命令行指定 entry 的位置,如果不指定,就是 src/index.js。mode 参数也可以从命令行参数传入。这样一些常用的生产环境打包优化都可以直接启用。

我们需要注意,Webpack 4 的零配置是有限度的,如果要加上自己想加的插件,或者要加多个 entry,还是需要一个配置文件。

虽然如此,Webpack 4 在各个方面都做了努力,努力让零配置可以做的事情更多。这种内置优化的方式使得我们在项目起步的时候,可以把主要精力放在业务开发上,等后期业务变复杂之后,才需要关注配置文件的编写。

在 Webpack 4 推出 mode 这个选项之前,如果想要为不同的开发环境打造不同的构建选项,我们只能通过建立多个 Webpack 配置且分别设置不同的环境变量值这种方式。这也是社区里的最佳实践。

Webpack 4 推出的 mode 选项,其实是一种对社区中最佳实践的吸收。这种思路我是很赞同的。开源项目来自于社区,在社区中成长,从社区中吸收养分,然后回报社区,这是一个良性循环。最近我在很多前端项目中都看到了类似的趋势。接下来要讲的其他几个 Webpack 4 的特性也是和社区的反馈离不开的。

那么上文中介绍的使用多个 Webpack 配置,以及手动环境变量注入的方式,是否在 Webpack 4 下就不适用了呢?其实不然。在Webpack 4 下,对于一个正经的项目,我们依然需要多个不同的配置文件。如果我们对为测试环境的打包做一些特殊处理,我们还需要在那个配置文件里用 webpack.DefinePlugin 手动注入 NODE_ENV 的值(比如 test)。

Webpack 4 下如果需要一个 test 环境,那 test 环境的 mode 也是 development。因为 mode 只有开发和生产两种,测试环境应该是属于开发阶段。

HashedModuleIdsPlugin

了解了 modulechunk之后,我们来研究一下 moduleId

首先要确定你的 filename 配置的是chunkhash(它与 hash 的区别可以看上篇文章)。

output: { path: path.join(__dirname, 'dist'), filename: '[name].[chunkhash].js', }

1
2
3
4
output: {
  path: path.join(__dirname, 'dist'),
  filename: '[name].[chunkhash].js',
}

我们在入口文件中随便引入一个新文件test.js

//main.js import "./test"; //test.js console.log("apple");

1
2
3
4
5
//main.js
import "./test";
 
//test.js
console.log("apple");

我们运行npm run build,发现了一件奇怪的事情,我只是多引入了一个文件,但发现有十几个文件发生了变化。这是为什么呢?

我们随便挑一个文件 diff 一下,发现两个文件只有 module id 的不同。

图片 6

这是因为:
webpack 内部维护了一个自增的 id,每个 module 都有一个 id。所以当增加或者删除 module 的时候,id 就会变化,导致其它文件虽然没有变化,但由于 id 被强占,只能自增或者自减,导致整个 id 的顺序都错乱了。

虽然我们使用 [chunkhash] 作为输出名,但仍然是不够的。
因为 chunk 内部的每个 module 都有一个 id,webpack 默认使用递增的数字作为 moduleId
如果引入了一个新文件或删掉一个文件,都可能会导致其它文件的 moduleId 发生改变,
那这样缓存失效了。如:

图片 7

本来是一个按序的 moduleId list,这时候我插入一个orange模块,插在第三个位置,这样就会导致它之后的所以 module id 都依次加了 1。

这到了原因,解决方案就很简单了。我们就不要使用一个自增的 id 就好了,这里我们使用HashedModuleIdsPlugin

或者使用optimization.moduleIds v4.16.0 新发布,文档还没有。查看 源码发现它有naturalnamedhashedsizetotal-size。这里我们设置为optimization.moduleIds='hash'等于HashedModuleIdsPlugin。源码了也写了webpack5会优化这部分代码。

它的原理是使用文件路径的作为 id,并将它 hash 之后作为 moduleId。

图片 8

使用了 HashedModuleIdsPlugin`,我们再对比一下发现 module id 不再是简单的 id 了,而是一个四位 hash 过得字符串(不一定都是四位的,如果重复的情况下会增加位数,保证唯一性 源码)。
这样就固定住了 module id 了。

NamedModulesPlugin 和 HashedModuleIdsPlugin 原理是相同的,将文件路径作为 id,只不过没有把路径 hash 而已,适用于开发环境方便调试。不建议在生产环境配置,因为这样不仅会增加文件的大小(路径一般偶读比较长),更重要的是为暴露你的文件路径。

第三方库 build 的选择

在 Webpack 3 时代,我们需要在生产环境的的 Webpack 配置里给第三方库设置 alias,把这个库的路径设置为 production build 文件的路径。以此来引入生产版本的依赖。

比如这样:

resolve: { extensions: [".js", ".vue", ".json"], alias: { vue$: "vue/dist/vue.runtime.min.js" } },

1
2
3
4
5
6
resolve: {
  extensions: [".js", ".vue", ".json"],
  alias: {
    vue$: "vue/dist/vue.runtime.min.js"
  }
},

在 Webpack 4 引入了 mode 之后,对于部分依赖,我们可以不用配置 alias,比如 React。React 的入口文件是这样的:

'use strict'; if (process.env.NODE_ENV === 'production') { module.exports = require('./cjs/react.production.min.js'); } else { module.exports = require('./cjs/react.development.js'); }

1
2
3
4
5
6
7
'use strict';
 
if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

这样就实现了 0 配置自动选择生产 build。

但大部分的第三库并没有做这个入口的环境判断。所以这种情况下我们还是需要手动配置 alias。

本文由67677新澳门手机版发布于新京葡娱乐场网址,转载请注明出处:带您用合理的架子使用webpack4,配置最棒实施

关键词: