提取第三方库,缓存,减少打包体积

1、 dll动态链接库, 使用DllPlugin DllReferencePlugin,将第三方库提取出来另外打包出来,然后动态引入html。可以提高打包速度和缓存第三方库

这种方式打包可以见京东团队的gaea方案

https://www.npmjs.com/package/gaea-cli

2、webpack4的splitChunks或者 webpack3 CommonsChunkPlugin 配合 externals (资源外置)

主要是分离 第三方库,自定义模块(引入超过3次的自定义模块被分离),webpack运行代码(runtime,minifest)。

配合externals,意思将第三方库外置,用cdn的形式引入,可以减少打包体积。

详细代码

在webpack.config.js(peoduction环境下)

externals: {
'vue': 'Vue', //vue 是包名 Vue是引入的全局变量
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios',
'iview': 'iview' //iview
},

然后再main.js或者任何地方不再引入 比如vue,直接使用上面提供的变量



上面没有import vue进来,项目中照常使用Vue这个全局变量。

既然没有import vue 自然不会打包vue,然后你会发现你的vendor.js会从700kb+ 减少到 30-40kb,非常棒的优化。

关于是否注释掉,这里有两张验证图,在webpack配置了externals的情况下





webpack4 splitChunk的配置

//提取node_modules里面的三方模块
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "initial",
test: path.resolve(__dirname, "node_modules") // 路径在 node_modules 目录下的都作为公共部分
name: "vendor", // 使用 vendor 入口作为公共部分
enforce: true,
},
},
},
},
}
//提取 manifest (webpack运行代码)
{
runtimeChunk: true;
}

webpack3 CommonsChunkPlugin 的配置,写在plugins中

 // split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),

下面两个问题详细见 https://www.jianshu.com/p/23dcabf35744

固定module id,为了缓存

chunk: 是指代码中引用的文件(如:js、css、图片等)会根据配置合并为一个或多个包,我们称一个包为 chunk。

module: 是指将代码按照功能拆分,分解成离散功能块。拆分后的代码块就叫做 module。可以简单的理解为一个 export/import 就是一个 module。

解决方案:

HashedModuleIdsPlugin 或者 webpack4 的 optimization.moduleIds='hash'

固定chunk id

我们在固定了 module id 之后同理也需要固定一下 chunk id,不然我们增加 chunk 或者减少 chunk 的时候会和 module id 一样,都可能会导致 chunk 的顺序发生错乱,从而让 chunk 的缓存都失效。

提供了一个叫NamedChunkPlugin的插件,但在使用路由懒加载的情况下,你会发现NamedChunkPlugin并没什么用。

原因:

使用自增 id 的情况下是不能保证你新添加或删除 chunk 的位置的,一旦它改变了,这个顺序就错乱了,就需要重排,就会导致它之后的所有 id 都发生改变了。

下面两种解决方案

第一种:

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

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

我们在结合 vue 的懒加载可以这样写。

{
path: '/test',
component: () => import(/* webpackChunkName: "test" */ '@/views/test')
},

还要记得配置chunkFilename

output: {
path: path.resolve(__dirname, 'dist'),
publicPath: config.publicPath + '/',//静态文件的处理,生产环境有效.开发环境其实是从内存中拿文件的
filename: 'js/[name].[chunkhash].js',
chunkFilename: 'js/[name].[chunkhash].js' //写成[name].xxxx,便于查找chunk源 详细见 NamedChunkPlugin
},

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

chunk 有了 name 之后就可以解决NamedChunksPlugin没有 name 的情况下的 bug 了。查看打包后的代码我们发现 chunkId 就不再是一个简单的自增 id 了。

推荐第一种,既可以固定chunk id(用的chunkname代替),又可以了解项目打包详情比如遇到大文件,到底是哪个chunk出了问题,直接映射问题源



我们可以直接看到786kb的大文件是来自于 test1.vue和test2.vue的vendor包(第三方库),然后进入test1.vue,echarts就是问题源,关于解决就是把echarts等第三方库外置。详细见上面资源外置。

第二种:

原理:根据每个chunk里面的module id 去唯一化这个chunk的name,只要里面的module没有增多或减小,那么它的名字是不会变的

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;
}
});

提取css为单独文件并压缩

