Babel
babel是怎么工作的?
parse->AST->transform->gengerate
babel应用场景
语法糖的polyfill
代码统一hack
相关概念介绍
babel-polyfill
依赖core-js,提供es*->es3的方法,只转化语法,不转换API(类Promise,WeakMap)
babel-helper
babel-register(0.7.0-beta)
babel的基础配置init
利用pirate对require进行劫持,在hook中进行babel 原理见
同时对babel后的code进行缓存,提高下次babel效率
function compile(code, filename) {
...
let cacheKey = `${JSON.stringify(opts)}:${babel.version}`;
const env = babel.getEnv(false);
if (env) cacheKey += `:${env}`;
//读取缓存 根据mtime判断是否需要重新babel
if (cache) {
const cached = cache[cacheKey];
if (cached && cached.mtime === mtime(filename)) {
return cached.code;
}
}
const result = babel.transform(code, {
...opts,
sourceMaps: opts.sourceMaps === undefined ? "both" : opts.sourceMaps,
ast: false,
});
if (cache) {
cache[cacheKey] = result;
result.mtime = mtime(filename);
}
if (result.map) {
if (Object.keys(maps).length === 0) {
installSourceMapSupport();
}
maps[filename] = result.map;
}
return result.code;
}
//hook中传入ext配置
function hookExtensions(exts) {
if (piratesRevert) piratesRevert();
piratesRevert = addHook(compile, { exts, ignoreNodeModules: false });
}
//入口函数
export default function register(opts?: Object = {}) {
// Clone to avoid mutating the arguments object with the 'delete's below.
opts = Object.assign({}, opts);
if (opts.extensions) hookExtensions(opts.extensions);
if (opts.cache === false && cache) {
registerCache.clear();
cache = null;
} else if (opts.cache !== false && !cache) {
registerCache.load();
cache = registerCache.get();
}
...
}
babel-core
提供基础的transform方法
如何写一个babel插件
babel-plugin其实是对code转出的ast进行操作,
准备工具
ast的解构可以类比成一个树状或者json嵌套结构,他的每一层结构都可以叫做一个节点,如下图
babel提供一个visitor的方法,允许我们在里面指定我们想要访问的节点,并且可以在命中该节点时做出自定义的的操作
实例分析
现在我们有一个需要移除整个业务bundle包里所有console.log的需求
1.那我们首先要知道console.log实际在ast是怎样的一个节点结构
形如
console.log('a')
实际ast的展现如下
对于各个节点具体含义,这里不做细讲,可以参考文末的babel手册
2.这里直接贴上代码讲吧
module.exports = function (babel) {
const { types: t, template } = babel;
const visitor = {
//需要访问的节点名
//访问器默认会被注入两个参数 path(类比成dom),state
ExpressionStatement(path, state) {
const node = path.node;
//延当前节点向内部访问,判断是否符合console解析出的ast的特征
const expressionNode = keyPathVisitor(node, ['expression']);
const isCallExpression = expressionNode.type === 'CallExpression';
if (isCallExpression) {
const objectName = keyPathVisitor(expressionNode, ['callee', 'object', 'name']);
const prototypeName = keyPathVisitor(expressionNode, ['callee', 'property', 'name']);
if (objectName === 'console' && prototypeName === 'log' && !MAC) {
//如果符合上述条件,直接移除该节点
path.remove();
}
}
}
};
return {
visitor
};
};
3.进阶版:如果我们想在babel-plugin中新增代码呢
差不多有三种方法
A:手动添加节点(很恶心~相信你不会想去了解)
B:先生成ast,直接path.insertBefore
C:使用babel-template
例子: 移除autobind装饰器,并在constructor中自动bind this
注意点
1.因为babel判断是否babel是根据modify time,所以babel插件写完想实时生效,需要给当前的env加上 BABEL_DISABLE_CACHE
//babel-register/cache.js
function load() {
if (process.env.BABEL_DISABLE_CACHE) return;
process.on("exit", save);
process.nextTick(save);
if (!_fs2.default.existsSync(FILENAME)) return;
try {
data = JSON.parse(_fs2.default.readFileSync(FILENAME));
} catch (err) {
return;
}
}
2.babel插件写完后发布npm时,记得一定要加上babel-plugin-前缀,因为配置在babelrc中的插件名都会被babel在加载时统一加上babel-plugin前缀,然后在模块系统中去查找
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。