阅读 5042

webpack4搭建现代Hybird-h5工程

一、前言

这篇文章会分享一下我是如何针对混合开发(Hybird h5端)搭建构建环境

内容涉及到如下几点:

(1)混合开发h5端页面特点以及需求

(2)如何利用webpack4进行多入口的代码分割以达到较优的缓存利用率

(3)如何针对首屏渲染进行优化

(4)使用webpack4如何同时输出ES next(现代)与es5(向后兼容)的包

(5)客户端打包h5资源到包内策略

代码仓库webpack-esnext-cli,大家也可以边看代码边理解,当然能自己动手搭建一次是最好的

二、混合开发h5端页面特点以及需求

混合开发的h5端页面有什么特点?

入口繁杂、部分页面仅文案、需要较快的首屏渲染速度、部分页面的使用率不高,依赖网络的速度快慢

(1)入口繁杂

入口繁杂其实意味着你的前端工程搭建必须是以多入口为起点搭建的,如webpack你可以配置entry,自行写一个脚本在构建时获取每一个页面的js入口,而多入口意味着你必须考虑页面之间共享的模块应该如何抽取以达到一个较优的模块利用率,这点我们在文章下一节详细讲。

(2)部分页面仅文案,且利用率不高

其实有做过混合开发你自然会发现有一些页面是只有文案,没有任何js交互的,这类的页面,类似用户协议、常见问题之类的文案类页面,使用率不高,对首屏又有要求的我们完全可以将html写在html文件中,不需要用vue这类的框架去写虚拟DOM依赖框架去渲染,这样可以节省掉请求脚本以及框架运行渲染的时间,保证文字可以快速出现在页面当中。

(3)较快的首屏渲染以及网络的快慢

因为我们的h5页面是比较依赖手机网络的,它不像客户端资源都是大包到本地的,现在虽然4g普及了,但是用户很多时候网络其实并不好,那在弱网的情况下对页面的打开速度就有了一定的挑战了,从工程角度上考虑,我们可不可以像客户端那样把h5需要的资源也打包进本地呢?答案当然是可以的,后面我会陆续讲到。

三、如何利用webpack4进行代码分割

根据上面第二点我们提到的页面特点和需求,我们的多页面共享的模块应该是下面这样的:

1、每个入口基本都需要用到的包应该长期缓存(hash值不变)

2、共享的chunk可自由分割

3、共享的chunk可在页面配置引用

那我们先来看看在webpack4中我们可以通过什么手段保证如上三点

例如在vue的工程当中,我们的每个入口基本都依赖于vue.js,而在我们打出的包中vue所占的资源大小比重也是比较大的,而这部分就是我们需要长期缓存的。关于这个点我在我的另一篇文章中讲过。这里我们在webpack4中会单独将vue打包成vendor作为项目的基础包供页面引入(当然你也可以将其余的模块打包进来)

...

optimization: {
    splitChunks: {
        ...
        
        'vendor': {
            test: /node_modules\/vue/,
            name: 'vendor',
            chunks: 'all',
            enforce: true,
            priority: 2
          },
          
        ...
    }
},
plugins: [
    // 稳定moduleId,避免引入了一个新模块后,导致模块ID变更使得vender和common的hash变化后缓存失效
    new webpack.HashedModuleIdsPlugin(),
]

...
复制代码

单独将vue打包进vendor后,我们就保证了一个基础模块是稳定的,但我们还需要一些灵活性,比如我们有一些复杂的页面可能会做成单页面,这时候我们就需要引入vue-router、vuex这类的工具。

你可能会说为什么不把这些包打包进vendor中?因为混合开发中页面的入口是非常繁杂的,如果用户打开一个普通的页面,仅依赖vue就可以了,但是因为你抽取的时候把vue-router也打包进去了,导致用户下载了一个它可能用不到的又比较大的文件,同时这样也会影响到其他页面的渲染速度,因为js包太大了,导致下载时间过长,而分开打包能起到一个增量下载的作用。

这时候我们在splitChunks中增加一项spa-vendor配置:

optimization: {
    splitChunks: {
        ...
        
        // 项目基础包
        'vendor': {
            test: /node_modules\/vue/,
            name: 'vendor',
            chunks: 'all',
            enforce: true,
            priority: 2
        },
        // 单页面需要引入vue-router, vuex,这里单独分割出来
        'spa-vendor': {
            test: /node_modules\/vue-router/g,
            name: 'spa-vendor',
            chunks: 'all',
            enforce: true, 
            priority: 10
        },
          
        ...
    }
},
复制代码

好了,到这里,我们已经把项目一些比较大的,不常变更的包独立分割出来并且做到持久缓存了,那剩余的大小不那么大的包我们就可以让webpack根据大小和引用率去自动打包了,这里我们加一个commons包的配置

