Merge pull request #3281 from DmitrySoshnikov/pass_per_preset
[RFC] Pass per preset
This commit is contained in:
@@ -8,7 +8,6 @@ import * as metadataVisitor from "./metadata";
|
||||
import convertSourceMap from "convert-source-map";
|
||||
import OptionManager from "./options/option-manager";
|
||||
import type Pipeline from "../pipeline";
|
||||
import type Plugin from "../plugin";
|
||||
import PluginPass from "../plugin-pass";
|
||||
import shebangRegex from "shebang-regex";
|
||||
import { NodePath, Hub, Scope } from "babel-traverse";
|
||||
@@ -61,8 +60,21 @@ export default class File extends Store {
|
||||
|
||||
this.pluginVisitors = [];
|
||||
this.pluginPasses = [];
|
||||
this.pluginStack = [];
|
||||
this.buildPlugins();
|
||||
|
||||
// Plugins for top-level options.
|
||||
this.buildPluginsForOptions(this.opts);
|
||||
|
||||
// If we are in the "pass per preset" mode, build
|
||||
// also plugins for each preset.
|
||||
if (this.opts.passPerPreset) {
|
||||
// All the "per preset" options are inherited from the main options.
|
||||
this.perPresetOpts = [];
|
||||
this.opts.presets.forEach(presetOpts => {
|
||||
let perPresetOpts = Object.assign(Object.create(this.opts), presetOpts);
|
||||
this.perPresetOpts.push(perPresetOpts);
|
||||
this.buildPluginsForOptions(perPresetOpts);
|
||||
});
|
||||
}
|
||||
|
||||
this.metadata = {
|
||||
usedHelpers: [],
|
||||
@@ -95,7 +107,6 @@ export default class File extends Store {
|
||||
|
||||
pluginVisitors: Array<Object>;
|
||||
pluginPasses: Array<PluginPass>;
|
||||
pluginStack: Array<Plugin>;
|
||||
pipeline: Pipeline;
|
||||
parserOpts: BabelParserOptions;
|
||||
log: Logger;
|
||||
@@ -165,21 +176,29 @@ export default class File extends Store {
|
||||
return opts;
|
||||
}
|
||||
|
||||
buildPlugins() {
|
||||
let plugins: Array<[PluginPass, Object]> = this.opts.plugins.concat(INTERNAL_PLUGINS);
|
||||
buildPluginsForOptions(opts) {
|
||||
if (!Array.isArray(opts.plugins)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let plugins: Array<[PluginPass, Object]> = opts.plugins.concat(INTERNAL_PLUGINS);
|
||||
let currentPluginVisitors = [];
|
||||
let currentPluginPasses = [];
|
||||
|
||||
// init plugins!
|
||||
for (let ref of plugins) {
|
||||
let [plugin, pluginOpts] = ref; // todo: fix - can't embed in loop head because of flow bug
|
||||
|
||||
this.pluginStack.push(plugin);
|
||||
this.pluginVisitors.push(plugin.visitor);
|
||||
this.pluginPasses.push(new PluginPass(this, plugin, pluginOpts));
|
||||
currentPluginVisitors.push(plugin.visitor);
|
||||
currentPluginPasses.push(new PluginPass(this, plugin, pluginOpts));
|
||||
|
||||
if (plugin.manipulateOptions) {
|
||||
plugin.manipulateOptions(this.opts, this.parserOpts, this);
|
||||
plugin.manipulateOptions(opts, this.parserOpts, this);
|
||||
}
|
||||
}
|
||||
|
||||
this.pluginVisitors.push(currentPluginVisitors);
|
||||
this.pluginPasses.push(currentPluginPasses);
|
||||
}
|
||||
|
||||
getModuleName(): ?string {
|
||||
@@ -419,11 +438,16 @@ export default class File extends Store {
|
||||
}
|
||||
|
||||
transform(): BabelFileResult {
|
||||
this.call("pre");
|
||||
this.log.debug(`Start transform traverse`);
|
||||
traverse(this.ast, traverse.visitors.merge(this.pluginVisitors, this.pluginPasses), this.scope);
|
||||
this.log.debug(`End transform traverse`);
|
||||
this.call("post");
|
||||
// In the "pass per preset" mode, we have grouped passes.
|
||||
// Otherwise, there is only one plain pluginPasses array.
|
||||
this.pluginPasses.forEach((pluginPasses, index) => {
|
||||
this.call("pre", pluginPasses);
|
||||
this.log.debug(`Start transform traverse`);
|
||||
traverse(this.ast, traverse.visitors.merge(this.pluginVisitors[index], pluginPasses), this.scope);
|
||||
this.log.debug(`End transform traverse`);
|
||||
this.call("post", pluginPasses);
|
||||
});
|
||||
|
||||
return this.generate();
|
||||
}
|
||||
|
||||
@@ -483,8 +507,8 @@ export default class File extends Store {
|
||||
return util.shouldIgnore(opts.filename, opts.ignore, opts.only);
|
||||
}
|
||||
|
||||
call(key: "pre" | "post") {
|
||||
for (let pass of (this.pluginPasses: Array<PluginPass>)) {
|
||||
call(key: "pre" | "post", pluginPasses: Array<PluginPass>) {
|
||||
for (let pass of pluginPasses) {
|
||||
let plugin = pass.plugin;
|
||||
let fn = plugin[key];
|
||||
if (fn) fn.call(pass, this);
|
||||
|
||||
@@ -183,5 +183,12 @@ module.exports = {
|
||||
moduleId: {
|
||||
description: "specify a custom name for module ids",
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
|
||||
passPerPreset: {
|
||||
description: "Whether to spawn a traversal pass per a preset. By default all presets are merged.",
|
||||
type: "boolean",
|
||||
default: false,
|
||||
hidden: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -157,22 +157,23 @@ export default class OptionManager {
|
||||
throw err;
|
||||
}
|
||||
|
||||
this.mergeOptions(opts, loc, null, path.dirname(loc));
|
||||
this.mergeOptions(opts, this.options, loc, null, path.dirname(loc));
|
||||
this.resolvedConfigs.push(loc);
|
||||
|
||||
return !!opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when we want to merge the input `opts` into our
|
||||
* base options.
|
||||
* This is called when we want to merge the input `opts` into the
|
||||
* base options (passed as the `extendingOpts`: at top-level it's the
|
||||
* main options, at presets level it's presets options).
|
||||
*
|
||||
* - `alias` is used to output pretty traces back to the original source.
|
||||
* - `loc` is used to point to the original config.
|
||||
* - `dirname` is used to resolve plugins relative to it.
|
||||
*/
|
||||
|
||||
mergeOptions(rawOpts?: Object, alias: string = "foreign", loc?: string, dirname?: string) {
|
||||
mergeOptions(rawOpts?: Object, extendingOpts?: Object, alias: string = "foreign", loc?: string, dirname?: string) {
|
||||
if (!rawOpts) return;
|
||||
|
||||
//
|
||||
@@ -221,8 +222,17 @@ export default class OptionManager {
|
||||
|
||||
// resolve presets
|
||||
if (opts.presets) {
|
||||
this.mergePresets(opts.presets, dirname);
|
||||
delete opts.presets;
|
||||
// If we're in the "pass per preset" mode, we resolve the presets
|
||||
// and keep them for further execution to calculate the options.
|
||||
if (opts.passPerPreset) {
|
||||
opts.presets = this.resolvePresets(opts.presets, dirname, (preset, presetLoc) => {
|
||||
this.mergeOptions(preset, preset, presetLoc, presetLoc, dirname);
|
||||
});
|
||||
} else {
|
||||
// Otherwise, just merge presets options into the main options.
|
||||
this.mergePresets(opts.presets, dirname);
|
||||
delete opts.presets;
|
||||
}
|
||||
}
|
||||
|
||||
// env
|
||||
@@ -233,29 +243,57 @@ export default class OptionManager {
|
||||
delete opts.env;
|
||||
}
|
||||
|
||||
// merge them into this current files options
|
||||
merge(this.options, opts);
|
||||
// Merge them into current extending options in case of top-level
|
||||
// options. In case of presets, just re-assign options which are got
|
||||
// normalized during the `mergeOptions`.
|
||||
if (rawOpts !== extendingOpts) {
|
||||
merge(extendingOpts, opts);
|
||||
} else {
|
||||
Object.assign(extendingOpts, opts);
|
||||
}
|
||||
|
||||
// merge in env options
|
||||
this.mergeOptions(envOpts, `${alias}.env.${envKey}`, null, dirname);
|
||||
this.mergeOptions(envOpts, extendingOpts, `${alias}.env.${envKey}`, null, dirname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all presets into the main options in case we are not in the
|
||||
* "pass per preset" mode. Otherwise, options are calculated per preset.
|
||||
*/
|
||||
mergePresets(presets: Array<string | Object>, dirname: string) {
|
||||
for (let val of presets) {
|
||||
this.resolvePresets(presets, dirname, (presetOpts, presetLoc) => {
|
||||
this.mergeOptions(
|
||||
presetOpts,
|
||||
this.options,
|
||||
presetLoc,
|
||||
presetLoc,
|
||||
path.dirname(presetLoc)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves presets options which can be either direct object data,
|
||||
* or a module name to require.
|
||||
*/
|
||||
resolvePresets(presets: Array<string | Object>, dirname: string, onResolve?) {
|
||||
return presets.map(val => {
|
||||
if (typeof val === "string") {
|
||||
let presetLoc = resolve(`babel-preset-${val}`, dirname) || resolve(val, dirname);
|
||||
if (presetLoc) {
|
||||
let presetOpts = require(presetLoc);
|
||||
this.mergeOptions(presetOpts, presetLoc, presetLoc, path.dirname(presetLoc));
|
||||
let val = require(presetLoc);
|
||||
onResolve && onResolve(val, presetLoc);
|
||||
return val;
|
||||
} else {
|
||||
throw new Error(`Couldn't find preset ${JSON.stringify(val)} relative to directory ${JSON.stringify(dirname)}`);
|
||||
}
|
||||
} else if (typeof val === "object") {
|
||||
this.mergeOptions(val);
|
||||
onResolve && onResolve(val);
|
||||
return val;
|
||||
} else {
|
||||
throw new Error("todo");
|
||||
throw new Error(`Unsupported preset format: ${val}.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addIgnoreConfig(loc) {
|
||||
@@ -266,7 +304,7 @@ export default class OptionManager {
|
||||
.map((line) => line.replace(/#(.*?)$/, "").trim())
|
||||
.filter((line) => !!line);
|
||||
|
||||
this.mergeOptions({ ignore: lines }, loc);
|
||||
this.mergeOptions({ ignore: lines }, this.options, loc);
|
||||
}
|
||||
|
||||
findConfigs(loc) {
|
||||
@@ -333,7 +371,7 @@ export default class OptionManager {
|
||||
}
|
||||
|
||||
// merge in base options
|
||||
this.mergeOptions(opts, "base", null, filename && path.dirname(filename));
|
||||
this.mergeOptions(opts, this.options, "base", null, filename && path.dirname(filename));
|
||||
|
||||
// normalise
|
||||
this.normaliseOptions(opts);
|
||||
|
||||
@@ -4,6 +4,7 @@ var Pipeline = require("../lib/transformation/pipeline");
|
||||
var sourceMap = require("source-map");
|
||||
var assert = require("assert");
|
||||
var File = require("../lib/transformation/file").default;
|
||||
var Plugin = require("../lib/transformation/plugin");
|
||||
|
||||
function assertIgnored(result) {
|
||||
assert.ok(result.ignored);
|
||||
@@ -44,6 +45,89 @@ suite("api", function () {
|
||||
});
|
||||
});
|
||||
|
||||
test("pass per preset", function () {
|
||||
var aliasBaseType = null;
|
||||
|
||||
function execTest(passPerPreset) {
|
||||
return babel.transform('type Foo = number; let x = (y): Foo => y;', {
|
||||
passPerPreset: passPerPreset,
|
||||
presets: [
|
||||
// First preset with our plugin, "before"
|
||||
{
|
||||
plugins: [
|
||||
new Plugin({
|
||||
visitor: {
|
||||
Function: function(path) {
|
||||
var node = path.node;
|
||||
var scope = path.scope;
|
||||
|
||||
var alias = scope
|
||||
.getProgramParent()
|
||||
.getBinding(node.returnType.typeAnnotation.id.name)
|
||||
.path
|
||||
.node;
|
||||
|
||||
// In case of `passPerPreset` being `false`, the
|
||||
// alias node is already removed by Flow plugin.
|
||||
if (!alias) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In case of `passPerPreset` being `true`, the
|
||||
// alias node should still exist.
|
||||
aliasBaseType = alias.right.type; // NumberTypeAnnotation
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
|
||||
// ES2015 preset
|
||||
require(__dirname + "/../../babel-preset-es2015"),
|
||||
|
||||
// Third preset for Flow.
|
||||
{
|
||||
plugins: [
|
||||
require(__dirname + "/../../babel-plugin-syntax-flow"),
|
||||
require(__dirname + "/../../babel-plugin-transform-flow-strip-types"),
|
||||
]
|
||||
}
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// 1. passPerPreset: true
|
||||
|
||||
var result = execTest(true);
|
||||
|
||||
assert.equal(aliasBaseType, "NumberTypeAnnotation");
|
||||
|
||||
assert.deepEqual([
|
||||
'"use strict";',
|
||||
'',
|
||||
'var x = function x(y) {',
|
||||
' return y;',
|
||||
'};'
|
||||
].join("\n"), result.code);
|
||||
|
||||
// 2. passPerPreset: false
|
||||
|
||||
aliasBaseType = null;
|
||||
|
||||
var result = execTest(false);
|
||||
|
||||
assert.equal(aliasBaseType, null);
|
||||
|
||||
assert.deepEqual([
|
||||
'"use strict";',
|
||||
'',
|
||||
'var x = function x(y) {',
|
||||
' return y;',
|
||||
'};'
|
||||
].join("\n"), result.code);
|
||||
|
||||
});
|
||||
|
||||
test("source map merging", function () {
|
||||
var result = babel.transform([
|
||||
'function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }',
|
||||
|
||||
Reference in New Issue
Block a user