diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 169b727b56..c1f84f5456 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -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; + } - if (! pkgJson || ! pkgJson.babel) { - return; + 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)); + } + + return this._inferHelper( + inputFile, + babelOptions, + this._babelrcCache[babelrcPath] + ); + } +}; + +BCp._inferFromPackageJson = function (inputFile, babelOptions) { + var pkgJsonPath = inputFile.findControlFile(".babelrc"); + if (pkgJsonPath) { + if (! hasOwn.call(this._babelrcCache, pkgJsonPath)) { + this._babelrcCache[pkgJsonPath] = + JSON.parse(fs.readFileSync(pkgJsonPath)).babel || null; + } + + return this._inferHelper( + inputFile, + babelOptions, + this._babelrcCache[pkgJsonPath] + ); + } +}; + +BCp._inferHelper = function (inputFile, babelOptions, babelrc) { + if (! babelrc) { + return false; } function infer(listName, prefix) { - const list = pkgJson.babel[listName]; - if (! Array.isArray(list)) { + var list = babelrc[listName]; + if (! Array.isArray(list) || list.length === 0) { return; } - function addPrefix(id) { - return isTopLevel ? prefix + id : id; - } - 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; +}; diff --git a/tools/isobuild/compiler-plugin.js b/tools/isobuild/compiler-plugin.js index 9d16f84072..9cb974611c 100644 --- a/tools/isobuild/compiler-plugin.js +++ b/tools/isobuild/compiler-plugin.js @@ -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,35 +256,32 @@ 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); @@ -293,7 +289,7 @@ class InputFile extends buildPluginModule.InputFile { dir = parentDir; } - return this._packageJson = null; + return this._controlFileCache[basename] = null; } resolve(id) {