webpack4 的 mini-css-extract-plugin

webpack3的ExtractTextPlugin

压缩js文件

webpack3 UglifyJsPlugin

webpack4 自带了UglifyJsPlugin功能,无需配置,需要开启mode production

tree shaking和sideEffects

去除没有被引用的代码, webpack4默认支持。

因为Tree Shaking这个功能是基于ES6 modules 的静态特性检测,来找出未使用的代码,所以如果你使用了 babel 插件的时候,如:babel-preset-env,它默认会将模块打包成commonjs,这样就会让Tree Shaking失效了。

sideEffects是webpack4才有的功能,目的是对第三方没有任何副作用的库进行按需加载。

webpack 的 sideEffects 可以帮助解决这个问题。现在 lodash 的 ES 版本 的 package.json 文件中已经有 sideEffects: false 这个声明了,当某个模块的 package.json 文件中有了这个声明之后,webpack 会认为这个模块没有任何副作用,只是单纯用来对外暴露模块使用,那么在打包的时候就会做一些额外的处理。

例如你这么使用 lodash:


import { forEach, includes } from 'lodash-es' forEach([1, 2], (item) => {
console.log(item)
}) console.log(includes([1, 2, 3], 1))

由于 lodash-es 这个模块的 package.json 文件有 sideEffects: false 的声明,所以 webpack 会将上述的代码转换为以下的代码去处理:

import { default as forEach } from 'lodash-es/forEach'
import { default as includes } from 'lodash-es/includes'
// ... 其他代码

最终 webpack 不会把 lodash-es 所有的代码内容打包进来,只是打包了你用到的那两个方法,这便是 sideEffects 的作用。

懒加载 import()

babel需要配置@babel/plugin-syntax-dynamic-import

按需加载 import(/* webpackChunkName: "Index" */ "xxx.vue")

命名设置规则在chunkFilename (如果没有设置,则按照默认的1.xxxx.js这样命名,其实也会分开打包,便于调试,打包时看到某个chunk比较大,可以查看该chunk对应的vue文件)

chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')

既然按需加载,就不会打包到 app.js(主entry chunk)中,肯定会分开打包,然后按需加载

babel 按需引入pollyfill

Babel 默认只转换 JavaScript 语法,而不转换新的 API,比如 Promise、Generator、Set、Maps、Symbol 等全局对象,一些定义在全局对象上的方法(比如 Object.assign)也不会被转码。如果想让未转码的 API 可在低版本环境正常运行,这就需要使用 polyfill。

babel6当前最普遍的解决方案

使用transform-runtime或者babel-polyfill

比较transform-runtimebabel-polyfill引入垫片的差异:

使用transform - runtime是按需引入,需要用到哪些polyfill,runtime就自动帮你引入哪些,不需要再手动一个个的去配置plugins,只是引入的polyfill不是全局性的,有些局限性。而且runtime引入的polyfill不会改写一些实例方法,比如Object和Array原型链上的方法,像前面提到的Array.protype.includes

注意使用transform-runtime需要安装babel-runtimebabel-runtime 是一个库,用于引入的 ,放在--save 而 babel-plugin-transform-runtime是帮助引入babel-runtime这个库的(自动的)

babel-runtimebabel-plugin-transform- runtime的区别是,相当一前者是手动挡而后者是自动挡,每当要转译一个api时都要手动加上require('babel-runtime') ,

babel - plugin - transform - runtime会由工具自动添加,主要的功能是为api提供沙箱的垫片方案,不会污染全局的api,因此适合用在第三方的开发产品中。

而重复引入会被webpack设置的commonChunkPlugin 给去重

babel - polyfill就能解决runtime的那些问题,它的垫片是全局的,而且全能,基本上ES6中要用到的polyfill在babel - polyfill中都有,它提供了一个完整的ES6 + 的环境。babel官方建议只要不在意babel - polyfill的体积,最好进行全局引入,因为这是最稳妥的方式。

一般的建议是开发一些框架或者库的时候使用不会污染全局作用域的babel - runtime,而开发web应用的时候可以全局引入babel - polyfill避免一些不必要的错误,而且大型web应用中全局引入babel - polyfill可能还会减少你打包后的文件体积(相比起各个模块引入重复的polyfill来说)。

以下为三种babel6解决ES6 API pollyfill的引入方式

①全局使用babel - polyfill(不设置babel-preset-env options项的useBuiltIns)

具体使用方法如下:

a.直接在index.html文件head中直接引入polyfill js或者CDN地址;

b.在package.json中添加babel - polyfill依赖, 在webpack配置文件增加入口: 如entry: ["babel-polyfill", './src/app.js'], polyfill将会被打包进这个入口文件中, 必须放在文件最开始的地方;

c.在入口文件顶部直接import ''babel-polyfill';

此方案的优点是简单、一次性可以解决浏览器的所有polyfill兼容性问题,缺点就是一次性引入了ES6 + 的所有polyfill, 打包后的js文件体积会偏大, 在现代浏览器上不需要全部的polyfill, 其次污染了全局对象,不太适合框架类的开发,框架类的开发建议下面的②方案。

注: polyfill.io库会根据你的使用的浏览器做相应的polyfill, 可以极大的解决引入过大的问题。

② 全局使用babel-polyfill(设置babel-preset-env options项的useBuiltIns)

具体使用方法如下:

  1. 引入babel-preset-env包;
  2. 在.babelrc文件预设presets中使用设置babel - preset - env options项

    useBuiltins: usage | entry

    (usage: 仅仅加载代码中用到的 polyfill.entry: 根据浏览器版本的支持,将 polyfill 需求拆分引入,仅引入有浏览器不支持的polyfill)

    targets.browsers: 浏览器兼容列表

    modules: false
  3. 在入口文件顶部直接import ''babel - polyfill';

此方案适合应用级的开发,babel会根据指定的浏览器兼容列表自动引入所有所需的polyfill。

③ 使用插件 babel-runtimebabel-plugin-tranform-runtime

babel-runtime会出现重复引用的问题,而babel-plugin-tranform-runtime抽离了公共模块, 避免了重复引入,下面的配置主要以babel-plugin-tranform-runtime来说。

  1. 引入babel-plugin-tranform-runtime包;
  2. .babelrc文件plugins中添加babel-plugin-tranform-runtime: "plugins": ["transform-runtime"];
  3. 配合上面方法②中的第2步中的预设presets的设置;

此方案无全局污染,依赖统一按需引入(polyfill是各个模块共享的), 无重复引入, 无多余引入,适合用来开发库。

安装包

"babel-core": "^6.22.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^7.1.1",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",

vue-cli的babel-cli的.babelrc


{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}

babel7解决方案(注意core-js的版本)

pollyfill 按需加载

@babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块用于模拟完整的 ES2015+ 环境。

不再需要手动引入import ''babel-polyfill';

只需要简单的配置就能自动智能化引入@babel/polyfill,设置useBuiltIns按需加载

.babelrc

{
"presets": [
["@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8", "Android >= 4", "iOS >= 8"]
},
"useBuiltIns": "usage" }]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import" ]
}

升级到7需要安装关于@babel的包

"@babel/core": "^7.1.2",
"@babel/plugin-syntax-dynamic-import": "7.0.0", //用于import()
"@babel/polyfill": "7.0.0",
"@babel/preset-env": "7.1.0",
"babel-loader": "8.0.4",

babel使用总结,建议使用babel7,构建速度更快,建议使用@babel/preset-env",建议开启useBuiltIns属性,让babel-polyfill按需加载。关于开启与不开启useBuiltIn构建包的大小详细见https://github.com/ab164287643/studyBabel/tree/master/7-babel-env

webpack3和webpack4的差异比较

1、增加了mode配置,只有两种值development | production,对不同的环境他会启用不同的配置。

2、默认生产环境开起了很多代码优化(minify, splite)

3、 开发时开启注视和验证,并加上了evel devtool

4、 生产环境不支持watching,开发环境优化了打包的速度

5、 生产环境开启模块串联(原ModulecondatenationPlugin)

6、自动设置process.env.NODE_EVN到不同环境,也就是不使用DefinePlugin了

7 、如果mode设置none,所有默认设置都去掉了。

8、在webpack4之前,我们处理公共模块的方式都是使用CommonsChunkPlugin,然后该插件的让开发这配置繁琐,并且公共代码的抽离,不够彻底和细致,因此新的splitChunks改进了这些能力。

9、默认开启 uglifyjs - webpack - plugin 的 cache 和 parallel,即缓存和并行处理,这样能大大提高 production mode 下压缩代码的速度。

生产环境和开发环境各自增加很多默认配置(比如UglifyJsPlugin默认用于生产环境),打包速度更快

图片压缩

使用tinify压缩要使用的图片。 详细脚本见 https://gitee.com/cchennlleii/MyTest/blob/master/webpack/.tinypng.js(此脚本来自于京东gaea)

关于图片格式优化

jpeg 有损压缩,体积小,不支持透明。

png 无损压缩,高保真,支持透明。

png - 8 2 ^ 8种色彩 256种

png - 24 2 ^ 24种色彩 1600w种

png - 32 2 ^ 24 ^ 8种 (还有8种透明度色彩通道)

颜色支持越多,体积越大

svg 矢量图 体积小 不失真,适用于小图标

base64 减小http请求,但不宜处理大图片,因为大图片增加页面大小,webpack的url - loader已经支持

webP 新兴格式,支持有损和无损压缩,支持透明,体积还特别小,与 PNG 相比,通常提供 3 倍的文件大小,浏览器兼容性低,局限性较大。

项目中的支持webp(参照自京东gaea):

在index.html中判断是否支持webP

window.supportWebp = false;
if (document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0) {
document.body.classList.add('webp');
window.supportWebp = true;
}

然后用上面的图片压缩脚本压缩图片,会在img下面生成一个webp文件,里面就是转换后的webp格式的图片。

css中写两套样式,比如

.banner{ background - image: url("xxxx.png") }
.webp .banner{ background - image: url("xxxx.webp") }

在js中根据window.supportWebp去判断用哪种图片。

开启gziped

使用compression - webpack - plugin

在生产环境下开启

if (config.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin');
//增加浏览器CPU(需要解压缩), 减少网络传输量和带宽消耗 (需要衡量,一般小文件不需要压缩的)
//图片和PDF文件不应该被压缩,因为他们已经是压缩的了,试着压缩他们会浪费CPU资源而且可能潜在增加文件大小。
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js|css)$/,
threshold: 10240,//达到10kb的静态文件进行压缩 按字节计算
minRatio: 0.8,//只有压缩率比这个值小的资源才会被处理
deleteOriginalAssets: false//使用删除压缩的源文件
})
)
}

当开启gziped压缩后,服务器需要做相应的配置,让服务器端可以传输压缩后的文件。

开启 nginx 服务端 gzip性能优化。找到nginx配置文件在 http 配置里面添加如下代码,然后重启nginx服务即可。

http: {
gzip on;
gzip_static on;
gzip_buffers 4 16k;
gzip_comp_level 5;
gzip_types text / plain application / javascript text / css application / xml text / javascript application / x - httpd - php image / jpeg
image / gif image / png;
}

开启apache gziped压缩

在 http.conf里面配置

找到下面这句去掉#

LoadModule deflate_module modules / mod_deflate.so

然后在最后面加上,记住不压缩图片

< IfModule mod_deflate.c >
# 告诉 apache 对传输到浏览器的内容进行压缩
SetOutputFilter DEFLATE
# 压缩等级 9
DeflateCompressionLevel 9
#设置不对后缀gif,jpg,jpeg,png的图片文件进行压缩
SetEnvIfNoCase Request_URI.(?: gif | jpe ? g | png)$ no - gzip dont - vary
</IfModule >
可以看到如下效果,http传输大小为173kb,而解压缩后大小为619kb



开启后会大大加快首页加载时长,效果非常不错。

图片懒加载

放个链接吧

https://juejin.im/post/5bbc60e8f265da0af609cd04

本文所有配置代码

https://gitee.com/cchennlleii/MyTest/tree/master/webpack

