Release 1.3.3 #7033

Merged
merged 135 commits into from Jun 10, 2016
Commits
+85 −40
Split
Viewing a subset of changes. View all

Support .babelrc presets and plugins.

In addition to package.json files with "babel" sections, BabelCompiler now
supports .babelrc files, though in both cases only the "presets" and
"plugins" fields are respected. If a .babelrc file is found, package.json
files are ignored.

Additional presets and plugins are now *prepended* to the original
babelOptions.{presets,plugins} lists, so that the custom plugins have a
chance to handle syntax differently than babel-preset-meteor would.

The inputFile.getPackageJson method has been replaced by a more general
method, inputFile.findControlFile.

Fixes #6351.
commit 49a60f155b64e00a5f3e879540ce4be7bbb74691 @benjamn benjamn committed May 20, 2016
@@ -5,10 +5,13 @@
*/
BabelCompiler = function BabelCompiler(extraFeatures) {
this.extraFeatures = extraFeatures;
+ this._babelrcCache = Object.create(null);
};
var BCp = BabelCompiler.prototype;
var excludedFileExtensionPattern = /\.es5\.js$/i;
+var fs = Npm.require("fs");
+var hasOwn = Object.prototype.hasOwnProperty;
var strictModulesPluginFactory =
Npm.require("babel-plugin-transform-es2015-modules-commonjs");
@@ -84,7 +87,7 @@ BCp.processFilesForTarget = function (inputFiles) {
babelOptions.plugins.push(babelModulesPlugin);
}
- inferExtraBabelOptions(inputFile, babelOptions);
+ self.inferExtraBabelOptions(inputFile, babelOptions);
babelOptions.sourceMap = true;
babelOptions.filename =
@@ -133,28 +136,64 @@ function profile(name, func) {
}
};
-function inferExtraBabelOptions(inputFile, babelOptions) {
- const pkgJson =
- inputFile.require &&
- inputFile.getPathInPackage &&
- inputFile.getPackageJson();
+BCp.inferExtraBabelOptions = function (inputFile, babelOptions) {
+ if (! inputFile.require ||
+ ! inputFile.findControlFile) {
+ return false;
+ }
+
+ return (
+ // If a .babelrc exists, it takes precedence over package.json.
+ this._inferFromBabelRc(inputFile, babelOptions) ||
+ this._inferFromPackageJson(inputFile, babelOptions)
+ );
+};
+
+BCp._inferFromBabelRc = function (inputFile, babelOptions) {
+ var babelrcPath = inputFile.findControlFile(".babelrc");
+ if (babelrcPath) {
+ if (! hasOwn.call(this._babelrcCache, babelrcPath)) {
+ this._babelrcCache[babelrcPath] =
+ JSON.parse(fs.readFileSync(babelrcPath));
+ }
- if (! pkgJson || ! pkgJson.babel) {
- return;
+ return this._inferHelper(
+ inputFile,
+ babelOptions,
+ this._babelrcCache[babelrcPath]
+ );
}
+};
- function infer(listName, prefix) {
- const list = pkgJson.babel[listName];
- if (! Array.isArray(list)) {
- return;
+BCp._inferFromPackageJson = function (inputFile, babelOptions) {
+ var pkgJsonPath = inputFile.findControlFile(".babelrc");
@abernix
abernix May 21, 2016 Collaborator

Was this meant to be .babelrc or package.json?

+ if (pkgJsonPath) {
+ if (! hasOwn.call(this._babelrcCache, pkgJsonPath)) {
+ this._babelrcCache[pkgJsonPath] =
+ JSON.parse(fs.readFileSync(pkgJsonPath)).babel || null;
}
- function addPrefix(id) {
- return isTopLevel ? prefix + id : id;
+ return this._inferHelper(
+ inputFile,
+ babelOptions,
+ this._babelrcCache[pkgJsonPath]
+ );
+ }
+};
+
+BCp._inferHelper = function (inputFile, babelOptions, babelrc) {
+ if (! babelrc) {
+ return false;
+ }
+
+ function infer(listName, prefix) {
+ var list = babelrc[listName];
+ if (! Array.isArray(list) || list.length === 0) {
+ return;
}
function req(id) {
- const isTopLevel = "./".indexOf(id.charAt(0)) < 0;
+ var isTopLevel = "./".indexOf(id.charAt(0)) < 0;
if (isTopLevel) {
// If the identifier is top-level, it will be prefixed with
// "babel-plugin-" or "babel-preset-". If the identifier is not
@@ -166,17 +205,27 @@ function inferExtraBabelOptions(inputFile, babelOptions) {
return inputFile.require(id);
}
- list.forEach(function (item) {
+ list.forEach(function (item, i) {
if (typeof item === "string") {
item = req(item);
} else if (Array.isArray(item) &&
typeof item[0] === "string") {
+ item = item.slice(); // defensive copy
item[0] = req(item[0]);
}
- babelOptions[listName].push(item);
+ list[i] = item;
});
+
+ // PREPEND additional plugins to the existing babelOptions[listName]
+ // list, so that they have a chance to handle syntax differently than
+ // babel-preset-meteor normally would.
+ var target = babelOptions[listName] || [];
+ target.unshift.apply(target, list);
+ babelOptions[listName] = target;
}
infer("presets", "babel-preset-");
infer("plugins", "babel-plugin-");
-}
+
+ return true;
+};
@@ -204,10 +204,9 @@ class InputFile extends buildPluginModule.InputFile {
// document.
this._resourceSlot = resourceSlot;
- // This `false` means we haven't read the package.json file governing
- // this InputFile yet. Once we read it, this cached value will be
- // either an object or null (meaning there was no package.json file).
- this._packageJson = false;
+ // Map from control file names (e.g. package.json, .babelrc) to
+ // absolute paths, or null to indicate absence.
+ this._controlFileCache = Object.create(null);
// Map from imported module identifier strings (possibly relative) to
// fully require.resolve'd module identifiers.
@@ -257,43 +256,40 @@ class InputFile extends buildPluginModule.InputFile {
return self._resourceSlot.inputResource.fileOptions || {};
}
- getPackageJson() {
- if (typeof this._packageJson === "object") {
- // Note that this._packageJson could be either an actual object or
- // null at this point, which may be the first time I've ever been
- // glad that typeof null === "object".
- return this._packageJson;
+ // Search ancestor directories for control files (e.g. package.json,
+ // .babelrc), and return the absolute path of the first one found, or
+ // null if the search failed.
+ findControlFile(basename) {
+ let absPath = this._controlFileCache[basename];
+ if (typeof absPath === "string") {
+ return absPath;
}
const sourceRoot = this._resourceSlot.packageSourceBatch.sourceRoot;
if (! _.isString(sourceRoot)) {
- return this._packageJson = null;
+ return this._controlFileCache[basename] = null;
}
let dir = files.pathDirname(this.getPathInPackage());
while (true) {
- const pkgJsonId = files.convertToPosixPath(
- files.pathJoin(sourceRoot, dir, "package.json"));
+ absPath = files.pathJoin(sourceRoot, dir, basename);
- try {
- // The require function will cache results across the process.
- return this._packageJson = require(pkgJsonId);
- } catch (e) {
- if (e.code !== "MODULE_NOT_FOUND") {
- throw e;
- }
+ const stat = files.statOrNull(absPath);
+ if (stat && stat.isFile()) {
+ return this._controlFileCache[basename] = absPath;
}
if (files.pathBasename(dir) === "node_modules") {
- return this._packageJson = null;
+ // The search for control files should not escape node_modules.
+ return this._controlFileCache[basename] = null;
}
let parentDir = files.pathDirname(dir);
if (parentDir === dir) break;
dir = parentDir;
}
- return this._packageJson = null;
+ return this._controlFileCache[basename] = null;
}
resolve(id) {