diff --git a/README.md b/README.md index fb54120e..a687eec5 100755 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ allow you to use latest Javascript in your Ember CLI project. + [Modules](#modules) + [Disabling Debug Tooling Support](#disabling-debug-tooling-support) + [Enabling TypeScript Transpilation](#enabling-typescript-transpilation) + * [Babel config file usage](#babel-config-usage) * [Addon usage](#addon-usage) + [Adding Custom Plugins](#adding-custom-plugins) + [Additional Trees](#additional-trees) @@ -291,7 +292,75 @@ module.exports = function(defaults) { return app.toTree(); } ``` +### Babel config usage +If you want to use the existing babel config from your project instead of the auto-generated one from this addon, then you would need to *opt-in* by passing the config `useBabelConfig: true` as a child property of `ember-cli-babel` in your `ember-cli-build.js` file. + +*Note: If you are using this option, then you have to make sure that you are adding all of the required plugins required for Ember to transpile correctly.* + +Example usage: + +```js +//ember-cli-build.js + +let app = new EmberAddon(defaults, { + "ember-cli-babel": { + useBabelConfig: true, + // ember-cli-babel related options + }, +}); +``` + +```js +//babel.config.js + +const { buildEmberPlugins } = require("ember-cli-babel"); + +module.exports = function (api) { + api.cache(true); + + return { + presets: [ + [ + require.resolve("@babel/preset-env"), + { + targets: require("./config/targets"), + }, + ], + ], + plugins: [ + // if you want external helpers + [ + require.resolve("@babel/plugin-transform-runtime"), + { + version: require("@babel/plugin-transform-runtime/package").version, + regenerator: false, + useESModules: true, + }, + ], + // this is where all the ember required plugins would reside + ...buildEmberPlugins(__dirname, { /*customOptions if you want to pass in */ }), + ], + }; +}; +``` + +#### Ember Plugins + +Ember Plugins is a helper function that returns a list of plugins that are required for transpiling Ember correctly. You can import this helper function and add it to your existing `babel.config` file. +The first argument is **required** which is the path to the root of your project (generally `__dirname`). +**Config options:** + +```js +{ + disableModuleResolution: boolean, // determines if you want the module resolution enabled + emberDataVersionRequiresPackagesPolyfill: boolean, // enable ember data's polyfill + shouldIgnoreJQuery: boolean, // ignore jQuery + shouldIgnoreEmberString: boolean, // ignore ember string + shouldIgnoreDecoratorAndClassPlugins: boolean, // disable decorator plugins + disableEmberModulesAPIPolyfill: boolean, // disable ember modules API polyfill +} +``` ### Addon usage #### Adding Custom Plugins diff --git a/index.js b/index.js index e5cbde06..6fd8b47e 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,21 @@ 'use strict'; +const { + _shouldCompileModules, + _shouldIncludeHelpers, + _shouldHandleTypeScript, + _getExtensions, + _parentName, + _shouldHighlightCode, +} = require("./lib/babel-options-util"); + const VersionChecker = require('ember-cli-version-checker'); const clone = require('clone'); +const babel = require('@babel/core'); const path = require('path'); -const semver = require('semver'); - -const defaultShouldIncludeHelpers = require('./lib/default-should-include-helpers'); const getBabelOptions = require('./lib/get-babel-options'); const findApp = require('./lib/find-app'); +const emberPlugins = require('./lib/ember-plugins'); const APP_BABEL_RUNTIME_VERSION = new WeakMap(); @@ -16,6 +24,9 @@ let count = 0; module.exports = { name: 'ember-cli-babel', configKey: 'ember-cli-babel', + // Note: This is not used internally for this addon, this is added for users to import this function for getting the ember specific + // babel plugins. Eg: adding ember specific babel plugins in their babel.config.js. + buildEmberPlugins: emberPlugins, init() { this._super.init && this._super.init.apply(this, arguments); @@ -24,7 +35,7 @@ module.exports = { let dep = checker.for('ember-cli', 'npm'); if (dep.lt('2.13.0')) { - throw new Error(`ember-cli-babel@7 (used by ${this._parentName()} at ${this.parent.root}) cannot be used by ember-cli versions older than 2.13, you used ${dep.version}`); + throw new Error(`ember-cli-babel@7 (used by ${_parentName(this.parent)} at ${this.parent.root}) cannot be used by ember-cli versions older than 2.13, you used ${dep.version}`); } }, @@ -35,31 +46,97 @@ module.exports = { _debugTree() { if (!this._cachedDebugTree) { - this._cachedDebugTree = require('broccoli-debug').buildDebugCallback(`ember-cli-babel:${this._parentName()}`); + this._cachedDebugTree = require('broccoli-debug').buildDebugCallback(`ember-cli-babel:${_parentName(this.parent)}`); } return this._cachedDebugTree.apply(null, arguments); }, + /** + * Default babel options + * @param {*} config + */ + _getDefaultBabelOptions(config = {}) { + let emberCLIBabelConfig = config["ember-cli-babel"]; + let providedAnnotation; + let throwUnlessParallelizable; + let sourceMaps = false; + let shouldCompileModules = _shouldCompileModules(config, this.project); + + if (emberCLIBabelConfig) { + providedAnnotation = emberCLIBabelConfig.annotation; + throwUnlessParallelizable = emberCLIBabelConfig.throwUnlessParallelizable; + } + + if (config.babel && "sourceMaps" in config.babel) { + sourceMaps = config.babel.sourceMaps; + } + + let options = { + annotation: providedAnnotation || `Babel: ${_parentName(this.parent)}`, + sourceMaps, + throwUnlessParallelizable, + filterExtensions: _getExtensions(config, this.parent), + plugins: [] + }; + + if (shouldCompileModules) { + options.moduleIds = true; + options.getModuleId = require("./lib/relative-module-paths").getRelativeModulePath; + } + + options.highlightCode = _shouldHighlightCode(this.parent); + options.babelrc = false; + options.configFile = false; + + return options; + }, + transpileTree(inputTree, _config) { + let config = _config || this._getAddonOptions(); let description = `000${++count}`.slice(-3); let postDebugTree = this._debugTree(inputTree, `${description}:input`); - - let options = this.buildBabelOptions(config); + let options = this._getDefaultBabelOptions(config); let output; - if (this._shouldDoNothing(options)) { + + const customAddonConfig = config['ember-cli-babel']; + const shouldUseBabelConfigFile = customAddonConfig && customAddonConfig.useBabelConfig; + + if (shouldUseBabelConfigFile) { + let babelConfig = babel.loadPartialConfig({ + root: this.parent.root, + rootMode: 'root', + envName: process.env.EMBER_ENV || process.env.BABEL_ENV || process.env.NODE_ENV || "development", + }); + + if (babelConfig.config === undefined) { + // should contain the file that we used for the config, + // if it is undefined then we didn't find any config and + // should error + + throw new Error( + "Missing babel config file in the project root. Please double check if the babel config file exists or turn off the `useBabelConfig` option in your ember-cli-build.js file." + ); + } + // If the babel config file is found, then pass the path into the options for the transpiler + // parse and leverage the same. + options = Object.assign({}, options, { configFile: babelConfig.config }); + } else { + options = Object.assign({}, options, this.buildBabelOptions(config)); + } + + if (!shouldUseBabelConfigFile && this._shouldDoNothing(options)) { output = postDebugTree; } else { let BabelTranspiler = require('broccoli-babel-transpiler'); let transpilationInput = postDebugTree; - if (this._shouldHandleTypeScript(config)) { + if (_shouldHandleTypeScript(config, this.parent)) { let Funnel = require('broccoli-funnel'); let inputWithoutDeclarations = new Funnel(transpilationInput, { exclude: ['**/*.d.ts'] }); transpilationInput = this._debugTree(inputWithoutDeclarations, `${description}:filtered-input`); } - output = new BabelTranspiler(transpilationInput, options); } @@ -69,7 +146,7 @@ module.exports = { setupPreprocessorRegistry(type, registry) { registry.add('js', { name: 'ember-cli-babel', - ext: this._getExtensions(this._getAddonOptions()), + ext: _getExtensions(this._getAddonOptions(), this.parent), toTree: (tree) => this.transpileTree(tree) }); }, @@ -98,36 +175,6 @@ module.exports = { } }, - _shouldIncludeHelpers(options) { - let appOptions = this._getAppOptions(); - let customOptions = appOptions['ember-cli-babel']; - - let shouldIncludeHelpers = false; - - if (!this._shouldCompileModules(options)) { - // we cannot use external helpers if we are not transpiling modules - return false; - } else if (customOptions && 'includeExternalHelpers' in customOptions) { - shouldIncludeHelpers = customOptions.includeExternalHelpers === true; - } else { - // Check the project to see if we should include helpers based on heuristics. - shouldIncludeHelpers = defaultShouldIncludeHelpers(this.project); - } - - let appEmberCliBabelPackage = this.project.addons.find(a => a.name === 'ember-cli-babel').pkg; - let appEmberCliBabelVersion = appEmberCliBabelPackage && appEmberCliBabelPackage.version; - - if (appEmberCliBabelVersion && semver.gte(appEmberCliBabelVersion, '7.3.0-beta.1')) { - return shouldIncludeHelpers; - } else if (shouldIncludeHelpers) { - this.project.ui.writeWarnLine( - `${this._parentName()} attempted to include external babel helpers to make your build size smaller, but your root app's ember-cli-babel version is not high enough. Please update ember-cli-babel to v7.3.0-beta.1 or later.` - ); - } - - return false; - }, - _getHelperVersion() { if (!APP_BABEL_RUNTIME_VERSION.has(this.project)) { let checker = new VersionChecker(this.project); @@ -154,7 +201,7 @@ module.exports = { // Helpers are a global config, so only the root application should bother // generating and including the file. let isRootBabel = this.parent === this.project; - let shouldIncludeHelpers = isRootBabel && this._shouldIncludeHelpers(this._getAppOptions()); + let shouldIncludeHelpers = isRootBabel && _shouldIncludeHelpers(this._getAppOptions(), this); if (!shouldIncludeHelpers) { return; } @@ -233,37 +280,6 @@ module.exports = { return (app && app.options) || {}; }, - _parentName() { - let parentName; - - if (this.parent) { - if (typeof this.parent.name === 'function') { - parentName = this.parent.name(); - } else { - parentName = this.parent.name; - } - } - - return parentName; - }, - - _getExtensions(config) { - let shouldHandleTypeScript = this._shouldHandleTypeScript(config); - let emberCLIBabelConfig = config['ember-cli-babel'] || {}; - return emberCLIBabelConfig.extensions || (shouldHandleTypeScript ? ['js', 'ts'] : ['js']); - }, - - _shouldHandleTypeScript(config) { - let emberCLIBabelConfig = config['ember-cli-babel'] || {}; - if (typeof emberCLIBabelConfig.enableTypeScriptTransform === 'boolean') { - return emberCLIBabelConfig.enableTypeScriptTransform; - } - let typeScriptAddon = this.parent.addons - && this.parent.addons.find(a => a.name === 'ember-cli-typescript'); - return typeof typeScriptAddon !== 'undefined' - && semver.gte(typeScriptAddon.pkg.version, '4.0.0-alpha.1'); - }, - _getTargets() { let targets = this.project && this.project.targets; @@ -290,18 +306,7 @@ module.exports = { * @method shouldCompileModules */ shouldCompileModules() { - return this._shouldCompileModules(this._getAddonOptions()); - }, - - // will use any provided configuration - _shouldCompileModules(options) { - let addonOptions = options['ember-cli-babel']; - - if (addonOptions && 'compileModules' in addonOptions) { - return addonOptions.compileModules; - } else { - return semver.gt(this.project.emberCLIVersion(), '2.12.0-alpha.1'); - } + return _shouldCompileModules(this._getAddonOptions(), this.project); }, // detect if running babel would do nothing... and do nothing instead diff --git a/lib/ember-plugins.js b/lib/ember-plugins.js new file mode 100644 index 00000000..275cfb83 --- /dev/null +++ b/lib/ember-plugins.js @@ -0,0 +1,183 @@ +const semver = require("semver"); +const resolvePackagePath = require("resolve-package-path"); + +function _getDebugMacroPlugins() { + const isProduction = process.env.EMBER_ENV === "production"; + const isDebug = !isProduction; + + return [ + [ + require.resolve("babel-plugin-debug-macros"), + { + flags: [ + { + source: "@glimmer/env", + flags: { DEBUG: isDebug, CI: !!process.env.CI }, + }, + ], + + externalizeHelpers: { + global: "Ember", + }, + + debugTools: { + isDebug, + source: "@ember/debug", + assertPredicateIndex: 1, + }, + }, + "@ember/debug stripping", + ], + [ + require.resolve("babel-plugin-debug-macros"), + { + // deprecated import path https://github.com/emberjs/ember.js/pull/17926#issuecomment-484987305 + externalizeHelpers: { + global: "Ember", + }, + + debugTools: { + isDebug, + source: "@ember/application/deprecations", + assertPredicateIndex: 1, + }, + }, + "@ember/application/deprecations stripping", + ], + ]; +} +function _emberVersionRequiresModulesAPIPolyfill() { + // once a version of Ember ships with the + // emberjs/rfcs#176 modules natively this will + // be updated to detect that and return false + return true; +} + +function _getEmberModulesAPIPolyfill(appRoot, config) { + if (config.disableEmberModulesAPIPolyfill) { + return; + } + + if (_emberVersionRequiresModulesAPIPolyfill()) { + const ignore = _getEmberModulesAPIIgnore(appRoot, config); + + return [ + [require.resolve("babel-plugin-ember-modules-api-polyfill"), { ignore }], + ]; + } +} + +function _shouldIgnoreEmberString(appRoot) { + return resolvePackagePath("@ember/string", appRoot) !== null; +} + +function _shouldIgnoreJQuery(appRoot) { + let packagePath = resolvePackagePath("@ember/jquery", appRoot); + if (packagePath === null) { + return true; + } + let pkg = require(packagePath); + return pkg && semver.gt(pkg.version, "0.6.0"); +} + +function _emberDataVersionRequiresPackagesPolyfill(appRoot) { + let packagePath = resolvePackagePath("ember-data", appRoot); + if (packagePath === null) { + return false; + } + let pkg = require(packagePath); + return pkg && semver.lt(pkg.version, "3.12.0-alpha.0"); +} + +function _getEmberModulesAPIIgnore(appRoot, config) { + const ignore = { + "@ember/debug": ["assert", "deprecate", "warn"], + "@ember/application/deprecations": ["deprecate"], + }; + + if (config.shouldIgnoreEmberString || _shouldIgnoreEmberString(appRoot)) { + ignore["@ember/string"] = [ + "fmt", + "loc", + "w", + "decamelize", + "dasherize", + "camelize", + "classify", + "underscore", + "capitalize", + "setStrings", + "getStrings", + "getString", + ]; + } + if (config.shouldIgnoreJQuery || _shouldIgnoreJQuery(appRoot)) { + ignore["jquery"] = ["default"]; + } + + return ignore; +} + +function _getEmberDataPackagesPolyfill(appRoot, config) { + if (config.emberDataVersionRequiresPackagesPolyfill) { + return [[require.resolve("babel-plugin-ember-data-packages-polyfill")]]; + } + return _emberDataVersionRequiresPackagesPolyfill(appRoot); +} + +function _getModuleResolutionPlugins(config) { + if (!config.disableModuleResolution) { + const resolvePath = require("../lib/relative-module-paths") + .resolveRelativeModulePath; + return [ + [require.resolve("babel-plugin-module-resolver"), { resolvePath }], + [ + require.resolve("@babel/plugin-transform-modules-amd"), + { noInterop: true }, + ], + ]; + } +} + +function _getProposalDecoratorsAndClassPlugins(config) { + if (!config.shouldIgnoreDecoratorAndClassPlugins) { + return [ + ["@babel/plugin-proposal-decorators", { legacy: true }], + ["@babel/plugin-proposal-class-properties"], + ]; + } +} + +/** + * This function allows returns all the required Ember specific babel plugins for the app to transpile correctly. + * As the first argument, you need to pass in the appRoot (which is usually the __dirname). + * As the second argument, which is optional, you can choose to turn the switch on and off of several plugins that this function returns. + * **List of supported configs** + * { + * disableModuleResolution: boolean, // determines if you want the module resolution enabled + * emberDataVersionRequiresPackagesPolyfill: boolean, // enable ember data's polyfill + * shouldIgnoreJQuery: boolean, // ignore jQuery + * shouldIgnoreEmberString: boolean, // ignore ember string + * shouldIgnoreDecoratorAndClassPlugins: boolean, // disable decorator plugins + * disableEmberModulesAPIPolyfill: boolean, // disable ember modules API polyfill + * } + * @param {string} appRoot - root directory of your project + * @param {object} config - config options to finetune the plugins + */ +module.exports = function (appRoot, config = {}) { + return [] + .concat( + _getProposalDecoratorsAndClassPlugins(config), + _getDebugMacroPlugins(appRoot), + _getEmberModulesAPIPolyfill(appRoot, config), + _getEmberDataPackagesPolyfill(appRoot, config), + _getModuleResolutionPlugins(config) + ) + .filter(Boolean); +}; + +module.exports.getDebugMacroPlugins = _getDebugMacroPlugins; +module.exports.getEmberModulesAPIPolyfill = _getEmberModulesAPIPolyfill; +module.exports.getEmberDataPackagesPolyfill = _getEmberDataPackagesPolyfill; +module.exports.getModuleResolutionPlugins = _getModuleResolutionPlugins; +module.exports.getProposalDecoratorsAndClassPlugins = _getProposalDecoratorsAndClassPlugins; diff --git a/lib/get-babel-options.js b/lib/get-babel-options.js index 7b812a1b..56b628f0 100644 --- a/lib/get-babel-options.js +++ b/lib/get-babel-options.js @@ -6,49 +6,29 @@ const { _shouldIncludeHelpers, _shouldHandleTypeScript, _shouldIncludeDecoratorPlugins, - _getExtensions, - _parentName, _getHelpersPlugin, _getDebugMacroPlugins, _getEmberModulesAPIPolyfill, _getEmberDataPackagesPolyfill, _getModulesPlugin, _getPresetEnv, - _shouldHighlightCode, } = require("./babel-options-util"); module.exports = function getBabelOptions(config, appInstance) { let { parent, project } = appInstance; let addonProvidedConfig = _getAddonProvidedConfig(config); - let shouldCompileModules = _shouldCompileModules(config, project); let shouldIncludeHelpers = _shouldIncludeHelpers(config, appInstance); let shouldHandleTypeScript = _shouldHandleTypeScript(config, parent); let shouldIncludeDecoratorPlugins = _shouldIncludeDecoratorPlugins(config); - let emberCLIBabelConfig = config["ember-cli-babel"]; + let emberCLIBabelConfig = config["ember-cli-babel"]; let shouldRunPresetEnv = true; - let providedAnnotation; - let throwUnlessParallelizable; if (emberCLIBabelConfig) { - providedAnnotation = emberCLIBabelConfig.annotation; shouldRunPresetEnv = !emberCLIBabelConfig.disablePresetEnv; - throwUnlessParallelizable = emberCLIBabelConfig.throwUnlessParallelizable; } - let sourceMaps = false; - if (config.babel && "sourceMaps" in config.babel) { - sourceMaps = config.babel.sourceMaps; - } - - let filterExtensions = _getExtensions(config, parent); - - let options = { - annotation: providedAnnotation || `Babel: ${_parentName(parent)}`, - sourceMaps, - throwUnlessParallelizable, - filterExtensions, - }; + let options = {}; let userPlugins = addonProvidedConfig.plugins; let userPostTransformPlugins = addonProvidedConfig.postTransformPlugins; @@ -74,7 +54,7 @@ module.exports = function getBabelOptions(config, appInstance) { _getDebugMacroPlugins(config), _getEmberModulesAPIPolyfill(config, parent, project), _getEmberDataPackagesPolyfill(config, parent), - shouldCompileModules && _getModulesPlugin(), + _shouldCompileModules(config, project) && _getModulesPlugin(), userPostTransformPlugins ).filter(Boolean); @@ -82,12 +62,5 @@ module.exports = function getBabelOptions(config, appInstance) { shouldRunPresetEnv && _getPresetEnv(addonProvidedConfig, project), ].filter(Boolean); - if (shouldCompileModules) { - options.moduleIds = true; - options.getModuleId = require("./relative-module-paths").getRelativeModulePath; - } - options.highlightCode = _shouldHighlightCode(parent); - options.babelrc = false; - return options; }; diff --git a/node-tests/addon-test.js b/node-tests/addon-test.js index 039433c3..d365a6e4 100644 --- a/node-tests/addon-test.js +++ b/node-tests/addon-test.js @@ -8,6 +8,7 @@ const CoreObject = require('core-object'); const AddonMixin = require('../index'); const CommonTags = require('common-tags'); const stripIndent = CommonTags.stripIndent; +const { ensureSymlinkSync } = require('fs-extra'); const FixturifyProject = require('fixturify-project'); const EmberProject = require('ember-cli/lib/models/project'); const MockCLI = require('ember-cli/tests/helpers/mock-cli'); @@ -19,6 +20,12 @@ const path = require('path'); const fs = require('fs'); const rimraf = require('rimraf'); const clone = require('clone'); +const { + _shouldHandleTypeScript, + _shouldIncludeHelpers, + _shouldCompileModules, + _getExtensions +} = require("../lib/babel-options-util"); function prepareAddon(addon) { addon.pkg.keywords.push('ember-addon'); @@ -801,7 +808,7 @@ describe('ember-cli-babel', function() { describe('_shouldHandleTypeScript', function() { it('should return false by default', function() { - expect(this.addon._shouldHandleTypeScript({})).to.be.false; + expect(_shouldHandleTypeScript({}, this.addon.parent)).to.be.false; }); it('should return true when ember-cli-typescript >= 4.0.0-alpha.1 is installed', function() { this.addon.parent.addons.push({ @@ -810,7 +817,7 @@ describe('ember-cli-babel', function() { version: '4.0.0-alpha.1', }, }); - expect(this.addon._shouldHandleTypeScript({})).to.be.true; + expect(_shouldHandleTypeScript({}, this.addon.parent)).to.be.true; }); it('should return false when ember-cli-typescript < 4.0.0-alpha.1 is installed', function() { this.addon.parent.addons.push({ @@ -819,13 +826,13 @@ describe('ember-cli-babel', function() { version: '3.0.0', }, }); - expect(this.addon._shouldHandleTypeScript({})).to.be.false; + expect(_shouldHandleTypeScript({}, this.addon.parent)).to.be.false; }); it('should return true when the TypeScript transform is manually enabled', function() { - expect(this.addon._shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: true } })).to.be.true; + expect(_shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: true } }, this.addon.parent)).to.be.true; }); it('should return false when the TypeScript transforms is manually disabled', function() { - expect(this.addon._shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: false } })).to.be.false; + expect(_shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: false } }, this.addon.parent)).to.be.false; }); it('should return false when the TypeScript transform is manually disabled, even when ember-cli-typescript >= 4.0.0-alpha.1 is installed', function() { this.addon.parent.addons.push({ @@ -834,7 +841,7 @@ describe('ember-cli-babel', function() { version: '4.0.0-alpha.1', }, }); - expect(this.addon._shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: false } })).to.be.false; + expect(_shouldHandleTypeScript({ 'ember-cli-babel': { enableTypeScriptTransform: false } }, this.addon.parent)).to.be.false; }); }); @@ -846,13 +853,13 @@ describe('ember-cli-babel', function() { }); it('should return false without any includeExternalHelpers option set', function() { - expect(this.addon._shouldIncludeHelpers({})).to.be.false; + expect(_shouldIncludeHelpers({}, this.addon)).to.be.false; }); it('should throw an error with ember-cli-babel.includeExternalHelpers = true in parent', function() { this.addon.parent.options = { 'ember-cli-babel': { includeExternalHelpers: true } }; - expect(() => this.addon._shouldIncludeHelpers({})).to.throw; + expect(() => _shouldIncludeHelpers({}, this.addon)).to.throw; }); it('should return true with ember-cli-babel.includeExternalHelpers = true in app and ember-cli-version is high enough', function() { @@ -860,7 +867,7 @@ describe('ember-cli-babel', function() { this.addon.app.options = { 'ember-cli-babel': { includeExternalHelpers: true } }; - expect(this.addon._shouldIncludeHelpers({})).to.be.true; + expect(_shouldIncludeHelpers({}, this.addon)).to.be.true; }); it('should return false when compileModules is false', function() { @@ -869,9 +876,9 @@ describe('ember-cli-babel', function() { this.addon.app.options = { 'ember-cli-babel': { includeExternalHelpers: true } }; // precond - expect(this.addon._shouldIncludeHelpers({})).to.be.true; + expect(_shouldIncludeHelpers({}, this.addon)).to.be.true; - expect(this.addon._shouldIncludeHelpers({ 'ember-cli-babel': { compileModules: false } })).to.be.false; + expect(_shouldIncludeHelpers({ 'ember-cli-babel': { compileModules: false } }, this.addon)).to.be.false; }); it('should return false with ember-cli-babel.includeExternalHelpers = true in app and write warn line if ember-cli-version is not high enough', function() { @@ -884,13 +891,13 @@ describe('ember-cli-babel', function() { this.addon.app.options = { 'ember-cli-babel': { includeExternalHelpers: true } }; - expect(this.addon._shouldIncludeHelpers({})).to.be.false; + expect(_shouldIncludeHelpers({}, this.addon)).to.be.false; }); it('should return false with ember-cli-babel.includeExternalHelpers = false in host', function() { this.addon.app.options = { 'ember-cli-babel': { includeExternalHelpers: false } }; - expect(this.addon._shouldIncludeHelpers({})).to.be.false; + expect(_shouldIncludeHelpers({}, this.addon)).to.be.false; }); describe('autodetection', function() { @@ -902,7 +909,7 @@ describe('ember-cli-babel', function() { } }); - expect(this.addon._shouldIncludeHelpers({})).to.be.true; + expect(_shouldIncludeHelpers({}, this.addon)).to.be.true; }); it('should return false if @ember-decorators/babel-transforms exists and write warn line if ember-cli-version is not high enough', function() { @@ -919,7 +926,7 @@ describe('ember-cli-babel', function() { } }); - expect(this.addon._shouldIncludeHelpers({})).to.be.false; + expect(_shouldIncludeHelpers({}, this.addon)).to.be.false; }); }) }); @@ -958,15 +965,15 @@ describe('ember-cli-babel', function() { describe('with ember-cli-babel.compileModules = true', function() { it('should return true', function() { - expect(this.addon._shouldCompileModules({ + expect(_shouldCompileModules({ 'ember-cli-babel': { compileModules: true } - })).to.eql(true); + }, this.addon.project)).to.eql(true); }); it('should not print deprecation messages', function() { - this.addon._shouldCompileModules({ + _shouldCompileModules({ 'ember-cli-babel': { compileModules: true } - }); + }, this.addon.project); let deprecationMessages = this.ui.output.split('\n').filter(function(line) { return line.indexOf('Putting the "compileModules" option in "babel" is deprecated') !== -1; @@ -1004,18 +1011,17 @@ describe('ember-cli-babel', function() { describe('_getExtensions', function() { it('defaults to js only', function() { - expect(this.addon._getExtensions({})).to.have.members(['js']); + expect(_getExtensions({}, this.addon.parent)).to.have.members(['js']); }); it('adds ts automatically', function() { this.addon._shouldHandleTypeScript = function() { return true; } - expect(this.addon._getExtensions({})).to.have.members(['js', 'ts']); + expect(_getExtensions({ 'ember-cli-babel': { enableTypeScriptTransform: true }}, this.addon.parent)).to.have.members(['js', 'ts']); }); it('respects user-configured extensions', function() { - expect(this.addon._getExtensions({ 'ember-cli-babel': { extensions: ['coffee'] } })).to.have.members(['coffee']); + expect(_getExtensions({ 'ember-cli-babel': { extensions: ['coffee'] } }, this.addon.parent)).to.have.members(['coffee']); }); it('respects user-configured extensions even when adding TS plugin', function() { - this.addon._shouldHandleTypeScript = function() { return true; } - expect(this.addon._getExtensions({ 'ember-cli-babel': { extensions: ['coffee'] } })).to.have.members(['coffee']); + expect(_getExtensions({ 'ember-cli-babel': { enableTypeScriptTransform: true, extensions: ['coffee'] } }, this.addon.parent)).to.have.members(['coffee']); }); }); @@ -1025,7 +1031,7 @@ describe('ember-cli-babel', function() { it('disables reading `.babelrc`', function() { let options = {}; - let result = this.addon.buildBabelOptions(options); + let result = this.addon._getDefaultBabelOptions(options); expect(result.babelrc).to.be.false; }); @@ -1035,7 +1041,7 @@ describe('ember-cli-babel', function() { name: 'derpy-herpy', dependencies() { return {}; }, }); - let result = this.addon.buildBabelOptions(); + let result = this.addon._getDefaultBabelOptions(); expect(result.annotation).to.include('derpy-herpy'); }); @@ -1044,7 +1050,7 @@ describe('ember-cli-babel', function() { name: 'derpy-herpy', dependencies() { return {}; }, }); - let result = this.addon.buildBabelOptions(); + let result = this.addon._getDefaultBabelOptions(); expect(result.annotation).to.include('derpy-herpy'); }); @@ -1055,7 +1061,7 @@ describe('ember-cli-babel', function() { } }; - let result = this.addon.buildBabelOptions(options); + let result = this.addon._getDefaultBabelOptions(options); expect(result.annotation).to.equal('Hello World!'); }); @@ -1066,14 +1072,14 @@ describe('ember-cli-babel', function() { } }; - let result = this.addon.buildBabelOptions(options); + let result = this.addon._getDefaultBabelOptions(options); expect(result.sourceMaps).to.equal('inline'); }); it('disables reading `.babelrc`', function() { let options = {}; - let result = this.addon.buildBabelOptions(options); + let result = this.addon._getDefaultBabelOptions(options); expect(result.babelrc).to.be.false; }); @@ -1171,22 +1177,28 @@ describe('ember-cli-babel', function() { }); it('includes resolveModuleSource if compiling modules', function() { - this.addon._shouldCompileModules = () => true; let expectedPlugin = require.resolve('babel-plugin-module-resolver'); - let result = this.addon.buildBabelOptions(); + let result = this.addon.buildBabelOptions({ + 'ember-cli-babel': { + compileModules: true, + } + }); let found = result.plugins.find(plugin => plugin[0] === expectedPlugin); expect(typeof found[1].resolvePath).to.equal('function'); }); it('does not include resolveModuleSource when not compiling modules', function() { - this.addon._shouldCompileModules = () => false; let expectedPlugin = require('babel-plugin-module-resolver').default; - let result = this.addon.buildBabelOptions(); + let result = this.addon.buildBabelOptions({ + 'ember-cli-babel': { + compileModules: false, + } + }); let found = result.plugins.find(plugin => plugin[0] === expectedPlugin); expect(found).to.equal(undefined); @@ -1574,6 +1586,165 @@ describe('EmberData Packages Polyfill - ember-cli-babel for ember-data', functio }); }); +describe('babel config file', function() { + this.timeout(0); + + let input; + let output; + let subject; + let setupForVersion; + let project; + let unlink; + + beforeEach(function() { + let self = this; + setupForVersion = co.wrap(function*(plugins) { + let fixturifyProject = new FixturifyProject('whatever', '0.0.1'); + + fixturifyProject.addDependency('ember-cli-babel', 'babel/ember-cli-babel#master'); + fixturifyProject.addDependency('random-addon', '0.0.1', addon => { + return prepareAddon(addon); + }); + let pkg = JSON.parse(fixturifyProject.toJSON('package.json')); + fixturifyProject.files['babel.config.js'] = + `module.exports = function (api) { + api.cache(true); + return { + plugins: [ + ${plugins} + ], + }; + }; + `; + const packageDir = path.dirname(require.resolve(path.join("@babel/plugin-transform-modules-amd", 'package.json'))); + // symlink the "@babel/plugin-transform-modules-amd" dependency into the project + // TODO: Move this function out so that it can be used by other tests in the future. + const writeSync = function () { + let stack = []; + fixturifyProject.writeSync(); + ensureSymlinkSync( + packageDir, + path.join( + fixturifyProject.root, + fixturifyProject.name, + "node_modules", + "@babel/plugin-transform-modules-amd" + ), + "dir" + ); + for (let dep of fixturifyProject.dependencies()) { + stack.push({ + project: dep, + root: path.join( + fixturifyProject.root, + fixturifyProject.name, + "node_modules" + ), + }); + } + }; + + writeSync(fixturifyProject) + + let linkPath = path.join(fixturifyProject.root, '/whatever/node_modules/ember-cli-babel'); + let addonPath = path.resolve(__dirname, '../'); + rimraf.sync(linkPath); + fs.symlinkSync(addonPath, linkPath, 'junction'); + unlink = () => { + fs.unlinkSync(linkPath); + }; + + let cli = new MockCLI(); + let root = path.join(fixturifyProject.root, 'whatever'); + project = new EmberProject(root, pkg, cli.ui, cli); + project.initializeAddons(); + + self.addon = project.addons.find(a => { return a.name === 'ember-cli-babel'; }); + self.addon.parent.options = { + "ember-cli-babel": { useBabelConfig: true }, + }; + + input = yield createTempDir(); + }); + }); + + afterEach(co.wrap(function*() { + unlink(); + + if (input) { + yield input.dispose(); + } + + if (output) { + yield output.dispose(); + } + + // shut down workers after the tests are run so that mocha doesn't hang + yield terminateWorkerPool(); + })); + + it("should transpile to amd modules based on babel config", co.wrap(function* () { + yield setupForVersion(`[ + require.resolve("@babel/plugin-transform-modules-amd"), + { noInterop: true }, + ]`); + input.write({ + "foo.js": `export default {};`, + }); + + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + + yield output.build(); + + expect(output.read()).to.deep.equal({ + "foo.js": `define(\"foo\", [\"exports\"], function (_exports) {\n \"use strict\";\n\n Object.defineProperty(_exports, \"__esModule\", {\n value: true\n });\n _exports.default = void 0;\n var _default = {};\n _exports.default = _default;\n});`, + }); + })); + + it("should not transpile to amd modules based on babel config", co.wrap(function* () { + yield setupForVersion(''); + input.write({ + "foo.js": `export default {};`, + }); + + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + + yield output.build(); + + expect( + output.read() + ).to.deep.equal({ + "foo.js": "export default {};" + }); + })); + + it("should not use babel config (even if present) if the 'useBabelConfig' option is set to false", co.wrap(function* () { + yield setupForVersion(`[ + "@babel/plugin-transform-modules-amd", + { noInterop: true }, + ]`); + + this.addon.parent.options = { + "ember-cli-babel": { useBabelConfig: false }, + }; + input.write({ + "foo.js": `export default {};`, + }); + + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + + yield output.build(); + + expect(output.read()).to.deep.equal({ + "foo.js": + 'define("foo", ["exports"], function (_exports) {\n "use strict";\n\n Object.defineProperty(_exports, "__esModule", {\n value: true\n });\n _exports.default = void 0;\n var _default = {};\n _exports.default = _default;\n});', + }); + })); +}); + function leftPad(str, num) { while (num-- > 0) { str = ` ${str}`; diff --git a/package.json b/package.json index a65763de..ed9edbb2 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "ember-cli-version-checker": "^4.1.0", "ensure-posix-path": "^1.0.2", "fixturify-project": "^1.10.0", + "resolve-package-path": "^3.1.0", "rimraf": "^3.0.1", "semver": "^5.5.0" }, diff --git a/yarn.lock b/yarn.lock index 1f6901ba..0fe555de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5572,6 +5572,13 @@ is-ci@2.0.0, is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-core-module@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -8236,6 +8243,14 @@ resolve-package-path@^2.0.0: path-root "^0.1.1" resolve "^1.13.1" +resolve-package-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-3.1.0.tgz#35faaa5d54a9c7dd481eb7c4b2a44410c9c763d8" + integrity sha512-2oC2EjWbMJwvSN6Z7DbDfJMnD8MYEouaLn5eIX0j8XwPsYCVIyY9bbnX88YHVkbr8XHqvZrYbxaLPibfTYKZMA== + dependencies: + path-root "^0.1.1" + resolve "^1.17.0" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8255,6 +8270,14 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3. dependencies: path-parse "^1.0.6" +resolve@^1.17.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + responselike@1.0.2, responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"