配备最佳实践,构建打包优化

手摸手,带您用合理的架势使用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
照旧1只雾水。

但是 Webpack
的支出团队已经写了一部分碎片的稿子,官网上也有了新版配置的文书档案。社区中有的开发者也已经成功试水,升级到了
Webpack 4,并且总括成了博客。所以自身也终于去打听了 Webpack 4
的具体意况。以下正是自家对搬迁到 Webpack 4 的有的经历。

正文的第壹在:

  • Webpack 4 在安插上带来了如何惠及?要迁移要求修改配置文件的哪些内容?
  • 在此以前的 Webpack 配置最佳实践在 Webpack 4 那些版本,还适用吗?

webpack是1个强有力的模块构建筑工程具,重要用于前端开发,能够和npm和bower很好的同盟。

本文介绍了React + Webpack 创设打包优化,分享给大家,具体如下:

暗中认可分包策略

webpack 4 最大的改动正是放任了 CommonsChunkPlugin 引入了
optimization.splitChunks

webpack 4 的Code Splitting 它最大的风味正是布署不难,假使您的 mode
production,那么 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来比喻,你能够把共用两次以上的机件只怕代码单独抽出来打包成2个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
配置文件是怎么样组织的。

和rails的asset相比较,它有无数的优势。

使用
babel-react-optimize对
React 代码进行优化

优化分包策略

就拿
vue-element-admin
来说,它是1个根据 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-ui
vue/react等也被打包在个中,就算你没改变它们,但它们的缓存也会趁着app.xxx.js变动而失效了,这就特别不创设的。所以大家必要协调来优化一下缓存策略。

咱俩前几日的方针是遵守体量大小、共用率、更新频率重新划分大家的包,使其尽或者的应用浏览器缓存。

澳门葡京 3

我们依照上表来重新划分大家的代码就改成了如此。

  • 基本功类库 chunk-libs

它是组成大家项目必不可少的有的基础类库,比如
vue+vue-router+vuex+axios
那种专业的一家子桶,它们的升官频率都不高,但各样页面都需求它们。(一些大局被共用的,容量非常小的第2方库也得以置身中间:比如
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
中,那样会导致许多的浪费。你有十一个页面引用了它,就会包重复打包12遍。所以理应将那么些被多量共用的零部件单独打包成chunk-commons

可是照旧要结合具体景况来看。一般景况下,你也能够将这几个非须求组件函数也在进口文件
entry 中引入,和必备组件函数同步装进到app.js当中也是没什么难题的。

  • 低频组件

低频组件和地点的共用组件 chunk-commons
最大的分别是,它们只会在有的特定业务场景下采用,比如富文本编辑器、js-xlsx前者
excel 处理库等。一般那么些库都以第②方的且高于 30kb,所以 webpack 4
会暗中认可打包成3个独自的 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
起来,成为1个整机的布置项。比如 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
分支里的始末就被移除了。那是一种编写翻译时的死代码优化。那种差别分裂的条件,并给环境变量设置不相同的值的施行,让大家打开了编写翻译时按环境对代码进行针对性优化的大概。

  1. 治本全部的包,通过npm可能bower

反省没有动用的库,去除 import 引用

博弈

其实优化便是2个博弈的进程,是让 a bundle 大学一年级点要么 b?
是让第三回加载快一些依然让 cache 的利用率高级中学一年级点?
但有有些要铭记,拆包的时候不要过度的言情颗粒化,什么都独立的打成1个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 一般是通过布置 康芒斯ChunkPlugin
来达成的。一个出类拔萃的布局如下,分别为 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
策略是这么的:给静态文件二个非常短的缓存过期时光,比如一年。然后在给文件名里加上2个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’)
},

2.方可本着五种能源(js png css coffeescript jade es6 jsx)

按需打包所用的类库,比如 lodash 、 echart 等

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 下的最佳实践

3.能用commonjs的科班来兑现模块信赖

lodash
能够选拔babel-plugin-lodash拓展优化。

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里提取出来,因为每3个 chunk 的 id 基本都是依据内容 hash
出来的,所以您每便变更都会潜移默化它,假设不将它提取出来的话,等于app.js老是都会转移。缓存就失效了。

单独抽离 runtimeChunk
之后,每趟打包都会变卦一个runtimeChunk.xxx.js。(默许叫那名字,可机关修改)

澳门葡京 5

优化

实际大家发现包裹生成的 runtime.js充足的小,gzip 之后一般只有几
kb,但那一个文件又平日会变动,大家每一次都急需再行请求它,它的 http
耗费时间远大于它的执行时间了,所以建议并非将它独自拆包,而是将它内联到大家的
index.html 之中(index.html 本来每回打包都会变)。

那边自身选择了
script-ext-html-webpack-plugin,首若是因为它还帮助preload
prefetch,正好须求就不想再多引用贰个插件了,你完全能够使用
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,看看那些点是不是都掩盖到了,就足以了。

4.能够异步加载通过 require.ensure

亟待专注的是

Module vs Chunk

大家平常来看xxxModuleIdsPluginxxxChunksPlugin,所以在 webpack 中
modulechunk到底是3个什么的涉及吗?

  • chunk:
    是指代码中引用的文件(如:js、css、图片等)会遵照安顿合并为1个或三个包,大家称两个包为
    chunk。
  • module:
    是指将代码依照效益拆分,分解成离散效率块。拆分后的代码块就叫做
    module。能够省略的掌握为3个 export/import 正是1个 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,仍旧必要3个布局文件。

固然那样,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
唯有付出和生育三种,测试环境应该是属于开发阶段。

预备干活

1.新建1个rails 项目

2.安装npm install webpack -g

3.安装bower
npm install bower -g,由于webpack并没有必要大家务必使用什么包管理器所以能够动用npm+bower

在 babel-react-optimize 中选择了
babel-plugin-transform-react-remove-prop-types
这一个插件。日常状态下,即使你在代码中并未引用到零部件的 PropTypes
,则完全没难点。倘诺你的组件用到了,那么使用该插件大概会造成难点。

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,发现了一件奇怪的事务,作者只是多引入了3个文件,但意识有二十一个文本发出了扭转。那是干什么吧?

咱俩随便挑四个文件 diff 一下,发现五个文件只有 module id 的不同。

澳门葡京 6

这是因为:
webpack 内部维护了三个自增的 id,每一种 module 都有2个id。所以当扩大照旧去除 module 的时候,id
就会转移,导致别的文件即便从未转变,但出于 id
被侵占,只好自增可能自减,导致整个 id 的一一都错乱了。

