我正在使用 webpack 为多个客户捆绑一个部署在多个位置的 React 应用程序。重要的是应用程序的每个版本都适用于所有客户。换句话说,只有一个构建过程。
这些客户(可以)拥有自定义翻译文本。这些以 JSON 文件的形式存储在存储库中。
比方说:
client_translations/en.ford.json
client_translations/en.tesla.json
client_translations/de.porsche.json
当用户登录时,应用程序会收到 customerId,从而知道要加载哪个翻译文件(使用
import
)。
const getClientTranslations = async (customerId: string, language: string) => {
try {
const clientTranslations = await import(
/* webpackChunkName: "client_translation-[index]" */
/* webpackMode: "lazy" */
`client_translations/${language}.${customerId}.json`
)
return clientTranslations ?? {}
}
}
因此,webpack 所做的就是生成像这样的漂亮块
client_translation-5318-bundle.js
,然后应用程序确定将其中哪一个注入到页面中。到目前为止一切顺利。
现在,主包中的某个位置存在文件名(包括客户名称)和
client_translation-XX-bundle.js
文件之间的映射。这样当我导入时 webpack 就可以找到该文件。但这也意味着我们的客户(例如“福特”)可以通过查看这些文件名来挖掘主包并发现我们的其他客户是“特斯拉”和“保时捷”。
这显然对我的客户来说是一个问题,所以我想做的是,导入带有哈希名称的 json:
`client_translations/${language}.${md5Hash(customerId)}.json`
因此导入将解析为
client_translations/en.80ca06abfa1a5104af9a770f485dad07.js
。 但是在存储库中,我希望保持文件名未散列,因为显然对于开发人员来说,当文件名全部散列时查找内容是非常烦人的。
那么我们可以在构建时进行这种散列或任何其他类型的混淆吗?
ps。我知道 MD5 不安全,但这不是重点。另一种类型的(快速)哈希对我来说也很好,但这个问题与此无关。
要实现此目标,您可以将翻译视为
普通资产,而不是使用动态import()
表达式导入它们。并且,我还将更改翻译下载机制:
function getClientTranslations<T>(customerId: string, language: string): Promise<T> {
// Sample hashing. But not useful as hashing algorithm will be seen by the client.
const hashed = btoa(customerId);
const translations = fetch(`/public/client_translations/${language}.${hashed}.json`)
.then(response => response.json())
.catch(() => ({}));
return translations;
}
通过使用 fetch
而不是
import()
,Webpack 将不会看到这些文件并将它们添加到包中。现在第二步将使用资产模块,如下所示:
import { btoa } from 'node:buffer';
import path from 'node:path';
const config = {
mode: 'production',
// Entry point and output
entry: './src/index.js',
output: {
path: process.cwd() + '/dist',
filename: 'output.js',
assetModuleFilename(pathData, _assetInfo) {
const { filename } = pathData;
// For each, en.ford.json, we get ['en', 'ford', 'json']
const [lang, customerId, ext] = path.basename(filename).split('.');
// We want to output the file as dist/translations/lang.{hashedId}.json
// Use base64 as a dummy hashing function
const hash = btoa(customerId);
return `translations/${lang}.${hash}.${ext}`;
},
},
module: {
rules: [
{
test: /\.json$/,
type: 'asset/resource',
// Added additional condition so that other imported
// JSON files are not included. Change it accordingly.
include: /client_translations/
},
],
},
};
export default config;
这里重要的配置是 assetModuleFilename
配置中的
output
以及使用
asset/resource
来处理 JSON 文件。最后,请记住,您需要告诉 Webpack 获取资源文件。为此,我们将使用
side-effect import
asset/resource
将确保您的主包不包含对翻译文件的任何类型的引用。在这种情况下,任何类型的散列所面临的挑战是,您必须使用相同的散列算法在客户端(捆绑代码)上映射
customerId
。所以,没关系。只要客户端知道其他客户的 ID,无论有或没有哈希,安全含义都是相同的。这里唯一的措施是通过删除对翻译文件的引用来进行一定程度的混淆。而且,还要注意,从缓存的角度来看,这是脆弱的。您应该使用基于文件内容哈希的正确哈希密钥。为了获得完全 100% 的安全性,您可以执行以下两件事之一: