Add import attributes support (#1388)

This commit is contained in:
Timofey Kachalov
2026-03-22 08:56:53 +04:00
committed by GitHub
parent 1aadbacc06
commit 560212c0c4
7 changed files with 214 additions and 7 deletions
+5
View File
@@ -1,5 +1,10 @@
Change Log
v5.4.0
---
* Add support for `import attributes`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1256
* Replaced `mkdirp` dependency with native `fs.mkdirSync({ recursive: true })`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1275. Thank you https://github.com/roli-lpci!
v5.3.1
---
* Fixed class expression name references inside class body being incorrectly resolved to an import binding with the same name, causing broken code at runtime. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1386
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "javascript-obfuscator",
"version": "5.3.1",
"version": "5.4.0",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
@@ -21,10 +21,11 @@
},
"types": "typings/index.d.ts",
"dependencies": {
"@javascript-obfuscator/escodegen": "2.3.1",
"@javascript-obfuscator/escodegen": "2.4.0",
"@javascript-obfuscator/estraverse": "5.4.0",
"@vercel/blob": ">=0.23.0",
"acorn": "8.15.0",
"acorn-import-attributes": "^1.9.5",
"assert": "2.1.0",
"chalk": "4.1.2",
"chance": "1.1.13",
+4 -1
View File
@@ -1,6 +1,9 @@
import * as acorn from 'acorn';
import * as ESTree from 'estree';
import chalk, { Chalk } from 'chalk';
import { importAttributesOrAssertions } from 'acorn-import-attributes';
const AcornParser = acorn.Parser.extend(importAttributesOrAssertions);
/**
* Facade over AST parser `acorn`
@@ -67,7 +70,7 @@ export class ASTParserFacade {
sourceType
};
const program: acorn.Node & ESTree.Program = <acorn.Node & ESTree.Program>acorn.parse(sourceCode, config);
const program: acorn.Node & ESTree.Program = <acorn.Node & ESTree.Program>AcornParser.parse(sourceCode, config);
if (comments.length) {
program.comments = comments;
+9
View File
@@ -0,0 +1,9 @@
declare module 'acorn-import-attributes' {
import { Parser } from 'acorn';
type TParserExtension = (BaseParser: typeof Parser) => typeof Parser;
export const importAttributes: TParserExtension;
export const importAssertions: TParserExtension;
export const importAttributesOrAssertions: TParserExtension;
}
@@ -1478,6 +1478,116 @@ describe('JavaScriptObfuscator', () => {
});
});
describe('Import attributes', () => {
describe('Variant #1: import with "with" syntax', () => {
const importAttributesRegExp: RegExp = /from\s*'\.\/config\.json'\s*with\s*\{\s*type\s*:\s*'json'\s*\}/;
let obfuscatedCode: string;
before(() => {
const code: string = `import config from './config.json' with { type: 'json' };\nconsole.log(config);`;
obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET
}).getObfuscatedCode();
});
it('should preserve import attributes in obfuscated code', () => {
assert.match(obfuscatedCode, importAttributesRegExp);
});
});
describe('Variant #2: import with multiple attributes', () => {
const importAttributesRegExp: RegExp = /from\s*'\.\/data\.json'\s*with\s*\{\s*type\s*:\s*'json'\s*,\s*integrity\s*:\s*'sha384-abc'\s*\}/;
let obfuscatedCode: string;
before(() => {
const code: string = `import data from './data.json' with { type: 'json', integrity: 'sha384-abc' };\nconsole.log(data);`;
obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET
}).getObfuscatedCode();
});
it('should preserve multiple import attributes in obfuscated code', () => {
assert.match(obfuscatedCode, importAttributesRegExp);
});
});
describe('Variant #3: import with "assert" syntax (deprecated)', () => {
const importAssertionsRegExp: RegExp = /from\s*'\.\/config\.json'\s*assert\s*\{\s*type\s*:\s*'json'\s*\}/;
let obfuscatedCode: string;
before(() => {
const code: string = `import config from './config.json' assert { type: 'json' };\nconsole.log(config);`;
obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET
}).getObfuscatedCode();
});
it('should preserve import assertions in obfuscated code', () => {
assert.match(obfuscatedCode, importAssertionsRegExp);
});
});
describe('Variant #4: export with "with" syntax', () => {
const exportAttributesRegExp: RegExp = /from\s*'\.\/config\.json'\s*with\s*\{\s*type\s*:\s*'json'\s*\}/;
let obfuscatedCode: string;
before(() => {
const code: string = `export { default as config } from './config.json' with { type: 'json' };`;
obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET
}).getObfuscatedCode();
});
it('should preserve export attributes in obfuscated code', () => {
assert.match(obfuscatedCode, exportAttributesRegExp);
});
});
describe('Variant #5: export all with "with" syntax', () => {
const exportAllAttributesRegExp: RegExp = /export\s*\*\s*from\s*'\.\/utils\.js'\s*with\s*\{\s*type\s*:\s*'javascript'\s*\}/;
let obfuscatedCode: string;
before(() => {
const code: string = `export * from './utils.js' with { type: 'javascript' };`;
obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET
}).getObfuscatedCode();
});
it('should preserve export all attributes in obfuscated code', () => {
assert.match(obfuscatedCode, exportAllAttributesRegExp);
});
});
describe('Variant #6: side-effect import with "with" syntax', () => {
const sideEffectImportRegExp: RegExp = /import\s*'\.\/styles\.css'\s*with\s*\{\s*type\s*:\s*'css'\s*\}/;
let obfuscatedCode: string;
before(() => {
const code: string = `import './styles.css' with { type: 'css' };`;
obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET
}).getObfuscatedCode();
});
it('should preserve side-effect import attributes in obfuscated code', () => {
assert.match(obfuscatedCode, sideEffectImportRegExp);
});
});
});
describe('getOptionsByPreset', () => {
describe('Variant #1: base behaviour', () => {
const optionsPresetName: TOptionsPreset = OptionsPreset.HighObfuscation;
@@ -87,5 +87,79 @@ describe('ASTParserFacade', () => {
});
});
});
describe('Import attributes', () => {
describe('Variant #1: import with "with" syntax', () => {
const sourceCode: string = `import config from "./config.json" with { type: "json" };`;
let astTree: any;
before(() => {
astTree = ASTParserFacade.parse(sourceCode, { ecmaVersion });
});
it('should parse import with "with" syntax without error', () => {
assert.exists(astTree);
assert.equal(astTree.type, 'Program');
assert.equal(astTree.body[0].type, 'ImportDeclaration');
});
it('should preserve import attributes in AST', () => {
const importDecl = astTree.body[0];
assert.exists(importDecl.attributes);
assert.isArray(importDecl.attributes);
assert.equal(importDecl.attributes.length, 1);
assert.equal(importDecl.attributes[0].key.name, 'type');
assert.equal(importDecl.attributes[0].value.value, 'json');
});
});
describe('Variant #2: import with "assert" syntax (deprecated)', () => {
const sourceCode: string = `import config from "./config.json" assert { type: "json" };`;
let astTree: any;
before(() => {
astTree = ASTParserFacade.parse(sourceCode, { ecmaVersion });
});
it('should parse import with "assert" syntax without error', () => {
assert.exists(astTree);
assert.equal(astTree.type, 'Program');
assert.equal(astTree.body[0].type, 'ImportDeclaration');
});
});
describe('Variant #3: export with "with" syntax', () => {
const sourceCode: string = `export { default as config } from "./config.json" with { type: "json" };`;
let astTree: any;
before(() => {
astTree = ASTParserFacade.parse(sourceCode, { ecmaVersion });
});
it('should parse export with "with" syntax without error', () => {
assert.exists(astTree);
assert.equal(astTree.type, 'Program');
assert.equal(astTree.body[0].type, 'ExportNamedDeclaration');
});
});
describe('Variant #4: dynamic import with "with" syntax', () => {
const sourceCode: string = `const config = await import("./config.json", { with: { type: "json" } });`;
let astTree: any;
before(() => {
astTree = ASTParserFacade.parse(sourceCode, { ecmaVersion });
});
it('should parse dynamic import with "with" syntax without error', () => {
assert.exists(astTree);
assert.equal(astTree.type, 'Program');
});
});
});
});
});
+9 -4
View File
@@ -329,10 +329,10 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
"@javascript-obfuscator/escodegen@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@javascript-obfuscator/escodegen/-/escodegen-2.3.1.tgz#a534e73740830d6c7546ca686773b40b09a6b9d1"
integrity sha512-Z0HEAVwwafOume+6LFXirAVZeuEMKWuPzpFbQhCEU9++BMz0IwEa9bmedJ+rMn/IlXRBID9j3gQ0XYAa6jM10g==
"@javascript-obfuscator/escodegen@2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@javascript-obfuscator/escodegen/-/escodegen-2.4.0.tgz#9d2b19b94793106cafa5cd5041c4e40d42943fb7"
integrity sha512-h9cJ/qb3Y3c1jMQPWypt2CGTFgP34V5OtWLqoOCjV6CT/DUXMZFpoTAfDHpuUrRP0oxNd0UwnVAsPtPuYsoXxQ==
dependencies:
"@javascript-obfuscator/estraverse" "^5.3.0"
esprima "^4.0.1"
@@ -1017,6 +1017,11 @@ abbrev@^2.0.0:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==
acorn-import-attributes@^1.9.5:
version "1.9.5"
resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
acorn-import-phases@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7"