@ainc/script
Version:
Script compiler for typescript
166 lines (135 loc) • 4.4 kB
text/typescript
/**
*****************************************
* Created by edonet@163.com
* Created on 2021-07-09 23:16:56
*****************************************
*/
'use strict';
/**
*****************************************
* 加载依赖
*****************************************
*/
import { types, NodePath, PluginPass } from '@babel/core';
import { declare } from '@babel/helper-plugin-utils';
import { dirname } from '@ainc/fs/dist/dirname';
import { includePaths } from '@ainc/fs/dist/includePaths';
import { resolvePath, Options as ResolvePathOptions } from '@ainc/fs/dist/resolvePath';
/**
*****************************************
* 插件状态
*****************************************
*/
interface State extends PluginPass {
dirname?: string;
calls?: string[];
resolveFile?(source: string, context?: string): void | string;
}
/**
*****************************************
* 转换的资源路径
*****************************************
*/
function transformSourceNode(expr: NodePath<types.StringLiteral>, state: State): void {
if (!expr || !state.resolveFile || !types.isStringLiteral(expr)) {
return;
}
// 获取路径
const sourcePath = expr.node.value;
const resolvedPath = state.resolveFile(sourcePath, state.dirname);
// 无需处理
if (!resolvedPath || sourcePath === resolvedPath) {
return;
}
// 替换路径
expr.replaceWith(types.stringLiteral(resolvedPath));
}
/**
*****************************************
* 匹配调用函数
*****************************************
*/
function matchSourceCall(name: string, expr: NodePath<types.Identifier>): boolean {
const { node } = expr;
// 匹配成员节点
if (types.isMemberExpression(node)) {
return expr.matchesPattern(name);
}
// 非标识府或匹配成员
if (!types.isIdentifier(node) || name.indexOf('.') > -1) {
return false;
}
// 匹配函数名
return node.name === name;
}
/**
*****************************************
* 访问调用表达式
*****************************************
*/
function visitCallExpression(expr: NodePath<types.CallExpression>, state: State): void {
// 处理`import`调用
if (types.isImport(expr.node.callee)) {
return transformSourceNode(expr.get('arguments.0') as NodePath<types.StringLiteral>, state);
}
// 不存在转换函数
if (!state.calls) {
return;
}
// 获取调用节点
const callee = expr.get('callee') as NodePath<types.Identifier>;
const isSourceCall = state.calls.some(value => matchSourceCall(value, callee));
// 处理函数调用
if (isSourceCall) {
transformSourceNode(expr.get('arguments.0') as NodePath<types.StringLiteral>, state);
}
}
/**
*****************************************
* 访问载入声明
*****************************************
*/
function visitImportDeclaration(expr: NodePath<types.ImportDeclaration | types.ExportDeclaration>, state: State): void {
transformSourceNode(expr.get('source') as NodePath<types.StringLiteral>, state);
}
/**
*****************************************
* 插件配置
*****************************************
*/
export interface Options extends ResolvePathOptions {
calls?: string[];
exclude?: string[];
}
/**
*****************************************
* 定义插件
*****************************************
*/
export default declare((api, opts: Options = {}) => {
const include = includePaths(opts.exclude || ['node_modules']);
const calls = ['require', 'require.resolve'];
const resolveFile = resolvePath(opts);
// 校验版本
api.assertVersion(7);
// 添加处理函数
opts.calls?.length && calls.push(...opts.calls);
// 返回插件配置
return {
name: 'module-paths-plugin',
pre(file) {
const { filename } = file.opts;
// 过滤文件
if (!filename || !include(filename)) {
this.calls = calls;
this.resolveFile = resolveFile;
this.dirname = filename ? dirname(filename) : process.cwd();
}
},
visitor: {
ImportDeclaration: visitImportDeclaration,
ExportDeclaration: visitImportDeclaration,
CallExpression: visitCallExpression,
},
};
});