就算大家选拔 [chunkhash] 作为输知名,但依然是不够的。
因为 chunk 内部的各类 module 都有二个 id,webpack 私下认可使用递增的数字作为
moduleId
万一引入了三个新文件或删掉三个文书,都恐怕会导致其余文件的 moduleId
爆发转移,
这那样缓存失效了。如:

澳门葡京 7

理所当然是3个按序的 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 了,而是1个四个人 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。

写webpack的着力配置音信

文件名用webpack.config.js

var path = require('path');
var webpack = require('webpack');

var config = {
  entry: './app/frontend/javascript/entry.js',
  context: __dir,
}

context: 钦命了the base directory (绝对路径的岗位)
entry: 打包的入口(the entry point for the
bundle),还不错三个单独的文本,能够承受数组,例如:

config.entry = ['./entry1.js', './entry2.js']

不无的公文都会运维时加载,末了1个当做出口;借使传入八个目的,那么会有三个bundles被创设,key是有的的名号,value是文件的职分依然数组,例如:

{
  entry: {
    page1: './page1',
    page2: ['./entry1', 'entry2'],
  },
  output: {
    filename: "[name].bundle.js",
    chunkFilename: '[id].bundle.js',
  }
}

末尾那么些文件会卓殊的纷纷,那里大家只写一些最中央的总得的配置,前边会写入愈来愈多的配备新闻。
今昔大家只需求多个输入文件,所以能够直接写一个文书地点,八个输入的时候,entry能够承受数组和指标。(webpack找入口文件来开端打包明确依赖关系,没有在进口文件中分明的公文,是不会被打包的)
下1天性能,大家要写output,分明打包好的文件输出到哪个地方

config.output = {
  path: path.join(__dirname, 'app', 'asset', 'javascripts'),
  filename: 'bunlde.js',
  publicPath: '/assets',
}

filename: 输出的公文的称号,那里不可不是相对的途径
path:输出文件的岗位

七个entries时候,filename对每叁个entry有三个唯一的name,有上边这多少个选取:

[name] 被chunk的name替换
[hash]被compilation的hash替换
[chunkhash]被chunk的hash替换

output的publicPath钦点了这么些能源在浏览器中被调用的公共url地址。
那边在浏览器中引用大家的能源只须求<script src='/assets/....'/>
output的chunkFilename是非输入点的文本片段(non-entry chunks as relative
to the output.path)

[id]
is replaced by the id of the chunk.
[name]
is replaced by the name of the chunk (or with the id when the chunk has
no name).
[hash]
is replaced by the hash of the compilation.
[chunkhash]
is replaced by the hash of the chunk.

于今大家要添加resolve属性,
resolve属性是告诉webpack怎么寻找打包文件中的重视关系,具体布署如下:

config.resolve = {

  extensions: ['', 'js'],
  modulesDirectories: ['node_modules', 'bower_components']
}

最后是plugin参数

config.plugin = {
  new webpack.ResolverPlugin([
    new webpack.ResulvePlugin.DiretoryDescriptionFilePlugin('.bower.json', ['main'])
  ])
}

以此布局告诉webpack对于bower的包怎么找entrypoints,因为恐怕没有package.json

执行命令

webpack -d --display-reasons --colors -- display-chunks --progress

澳门葡京 9

Paste_Image.png

于今rails的assets/scripts中就曾经成形了有关的chunk.
在rails中央银行使通过<%= javascript_include_tag 'bundle'%>

具体见:

NamedChunkPlugin

大家在一定了 module id 之后同理也亟需一定一下 chunk id,不然大家增添chunk 或许减小 chunk 的时候会和 module id 一样,都也许会造成 chunk
的逐一发生错乱,从而让 chunk 的缓存都失效。

作者也意识到了那几个难题,提供了二个叫NamedChunkPlugin的插件,但在选取路由懒加载的事态下,你会发觉NamedChunkPlugin并没关系用。
供了一个线上demo,能够自行测一下。那里提就径直贴一下结实:

澳门葡京 10

发出的缘故前边也讲了,使用自增 id 的情事下是不能够担保你新添加或删除 chunk
的任务的,一旦它改变了,那么些顺序就错乱了,就要求重排,就会促成它之后的持有
id 都发生转移了。

紧接着我们
翻看源码
还发现它只对有 name 的 chunk
才生效!所以大家那个异步懒加载的页面都以对事情没有什么益处的。那启不是坑爹!大家迭代业务自然会频频的拉长删除页面,那岂不是每新增3个页面都会让前边的缓存都失效?那大家事先还费这么努力优化什么拆包呢?

实在这是3个古老的题材了 相关 issue: Vendor chunkhash changes when app
code changes 早在 2014年就有人提了那几个标题,那么些难题也直接研商到现在,’网民们’也提供了各个奇淫巧技,可是大多数乘胜
webpack 的迭代已经不适用大概是修复了。

此间作者就整合一下 timse(webpack
第②多进献)写的有始有终缓存的文章(在
medium 上急需翻墙)
小结一下脚下能一蹴即至那几个题指标三种方案。

近年来化解方案有三种

  • records
  • webpackChunkName
  • 自定义 nameResolver

Code Splitting

Webpack 4 下还有一个大改变,正是扬弃了 CommonsChunkPlugin,引入了
optimization.splitChunks 这一个选项。

optimization.splitChunks 暗中同意是不用安装的。借使 mode 是 production,那Webpack 4 就会打开 Code Splitting。

暗中认可 Webpack 5只会对按需加载的代码做分割。假诺大家供给布置开头加载的代码也投入到代码分割中,能够设置
splitChunks.chunks 为 ‘all’。

Webpack 4 的 Code Splitting
最大的性状便是布署不难(0配置起步),和基于内置规则自动拆分。内置的代码切分的规则是这么的:

  • 新 bundle 被四个及以上模块引用,只怕来自 node_modules
  • 新 bundle 大于 30kb (压缩以前)
  • 异步加载并发加载的 bundle 数无法超出 5 个
  • 始发加载的 bundle 数不可能压倒 3 个

简易的说,Webpack
会把代码中的公共模块自动抽出来,变成2个包,前提是其一包大于 30kb,不然Webpack 是不会抽出公共代码的,因为扩张1回呼吁的本金是不可能忽视的。