optimization: {
    splitChunks: {
        ...
        
        // 项目基础包
        'vendor': {
            test: /node_modules\/vue/,
            name: 'vendor',
            chunks: 'all',
            enforce: true,
            priority: 2
        },
        // 单页面需要引入vue-router, vuex,这里单独分割出来
        'spa-vendor': {
            test: /node_modules\/vue-router/g,
            name: 'spa-vendor',
            chunks: 'all',
            enforce: true, 
            priority: 10
        },
        // 剩余chunk自动分割
        'commons': {
            name: 'commons',
            minChunks: 5, // 引用次数大于5则打包进commons
            minSize: 3000, // chunk大小大于这个值才允许打包进commons
            chunks: 'all',
            enforce: true,
            priority: 1
        }
        ...
    }
},
复制代码

大家看到这里会看到splitChunk中每个chunk的priority(优先级)是不一样的,commons的优先级是最低的,因为要等到spa-vendor和vendor抽取完成后才会到commons抽取

完成后,打出的包是下面这样的

vendor(60k):

spa-vendor(23k,还是比较大的):

commons:

自由分割和长期缓存我们已经做到了,那剩下的就是chunk在页面中自由引入了。在我写的webpack-esnext-cli中,我是用了nunjucks模板引擎去做页面的资源引入的,利用webpack4和webpack-manifest-plugin插件在打包后输出的资源表,对资源进行页面的自由配置

比如,需要引入spa-vendor的单页,我们会引入manifest、vendor、spa-vendor、commons包、页面入口js(业务文件)默认引入

<!DOCTYPE html>
<html lang="en" bgc-f7f7f7>
<head>
  ...
</head>
<body>
  <div id="app"></div>
  <!-- 以注释的方式添加模板语法,addAssets方法可以注入对应模块组(按顺序) -->
  <!-- {{ 'js' | addAssets(['manifest', 'vendor', 'spa-vendor', 'commons']) }} -->
</body>
</html>
复制代码

如果仅仅是普通的只需要基于vue的,我们会引入manifest、vendor、commons(这里的addAssets方法传入空数组默认引入这几个chunk),这样我们在页面打开时,就不需要加载spa-vendor了,达到一个模块冗余的作用

<!DOCTYPE html>
<html lang="en" bgc-f7f7f7>
<head>
  ...
</head>
<body>
  <div id="app"></div>
  <!-- 以注释的方式添加模板语法,addAssets方法可以注入对应模块组(按顺序) -->
  <!-- {{ 'js' | addAssets([]) }} -->
</body>
</html>
复制代码

当然你可以更细致的区分你的chunk应该如何去分割,这里只是演示一下。

四、使用webpack4输出ES next语法的包

PHILIP WALTON这篇文章讲解了输出es next的原理,那时候看得我是激情澎湃,所以就自己花了时间去实现了一套

这里就讲一下思路吧,代码实现大家可以自行去看仓库代码

在打包的时候,我们需要输出两套包

原理其实就是通过改变broswerList让babel编译出不同语法的包

modern(es6):

legacy(es5):

构建es6语法的包后我们需要输出es5的入口文件,为了避免输出的资源表重叠的情况需要给es5的入口重新命名

其中a.js为我们的es6构建入口,脚本创建的a-legacy.js为我们的es5包构建入口,内容如下:

// a-legacy.js

import './a.js'
复制代码

打包时,我们根据js入口生成对应的html文件后,根据每次打包生成的资源表选择资源进行插入

资源表:

如上图,资源表对应了原本的路径与输出后的资源路径,在输出html时根据路径去做资源匹配就可以了

输出后的html如下(modern包的manifest文件内联了):

其中支持type=module语法的浏览器就会自动加载es6语法的包,不支持则加载es5的向后兼容包

这么做其实效益是非常大的,下面引用两张PHILIP WALTON文章的图

可见输出的es6的包不管在size还是解析速度都是优于es5语法的包的,对于移动端加速效果还是非常大的

五、加速首屏与将h5资源打包进客户端

加速首屏要做什么?就是让内容尽快的出现啊。

在之前我们已经通过模块分割和输出es next包让我们的资源利用率和大小,还有代码解析的速度都得到了提升了,接下来我们应该要考虑一下如何利用客户端的能力进行优化了。

加速首屏,无非就是加快webview的启动速度,和减少包的下载时间嘛,因为首屏的速度很大一部分原因是因为资源下载导致页面阻塞。

设想一下,如果webview可以拦截我们的资源请求,那我们是不是就可以把我们页面的js与css等静态资源一起打包到客户端中,在客户端开启webview后,通过拦截url,对本地资源进行url的匹配,命中则读取本地文件,文件过期则再次从服务器上拉取,甚至可以让服务端做一个推送服务更新资源文件,能做到这样h5页面在客户端基本能达到秒开了,也能减轻服务器的压力,在弱网的情况下优化非常明显。

当然首屏你还可以通过构建预渲染一部分html到html文件中,我的另一篇文章中有讲--如何在webpack中做预渲染降低首屏空白时间,这里就不再说了

六、总结

Hybird h5的工程搭建,其实更多是根据需求去做的,使用webpack只是一种方式,更重要的我觉得是对资源加载、缓存的理解和运用。不说了,不说了,该时候搬砖了。有什么疑问或者建议欢迎提出。

文章分类
前端