From 8b193864fd35795bd46d5553e4abc48b0ed1b7c8 Mon Sep 17 00:00:00 2001 From: alex8088 <244096523@qq.com> Date: Mon, 20 Oct 2025 23:35:21 +0800 Subject: [PATCH] feat: enhanced string protection --- src/plugins/bytecode.ts | 66 ++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/plugins/bytecode.ts b/src/plugins/bytecode.ts index 9b49adc..502959e 100644 --- a/src/plugins/bytecode.ts +++ b/src/plugins/bytecode.ts @@ -217,9 +217,6 @@ export function bytecodePlugin(options: BytecodeOptions = {}): Plugin | null { } supported = output.format === 'cjs' && !useInRenderer } - if (supported && config.build.minify && protectedStrings.length > 0) { - config.logger.warn(colors.yellow('Strings cannot be protected when minification is enabled.')) - } }, renderChunk(code, chunk, { sourcemap }): { code: string; map?: SourceMapInput } | null { if (supported && isBytecodeChunk(chunk.name) && shouldTransformBytecodeChunk) { @@ -359,28 +356,69 @@ interface ProtectStringsPluginState extends babel.PluginPass { function protectStringsPlugin(api: typeof babel & babel.ConfigAPI): babel.PluginObj { const { types: t } = api + + function createFromCharCodeFunction(value: string): babel.types.CallExpression { + const charCodes = Array.from(value).map(s => s.charCodeAt(0)) + const charCodeLiterals = charCodes.map(code => t.numericLiteral(code)) + + // String.fromCharCode + const memberExpression = t.memberExpression(t.identifier('String'), t.identifier('fromCharCode')) + // String.fromCharCode(...arr) + const callExpression = t.callExpression(memberExpression, [t.spreadElement(t.identifier('arr'))]) + // return String.fromCharCode(...arr) + const returnStatement = t.returnStatement(callExpression) + // function (arr) { return ... } + const functionExpression = t.functionExpression(null, [t.identifier('arr')], t.blockStatement([returnStatement])) + + // (function(...) { ... })([x, x, x]) + return t.callExpression(functionExpression, [t.arrayExpression(charCodeLiterals)]) + } + return { name: 'protect-strings-plugin', visitor: { StringLiteral(path, state) { + // obj['property'] + if (path.parentPath.isMemberExpression({ property: path.node, computed: true })) { + return + } + + // { 'key': value } + if (path.parentPath.isObjectProperty({ key: path.node, computed: false })) { + return + } + + // require('fs') if ( - path.parentPath.isImportDeclaration() || // import x from 'module' - path.parentPath.isExportNamedDeclaration() || // export { x } from 'module' - path.parentPath.isExportAllDeclaration() || // export * from 'module' - path.parentPath.isObjectProperty({ key: path.node, computed: false }) // { 'key': 'value' } + path.parentPath.isCallExpression() && + t.isIdentifier(path.parentPath.node.callee) && + path.parentPath.node.callee.name === 'require' && + path.parentPath.node.arguments[0] === path.node ) { return } + // Only CommonJS is supported, import declaration and export declaration checks are ignored + const { value } = path.node if (state.opts.protectedStrings.has(value)) { - const charCodes = Array.from(value).map(s => s.charCodeAt(0)) - const charCodeLiterals = charCodes.map(code => t.numericLiteral(code)) - const replacement = t.callExpression( - t.memberExpression(t.identifier('String'), t.identifier('fromCharCode')), - charCodeLiterals - ) - path.replaceWith(replacement) + path.replaceWith(createFromCharCodeFunction(value)) + } + }, + TemplateLiteral(path, state) { + // Must be a pure static template literal + // expressions must be empty (no ${variables}) + // quasis must have only one element (meaning the entire string is a single static part). + if (path.node.expressions.length > 0 || path.node.quasis.length !== 1) { + return + } + + // Extract the raw value of the template literal + // path.node.quasis[0].value.raw is used to get the raw string, including escape sequences + // path.node.quasis[0].value.cooked is used to get the processed/cooked string (with escape sequences handled) + const value = path.node.quasis[0].value.cooked + if (value && state.opts.protectedStrings.has(value)) { + path.replaceWith(createFromCharCodeFunction(value)) } } }