切实的政工场景下,具体的拆分逻辑,可以看 SplitChunksPlugin
的文档以及
webpack 4: Code Splitting, chunk graph and the splitChunks
optimization
那篇博客。那两篇小说基本陈列了全部可能出现的景观。

倘如果普普通通的采取,Webpack 4 内置的规则就丰裕了。

倘若是卓殊的急需,Webpack 4 的 optimization.splitChunks API也得以满意。

splitChunks 有二个参数叫 cacheGroups,这一个参数近似事先的 CommonChunks
实例。cacheGroups 里每一个对象正是1个用户定义的 chunk。

在此之前大家讲到,Webpack 4 内置有一套代码分割的规则,那用户也得以自定义
cacheGroups,也正是自定义 chunk。那个 module 应该被抽到哪个 chunk
呢?那是由 cacheGroups 的抽取范围控制的。每一个 cacheGroups
都得以定义自个儿抽取模块的限制,也正是怎么着文件中的公共代码会抽取到本人那几个chunk 中。不一样的 cacheGroups 之间的模块范围假设有搅和,我们能够用
priority 属性控制优先级。Webpack 4
暗许的抽取的优先级是最低的,所以模块会预先被抽取到用户的自定义 chunk
中。

splitChunksPlugin 提供了二种控制 chunk 抽取模块范围的办法。一种是 test
属性。这几个个性能够流传字符串、正则也许函数,全数的 module 都会去匹配
test 传入的标准化,倘若基准适合,就被纳入这一个 chunk
的备选模块范围。倘若我们传入的口径是字符串或许正则,那匹配的流程是那般的:首先匹配
module 的途径,然后匹配 module 此前所在 chunk 的 name。

例如大家想把具有 node_modules 中引入的模块打包成2个模块:

vendors1: { test: /[\/]node_modules[\/]/, name: ‘vendor’, chunks:
‘all’, }

1
2
3
4
5
  vendors1: {
    test: /[\/]node_modules[\/]/,
    name: ‘vendor’,
    chunks: ‘all’,
  }

因为从 node_modules 中加载的正视路径中都涵盖
node_modules,所以那些正则会同盟全部从 node_modules 中加载的借助。

test 属性能够以 module 为单位控制 chunk
的抽取范围,是一种细粒度相比较小的法子。splitChunksPlugin
的第二种控制抽取模块范围的办法正是 chunks 属性。chunks
能够是字符串,比如 ‘all’|’async’|’initial’,分别表示了百分百chunk,按需加载的 chunk 以及初阶加载的 chunk。chunks
也得以是3个函数,在那么些函数里大家得以获得chunk.name。那给了大家通过输入来划分代码的力量。那是一种细粒度相比大的不二法门,以
chunk 为单位。

举个例子,比如大家有 a, b, c 八个入口。我们期望 a,b
的公家代码单独打包为 common。也正是说 c 的代码不加入集体代码的剪切。

大家能够定义八个 cacheGroups,然后设置 chunks
属性为多个函数,那个函数负责过滤那么些 cacheGroups 包括的 chunk
是怎么着。示例代码如下:

optimization: { splitChunks: { cacheGroups: { common: { chunks(chunk) {
return chunk.name !== ‘c’; }, name: ‘common’, minChunks: 2, }, }, }, },

1
2
3
4
5
6
7
8
9
10
11
12
13
  optimization: {
    splitChunks: {
      cacheGroups: {
        common: {
          chunks(chunk) {
            return chunk.name !== ‘c’;
          },
          name: ‘common’,
          minChunks: 2,
        },
      },
    },
  },

上边配置的意味正是:大家想把 a,b 入口中的公共代码单独打包为二个名为
common 的 chunk。使用 chunk.name,我们得以轻松的完成这么些要求。

在上边的景色中,大家明白 chunks
属性能够用来按入口切分几组公共代码。以后我们来看三个稍微复杂一些的情状:对两样分组入口中引入的
node_modules 中的注重实行分组。

比如说大家有 a, b, c, d 多少个入口。我们意在 a,b 的信赖打包为 vendor1,c, d
的借助打包为 vendor2。

其一供给须要大家对进口和模块都做过滤,所以我们须要利用 test
属性那几个细粒度相比较小的法门。大家的笔触正是,写七个 cacheGroup,一个cacheGroup 的测量标准是:即使 module 在 a 或然 b chunk 被引入,并且
module 的不二法门包涵 node_modules,那这几个 module 就相应被打包到 vendors1中。 vendors2 同理。

vendors1: { test: module => { for (const chunk of
module.chunksIterable) { if (chunk.name && /(a|b)/.test(chunk.name)) {
if (module.nameForCondition() &&
/[\/]node_modules[\/]/.test(module.nameForCondition())) { return
true; } } } return false; }, minChunks: 2, name: ‘vendors1’, chunks:
‘all’, }, vendors2: { test: module => { for (const chunk of
module.chunksIterable) { if (chunk.name && /(c|d)/.test(chunk.name)) {
if (module.nameForCondition() &&
/[\/]node_modules[\/]/.test(module.nameForCondition())) { return
true; } } } return false; }, minChunks: 2, name: ‘vendors2’, chunks:
‘all’, }, };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  vendors1: {
    test: module => {
      for (const chunk of module.chunksIterable) {
            if (chunk.name && /(a|b)/.test(chunk.name)) {
                if (module.nameForCondition() && /[\/]node_modules[\/]/.test(module.nameForCondition())) {
                 return true;
             }
            }
       }
      return false;
    },
    minChunks: 2,
    name: ‘vendors1’,
    chunks: ‘all’,
  },
  vendors2: {
    test: module => {
      for (const chunk of module.chunksIterable) {
            if (chunk.name && /(c|d)/.test(chunk.name)) {
                if (module.nameForCondition() && /[\/]node_modules[\/]/.test(module.nameForCondition())) {
                 return true;
             }
            }
       }
      return false;
    },
    minChunks: 2,
    name: ‘vendors2’,
    chunks: ‘all’,
  },
};

怎么把一部分模块作为global模块

  1. 在每三个模块都能够引用的到一些模块

前日咱们是通过
var jquery = require('jquery')在三个模块中接纳。可是在每多个模块中都要动用那几个模块,就要在每1个模块中都写。那么有没有global
module那个东西吧?
我们经过ProviderPlugin来实现,具体如下:

config.plugins = [
  .....
  new webpack.ProviderPlugin({
    $: 'jquery',
    jQuery: 'jquery',
  })
]

