Recently, the product I’ve been involved in developing has both domestic and international versions, but they use the same codebase. The main difference between the international and domestic versions, apart from some specific business features that are missing, is the distinction in link addresses.
For maintainability, we still write domestic site links in the code. If an international user accesses the site and clicks on a link, it will automatically 302 redirect to the international domain, which is fast enough and provides a good experience.
However, due to various reasons, the automatic redirect has been disabled recently. To maintain a single codebase that works for both international and domestic sites, we need to update the scattered domestic site links in the project and replace them with variable notation instead of fixed domains. For example, replacing
https://1991421.cn/2021/09/21/844e39cc/withhttps://${window.basicHost}/2021/09/21/844e39cc/. How can we solve this problem elegantly?
Direct regex replacement?
First, the idea of using direct regex replacement comes to mind, but after trying it, I found that it doesn’t work. The reason is that there are too many ways to write code under ES6+, and this problem essentially involves changing constants to variable concatenation, so direct text replacement is not feasible.
Let’s list the different context writing methods for links in the code:
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 + regex replacement
The direct reason why direct regex replacement doesn’t work is the different writing styles. However, after compiling the code to ES5, the writing style becomes fixed as a separate string, such as "//1991421.cn/2021/09/21/" + postId. This time point can be easily found in webpack, such as compilation.hooks.optimizeChunkAssets.
In theory, regex can be used for string replacement at this stage. However, replacement also needs to solve the problem of obtaining the symbol of the string to concatenate and add JS object variables. This is more complicated with pure regex, but much simpler with AST because AST analyzes JS code from a grammatical perspective, directly finds string variables, and simultaneously obtains symbols. Of course, if the idea is feasible, then we need to write a webpack plugin.
Practice
Webpack plugin
Since the ultimate goal is to parse JS code into an AST and then use regex to replace some strings, a webpack plugin is the most suitable approach.
The specific plugin can be downloaded here for use.
- For the webpack plugin part, we just need to select the hook after chunk generation according to the hooks exposed by webpack.
- Obtain the chunk source code and perform AST parsing to achieve replacement.
The key replacement logic is as follows:
function replaceDomainLoader(source) {
const tokenization = esprima.tokenize(source);
tokenization.forEach((item) => {
if (item.type !== 'String') return;
const mark = item.value.charAt(0); // Get the string's quote type
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;
}
Plugin issues under HMR
Currently, there may be replacement errors under development mode with HMR, so it is recommended to add environment judgment to the plugin, such as:
...
process.env.NODE_ENV === 'production' && new DomainReplacePlugin()
...
At the same time, if you are building locally, pay attention to environment variable issues. It is recommended to use cross-env to solve this problem, such as:
npx cross-env NODE_ENV=production npm run build:dev
Final Thoughts
- After the above solution, the business code can still work normally.

