Этот плагин Babel:
module.exports = function(){
return {
visitor:{
Program:{
enter(){ console.info('Enter') },
exit(){ console.info('Exit') }
}
},
pre(){ console.info('Pre') },
post(){ console.info('Post') }
}
}
производит этот вывод для любого файла javascript:
Pre
Enter
Exit
Post
pre() вызывается непосредственно перед Program.enter() и post() сразу после Program.exit().
Если я хочу запустить какой-то код в начале / конце обхода AST, есть ли причина, по которой я должен поместить этот код в pre / post вместо Program.enter / Program.exit?
Какая разница?





Нет никакой разницы, AFAIK. Оба вызываются до / после полного обхода синтаксического дерева.
Единственное отличие состоит в том, что параметры, передаваемые в Program.enter / Program.exit, отличаются от параметров, передаваемых в pre / post.
module.exports = function(){
return {
visitor:{
Program:{
enter(path, state){
//path.node
//path.parent
//state.opts
},
}
},
pre(state){
//state.scope
//state.scope.globals
//state.scope.plugins
},
}
}
Например, из Program.enter() у вас есть доступ к state.opts с параметрами вашего плагина, а из pre() - нет.
pre и post имеют более фундаментальное применение: они запускаются для всех плагинов до / после всех обходов. Порядок для всех плагинов:
pre работает со всеми плагинамиvisitor работает со всеми плагинамиpost работает со всеми плагинами.Чтобы ответить на ваш вопрос: visitor.Program.enter и pre в большинстве случаев ведут себя одинаково, то есть если вам все равно, начали ли другие плагины посещать Program к моменту запуска visitor вашего собственного плагина. Основное различие можно резюмировать двумя пунктами:
pre гарантированно запускается до начала обхода любой плагин.pre гарантированно будет использовать только беги один раз, в то время как посетители узла могут запускаться много раз, поскольку изменения AST посетителями (вашими или другими плагинами) могут потребовать неограниченного количества повторных посещений.Обратите внимание, что порядок выполнения плагинов - это открытая проблема, которая активно обсуждается (см. здесь для первого введения), а pre и post помогают облегчить часть этой боли для плагинов, которые потенциально могут конфликтовать с другими плагинами.
Для справки, это порядок выполнения плагинов и пресетов в Babel7 (при запуске с babel.config.js, указанным ниже):
[PLUGIN] pre plugin1
[PLUGIN] pre plugin2
[PLUGIN] pre pres2
[PLUGIN] pre pres1
[PLUGIN] Program plugin1
[PLUGIN] Program plugin2
[PLUGIN] Program pres2
[PLUGIN] Program pres1
[PLUGIN] post plugin1
[PLUGIN] post plugin2
[PLUGIN] post pres2
[PLUGIN] post pres1
babel.config.js:function makeReporterPlugin(msg) {
return () => {
return {
pre() {
console.info('[PLUGIN] pre', msg);
},
visitor: {
Program() {
console.info('[PLUGIN] Program', msg);
}
},
post() {
console.info('[PLUGIN] post', msg);
},
};
};
}
const pres1 = {
plugins: [
makeReporterPlugin('pres1')
]
};
const pres2 = {
plugins: [
makeReporterPlugin('pres2')
]
};
const plugin1 = makeReporterPlugin('plugin1');
const plugin2 = makeReporterPlugin('plugin2');
module.exports = {
"presets": [
pres1,
pres2
],
"plugins": [
plugin1,
plugin2
]
};
Когда я писал свой первый плагин, я сам был несколько сбит с толку. Казалось, что он запускается после @babel/preset-env, хотя, согласно документации по заказу плагинов, presets должен запускаться после plugins. Однако, как объяснено здесь, на самом деле не все так просто: visitors всех плагинов и пресетов проходят параллельно, в то время как порядок, описанный в документации (плагины перед пресетами), гарантируется только для каждого узла индивидуально, а не для всего обхода AST. Эта болевая точка является основной мотивацией для pre и post.