那么在每七个模块中都能够间接通过$也许jQuery来访问到’jquery’那几个模块。

2.怎么把部分模块绑定到全局变量上(window)

例如把jquery暴光到全局上。

此地供给一个loader 叫做expose。 那几个expose
loader把1个module间接绑定到全局context上。

怎么用吗?代码如下:

require('expose?$!expose?jQuery!jquery');

以此语法看起来有一对怪,其实那么些语法是选用四回expose用!连接,用?来传播参数。(expose?$

  • ! + expose?jQuery + ! +
    jquery)。loader的语法正是这么的,比如引用一个css,用
    loader能够如此写:
    require('style!css! ./style.css');//载入css文件

webpack records

广大人只怕连那一个布局项都并未留神过,可是早在 二零一四年就已经被规划出来让你更好的运用
cache。法定文书档案

要采取它铺排也很简单:

recordsPath: path.join(__dirname, “records.json”);

1
recordsPath: path.join(__dirname, "records.json");

对,只要这一行代码就能张开那些选项,并封装的时候会自动生成1个 JSON
文件。它包罗 webpack 的 records 记录 – 即「用于存款和储蓄跨数十次创设(across
multiple
builds)的模块标识符」的数据片段。能够行使此文件来跟踪在历次营造之间的模块变化。

大白话正是:等于每一回营造都是基于上次营造的底蕴上拓展的。它会先读取你上次的
chunk 和 module id 的音讯之后再进行打包。所以此时你再添加可能去除
chunk,并不会招致后边所说的乱序了。

简单易行看一下创设出来的 JSON 长啥样。

