插件设计
@tc-i18n
在解析阶段提供了丰富的钩子用于插件开发,基本每一个解析阶段都可以做自定义编辑。
遵循规范
命名
命名采用 plugin-
+ 功能说明
形式,采用 小写+短横线,例如 plugin-do-something
。
结构
插件通常以类的方式提供,插件的入口文件需要导出一个 class
实例。
type PluginOptions = {
code: string,
sourceType: string,
scene?: string,
config: Tci18nConfig,
};
enum SOURCE_TYPE {
JS = 'js',
TS = 'ts',
JSX = 'jsx',
TSX = 'tsx',
VUE = 'Vue',
HTML = 'html',
WXML = 'wxml',
WXS = 'wxs',
}
interface Plugin {
name: string; // 插件名称
exts?: string[]; // 插件需要应用在哪些文件类型上
sourceTypeScope?: string[] | ((options: PluginOptions) => void | boolean | Record<string, SOURCE_TYPE | undefined>); // 解析的新文件类型需要按照哪种文件类型进行解析
apply: (transformer: Hooks, options: PluginOptions) => void; // 插件的执行方法
}
export default class PluginDoSomething implements Plugin {
...
}
解析阶段
@tc-i18n
目前支持解析 .html
, .js
, .ts
, .jsx
, .tsx
, .Vue
, .wxml
, .wxs
文件,但最终的浏览器能使用的文件只有 .html
, .js
, 所以将上面的文件类型分成了三类解析器进行解析。
.html
,.wxml
: 按照 HTML 解析器进行解析.js
,.ts
,.jsx
,.tsx
: 按照 JS 解析器进行解析.Vue
: 按照 Vue 解析器进行解析
内部也提供了 parseHTML
, parseJS
, parseVUE
三个解析器。
TIP
由于 .Vue
文件是 html
+ js
+ css
三种文件混合在一起的,所以 parseVUE
解析器也会需要用到 parseHTML
, parseJS
两个解析器,所以存在相互使用。
每个解析器中都提供了 hooks
属性用来存储解析过程,每次解析都分为三个阶段:
parse
: 解析前的钩子parsing
: 解析中的钩子parsed
: 解析后的钩子
每个阶段又包含自己的解析步骤,另外不同的解析器,三个阶段的解析步骤不同,具体如下:
parseHTML.hooks
type Hook = {
parse: Notify<{
parseCode: (ast: parse5.ParseRootNode) => HookReturn | void; // 解析html代码前的钩子
ignoreLines: (lines: string[]) => number[]; // 解析代码中注释行
}>,
parsing: {
parseHTML: Notify<{
parseNode: (node: parse5.ParseChildNode) => HookReturn | void; // 解析html节点时
parseAttrs: (node: parse5.ParseTagNode) => HookReturn | void; // 解析html当前节点全部属性时
parseAttr: (node: parse5.ParseTagNode, attr: parse5.ParseAttribute) => HookReturn | void; // 解析html当前节点具体属性时
parseInnerText: (node: parse5.ParseTextNode) => HookReturn | void; // 解析html当前节点文本内容时
}>, // 解析html中的html代码时
parseJS: ParseJSType.Hook // 解析html中的js代码时,具体见 parseJS
},
parsed: Notify<{
parseCode: (code: string) => string; // 解析html代码后的钩子
}>,
};
parseJS.hooks
type Hook = {
parse: Notify<{
ignoreLines: (comments: ParseResult['comments']) => number[]; // 解析代码中注释行
}>,
parsing: Notify<{
TemplateLiteral: (path: babel.NodePath<babel.types.TemplateLiteral>, t: typeof TType) => void | HookReturn;
StringLiteral: (path: babel.NodePath<babel.types.StringLiteral>, t: typeof TType) => void | HookReturn;
ImportDeclaration: (path: babel.NodePath<babel.types.ImportDeclaration>, t: typeof TType) => void | HookReturn;
ExpressionStatement: (path: babel.NodePath<babel.types.ExpressionStatement>, t: typeof TType) => void | HookReturn;
Identifier: (path: babel.NodePath<babel.types.Identifier>, t: typeof TType) => void | HookReturn;
DirectiveLiteral: (path: babel.NodePath<babel.types.DirectiveLiteral>, t: typeof TType) => void | HookReturn;
ObjectExpression: (path: babel.NodePath<babel.types.ObjectExpression>, t: typeof TType) => void | HookReturn;
CallExpression: (path: babel.NodePath<babel.types.CallExpression>, t: typeof TType) => void | HookReturn;
ArrowFunctionExpression: (path: babel.NodePath<babel.types.ArrowFunctionExpression>, t: typeof TType) => void | HookReturn;
ObjectMethod: (path: babel.NodePath<babel.types.ObjectMethod>, t: typeof TType) => void | HookReturn;
FunctionDeclaration: (path: babel.NodePath<babel.types.FunctionDeclaration>, t: typeof TType) => void | HookReturn;
JSXElement: (path: babel.NodePath<babel.types.JSXElement>, t: typeof TType) => void | HookReturn;
JSXText: (path: babel.NodePath<babel.types.JSXText>, t: typeof TType) => void | HookReturn;
JSXAttribute: (path: babel.NodePath<babel.types.JSXAttribute>, t: typeof TType) => void | HookReturn;
makeI18nFunction: (args: Array<babel.types.ArgumentPlaceholder | babel.types.SpreadElement | babel.types.Expression>, t: typeof TType) => void | HookReturn;
}>,
parsed: Notify<{
parseCode: (code: string) => string; // 解析js代码后的钩子
}>,
}
parseVUE.hooks
type Hook = {
parse: Notify<{
parseSFC: (sfc: vue3Compiler.SFCDescriptor) => void;
parseCode: (sourceCode: string) => string;
parseTemplate: (template: vue3Compiler.SFCTemplateBlock) => void;
parseScript: (script: vue3Compiler.SFCScriptBlock) => void;
}>,
parsing: {
template: ParseHTMLType.Hook, // 解析vue中的html代码时, 具体见 parseHTML
script: ParseJSType.Hook // 解析vue中的js代码时, 具体见 parseJS
},
parsed: Notify<{
parseCode: (code: string, sourceCode: string) => string;
parseTemplate: (template: vue3Compiler.SFCTemplateBlock) => void;
parseScript: (script: vue3Compiler.SFCScriptBlock) => void;
}>,
}
插件编写
插件执行
插件的执行主要是在 apply
函数中取得当前 transformer
解析器示例后,通过 tap
函数订阅不同解析步骤去开发。
export default class PluginDoSomething implements Plugin {
apply(transformer: tci18nPlugin.Transformer, options: tci18nPlugin.PluginOptions) {}
}
PluginOptions
为插件的配置参数,包含 tci18n.config.json
配置信息。
每次解析新文件时都会创建的transformer
实例,并传入 apply
中,也分为三个阶段
beforeTransform
: 转换文件前的钩子transforming
: 转换文件中的钩子,该阶段可以获取到parseHTML
,parseJS
,parseVUE
解析器。afterTransform
: 转换文件后的钩子
插件主要是在 transforming
阶段,获取到回调回来的 hooks
进行开发。
export default class PluginDoSomething implements Plugin {
apply(transformer: tci18nPlugin.Transformer, options: tci18nPlugin.PluginOptions) {
transformer.transforming.tap('transformCode', (hooks) => {
hooks.parseVUE.parsing.template.parse.tap('ignoreLines', (lines) => {
// do something..;
});
hooks.parseHTML.parse.tap('ignoreLines', (lines) => {
// do something..;
});
hooks.parseVUE.parse.tap('parseCode', (sourceCode) => {
// do something..
});
hooks.parseVUE.parse.tap('parseSFC', (sfc) => {
// do something..
});
hooks.parseVUE.parsed.tap('parseCode', (code, sourceCode) => {
// do something..
});
});
}
}
hooks 使用
所有 hooks
采用 发布订阅模式 , 通过 tap
函数订阅具体的钩子函数名字并传入回调函数来控制解析器中具体步骤的执行。
每个解析器中的所有的 Notify
类型都支持 tap
函数订阅,并传入回调函数。比如以 parseVUE
的 parse
解析前为例,想对 SFC 结构做自定义,可以使用:
parseVUE.parse.tab('parseSFC', (sfc) => {
// do something..
})
TIP
hooks
支持链式调用,可以精准定位到具体某个具体码解析。
// vue文件解析template中的html代码时候,修改忽略注释行的钩子
hooks.parseVUE.parsing.template.parse.tap('ignoreLines', (lines) => {
// do something..;
});
Plugin类
Plugin.name
- type:
string
插件名称,参考命名规范
Plugin.exts
- type:
string[]
用于指定插件需要应用的文件类型,@tc-i18n
默认支持的 .js
, .jsx
, .ts
, .tsx
, .Vue
, .wxs
, .wxml
类型文件,如需解析其他文件类型,可用该配置自行开发解析逻辑插件。
Plugin.sourceTypeScope
- type:
string[] | Function
指定文件的解析类型,@tc-i18n
支持 html
, javascript
, Vue
三种类型的代码,如果插件解析的文件不属于默认类型文件,可通过该配置指定文件安装哪种类型进行解析。
Plugin.apply
- type:
(transformer: Hooks, options: PluginOptions) => void
插件执行函数,当 @tc-i18n
解析文件时,会执行该函数,并传入当前 transformer
实例和插件配置参数。