前端项目代码批量替换域名问题

最近参与开发的产品有国内站和国际站之分,但代码维护的同一份,国际站与国内站,除了特定业务的不同,比如某些产品没有之外,主要即链接地址区别,

为了可维护性,代码中我们仍然写的是国内站链接,如果国际站用户访问,点击链接,则会302自动跳转到国际站链接下,足够的快,这个体验还OK。

但最近多种原因下,自动跳转关掉了,为了还是保持一份代码适应国际站/国内站,我们需要更新项目中已经散落各处的国内站链接,将其替换为不固定域名方式即变量写法,,比如将https://1991421.cn/2021/09/21/844e39cc/替换为https://${window.basicHost}/2021/09/21/844e39cc/。如何优雅的解决这个问题呢?

直接正则替换?

首先,想到的是直接的正则替换,但是尝试了下发现并不行,原因是ES6加下的代码写法种类太多,且这个问题根本上来说是将常量改成了变量拼接,因此直接文本替换是不行的。

列举下,代码中牵扯到链接的不同上下文写法

1
2
3
4
5
6
7
8
href="https://1991421.cn/2021/09/21/844e39cc/"

href="//1991421.cn/2021/09/21/844e39cc/"

href={`//1991421.cn/2021/09/21/${postId}`}

getUrl(`//1991421.cn/2021/09/21/${postId}`)

AST+正则替换

直接正则替换无法解决的直接原因是写法的不同,但是如果在代码编译到ES5后,写法即固定了,一定是单独的字符串,比如”//1991421.cn/2021/09/21/“+ postId。而这个时间点可以在webpack中很容易找到找到,比如compilation.hooks.optimizeChunkAssets

理论上到此可以正则进行字符串替换即可。但是替换还要解决,将字符串的符号获取,从而拼接增加JS对象变量。而这单纯正则搞会麻烦些,而AST来解决就简单多了。根本原因在于AST是从语法角度分析JS代码,直接找到字符串变量,同时获取符号即可。当然思路OK,那就是webpack插件编写了。

实践

webpack插件

因为最终是将JS代码进行解析转为AST,从而正则替换部分字符串,因此webpack插件来做最为合适。

具体插件可以在这里下载使用

  1. webpack插件部分,根据webpack暴露的hook选择chunk生成后的hook即可。
  2. 获取chunk源码,做AST解析从而实现替换即可。

关键替换logic如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

function replaceDomainLoader(source) {
const tokenization = esprima.tokenize(source);
tokenization.forEach((item) => {
if (item.type !== 'String') return;
const mark = item.value.charAt(0); // 获取字符串的标签
const {value} = item;
let res = value;
replaceQueue.forEach((obj) => {
res = res.replace(obj.reg, `${mark} + ${obj.value} + ${mark}`);
});
if (value === res) return;
source = source.replace(value, res);
});
return source;
}

HMR下插件问题

当前如果是开发模式- HMR下会出现替换错误,因此建议插件增加环境判定,比如

1
2
3
...
process.env.NODE_ENV === 'production' && new DomainReplacePlugin()
...

同时如果本地进行构建打包,注意环境变量问题,推荐cross-env解决,比如

1
npx cross-env NODE_ENV=production npm run build:dev

写在最后

  1. 如上解决后,以后业务代码中还是可以正常的

参考文档