{ “modules”: { “byIdentifier”: { “demo/vendor.js”: 0,
“demo/vendor-two.js”: 1, “demo/index.js”: 2, …. }, “usedIds”: { “0”:
0, “1”: 1, “2”: 2, … } }, “chunks”: { “byName”: { “vendor-two”: 0,
“vendor”: 1, “entry”: 2, “runtime”: 3 }, “byBlocks”: {}, “usedIds”: [
0, 1, 2 } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "modules": {
    "byIdentifier": {
      "demo/vendor.js": 0,
      "demo/vendor-two.js": 1,
      "demo/index.js": 2,
      ….
    },
    "usedIds": {
      "0": 0,
      "1": 1,
      "2": 2,
      …
    }
  },
  "chunks": {
    "byName": {
      "vendor-two": 0,
      "vendor": 1,
      "entry": 2,
      "runtime": 3
    },
    "byBlocks": {},
    "usedIds": [
      0,
      1,
      2
  }
}

大家和事先同一,在路由里面添加二个懒加载的页面,打包相比较后意识 id
并不会像在此以前那样依据遍历到的相继插入了,而是基于从前的 id
依次拉长了。一般新增页面都会在最终填写一个新 id,删除 chunk
的话,会将原本代表 chunk 的 id,保留,但不会再选用。

澳门葡京 11

但这一个方案不被大家知道首要缘由正是维护这些records.json相比较麻烦。如若你是在地面打包运维webpack的话,你假设将records.json作为普通文书上传来githubgitlab或别的版本控制仓库。

但今日貌似公司都会将包裹放在
CI里面,用docker包装,那时候那份records.json存在何地正是三个题材了。它不仅必要每趟打包从前先读取你那份
json,打包完事后它还亟需再立异这份
json,并且还要找地点储备,为了下次创设再使用。你能够存在 git
中还是找八个劳务器存,但存在哪些地其他方都感觉蹊跷。

借使你使用
Circle CI能够使用它的store_artifacts,相关教程。

本身在选拔明白后照旧放任了那个方案,使用资金略高。前端打包应该特别的纯粹,不供给注重太多别的的事物。

Long-term caching

Long-term caching 那里,基本的操作和 Webpack 3 是平等的。可是 Webpack 3
的 Long-term caching 在操作的时候,有个小标题,这一个难题是有关 chunk
内容和 hash 变化分歧等的:

在集体代码 Vendor 内容不变的图景下,添加 entry,可能 external
正视,也许异步模块的时候,Vendor 的 hash 会改变

从前 Webpack 官方的特辑里面有一篇小说讲这一个题材:Predictable long term
caching with
Webpack。给出了1个解决方案。

这几个方案的基本就是,Webpack 内部维护了三个自增的 id,各样 chunk 都有3个id。所以当扩张 entry 恐怕其余品种 chunk 的时候,id
就会扭转,导致内容没有成形的 chunk 的 id 也产生了转变。

对此大家的答疑方案是,使用 webpack.NamedChunksPlugin 把 chunk id
变为叁个字符串标识符,那个字符包一般就是模块的相对路径。那样模块的 chunk
id 就足以稳定下来。

澳门葡京 12

这里的 vendors1 就是 chunk id

HashedModuleIdsPlugin
的效应和 NamedChunksPlugin 是一样的,只然而 HashedModuleIdsPlugin
把依照模块相对路径生成的 hash 作为 chunk id,那样 chunk id
会更短。因而在生养中更推荐用 HashedModuleIdsPlugin。

那篇作品说还讲到,webpack.NamedChunksPlugin 只可以对一般性的 Webpack
模块起效果,异步模块,external 模块是不会起效率的。

异步模块能够在 import 的时候添加 chunkName 的笺注,比如那样:import(/
webpackChunkName: “lodash” / ‘lodash’).then() 那样就有 Name 了

就此大家必要再使用一个插件:name-all-modules-plugin

本条插件中用到部分老的 API,Webpack 4 会发出警示,那么些
pr
有新的版本,可是作者不自然会 merge。我们采纳的时候能够一直 copy
那些插件的代码到大家的 Webpack 配置内部。

做了那么些工作现在,大家的 Vendor 的 ChunkId
就再也不会发生不应当爆发的变通了。

source map的作用

上边的布署音讯用webpack打包会自动发出贰个bundle.map.js,这些文件就是source map 文件。 那么些source
map的机能丰富的大,我们打包之后下载一个bundle.js文件就好了永不再下载拾贰个或贰13个文本(那样非常快),可是一旦产生错误了,怎么做,有了source
map我们能够见到那几个错误在原来的individual files。

Webpack 塑造打包优化

webpackChunkName

在 webpack2.4.0 版本之后能够自定义异步 chunk 的名字了,例如:

import(/* webpackChunkName: “my-chunk-name” */ “module”);

1
import(/* webpackChunkName: "my-chunk-name" */ "module");

我们在重组 vue 的懒加载能够这么写。

{ path: ‘/test’, component: () => import(/* webpackChunkName: “test”
*/ ‘@/views/test’) },

1
2
3
4
{
    path: ‘/test’,
    component: () => import(/* webpackChunkName: "test" */ ‘@/views/test’)
  },

打包之后就生成了名为 test的 chunk 文件。

澳门葡京 13

chunk 有了 name 之后就能够化解NamedChunksPlugin并未 name 的情况下的
bug 了。查看包装后的代码大家发现 chunkId 就不再是3个粗略的自增 id 了。

不过那种写法依然有坏处的,首先你须求手动编写每二个 chunk 的
name,同时还必要确认保证它的唯一性,当页面一多,维护起来依旧很劳顿的。那就违背了程序员的规则:能偷懒就偷懒。

由此有怎么样方法能够自动生成3个 name 给 chunk 么 ?查看 webpack
源码大家发现了NamedChunksPlugin骨子里能够自定义 nameResolver 的。

总结

Webpack 4 的改动首假若对社区中极品实践的收受。Webpack 4 经过新的 API
大大进步了 Code Splitting 的体会。但 Long-term caching 中 Vendor hash
的标题依然不曾缓解,需求手动配置。本文主要介绍的正是 Webpack
配置最佳实践在 Webpack 3.x 和 4.x 背景下的异议。希望对读者的 Webpack 4
项目标安插文件协会有所帮忙。

另外,推荐 SURVIVEJS – WEBPACK
这一个在线教程。那一个科目计算了 Webpack
在事实上开发中的实践,并且把资料更新到了新式的 Webpack 4。

1 赞 4 收藏
评论

澳门葡京 14

编造资源路径

在chrome中, webpack暗中同意产生的source
map会把拥有的东西放到webpack://途径下,但是那个不太雅观明了,可以经过output参数来安装,如下:

config.output = {
  ...
devtoolModuleFilenameTemplate: '[resourcePath]',
devtoolFallbackModuleFilenameTemplate: '[resourcePath]?[hash]',
}

当今虚拟能源在domain > assets了.
directory in the Sources tab.

Webpack 构建打包存在的题材主要性汇聚于上面四个地方:

自定义 nameResolver

NamedChunksPlugin支撑本身写 nameResolver
的条条框框的。但日前多数连锁的小说里的自定义函数是不吻合 webpack4
,而且在结合 vue 的景色下还会报错。

社区旧方案:

new webpack.NamedChunksPlugin(chunk => { if (chunk.name) { return
chunk.name; } return chunk.modules.map(m => path.relative(m.context,
m.request)).join(“_”); });

1
2
3
4
5
6
new webpack.NamedChunksPlugin(chunk => {
  if (chunk.name) {
    return chunk.name;
  }
  return chunk.modules.map(m => path.relative(m.context, m.request)).join("_");
});

适配 webpack4 和 vue 的新完成方案:

new webpack.NamedChunksPlugin(chunk => { if (chunk.name) { return
chunk.name; } return Array.from(chunk.modulesIterable, m =>
m.id).join(“_”); });

1
2
3
4
5
6
new webpack.NamedChunksPlugin(chunk => {
  if (chunk.name) {
    return chunk.name;
  }
  return Array.from(chunk.modulesIterable, m => m.id).join("_");
});

理所当然那一个方案恐怕有一部分弊病的因为 id 会可能相当短,假如三个 chunk
正视了众两个 module 的话,id
大概有几十一人,所以大家还亟需减弱一下它的长短。大家首先将拼接起来的 id
hash 以下,而且要有限接济 hash
的结果位数也能太长,浪费字节,但太短又便于发生碰撞,所以最后大家大家采用4 位长度,并且手动用 Set 做一下猛击校验,发生冲击的情形下位数加
1,直到碰撞结束。详细代码如下:

const seen = new Set(); const nameLength = 4; new
webpack.NamedChunksPlugin(chunk => { if (chunk.name) { return
chunk.name; } const modules = Array.from(chunk.modulesIterable); if
(modules.length > 1) { const hash = require(“hash-sum”); const
joinedHash = hash(modules.map(m => m.id).join(“_”)); let len =
nameLength; while (seen.has(joinedHash.substr(0, len))) len++;
seen.add(joinedHash.substr(0, len)); return
`chunk-${joinedHash.substr(0, len)}`; } else { return modules[0].id;
} });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const seen = new Set();
const nameLength = 4;
 
new webpack.NamedChunksPlugin(chunk => {
  if (chunk.name) {
    return chunk.name;
  }
  const modules = Array.from(chunk.modulesIterable);
  if (modules.length > 1) {
    const hash = require("hash-sum");
    const joinedHash = hash(modules.map(m => m.id).join("_"));
    let len = nameLength;
    while (seen.has(joinedHash.substr(0, len))) len++;
    seen.add(joinedHash.substr(0, len));
    return `chunk-${joinedHash.substr(0, len)}`;
  } else {
    return modules[0].id;
  }
});

我给 vue-cli 官方也提了3个相关
issue尤雨溪最终也采用了这么些方案。
于是假若你未来下载最新
vue-cli@3上边啰嗦了半天的事物,其实都曾经暗中同意配置好了(但我自己为了找到这么些hack 方法漫天花了二日时间 o(╥﹏╥)o)。

当下测试了一段时间没发现有如何难题。然则有一些不是很精通,不知晓 webpack
出于什么的原因,官方一向没有修复那一个题材?只怕是在等 webpack5
的时候放大招吧。

loading coffeescript 和 其余编译语言

咱俩能够loader来自动的翻译这一个语言。
理所当然,全体的loader都相同能够通过在require中处理,也能够经过设置config.js来的module.loaders来安装。
首先安装coffee loader
npm install coffee-loader --save-dev
今后大家得以设置resolve.extensions来使用coffeescript不必要后缀。

extensions: ['', '.js', '.coffee']

config中的module.loaders能够加上3个coffeescript的布局消息,如下

config.module = {
  loaders: [
    {
      test: /\.coffee$/,
      loader: 'coffee-loader'
    }
  ]
}
  1. Webpack 创设速度慢
  2. Webpack 打包后的文书体量过大

总结

拆包策略:

  • 基础类库 chunk-libs
  • UI 组件库 chunk-elementUI
  • 自定义共用组件/函数 chunk-commons
  • 低频组件 chunk-eachrts/chunk-xlsx
  • 澳门葡京 ,事务代码 lazy-loading xxxx.js

持久化缓存:

  • 使用 runtimeChunk 提取 manifest,使用
    script-ext-html-webpack-plugin等插件内联到index.html压缩请求
  • 使用 HashedModuleIdsPlugin 固定 moduleId
  • 使用 NamedChunkPlugin整合自定义 nameResolver 来固定 chunkId

上述说的题材大多数在 webpack
官方文书档案都没分明建议,唯一能够参照的就是那份 cache
文档,在刚更新 webpack4
的时候,作者认为官方已经将 id
不能够一定的标题一举成功了,但具体是凶恶的,结果并不完美。但是小编也在许多的
issue 中说她正在初始优化 long term caching

We plan to add another way to assign module/chunk ids for long term
caching, but this is not ready to be told yet.

在 webpack 的 issue 和源码中也时常见到
Long term caching will be improved in webpack@5TODO webpack 5 xxxx诸如此类的代码注释。那让小编对webpack 5很希望。真心愿意webpack 5能真的的化解方今多少个难题,并且让它特别的out-of-the-box,尤其的大约和智能,就像webpack 4optimization.splitChunks,你基本不用做什么,它就能很好的帮你拆分好bundle包,同时又给你十一分的自由发挥空间。

代码分割和lazy loading module(异步加载模块)

webpack能够经过require.ensure([‘ace’],
function(require){})来兑现异步加载。
譬如说大家使用ace那些editor,
由于那个editor照旧相比重的,所以唯有在用的时候才加载,那么webpack能够split来规定的modules在二个友好单身的chunk
file
中,唯有利用的时候才调用,webpack会通过jsonp来兑现,具体应用例子如下:

function Editor() {};
Editor.prototype.open = function() {
  require.ensure(['ace'], function(require) {
    var ace = require('ace');
    var editor = ace.edit('code-editor');
    editor.setTheme('ace/theme/textmate');
    editor.gotoLine(0);
  });
};

var editor = new Editor();
$('a[data-open-editor]').on('click', function(e) {
  e.preventDefault();
  editor.open();
});

Webpack 创设速度慢

展望

Whats
next?
官方在那篇小说中展望了一晃 webpack5
和讲述了一晃前景的布署–持续立异用户体验、升高构建速度和性质,下跌利用门槛,完善Persistent Caching等等。同时
webpack 也一度协助
Prefetching/Preloading modules,作者相信之后也会有越多的网站会动用这一属性。

再就是 webpack
的团组织已经答应会透过投票的章程来支配部分效果。比如不久前提倡的投票。

澳门葡京 15

世家能够关心 Tobias Koppers 的 twitter
进行投票。

最终依旧愿意一下 webpack5 和它之后的腾飞吗。假设没有
webpack,也就不会有前几日的前端。

实际上如一开首就讲的,vue 有vue-cli、react
creat-react-app,以后新建项目为主都以依据脚手架的,很少有人从零开始写
webpack 配置文件的,而且一般开发中,一般程序员也不供给平日去修改 webpack
的布署。webpack 官方自个儿也在不断完善暗中认可配置项,相信 webpack
的安顿门槛也会越来低多。

愿世间再无 webpack 配置工程师。

澳门葡京 16

多入口

实质上上边的配备消息,对于单页面程序是一贯不难点的了,不过大家的rails,只怕项目变大了,是多页面包车型客车。那么怎么处理吧?

  1. 每1个页面二个entry

Each file would look something like this:

var SignupView = require('./views/users/signup');
var view = new SignupView();
view.render({ el: $('[data-view-container]')});

The Rails view just needs to have an element on the page with the
data-view-container
attribute, include two bundles, and we’re done. No <script>
tags necessary.
<%= javascript_include_tag ‘users-signup-bundle’ %>

  1. 贰个输入,五个模块揭发到global(window)

使用webpack的exposeloader能够把它曝光到global上。

代码如下:

// entry.js
var $app = require('./app');

$app.views.users.Signup = require('./views/users/signup');
$app.views.users.Login = require('./views/users/login');

// app.js
module.exports = {
  views = {
    users: {}
  }
}

# ./views/users/signup.coffee
module.exports = class SignupView extends Backbone.View
  initialize: ->
    # do awesome things

配置loader

loaders: [
  {
    test: path.join(__dirname, 'app', 'frontend', 'javascripts', 'app.js'),
    loader: 'expose?$app'
  },
]

This will add the module.exports of the app.js module to window.$app, to
be used by any <script> tag in a Rails view:

(function() {
  var view = new $app.views.users.Signup({ el: $('#signup-page') });
  view.render();
})();

能够行使 Webpack.DDLPlugin , HappyPack 来增强营造速度。具体参见小铭在
DMP DDLPlugin 的文档。最初的作品如下:

展开阅读

  • Webpack 4
    和单页应用入门
  • Long term caching using Webpack
    records
  • Predictable long term caching with
    Webpack
  • 从 Bundle 文件看 Webpack
    模块机制
  • minipack
  • 种种可视化分析 webpack bundle

    1 赞 收藏
    评论

澳门葡京 17

而且利用多入口和expose

八个输入的法子,webpack有1个高招,能够把集体的一部分提取出来。

比如entry_1和entry_2都要求react和jquery,那么webpack能够把她们领取出来放到八个公共的chunk中。

那些意义能够经过webpack的康芒斯ChunkPlugin来落实。

代码如下:

plugins: [
  new webpack.optimize.CommonsChunkPlugin('common-bundle.js')
]

以此将output三个国有的公文common-bundle.js,那一个里面包含最少的webpack
bootstrap code 和 多少个模块公用的modules。你能够一贯在html中援引它

<%= javascript_include_tag 'common-bundle' %>
<%= javascript_include_tag 'public-bundle' %>

Webpack.DLLPlugin

webpack的生育环境

那么大家把原先的webpack.config.js,删掉,新建五个文本common.config.js、development.config.js和production.config.js。当中config/webpack/common.config.js来写入一些基本的安排音讯,如下:

var path = require('path');
var webpack = require('webpack');

var config = module.exports = {
  context: path.join(__dirname, '../', '../'),
};

var config.entry = {
  // your entry points
};

var config.output = {
  // your outputs
  // we'll be overriding some of these in the production config, to support
  // writing out bundles with digests in their filename
}

config/webpack/development.config.js如下:

var webpack = require('webpack');
var _ = require('lodash');
var config = module.exports = require('./main.config.js');

config = _.merge(config, {
  debug: true,
  displayErrorDetails: true,
  outputPathinfo: true,
  devtool: 'sourcemap',
});

config.plugins.push(
  new webpack.optimize.CommonsChunkPlugin('common', 'common-bundle.js')
);

config/webpack/production.config.js代码如下:

var webpack = require('webpack');
var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
var _ = require('lodash');
var path = require('path');

var config = module.exports = require('./main.config.js');

config.output = _.merge(config.output, {
  path: path.join(config.context, 'public', 'assets'),
  filename: '[name]-bundle-[chunkhash].js',
  chunkFilename: '[id]-bundle-[chunkhash].js',
});

config.plugins.push(
  new webpack.optimize.CommonsChunkPlugin('common', 'common-[chunkhash].js'),
  new ChunkManifestPlugin({
    filename: 'webpack-common-manifest.json',
    manfiestVariable: 'webpackBundleManifest',
  }),
  new webpack.optimize.UglifyJsPlugin(),
  new webpack.optimize.OccurenceOrderPlugin()
);

那边我们把出口目录换来了publice/assets,同时把文件名称添加chunkhash,来标记。
与此同时添加了ChunkManifestPlugin那几个plugin。
UglifyJsPlugin来压缩
OccurenceOrderPlugin,which will shorten the IDs of modules which are
included often, to reduce filesize.

累加多个 webpack.dll.config.js
首借使用到二个 DllPlugin 插件,把一部分第叁方的能源独立包装,同时内置一个manifest.json 配置文件中,

创设1个rake,当然用gulp也许grunt也得以

namespace :webpack do
  desc 'compile bundles using webpack'
  task :compile do
    cmd = 'webpack --config config/webpack/production.config.js --json'
    output = `#{cmd}`

    stats = JSON.parse(output)

    File.open('./public/assets/webpack-asset-manifest.json', 'w') do |f|
      f.write stats['assetsByChunkName'].to_json
    end
  end
end

中间的–json是让webpack再次回到三个json的结果。
其中的stats[‘assetsByChunkName’]是一个entry name -> bundle
name的json文件。

如下:

{
  "common": "common-4cdf0a22caf53cdc8e0e.js",
  "authenticated": "authenticated-bundle-2cc1d62d375d4f4ea6a0.js",
  "public":"public-bundle-a010df1e7c55d0fb8116.js"
}

如此这般在组件中更新后,就不会再也 build 那么些第贰方的能源,

添加webpack的布局新闻到rails

config/applicaton.rb

config.webpack = {
  :use_manifest => false,
  :asset_manifest => {},
  :common_manifest => {},
}

config/initializers/webpack.rb

if Rails.configuration.webpack[:use_manifest]
  asset_manifest = Rails.root.join('public', 'assets', 'webpack-asset-manifest.json')
  common_manifest = Rails.root.join('public', 'assets', 'webpack-common-manifest.json')

  if File.exist?(asset_manifest)
    Rails.configuration.webpack[:asset_manifest] = JSON.parse(
      File.read(asset_manifest),
    ).with_indifferent_access
  end

  if File.exist?(common_manifest)
    Rails.configuration.webpack[:common_manifest] = JSON.parse(
      File.read(common_manifest),
    ).with_indifferent_access
  end
end

如果要Rails.configuration[:use_manifest]那正是说正是布署asset_manifest和common_manifest。

config/environments/production.rb中

config.webpack[:use_manifest] = true
  1. 再就是独立安插 dll/vendors.js 文件,提供给 webpack.dll.config.js
  2. 修改 package.json

写一个helper来实现development和production对entry的调用

# app/helpers/application_helper.rb

def webpack_bundle_tag(bundle)
  src =
    if Rails.configuration.webpack[:use_manifest]
      manifest = Rails.configuration.webpack[:asset_manifest]
      filename = manifest[bundle]

      "#{compute_asset_host}/assets/#{filename}"
    else
      "#{compute_asset_host}/assets/#{bundle}-bundle"
    end

  javascript_include_tag(src)
end

中间的webpack-asset-manifest.json, 大约如下:

{
"common":"common-b343fccb2be9bef14648.js",
"ques":"ques-bundle-ad8e6456e397dd8e7144.js",
"activities":"activities-bundle-806617bb69dfc78f4772.js",
"pages":"pages-bundle-77b73a5a1a91cd3b92bd.js",
"pages_front":"pages_front-bundle-3e4ed8bdff3d2fc59a70.js"
}

由此能够选取那么些来需找 entry name和bundle的文件的呼应关系。

个中webpack-common-manifest.json,大致如下

{
"1":"ques-bundle-ad8e6456e397dd8e7144.js",
"2":"activities-bundle-806617bb69dfc78f4772.js",
"3":"pages-bundle-77b73a5a1a91cd3b92bd.js",
"4":"pages_front-bundle-3e4ed8bdff3d2fc59a70.js"
}

webpack会生出多少个id为每贰个entrypoint,暗中认可webpack把这么些ids存在common
bundle中。然而难点是,不论什么日期你转移任何3个entrypoint的代码引发id的更动,那么common的代码都要翻新,由此缓存就一直不什么样含义了。


ChunkManifestPlugin的职能就是不写在common中,而是写在外头的二个文件中,大家在rails中把它解析了还要绑到了window上的webpackBundleManifest变量,所以我们的webpack会本人去找这一个变量.
从而大家的第①个helper正是来绑定那一个变量,代码如下:

def webpack_manifest_script
  return '' unless Rails.configuration.webpack[:use_manifest]
  javascript_tag "window.webpackManifest = #{Rails.configuration.webpack[:common_manifest]}"
end

在 scripts 中添加: “dll”: “webpack –config webpack.dll.config.js
–progress –colors “, 。

实施 npm run dll 未来,会在 dll 目录下生产 多少个公文 vendor-manifest.json
,vendor.dll.js

布局 webpack.dev.config.js 文件,插足二个 DllReferencePlugin
插件,并点名 vendor-manifest.json 文件

new webpack.DllReferencePlugin({
 context: join(__dirname, 'src'),
 manifest: require('./dll/vendor-manifest.json')
})

修改 html

<% if(htmlWebpackPlugin.options.NODE_ENV ==='development'){ %>
 <script src="dll/vendor.dll.js"></script>
<% } %>

只顾,须求在 htmlWebpackPlugin 插件中配备 NODE_ENV 参数

Happypack

透过多线程,缓存等方法提高 rebuild 功能

在 webpack.dev.config.js 中针对不一致的能源创立多少个 HappyPack, 比如 js 3个,less 1 个,并设置好 id

new HappyPack({
 id: 'js',
 threadPool: happyThreadPool,
 cache: true,
 verbose: true,
 loaders: ['babel-loader?babelrc&cacheDirectory=true'],
}),
new HappyPack({
 id: 'less',
 threadPool: happyThreadPool,
 cache: true,
 verbose: true,
 loaders: ['css-loader', 'less-loader'],
})

在 module.rules 中配置 use 为 happypack/loader, 设置 id

{
 test: /\.js$/,
 use: [
 'happypack/loader?id=js'
 ],
 exclude: /node_modules/
}, {
 test: /\.less$/,
 loader: extractLess.extract({
 use: ['happypack/loader?id=less'],
 fallback: 'style-loader'
 })
}

缩减 Webpack 打包后的公文体量大小

率先须求对我们凡事 bundle 进行剖析,由哪些东西组成及各组成都部队分所占大小。

那边推荐
webpack-bundle-analyzer
。安装后在 webpack.dev.config.js
中丰裕插件即可,就能在历次运转后活动在网站打开分析结果,如下图

plugins.push( new BundleAnalyzerPlugin());

澳门葡京 18

除外,还是能够将打包进度输出成json文件

webpack --profile --json -> stats.json

接下来到上边那多少个网站开始展览辨析

  1. webpack/analyse
  2. Webpack Chart

由此上边包车型客车图形分析能够领略得看看,整个 bundle.js
的组成部分及相应的尺寸。

消除 bundle.js 体量过大的解决思路如下:

  1. 生育条件启用压缩等插件,去除不须要插件
  2. 拆分业务代码与第3方库及公共模块
  3. webpack 开启 gzip 压缩
  4. 按需加载

生育环境启用压缩等插件,去除不供给插件

保险在生产条件运维 webpack.DefinePlugin 和
webpack.optimize.UglifyJsPlugin 。

const plugins = [
 new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
 }),
  new webpack.optimize.UglifyJsPlugin({
  compress: {
   warnings: false,
   drop_console: false //eslint-disable-line
  }
  })   
]

