背景
现在的web页面中,绝大多数资源都采用了CDN资源,CDN资源节省了服务器资源,也可以带来静态资源加载的速度提升,可以说是不二选择。那这带来的问题是什么呢?一旦出现部分域名被运营商封禁,或是CDN服务商出现问题,可能会导致全量的静态资源不可用。所以对于页面内的所有CDN的静态资源必须有域名的的容灾方案才行。
页面中有哪些CDN资源
- 项目中的构建出来的js资源
- 项目中的样式资源
- 项目中的图片视频等静态资源
- 服务端下发需要展示的一些静态资源
- 动态化方案中下发的嵌入的页面资源或者是代码资源
- 可能使用的第三方库中存在的静态资源
如何处理
上面的六种资源从出现的来源上基本上可以分为三类:1,2,3是由前端代码产生的,4和5是从接口中获取的,6是前端引入第三方库导致的。先看4和5,这些接口返回的数据我们可以很轻易的通过,后端动态配置或者是接口数据清洗来做到。然后看一下6,显然第三方的代码没有可能来支持这种容灾的场景,所以先直接忽略这些资源吧。
前端CDN资源
前端项目中产生的CDN资源一般有两种方式带来:一种是在代码中写死的CDN静态资源,包含了css中常见引入的背景图地址,js中可能引入的图片地址;另一种是项目中构建生成的js,css及一些静态资源会在线上构建后被同一上传到cdn上,项目中则是通过publicpath+资源名的形式去引用,这部分一般是通过webpack实现的。
publicPath动态化
首先我们知道线上的publicPath是由我们写死的cdnURL+ 项目path组成的。在构建产物中会存在一个常量publicPath,而所有构建生成的所有静态资源都是用这个常量做的拼接。那么我们的问题就是如何让现在的publicPath变成一个动态的地址+项目path。
可以发现HTML资源一般就是是通过域名访问ng转发的方式返回给用户的,所以只要返回的html中可以动态的插入一段js,例如插<script>window.globalCDNUrl = 'xxx.com'</script>。这个工作在ng服务器上可以通过文件字符串处理的脚本实现,比如脚本会扫描html文件,将其中的##globalCDNUrl字符串替换为xxx.com,而项目中输出的HTML文件头部加上
<script>
window.globalCDNUrl = '##globalCDNUrl';
if(window.globalCDNUrl === ('#' + '#globalCDNUrl') ) {
window.globalCDNUrl === 'alicdn.com'
}
</script>
兜底处理一下ng上脚本失败的场景。 最后我们的publicPath = window.globalCDNUrl + '/path'的方式,就可以做到publicPath动态化了。最后一步publicPath如何设置为一个变量的方式,可以通过现成的webpack插件来实现。
静态代码的处理
这里比较粗暴的方案是代码搜索手动替换,问题是代码侵入性比较高,需要消耗大量人力。下面简单看一下使用插件的方案。
js中的静态地址
目的是扫描到所有的静态cdn地址,替换为 window.globalCDNUrl的变量 考虑写一个babel的插件,demo如下
module.exports = ({type: t}) => {
return {
visitor: {
StringLiteral: (path, state) => {
state.opts.rules.map((opt) => {
//判断是不是字符串
const isStringLiteral = path.node.type === 'StringLiteral';
//判断有没有cdn的静态地址,这里可以用正则
if(isStringLiteral && path.node.value.match('cdnpath')) {
//地址字符串替换
let newPath = path.node.value.replace('cdnpath', '/');
//以下以react项目为例子
if (path.parentPath.node.type === 'JsxArribute' || path.parentPath.node.type === 'JSXArribute') {
//需要转为jsx 代码
path.replaceWith(t.JSXExpressionContainer(t.binaryExpression('+', t.Identifier('globalCDNUrl'), t.stringLiteral(newPath))))
} else {
//需要转为变量名+newPath
path.replaceWith(t.binaryExpression('+', t.Identifier('globalCDNUrl'), t.stringLiteral(newPath)))
}
}
})
}
}
}
}
css中的静态地址
css中的静态资源只要都改为相对路径即可,因为css中的相对路径是相对于css资源的地址的。 这里可以用一个loader来把css中的绝对路径直接都改为相对路径。
module.exports = function(source) {
//这里就把需要的正则加上
source = source.replace('your reg', '');
return source;
};
如何验证
- 仓库代码扫描cdn地址
- 使用puppeteer工具启动页面进行资源扫描,可以手动替换globalCDNUrl进行实验检测,有遗漏
- 容灾演练
问题
- 第三方资源不容易处理
- 验证过程未见得完备