前言
H5 基础脚手架:极速构建项目
上一篇讲到了快速构建项目的通用 webpack 构建,此篇将结合业务修改 H5 的脚手架
小声 BB,不是一定适合你的项目,具体项目具体对待,符合自身业务的才是最好的
资源添加版本号
看过之前博客的同学,应该知道在创建版本的时候引入了版本号的概念,在创建分支版本的时候,带上版本号,创建的分支名为 feat/0.0.01,而我们发布的静态资源也是带了版本
之前有让同学关注过 url 上面的版本号哈
改造 Webpack 路径
const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().replace(/\s+/, '')
const version = branch.split('/')[1] // 获取分支版本号
output: {
publicPath: `./${version}`,
}
如上,我们先将分支版本号获取,再修改资源引用路径,即可完成资源版本的处理,如下图所示,h5 链接被修改成常规 url,引用资源带上了版本号
版本号的优势
- 可以快速定位 code 版本,针对性的修复
- 每个版本资源保存上在 cdn 上,快速回滚只需要刷新 html,不必重新构建发布
Webpack Plugin 开发
直接添加版本的方法是不是蠢出天际,so 我们随便写个插件玩玩好了
const HtmlWebpackPlugin = require('html-webpack-plugin');
const childProcess = require('child_process')
const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().replace(/\s+/, '')
const version = branch.split('/')[1]
class HotLoad {
apply(compiler) {
compiler.hooks.beforeRun.tap('UpdateVersion', (compilation) => {
compilation.options.output.publicPath = `./${version}/`
})
}
}
module.exports = HotLoad;
module.exports = {
plugins: [
new HotLoad()
]}
如上,我们创建一个 webpack plugin,通过监听 webpack hooks 在任务执行之前修改对应的资源路径,通用性上升。
高级定制化
CDN 资源引入
此外之前的博客我们还引入了 cdn 的概念,我们可以将上述插件升级,构建的时候引入通用的 cdn 资源,减少构建与加载时间。
const scripts = [
'https://cdn.bootcss.com/react-dom/16.9.0-rc.0/umd/react-dom.production.min.js',
'https://cdn.bootcss.com/react/16.9.0/umd/react.production.min.js'
]
class HotLoad {
apply(compiler) {
compiler.hooks.beforeRun.tap('UpdateVersion', (compilation) => {
compilation.options.output.publicPath = `./${version}/`
})
compiler.hooks.compilation.tap('HotLoadPlugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('HotLoadPlugin', (data, cb) => {
scripts.forEach(src => [
data.assetTags.scripts.unshift({
tagName: 'script',
voidTag: false,
attributes: { src }
})
])
cb(null, data)
})
})
}
}
上述我们借助了 HtmlWebpackPlugin 提供的 alterAssetTags hooks,主动添加了 react 相关的第三方 cdn 链接,这样在生产环境中,同域名下面的项目,可以复用资源。
通过缓存解决加载 js
对于长期不会改变的静态资源,可以直接将资源缓存在本地,下次项目打开的时候可以直接从本地加载资源,提高二次开启效率。
首先,我们选择 indexDB 来进行缓存,因为 indexDB 较 stroage 来说,容量会更大,我们本身就需要缓存比较大的静态资源所以需要更大容量的 indexDB 来支持
import scripts from './script.json';
import styles from './css.json';
import xhr from './utils/xhr'
import load from './utils/load'
import storage from './stroage/indexedDb'
const _storage = new storage()
const _load = new load()
const _xhr = new xhr()
class hotLoad {
constructor(props) {
this.loadScript(props)
this.issafariBrowser = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
}
async loadScript(props = []) {
const status = await _storage.init()
let _scripts = scripts
const expandScripts = props
if (status) {
for (let script of _scripts) {
const { version, type = 'js', name, url } = script
if (this.issafariBrowser) {
await _load.loadJs({ url })
} else {
const value = await _storage.getCode({ name, version, type });
if (!value) {
const scriptCode = await _xhr.getCode(url || `${host}/${name}/${version}.js`)
if (scriptCode) {
await _load.loadJs({ code: scriptCode })
await _storage.setCode({ scriptName: `${name}_${version}_${type}`, scriptCode: scriptCode })
}
} else {
await _load.loadJs({ code: value })
}
}
}
for (let style of styles) {
const { url, name, version, type = 'css' } = style
if (this.issafariBrowser) {
await _load.loadCSS({ url })
} else {
const value = await _storage.getCode({ name, version, type })
if (!value) {
const cssCode = await _xhr.getCode(url || `${host}/${name}/${version}.css`)
_storage.setCode({ scriptName: `${name}_${version}_${type}`, scriptCode: cssCode })
_load.loadCSS({ code: cssCode })
} else {
_load.loadCSS({ code: value })
}
}
}
} else {
for (let script of _scripts) {
const { version, name } = script
const scriptCode = await _xhr.getCode(script.url || `${host}/${name}/${version}.js`)
if (scriptCode) {
await _load.loadJs({ code: scriptCode })
}
}
for (let style of styles) {
const { url, name, version } = style
const cssCode = await _xhr.getCode(url || `${host}/${name}/${version}.css`)
_load.loadCSS({ code: cssCode })
}
}
for (let script of expandScripts) {
const { url } = script
await _load.loadJs({ url })
}
}
}
window.hotLoad = hotLoad
上述代码是将第三方资源,通过 xhr 获取之后,使用 Blob + URL.createObjectURL 制造本地链接,使用 js 动态添加到页面中去。
class load {
constructor() { }
// 加载js
loadJs({ url, code, callback }) {
let oHead = document
.getElementsByTagName('HEAD')
.item(0);
let script = document.createElement('script');
script.type = "text/javascript";
return new Promise(resolve => {
if (url) {
script.src = url
} else {
let blob = new Blob([code], { type: "application/javascript; charset=utf-8" });
script.src = URL.createObjectURL(blob);
}
oHead.appendChild(script)
if (script.readyState) {
script.onreadystatechange = () => {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback && callback();
resolve(true)
}
}
} else {
script.onload = () => {
callback && callback();
resolve(true)
}
}
})
}
// 加载css
loadCSS({ url, code }) {
let oHead = document
.getElementsByTagName('HEAD')
.item(0);
let cssLink = document.createElement("link");
cssLink.rel = "stylesheet"
return new Promise(resolve => {
if (url) {
cssLink.href = url
} else {
let blob = new Blob([code], { type: "text/css; charset=utf-8" });
cssLink.type = "text/css";
cssLink.rel = "stylesheet";
cssLink.rev = "stylesheet";
cssLink.media = "screen";
cssLink.href = URL.createObjectURL(blob);
}
oHead.appendChild(cssLink);
resolve(true)
})
}
}
// 通过 xhr 拉取静态资源
class xhr {
constructor() {
this.xhr;
if (window.XMLHttpRequest) {
this.xhr = new XMLHttpRequest();
} else {
this.xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
}
// 同步请求js
getCode(url) {
return new Promise(resolve => {
this.xhr.open('get', url, true);
this.xhr.send(null);
this.xhr.onreadystatechange = () => {
if (this.xhr.readyState == 4) {
if (this.xhr.status >= 200 && this.xhr.status < 300 || this.xhr.status == 304) {
resolve(this.xhr.responseText)
}
}
}
})
}
}
弱网环境下的直接加载
弱网环境下的缓存加载
对比上图,可以明显看出,在网络环境波动的情况下,有缓存加持的网页二次开启的速度会明显提效,当然在性能上,由于需要判断第三方静态资源版本以及从本地读取资源,会消耗部分时间,可以针对业务自行取舍
优劣势对比
优势
- 统一接管项目的依赖,可以针对性的升级通用资源
- 资源有版本依赖概念,缓存在本地的时候,可以快速切换版本
- 二次加载速度会上升
- 配合 Service Worker 有奇效
劣势
- 统一升级的过程,可能有引用项目存在不匹配造成程序崩溃的情况
- 其实强缓存所有共用静态 cdn 资源也是 ok 的,干嘛那么费劲呢
上述的插件有没有同学想要用的,需要的留言,我放到 github 上去
全系列博文目录
后端模块
- DevOps - Gitlab Api使用(已完成,点击跳转)
- DevOps - 搭建 DevOps 基础平台 基础平台搭建上篇 | 基础平台搭建中篇 | 基础平台搭建下篇
- DevOps - Gitlab CI 流水线构建
- DevOps - Jenkins 流水线构建
- DevOps - Docker 使用
- DevOps - 发布任务流程设计
- DevOps - 代码审查卡点
- DevOps - Node 服务质量监控
前端模块
- DevOps - H5 基础脚手架
- DevOps - React 项目开发
尾声
此项目是从零开发,后续此系列博客会根据实际开发进度推出(真 TMD 累),项目完成之后,会开放部分源码供各位同学参考。
为什么是开放部分源码,因为有些业务是需要贴合实际项目针对性开发的,开放出去的公共模块我写的认真点
为了写个系列博客,居然真撸完整个系统(不是一般的累),觉得不错的同学麻烦顺手三连(点赞,关注,转发)。