Babel插件源码分析与babel.transform和babylon.parse操作AST实现效果
2017-04-20 16:38
671 查看
因本人也初学这部分内容,很多深入的内容并没有深入,以后遇到会及时补充到github上,因此本人也建议在github上阅读,同时欢迎star,issue
下面是细节部分:如果使用了ES6的export default
如果使用了ES6的export那么:
如果使用了module.exports(commonjs),那么我们的该插件不会做任何处理!!!
至于上面的exit时机,可以查看这里,其中enter表示进入节点而leave表示离开节点!
总之:该插件只会处理
我们先不分析Plugin这个插件到底做了什么,我们先分析下上面这段代码到底做了什么?以及我们到底能学到什么?
(1)首先看看下面这段代码:
(2)然后看看下面这段代码:
我们的Program其实是AST最高层的节点,其中我们对babel的plugin传入的参数可以在方法Program中获取到!如下:
其中完整的Program方法传入的第二个参数的内容可以点击这里,他是一个PluginPass对象,你可以在上面说的babel-plugin-add-module-export这个插件中手动输出该参数,然后看一下!
(3)看看下面代码
也就是说当AST解析的时候会自动调用我们插件Plugin中的相应的方法。而且有一点一定要注意:我们的每一个方法传入的arguments对象包含两个属性,
下面我们看看Plugin具体做了什么?我们给出主要部分:
具体代码我们已经注释过了,如果需要进一步学习,请查看babel-traverse
其中addImport方法的源码也贴出来:
下面我们给出要转化的代码:
我们要转化为的内容为如下形式:
我们先通过AST查看器(参考资料有链接)查看结构。如下图:
所以代码就很容易写了:
第一步:参考上图,首先构造FunctionDeclaration.body.body,其很显然是一个returnStatement
第二步:构建BlockStament(签名为:t.blockStatement(body, directives))
所以此时我们构建了下面的AST结构了(也就是BlockStatement的body是一个returnStatement):
第三步:我们构建functionDeclaration(签名为:t.functionDeclaration(id, params, body, generator, async))
此时我们构建了一个functionDeclaration的body是我们上面构建的BlockStament。而且第一个参数是Identifier,所以此时我们就变成了下面的结果了:
也就是说,得到的结果为:”functionDeclaration的Identifier为preview,body是一个BlockStament,这个BlockStament的body是一个returnStatement”。这样的结果和将下面的代码转化为AST得到的结果是相同的:
到这里我们就已经把上面的那部分代码转化为函数了。下面我们再分析一下下面的这个函数:
其实这里的写法是很简单的,你只要将”var ReactDOM = require(“react-dom”)”放到AST查看器中就可以轻而易举的写出来~
注意:通过上面的处理,我们最后得到的
通过babel-generator我们最后得到
4.2结合webpack的loader操作AST得到真正的函数封装到对象上
下面是个webpack的loader内容:
我们看看transformer做了什么处理:
通过上面的分析,这里已经很好理解了,如果你对这里的babylon有疑问,你可以查看这里。正如官网所说的:
baylon将输入的代码作为ECMAScript程序来解析,所以我们的源代码通过这个loader就会变成如下形式(
今天就写到这里,以后遇到了这类问题也会及时更新中。。。。。
参考资料:
【译】通过开发 Babel 插件理解抽象语法树(AST)
Babel.js 插件开发(一):Babel 与 AST
Babel.js插件开发之二 - 开始编写
理解 Babel 插件
AST查看器
babel-plugin-add-module-export
ES6学习——模块化:import和export
babel-types558)
1.babel-plugin-add-module-export的plugin
下面展示的是如何写一个babel插件module.exports = ({types}) => ({ visitor: { Program: { //exit表示进入节点 exit (path) { if (path.BABEL_PLUGIN_ADD_MODULE_EXPORTS) { //是否已经处理过了一次,这里的path是整个AST的最高层program //因为我们这里是对visitor中的program进行处理的 return } let hasExportDefault = false let hasExportNamed = false //得到语法树所有的`body`部分,如果需要访问该Node请使用path.node! path.get('body').forEach((path) => { if (path.isExportDefaultDeclaration()) { //是否是通过export default来向外导出的,如果是那么我们继续循环下面的导出语句,这里的forEach语句会继续执行下面的循环 hasExportDefault = true return } //export Test这种方式 if (path.isExportNamedDeclaration()) { if (path.node.specifiers.length === 1 && path.node.specifiers[0].exported.name === 'default') { hasExportDefault = true //已经导出了default } else { hasExportNamed = true //表示导出了 } return } }) //如果有了export default类型导出,同时没有export { foo, awesome, bar }; 这种导出,那么我们就会处理export default为module.exports =exports.default if (hasExportDefault && !hasExportNamed) { path.pushContainer('body', [ //t.assignmentExpression(operator, left, right) types.expressionStatement(types.assignmentExpression( '=', types.memberExpression(types.identifier('module'), types.identifier('exports')), types.memberExpression(types.identifier('exports'), types.stringLiteral('default'), true) //t.memberExpression(object, property, computed) //此时module.exports = exports.default //最后采用pushContainer将其放在Program的body的最后面 )) ]) } path.BABEL_PLUGIN_ADD_MODULE_EXPORTS = true } } } })
下面是细节部分:如果使用了ES6的export default
export default class Test {} //此时path.isExportDefaultDeclaration()为true
如果使用了ES6的export那么:
export class Test {} //此时path.isExportNamedDeclaration()为true,但是此时我们的node的specifier是[],即空数组
如果使用了module.exports(commonjs),那么我们的该插件不会做任何处理!!!
export { foo, awesome, bar }; //此时specifier如下
export{ foo as default }; //此时将会满足path.node.specifiers.length === 1 && path.node.specifiers[0].exported.name === 'default'
至于上面的exit时机,可以查看这里,其中enter表示进入节点而leave表示离开节点!
总之:该插件只会处理
export default/export{ foo as default }这种类型,将它处理为
module.exports=exports.default,而像如
export { foo, awesome, bar };这种类型是不会处理的,他会
原样保存到源码中!
2.下面是babel-plugin-import的plugin源码
import assert from 'assert'; import Plugin from './Plugin'; const util = require('util'); //默认这里传入的参数是babel,可以通过babel.types获取types,这里使用了解构的方式 //在babelrc中配置内容如下: // ["aa", { "libraryName": "antd" }] export default function ({ types }) { let plugins = null; // Only for test global.__clearBabelAntdPlugin = () => { plugins = null; }; //t.program(body, directives) //这里的body是将源代码转化为AST的结果,program是语法树的最高层 //opts表示获取到的对象,即{ libraryName: 'antd' } function Program(path, { opts }) { // console.log(util.inspect(opts,{showHidden:true,depth:4})); // console.log("--------------------------"); // Init plugin instances once. if (!plugins) { if (Array.isArray(opts)) { plugins = opts.map(({ libraryName, libraryDirectory, style, camel2DashComponentName, camel2UnderlineComponentName }) => { assert(libraryName, 'libraryName should be provided'); return new Plugin(libraryName, libraryDirectory, style, camel2DashComponentName, camel2UnderlineComponentName, types); }); } else { opts = opts || {}; assert(opts.libraryName, 'libraryName should be provided'); //必须提供libraryName plugins = [ new Plugin(opts.libraryName, opts.libraryDirectory, opts.style, opts.camel2DashComponentName, opts.camel2UnderlineComponentName, types), ]; } } // console.log('>>>>arguments>>>',arguments); // 这里的arguments一个是第一个path,第二个是PluginPass对象 applyInstance('Program', arguments, this); } const methods = [ 'ImportDeclaration', 'CallExpression', 'MemberExpression', 'Property', 'VariableDeclarator', 'LogicalExpression', 'ConditionalExpression', 'IfStatement', 'ExpressionStatement', 'ExportDefaultDeclaration', ]; //此处的method就是如"ImportDeclaration"等 //如果该plugin能够处理import,那么我们就使用这个babel的这个plugin来处理 function applyInstance(method, args, context) { for (const plugin of plugins) { if (plugin[method]) { plugin[method].apply(plugin, [...args, context]); } } } //默认visitor中有一个Program:function Program(){} //detail:https://github.com/liangklfang/babel/tree/master/packages/babel-types //t.program(body, directives) const ret = { visitor: { Program }, }; for (const method of methods) { //为我们的Visitor添加babel处理规则,其中visitor中每一个属性的值都是一个函数 ret.visitor[method] = function () { //t.importDeclaration(specifiers, source) //这里的arguments在调用的时候会自动执行,ret.visitor是为了维持上下文而已 applyInstance(method, arguments, ret.visitor); }; } return ret; }
我们先不分析Plugin这个插件到底做了什么,我们先分析下上面这段代码到底做了什么?以及我们到底能学到什么?
(1)首先看看下面这段代码:
export default function ({ types }) {} //这里传入的其实是babel对象,其含有的内容很丰富,但是我们这里只是关注types而已,具体你可以查看下面的参考资料
(2)然后看看下面这段代码:
const ret = { visitor: { Program }, };
我们的Program其实是AST最高层的节点,其中我们对babel的plugin传入的参数可以在方法Program中获取到!如下:
function Program(path, { opts }) {} //通过opts获取配置的参数
其中完整的Program方法传入的第二个参数的内容可以点击这里,他是一个PluginPass对象,你可以在上面说的babel-plugin-add-module-export这个插件中手动输出该参数,然后看一下!
(3)看看下面代码
for (const method of methods) { //为我们的Visitor添加babel处理规则,其中visitor中每一个属性的值都是一个函数 ret.visitor[method] = function () { //t.importDeclaration(specifiers, source) //这里的arguments在调用的时候会自动执行,ret.visitor是为了维持上下文而已 applyInstance(method, arguments, ret.visitor); }; }
也就是说当AST解析的时候会自动调用我们插件Plugin中的相应的方法。而且有一点一定要注意:我们的每一个方法传入的arguments对象包含两个属性,
其中第一个就是我们的path对象,而第二个对象就是我们上面说的PluginPass对象],这个对象的opts就是我们配置该babel插件时候传入的参数。完整的arguments对象可以点击这里。总之,
不仅仅是上面的Program,包括其他的如ImportDeclaration传入的都是一样的参数,第一个参数是path,而第二个参数是PluginPass对象!
下面我们看看Plugin具体做了什么?我们给出主要部分:
export default class Plugin { //实例化过程:new Plugin(libraryName, libraryDirectory, style, camel2DashComponentName, camel2UnderlineComponentName, types); //其中types就是传入到babel插件的types,detail:http://www.tuicool.com/articles/rMFRF32 constructor(libraryName, libraryDirectory, style, camel2DashComponentName, camel2UnderlineComponentName, types) { this.specified = null; this.libraryObjs = null; this.selectedMethods = null; this.libraryName = libraryName; //libraryName配置,如配置的'antd'等 this.libraryDirectory = typeof libraryDirectory === 'undefined' ? 'lib' : libraryDirectory; //libraryDirectory默认是lib目录下 this.camel2DashComponentName = typeof camel2DashComponentName === 'undefined' ? true : camel2DashComponentName; //如果camel2DashComponentName没有配置那么我们转化为中间线链接的名字 this.camel2UnderlineComponentName = camel2UnderlineComponentName; //camel2UnderlineComponentName转化为下划线 this.style = style || false; //配置的时候添加的style,例如"css" this.types = types; //babel插件原生的types,来自于babel-types库 } // this.importMethod(this.specified[name], file, opts) // 作用是:导入组件的某个方法,同时如果配置了css那么导入css importMethod(methodName, file, opts) { if (!this.selectedMethods[methodName]) { const libraryDirectory = this.libraryDirectory; //导入目录 const style = this.style; //配置的style const transformedMethodName = this.camel2UnderlineComponentName ? camel2Underline(methodName) : this.camel2DashComponentName ? camel2Dash(methodName) : methodName; //对方法名称进行处理 const path = winPath(join(this.libraryName, libraryDirectory, transformedMethodName)); //将特定插件,特定目录,特定的方法进行处理 this.selectedMethods[methodName] = file.addImport(path, 'default'); //引入这个特定方法的default对象,这里是导出了这个组件本身 module.exports.default= require('path'); //addImport返回import导入时候一个独立的id值 if (style === true) { file.addImport(`${path}/style`, 'style'); //将目录`${path}/style`下的index.js导入到style对象上,相当于module.exports.style =require(`${path}/style`) } else if(style === 'css') { file.addImport(`${path}/style/css`, 'style'); //将文件`${path}/style/css.js`导入到style对象上 } } //导入组件的default方法 return this.selectedMethods[methodName]; } }
具体代码我们已经注释过了,如果需要进一步学习,请查看babel-traverse
其中addImport方法的源码也贴出来:
function addImport(source, imported) { var name = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : imported; //如果只有两个参数那么name就是imported,否则就是 arguments[2] var alias = source + ":" + imported; //source是一个路径,如file.addImport(`${path}/style/css`, 'style'); //所以alias= `${path}/style/css` : "style" var id = this.dynamicImportIds[alias]; //引入id if (!id) { source = this.resolveModuleSource(source); //解析路径指向的资源,即`${path}/style/css` id = this.dynamicImportIds[alias] = this.scope.generateUidIdentifier(name); //产生一个独一无二的Identifier,name就是我们第二个参数。 //也就是通过我们的第二个参数产生一个唯一的Identifier,如上面指定的'style' //import style from 'antd'; var specifiers = []; if (imported === "*") { specifiers.push(t.importNamespaceSpecifier(id)); } else if (imported === "default") { specifiers.push(t.importDefaultSpecifier(id)); } else { specifiers.push(t.importSpecifier(id, t.identifier(imported))); } //这里有三个Specifier,即importNamespaceSpecifier,importDefaultSpecifier,importSpecifier var declar = t.importDeclaration(specifiers, t.stringLiteral(source)); //以source为名,也就是完整的路径名称来产生一个import declar._blockHoist = 3; //将这个import添加到body的最前面 this.path.unshiftContainer("body", declar); } return id; }
3.babel-plugin-check-es2015-constants
export default function ({ messages }) { return { visitor: { Scope({ scope }) { //scope for (const name in scope.bindings) { const binding = scope.bindings[name]; //binding对象 if (binding.kind !== "const" && binding.kind !== "module") continue; //只关注const与module种类 for (const violation of (binding.constantViolations: Array)) { throw violation.buildCodeFrameError(messages.get("readOnly", name)) ; } } }, } }; }
4.操作AST实现效果
4.1通过babel.transform将代码直接转化为函数字符串放到html模版中下面我们给出要转化的代码:
const code = ` import { Button, Icon } from 'antd'; const ButtonGroup = Button.Group; ReactDOM.render( <div> <h4>Basic</h4> <ButtonGroup> <Button>Cancel</Button> <Button type="primary">OK</Button> </ButtonGroup> <ButtonGroup> <Button disabled>L</Button> <Button disabled>M</Button> <Button disabled>R</Button> </ButtonGroup> <ButtonGroup> <Button type="primary">L</Button> <Button>M</Button> <Button type="ghost">M</Button> <Button type="dashed">R</Button> <\/ButtonGroup> <h4>With Icon<\/h4> <ButtonGroup> <Button type="primary"> <Icon type="left" \/>Go back <\/Button> <Button type="primary"> Go forward<Icon type="right" \/> <\/Button> <\/ButtonGroup> <ButtonGroup> <Button type="primary" icon="cloud" \/> <Button type="primary" icon="cloud-download" \/> <\/ButtonGroup> <\/div>, mountNode );`
我们要转化为的内容为如下形式:
function preview(){ return{ } }
我们先通过AST查看器(参考资料有链接)查看结构。如下图:
所以代码就很容易写了:
'use strict'; const babel = require('babel-core'); const types = require('babel-types'); const traverse = require('babel-traverse').default; const generator = require('babel-generator').default; const errorBoxStyle = { padding: 10, background: 'rgb(204, 0, 0)', color: 'white', fontFamily: 'sans-serif', fontSize: '16px', fontWeight: 'bold', overflow: 'auto', }; //var ReactDOM = require('react-dom') //要转化为什么,我们可以先通过AST查看器查看然后编写~~ function requireGenerator(varName, moduleName) { return types.variableDeclaration('var', [ types.variableDeclarator( //t.variableDeclarator(id, init) //id就是identifier //此处的init必须是一个Expression types.identifier(varName), //t.callExpression(callee, arguments) types.callExpression( types.identifier('require'), [types.stringLiteral(moduleName)] ) ), ]); } const defaultBabelConfig = { presets: ['es2015-ie', 'react', 'stage-0'], }; module.exports = function transformer( code, babelConfig = {}, noreact ) { let codeAst = null; try { const { ast } = babel.transform(code, Object.assign({}, defaultBabelConfig, babelConfig)); codeAst = ast; //使用babel.transform转化为AST树 } catch(e) { console.error(e); return `function() { ` + ` var React = require('react');` + ` return React.createElement('pre', {` + ` style: ${JSON.stringify(errorBoxStyle)}` + ` }, '${e.toString()}'); ` + `}`; } let renderReturn = null; traverse(codeAst, { CallExpression: function(callPath) { const callPathNode = callPath.node; if (callPathNode.callee && callPathNode.callee.object && callPathNode.callee.object.name === 'ReactDOM' && callPathNode.callee.property && callPathNode.callee.property.name === 'render') { renderReturn = types.returnStatement( callPathNode.arguments[0] ); //将ReactDOM.render方法的第一个参数作为return返回 //上面已经经过babel.transform转化了~~ callPath.remove(); } }, }); const astProgramBody = codeAst.program.body; //引入对react与ReactDOM的引入 if (!noreact) { astProgramBody.unshift(requireGenerator('ReactDOM', 'react-dom')); astProgramBody.unshift(requireGenerator('React', 'react')); } // ReactDOM.render always at the last of preview method if (renderReturn) { astProgramBody.push(renderReturn); } const codeBlock = types.BlockStatement(astProgramBody); //将astProgramBody转化为BlockStatement //t.functionDeclaration(id, params, body, generator, async) const previewFunction = types.functionDeclaration( types.Identifier('functionName'), [], codeBlock ); //t.program(body, directives) //body: Array<Statement> (required) // directives: Array<Directive> (default: []) return generator(types.program([previewFunction]), null, code).code; };
第一步:参考上图,首先构造FunctionDeclaration.body.body,其很显然是一个returnStatement
renderReturn = types.returnStatement( callPathNode.arguments[0] );
第二步:构建BlockStament(签名为:t.blockStatement(body, directives))
const codeBlock = types.BlockStatement(astProgramBody); //第一个参数就是BlockStatement的body,而body我们设置为returnStatement
所以此时我们构建了下面的AST结构了(也就是BlockStatement的body是一个returnStatement):
第三步:我们构建functionDeclaration(签名为:t.functionDeclaration(id, params, body, generator, async))
const previewFunction = types.functionDeclaration( types.Identifier('preview'), [], codeBlock //第三个参数是body );
此时我们构建了一个functionDeclaration的body是我们上面构建的BlockStament。而且第一个参数是Identifier,所以此时我们就变成了下面的结果了:
也就是说,得到的结果为:”functionDeclaration的Identifier为preview,body是一个BlockStament,这个BlockStament的body是一个returnStatement”。这样的结果和将下面的代码转化为AST得到的结果是相同的:
function preview(){ return{ } }
到这里我们就已经把上面的那部分代码转化为函数了。下面我们再分析一下下面的这个函数:
function requireGenerator(varName, moduleName) { return types.variableDeclaration('var', [ types.variableDeclarator( //t.variableDeclarator(id, init) //id就是identifier //此处的init必须是一个Expression types.identifier(varName), //t.callExpression(callee, arguments) types.callExpression( types.identifier('require'), [types.stringLiteral(moduleName)] ) ), ]); }
其实这里的写法是很简单的,你只要将”var ReactDOM = require(“react-dom”)”放到AST查看器中就可以轻而易举的写出来~
注意:通过上面的处理,我们最后得到的
依然是string类型,可以通过babel官方转换过程看到:
input string -> babylon parser -> AST -> transformer[s] -> AST -> babel-generator -> output string
通过babel-generator我们最后得到
依然是string。其作用也很明显点击这里,即通过操作AST将代码转化为一个函数,并结合nunjucks将该函数放在html模板中,从而可以直接调用,实现预览效果~~~
4.2结合webpack的loader操作AST得到真正的函数封装到对象上
下面是个webpack的loader内容:
'use strict'; const loaderUtils = require('loader-utils'); const generator = require('babel-generator').default; const transformer = require('./transformer'); const fs = require('fs'); //前一个loader虽然通过module.exports={}导出,但是这里content仍然会是内容,这一点一定要注意,因为loader只会为Buffer或者string而不会是对象 module.exports = function jsonmlReactLoader(content) { if (this.cacheable) { this.cacheable(); } const query = loaderUtils.getOptions(this); const lang = query.lang || 'react-example'; //we get configured language in dora-plugin-preview const res = transformer(content, lang); //we input jsonml as content to tranformer function const inputAst = res.inputAst; const imports = res.imports; for (let k = 0; k < imports.length; k++) { inputAst.program.body.unshift(imports[k]); } const code = generator(inputAst, null, content).code; //Turns an AST into code. const noreact = query.noreact; //You can pass noreact to refuse to import react if (noreact) { return code; } const processedCode= 'const React = require(\'react\');\n' + 'const ReactDOM = require(\'react-dom\');\n'+ code; return processedCode; };
我们看看transformer做了什么处理:
'use strict'; const babylon = require('babylon'); const types = require('babel-types'); const traverse = require('babel-traverse').default; function parser(content) { return babylon.parse(content, { sourceType: 'module', //sourceType: Indicate the mode the code should be parsed in. // Can be either "script" or "module". plugins: [ 'jsx', 'flow', 'asyncFunctions', 'classConstructorCall', 'doExpressions', 'trailingFunctionCommas', 'objectRestSpread', 'decorators', 'classProperties', 'exportExtensions', 'exponentiationOperator', 'asyncGenerators', 'functionBind', 'functionSent', ], //Array containing the plugins that you want to enable. }); } module.exports = function transformer(content, lang) { let imports = []; const inputAst = parser(content); //we transform our input to AST traverse(inputAst, { //Here, our path.node is an array ArrayExpression: function(path) { const node = path.node; const firstItem = node.elements[0]; //tagName const secondItem = node.elements[1]; //attributes or child element let renderReturn; if (firstItem && firstItem.type === 'StringLiteral' && firstItem.value === 'pre' && secondItem.properties[0].value.value === lang) { let codeNode = node.elements[2].elements[1]; let code = codeNode.value; //得到代码的内容了,也就是demo的代码内容 const codeAst = parser(code); //继续解析代码内容~~~ traverse(codeAst, { ImportDeclaration: function(importPath) { imports.push(importPath.node); importPath.remove(); }, CallExpression: function(CallPath) { const CallPathNode = CallPath.node; if (CallPathNode.callee && CallPathNode.callee.object && CallPathNode.callee.object.name === 'ReactDOM' && CallPathNode.callee.property && CallPathNode.callee.property.name === 'render') { //we focus on ReactDOM.render method renderReturn = types.returnStatement( CallPathNode.arguments[0] ); //we focus on first parameter of ReactDOM.render method CallPath.remove(); } }, }); const astProgramBody = codeAst.program.body; //program.body are updated through previous manipulation const codeBlock = types.BlockStatement(astProgramBody); // ReactDOM.render always at the last of preview method if (renderReturn) { astProgramBody.push(renderReturn); } const coceFunction = types.functionExpression( types.Identifier('jsonmlReactLoader'), //here is an Identifier of function [], codeBlock ); path.replaceWith(coceFunction); } }, //End of Array Expression }); return { imports: imports, inputAst: inputAst, }; };
通过上面的分析,这里已经很好理解了,如果你对这里的babylon有疑问,你可以查看这里。正如官网所说的:
babylon.parse(code, [options]) babylon.parseExpression(code, [options]) parse() parses the provided code as an entire ECMAScript program, while parseExpression() tries to parse a single Expression with performance in mind. When in doubt, use .parse().
baylon将输入的代码作为ECMAScript程序来解析,所以我们的源代码通过这个loader就会变成如下形式(
会有一个函数而非函数字符串出现),进而提供给babel-loader进一步处理~~:
const React = require('react'); const ReactDOM = require('react-dom'); import { Button } from 'antd'; module.exports = { "content": ["article", ["h3", "1.mark-twain解析出来的无法解析成为ast"], function jsonmlReactLoader() { return <div> <Button type="primary" shape="circle" icon="search" /> <Button type="primary" icon="search">Search</Button> <Button shape="circle" icon="search" /> <Button icon="search">Search</Button> <br /> <Button type="ghost" shape="circle" icon="search" /> <Button type="ghost" icon="search">Search</Button> <Button type="dashed" shape="circle" icon="search" /> <Button type="dashed" icon="search">Search</Button> </div>; }], "meta": {} };
今天就写到这里,以后遇到了这类问题也会及时更新中。。。。。
参考资料:
【译】通过开发 Babel 插件理解抽象语法树(AST)
Babel.js 插件开发(一):Babel 与 AST
Babel.js插件开发之二 - 开始编写
理解 Babel 插件
AST查看器
babel-plugin-add-module-export
ES6学习——模块化:import和export
babel-types558)
相关文章推荐
- opencvbase 实现opencv打开摄像头和初步处理等效果操作(附源码)
- jquery插件jquery.LightBox.js实现点击放大图片并左右点击切换效果(附demo源码下载)
- jQuery插件Echarts实现的双轴图效果示例【附demo源码下载】
- jQuery flip插件实现的翻牌效果示例【附demo源码下载】
- jQuery插件echarts实现的去掉X轴、Y轴和网格线效果示例【附demo源码下载】
- 源码分析使用Cocos2d-x实现2D光线效果
- C++虚函数实现一般化操作内幕分析(源码VC6.0可以直接调试)
- jQuery插件FusionCharts实现的2D柱状图效果示例【附demo源码下载】
- jQuery插件FusionCharts实现的2D饼状图效果【附demo源码下载】
- jQuery插件HighCharts实现的2D回归直线散点效果示例【附demo源码下载】
- Jquery左右滑动插件之实现超级炫酷动画效果附源码下载
- jquery拖动层效果插件用法实例分析(附demo源码)
- JS基于ocanvas插件实现的简单画板效果代码(附demo源码下载)
- jQuery插件echarts实现的单折线图效果示例【附demo源码下载】
- 在eclipse3.7中配置hadoop1.0.1插件 Hadoop 源码分析 使用java api操作Hadoop文件
- Jquery左右滑动插件之实现超级炫酷动画效果附源码下载
- jQuery内容折叠效果插件用法实例分析(附demo源码)
- jQuery内容折叠效果插件用法实例分析(附demo源码)
- jQuery插件FusionCharts实现的2D面积图效果示例【附demo源码下载】
- jQuery插件echarts实现的循环生成图效果示例【附demo源码下载】