拆分业务代码与第1方库及国有模块

是因为品种的事务代码变更频率很高,而第3方库的代码变化则相对没有那么频率。假使将事情代码和第叁库打包到同2个chunk
的话,在历次创设的时候,哪怕业务代码只改了一行,就算第叁方库的代码没有发生变化,会招致整个
chunk 的 hash
跟上贰回区别。那不是大家想要的结果。大家想要的是,假诺第①方库的代码没有转变,那在创设的时候也要保管相应的
hash
没有产生变化,从而能动用浏览器缓存,更好的滋长页面加载品质和抽水页面加载时间。

从而能够将第①库的代码单独拆分成 vendor
chunk,与作业代码分离。那样即便业务代码再怎么产生变化,只要第二方库代码没有爆发变化,对应的
hash 就不变。

先是 entry 配置七个 app 和 vendor 八个chunk

entry: {
 vendor: [path.join(__dirname, 'dll', 'vendors.js')],
 app: [path.join(__dirname, 'src/index')]
},
output: {
 path: path.resolve(__dirname, 'build'),
 filename: '[name].[chunkhash:8].js'
},

中间 vendros.js 是投机定义的什么第②方库需求纳入 vendor 中,如下:

require('babel-polyfill');
require('classnames');
require('intl');
require('isomorphic-fetch');
require('react');
require('react-dom');
require('immutable');
require('redux');