vue客户端渲染首屏优化之道的更多相关文章

  1. 基于 SSR 的预渲染首屏直出方案

    基于 SSR 的预渲染首屏直出方案 Create React Doc 是一个使用 React 的 markdown 文档站点生成工具.此前在 Create React Doc 中引入了预渲染技术来预先 ...

  2. Vue 单页应用 的 首屏优化

    对于单页应用,要在一个页面上为用户提供产品的所有功能,在这个页面加载的时候,首先要加载大量的静态资源,这个加载时间相对比较长.所以我们需要做一些相应的优化,减少响应时间,尽快把首屏显示出来. 1.压缩 ...

  3. angular 首屏优化

    前一段时间把公司的一个angular项目做了一次大的优化,记录一下过程. 起因: 起因是用户反映网站加载时间过长,从loading画面显示到页面可响应要13s,对于一般的页面恐怕没有用户愿意等待这么久 ...

  4. vue.js 首屏优化

    我们以 vue-cli 工具为例,使用 vue-router 搭建SPA应用,UI框架选用 element-ui , ajax方案选用 axios, 并引入 vuex ,使用 vuex-router- ...

  5. prerender-spa-plugin Vue预渲染配合meta-info优化seo

    记录一下解决方案的过程 先安装prerender和puppeteer插件  这个国外大神写的  github地址就不附上了(百度有) cnpm install prerender-spa-plugin ...

  6. vue 客户端渲染和服务端渲染

    参考链接 https://www.cnblogs.com/tiedaweishao/p/6644267.html

  7. Vue SPA 首屏加载优化实践

    写在前面 本文记录笔者在Vue SPA项目首屏加载优化过程中遇到的一些坑及优化方案! 我们以 vue-cli 工具为例,使用 vue-router 搭建SPA应用,UI框架选用 element-ui ...

  8. 腾讯优测优分享 | 探索react native首屏渲染最佳实践

    腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~ 此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app ...

  9. 探索react native首屏渲染最佳实践

    文 / 腾讯 龚麒 0.前言 react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react n ...

随机推荐

  1. 详解Java的Spring框架中的注解的用法

    转载:http://www.jb51.net/article/75460.htm 1. 使用Spring注解来注入属性 1.1. 使用注解以前我们是怎样注入属性的 类的实现: class UserMa ...

  2. python实现四则运算和效能分析

    代码github地址:https://github.com/yiduobaozhi/-1 PSP表格: 预测时间(分钟) planning 计划 15 Estimate 估计这个任务需要多少时间 10 ...

  3. Spring温故而知新 – Spring AOP

    AOP的相关专业术语 通知(Advice):定义在连接点做什么 Spring中通知类型:前置通知,后置通知,返回通知,异常通知,环绕通知 连接点(JoinPoint):程序执行过程中拦截的点,Spin ...

  4. DX11 Without DirectX SDK--03 渲染一个立方体

    回到 DirectX11--使用Windows SDK来进行开发 一个立方体有8个顶点,然而绘制一个立方体需要画12个三角形,如果按照前面的方法绘制的话,则需要提供36个顶点,而且这里面的顶点数据会重 ...

  5. pycharm linux版快捷方式创建

    ****************************pycharm_linux安装and快捷方式创建******************1.下载好安装包之后解压:    tar -xfz 压缩包名 ...

  6. 【转】网上看到的“12个非常有用的JavaScript技巧”

    1) 使用!!将变量转换成布尔类型 有时,我们需要检查一些变量是否存在,或者它是否具有有效值,从而将它们的值视为true.对于做这样的检查,你可以使用!!(双重否定运算符),它能自动将任何类型的数据转 ...

  7. Elasticsearch结构化搜索_在案例中实战使用term filter来搜索数据

    1.根据用户ID.是否隐藏.帖子ID.发帖日期来搜索帖子 (1)插入一些测试帖子数据 POST /forum/article/_bulk { "index": { "_i ...

  8. PHP使用文件排它锁,应对小型并发

    总所知周,并发容易造成数据的重复处理,我的一个项目给游戏客户端提供了接口,游戏用户相对较多,如果使用数据库排它锁消耗的资源较高,因而使用了文件的排它锁来应对并发. 思路是打开一个文件并取得文件的独占锁 ...

  9. HTTP协议(一)

    http协议 协议:是一种规则或者规定 tcp/ip协议:规则了tcp客户端与tcp服务器数据的通讯格式 1.知识点是什么:http协议 2.知识点有什么:规定浏览器与服务器(tcp服务器)之间的数据 ...

  10. LeetCode Javascript实现 100. Same Tree 171. Excel Sheet Column Number

    100. Same Tree /** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; ...