@ainc/script
Version:
Script compiler for typescript
292 lines (239 loc) • 8.26 kB
text/typescript
/**
*****************************************
* Created by edonet@163.com
* Created on 2021-08-14 11:03:15
*****************************************
*/
'use strict';
/**
*****************************************
* 加载依赖
*****************************************
*/
import * as ts from 'typescript';
/**
*****************************************
* 声明节点源属性
*****************************************
*/
declare module 'typescript' {
interface Node {
original?: ts.Node;
}
}
/**
*****************************************
* 获取工厂
*****************************************
*/
const factory = ts.factory;
/**
*****************************************
* 判断是否为入口文件
*****************************************
*/
function isMainFile(path: string): boolean {
return path.endsWith('index.ts') || path.endsWith('index.js');
}
/**
*****************************************
* 判断标识值
*****************************************
*/
function isIdentifierValueOf(node: ts.Node, value: string): node is ts.Identifier {
return ts.isIdentifier(node) && node.text === value;
}
/**
*****************************************
* 判断是否为载入函数
*****************************************
*/
function isRequireCallExpression(node?: ts.Node): node is ts.CallExpression {
return node ? ts.isCallExpression(node) && isIdentifierValueOf(node.expression, 'require') : false;
}
/**
*****************************************
* 判断是否为导出标识
*****************************************
*/
function isExportIdentifier(node: ts.Node): node is ts.Identifier {
return ts.isIdentifier(node) && ts.isExportDeclaration(getOriginalNode(node));
}
/**
*****************************************
* 获取源节点
*****************************************
*/
function getOriginalNode(node: ts.Node): ts.Node {
return node.original ? getOriginalNode(node.original) : node;
}
/**
*****************************************
* 创建函数调用声明
*****************************************
*/
function createCallExpression(name: ts.Identifier, args?: readonly ts.Expression[]): ts.CallExpression {
return factory.createCallExpression(name, [], args || []);
}
/**
*****************************************
* 创建匿名函数表达式
*****************************************
*/
function createAnonymousFunctionExpression(statements: readonly ts.Statement[]): ts.FunctionExpression {
return factory.createFunctionExpression(
undefined,
undefined,
undefined,
[],
[],
undefined,
factory.createBlock(statements, false),
);
}
/**
*****************************************
* 创建懒加载函数表达式
*****************************************
*/
function createLazyRequireFunctionExpression(name: ts.Identifier, call: ts.CallExpression): ts.FunctionExpression {
const moduleId = factory.createIdentifier('__exports');
const moduleDecl = factory.createVariableDeclaration(moduleId, undefined, undefined, call);
// 创建函数
return createAnonymousFunctionExpression([
factory.createVariableStatement(
undefined,
factory.createVariableDeclarationList([moduleDecl], ts.NodeFlags.None)
),
factory.createExpressionStatement(
factory.createBinaryExpression(
name,
factory.createToken(ts.SyntaxKind.EqualsToken),
createAnonymousFunctionExpression([factory.createReturnStatement(moduleId)]),
),
),
factory.createReturnStatement(moduleId),
]);
}
/**
*****************************************
* 创建属性定义声明
*****************************************
*/
function createObjectDefinePropertyExpression(target: ts.Identifier, prop: string, initializer: ts.Expression): ts.Expression {
return factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier('Object'),
factory.createIdentifier('defineProperty'),
),
undefined,
[
target,
factory.createStringLiteral(prop),
factory.createObjectLiteralExpression(
[
factory.createPropertyAssignment(factory.createIdentifier('enumerable'), factory.createTrue()),
factory.createPropertyAssignment(factory.createIdentifier('get'), initializer),
],
false,
),
],
);
}
/**
*****************************************
* 转换文件内容
*****************************************
*/
function transformSourceFile(sourceFile: ts.SourceFile, context: ts.TransformationContext): ts.SourceFile {
const moduleSymbols = new Set();
// 访问文件子节点
function visitNode(node: ts.Node): ts.Node {
// 处理载入标识
if (ts.isIdentifier(node) && moduleSymbols.has(node)) {
return createCallExpression(node);
}
// 返回节点
return ts.visitEachChild(node, visitNode, context);
}
// 访问变量声明
function visitVariableDeclaration(node: ts.Node): ts.Node {
// 判断非声明语句
if (!ts.isVariableDeclaration(node)) {
return ts.visitEachChild(node, visitVariableDeclaration, context);
}
// 获取初始初化声明
const call = node.initializer;
const name = node.name;
// 判断非载入函数初始化
if (!call || !isRequireCallExpression(call) || !isExportIdentifier(name)) {
return ts.visitEachChild(node, visitNode, context);
}
// 添加标识
moduleSymbols.add(name);
// 替换声明节点
return factory.updateVariableDeclaration(
node,
name,
node.exclamationToken,
node.type,
createLazyRequireFunctionExpression(name, call),
);
}
// 访问赋值表达式
function visitBinaryExpression(node: ts.Node): ts.Node {
// 判断非载入函数初始化
if (!ts.isBinaryExpression(node) || !isRequireCallExpression(node.right)) {
return ts.visitEachChild(node, visitNode, context);
}
// 获取表达式值
const expr = node.left;
const call = node.right;
// 判断非导出属性
if (!ts.isPropertyAccessExpression(expr) || !isIdentifierValueOf(expr.expression, 'exports')) {
return ts.visitEachChild(node, visitNode, context);
}
// 创建赋值语句
return createObjectDefinePropertyExpression(
expr.expression,
expr.name.text,
createAnonymousFunctionExpression([factory.createReturnStatement(call)]),
);
}
// 访问文件子节点
function visitSourceFile(node: ts.Node): ts.Node {
// 处理载入函数
if (ts.isVariableStatement(node)) {
return ts.visitEachChild(node, visitVariableDeclaration, context);
}
// 处理二元表达式
if (ts.isExpressionStatement(node)) {
return ts.visitEachChild(node, visitBinaryExpression, context);
}
// 处理载入标识
if (moduleSymbols.size) {
return ts.visitEachChild(node, visitNode, context);
}
// 返回节点
return node;
}
// 访问节点
return ts.visitEachChild(sourceFile, visitSourceFile, context);
}
/**
*****************************************
* 转换器
*****************************************
*/
export function transformer(context: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
const { target, module: moduleKind } = context.getCompilerOptions();
const isCommonJS = !moduleKind || moduleKind === ts.ModuleKind.CommonJS;
// 不做处理
if (!target || !isCommonJS) {
return sourceFile => sourceFile;
}
// 转换文件节点
return (sourceFile: ts.SourceFile) => {
return isMainFile(sourceFile.fileName) ? transformSourceFile(sourceFile, context) : sourceFile;
};
}