然后经过 CommonsChunkPlugin 拆分第叁库

plugins.push(
 // 拆分第三方库
 new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }),
 // 拆分 webpack 自身代码
 new webpack.optimize.CommonsChunkPlugin({
  name: 'runtime',
  minChunks: Infinity
 })
);

地方的安顿有八个细节须要注意

  1. 使用 chunkhash 而不用 hash
  2. 独立拆分 webpack 本人代码

使用 chunkhash 而不用 hash

先来探望那二者有什么分化:

  1. hash 是 build-specific
    ,任何2个文书的改动都会导致编写翻译的结果不一致,适用于开发阶段
  2. chunkhash 是 chunk-specific ,是基于各种 chunk 的始末总括出的
    hash,适用于生产

故而为了保证第二方库不变的情形下,对应的 vendor.js 的 hash
也要维持不变,大家再 output.filename 中动用了 chunkhash

独自拆分 webpack 本身代码

Webpack 有个已知难题:

webpack 本身的 boilerplate 和 manifest 代码恐怕在每一趟编写翻译时都会转移。

那致使大家只是在 入口文件 改了一行代码,但编译出的 vendor 和 entry chunk
都变了,因为它们自个儿都包蕴那部分代码。

那是不成立的,因为实在大家的第1方库的代码没变,vendor
不应有在我们业务代码变化时发生变化。

