slightning-coco-widget--webpack
Version:
SLIGHTNING 的 CoCo 控件框架 —— webpack 相关工具。
291 lines (290 loc) • 10.5 kB
JavaScript
import * as t from "@babel/types";
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import * as template from "@babel/template";
export const BYPASS_MAP = {
"XMLHttpRequest": {
replace: "XML_Http_Request",
importSource: "slightning-coco-widget--webpack/runtime/bypass/xml-http-request"
},
"XMLHttpRequestEventTarget": {
replace: "XML_Http_RequestEventTarget",
importSource: "slightning-coco-widget--webpack/runtime/bypass/xml-http-request-event-target"
},
"fetch": {
importSource: "slightning-coco-widget--webpack/runtime/bypass/fetch"
},
".fetch": {
replace: "._fetch"
},
"location": {},
"parent.location": {
replace: "parent._location"
},
"top.location": {
replace: "top._location"
},
"WebSocket": {
replace: "Web_Socket",
importSource: "slightning-coco-widget--webpack/runtime/bypass/web-socket"
},
"WebSocketError": {
replace: "Web_SocketError",
importSource: "slightning-coco-widget--webpack/runtime/bypass/web-socket-error"
},
"WebSocketStream": {
replace: "Web_SocketStream",
importSource: "slightning-coco-widget--webpack/runtime/bypass/web-socket-stream"
},
"codemao": {},
"socketcv.codemao.cn": {
replace: "socketcv_.codemao_.cn"
}
};
export const BYPASS_FULL_KEY_WORDS = Object.entries(BYPASS_MAP).filter(([, bypass]) => bypass.replace != null).map(([name]) => name);
export function includesFullKeyWords(value) {
return BYPASS_FULL_KEY_WORDS.some((name) => {
return value.includes(name);
});
}
export const BYPASS_KEY_WORDS = Object.keys(BYPASS_MAP);
export function includesKeyWords(value) {
return BYPASS_KEY_WORDS.some((name) => {
return value.includes(name);
});
}
export function replaceKeyWordsInString(value) {
for (const [search, bypass] of Object.entries(BYPASS_MAP)) {
if (bypass.replace != null) {
value = value.split(search).join(bypass.replace);
}
}
return value;
}
export function bypassAddImport(source) {
if (!includesFullKeyWords(source)) {
return source;
}
const AST = parse(source, {
sourceType: "unambiguous"
});
ASTBypassAddImport(AST);
return generate(AST).code;
}
export function ASTBypassAddImport(AST) {
imports = {};
traverse(AST, BypassAddImportVisitor);
for (const [name, source] of Object.entries(imports)) {
AST.program.body.unshift(t.importDeclaration([t.importDefaultSpecifier(t.identifier(name))], t.stringLiteral(source)));
}
}
let imports = {};
const BypassAddImportVisitor = {
Identifier(identifierPath) {
const { parentPath, node: identifier } = identifierPath;
if (parentPath.isImportSpecifier() ||
parentPath.isExportSpecifier()) {
return;
}
if (parentPath.isObjectProperty({ key: identifier }) ||
parentPath.isObjectMethod({ key: identifier }) ||
parentPath.isClassProperty({ key: identifier }) ||
parentPath.isClassMethod({ key: identifier })) {
return;
}
const { name: identifierName } = identifier;
if (identifierPath.scope.getBinding(identifierName) != undefined) {
return;
}
const bypass = BYPASS_MAP[identifierName];
if (bypass != null && bypass.importSource != null) {
imports[identifierName] = bypass.importSource;
}
}
};
export function bypassKeyWordsCheck(source) {
if (!includesFullKeyWords(source)) {
return source;
}
const AST = parse(source, {
sourceType: "unambiguous"
});
ASTBypassKeyWordsCheck(AST);
return generate(AST).code;
}
export function ASTBypassKeyWordsCheck(AST) {
var _a;
traverse(AST, BypassKeyWordsCheckVisitor);
for (const comment of (_a = AST.comments) !== null && _a !== void 0 ? _a : []) {
comment.value = replaceKeyWordsInString(comment.value);
}
}
const BypassKeyWordsCheckVisitor = {
ImportSpecifier(path) {
replaceInIdentifier(path.get("local"));
},
ExportSpecifier(path) {
replaceInIdentifier(path.get("local"));
},
ExportNamedDeclaration(path) {
const { node } = path;
const declaration = path.get("declaration");
if (declaration.node != null) {
path.insertBefore(declaration.node);
node.declaration = null;
node.specifiers.push(...getDeclarationNames(declaration).map((name) => t.exportSpecifier(t.identifier(name), t.identifier(name))));
}
},
ObjectProperty: setPropertyKeyComputed,
ObjectMethod: setPropertyKeyComputed,
ClassProperty: setPropertyKeyComputed,
ClassMethod: setPropertyKeyComputed,
MemberExpression(memberExpressionPath) {
const { node: memberExpression } = memberExpressionPath;
if (memberExpression.computed) {
return;
}
const { property } = memberExpression;
if (t.isIdentifier(property) && includesKeyWords(property.name)) {
memberExpression.property = t.stringLiteral(property.name);
memberExpression.computed = true;
}
else if (t.isStringLiteral(property) && includesKeyWords(property.value)) {
memberExpression.computed = true;
}
},
Identifier(identifierPath) {
const { parentPath, node: identifier } = identifierPath;
if (parentPath.isImportSpecifier() ||
parentPath.isExportSpecifier()) {
return;
}
if (parentPath.isMemberExpression({ property: identifier, computed: false }) ||
parentPath.isObjectProperty({ key: identifier, computed: false }) ||
parentPath.isObjectMethod({ key: identifier, computed: false }) ||
parentPath.isClassProperty({ key: identifier, computed: false }) ||
parentPath.isClassMethod({ key: identifier, computed: false })) {
return;
}
replaceInIdentifier(identifierPath);
},
StringLiteral(stringLiteralPath) {
var _a, _b;
const { parentPath, node: stringLiteral } = stringLiteralPath;
if (parentPath.isImportDeclaration() ||
parentPath.isExportDeclaration()) {
return;
}
for (const [name, bypass] of Object.entries(BYPASS_MAP)) {
if (bypass.replace == null) {
continue;
}
if (stringLiteral.value.includes(name)) {
const strings = stringLiteral.value.split(name);
stringLiteralPath.replaceWith(t.stringLiteral((_a = strings.shift()) !== null && _a !== void 0 ? _a : ""));
while (strings.length > 0) {
stringLiteralPath.replaceWith(t.binaryExpression("+", t.binaryExpression("+", stringLiteralPath.node, getStringReplaceExpression(bypass)), t.stringLiteral((_b = strings.shift()) !== null && _b !== void 0 ? _b : "")));
}
return;
}
}
},
TemplateLiteral(path) {
const { node } = path;
for (const [name, bypass] of Object.entries(BYPASS_MAP)) {
if (bypass.replace == null) {
continue;
}
for (let i = node.quasis.length - 1; i >= 0; i--) {
const parts = node.quasis[i].value.raw.split(name);
node.quasis.splice(i, 1, ...parts.map((part) => ({
type: "TemplateElement",
value: {
raw: part
},
tail: false
})));
for (let j = 1; j < parts.length; j++) {
node.expressions.splice(i, 0, getStringReplaceExpression(bypass));
}
}
}
}
};
function getDeclarationNames(declarationPath) {
const { node: declaration } = declarationPath;
const result = [];
if (declarationPath.isVariableDeclaration()) {
for (const declaratorPath of declarationPath.get("declarations")) {
const idPath = declaratorPath.get("id");
if (idPath.isIdentifier()) {
result.push(idPath.node.name);
}
else {
declarationNames = [];
idPath.traverse(GetDeclarationNamesVisitor);
result.push(...declarationNames);
}
}
}
else if (t.isFunctionDeclaration(declaration) ||
t.isClassDeclaration(declaration)) {
if (t.isIdentifier(declaration.id)) {
result.push(declaration.id.name);
}
}
return result;
}
const GetDeclarationNamesVisitor = {
Property(propertyPath) {
propertyPath.get("value").skip();
},
AssignmentExpression(assignmentExpressionPath) {
assignmentExpressionPath.get("right").skip();
},
Identifier(identifierPath) {
const { parentPath: objectPath, node: identifier } = identifierPath;
if (objectPath.isObjectProperty({ key: identifier })) {
return;
}
declarationNames.push(identifier.name);
}
};
let declarationNames = [];
function setPropertyKeyComputed(propertyPath) {
const { node: property } = propertyPath;
if (property.computed) {
return;
}
const { key } = property;
if (t.isIdentifier(key) && includesKeyWords(key.name)) {
property.key = t.stringLiteral(key.name);
property.computed = true;
}
else if (t.isStringLiteral(key) && includesKeyWords(key.value)) {
property.computed = true;
}
}
function replaceInIdentifier(identifierPath) {
const { node: identifier } = identifierPath;
identifier.name = replaceKeyWordsInString(identifier.name);
}
function getStringReplaceExpression(bypass) {
var _a;
return StringReplaceTemplate({ STRING: t.stringLiteral((_a = bypass.replace) !== null && _a !== void 0 ? _a : "") });
}
const StringReplaceTemplate = template.expression(`
STRING.replace(/_/g, "")
`);
export function bypassRestrictions(source) {
if (!includesFullKeyWords(source)) {
return source;
}
const AST = parse(source, {
sourceType: "unambiguous"
});
ASTBypassAddImport(AST);
ASTBypassKeyWordsCheck(AST);
return generate(AST).code;
}