从而大家供给将 webpack 那部分代码分离抽离

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

中间的 name 只要不在 entry 即可,经常使用 “runtime” 或 “manifest” 。

除此以外1个参数 minChunks 表示:在传出公共chunk(commons chunk)
以前所急需包括的起码数量的 chunks。数量必须大于等于2,或许简单等于
chunks的数码,传入 Infinity 会立即生成 公共chunk,但里边没有模块。

更加多关于 CommonChunkPlugin 能够查阅
官方文书档案

拆分公共能源

同 下面的拆分第叁方库一样,拆分公共财富得以将公用的模块单独打出八个chunk,你可以安装 minChunk
来挑选是共用多少次模块才将它们抽离。配置如下:

new webpack.optimize.CommonsChunkPlugin({
 name: 'common',
 minChunks: 2,
}),

是还是不是须求展开这一步优化能够自行依据项指标工作复开销来判断。

开启 gzip

运用 CompressionPlugin 插件开启 gzip 即可:

// 添加 gzip
new CompressionPlugin({
 asset: '[path].gz[query]',
 algorithm: 'gzip',
 test: /\.(js|html)$/,
 threshold: 10240,
 minRatio: 0.8
})

上述就是本文的全体内容,希望对大家的读书抱有援救,也盼望大家多多支持脚本之家。

你或者感兴趣的篇章:

  • vue
    webpack打包优化操作技能
  • vue-cli
    webpack2项目打包优化分享
  • webpack4.0打包优化策略整理小结

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website