Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ module.exports = {
'blueprints/*/index.js',
'config/**/*.js',
'tests/dummy/config/**/*.js',
'lib/**/*.js'
'lib/**/*.js',
'scripts/**/*.js'
],
excludedFiles: [
'addon/**',
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ jobs:
- name: test
run: pnpm test:node

node-tests-babel-8:
name: "Node: Babel 8"
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 20
cache: pnpm
- name: pin dependencies to Babel 8
run: node scripts/use-babel-8.js
- name: install dependencies
run: pnpm install --no-frozen-lockfile
- name: test
run: pnpm test:node

acceptance-tests:
name: "Acceptance: Node ${{ matrix.node }} - ${{ matrix.os }}"
runs-on: "${{matrix.os}}-latest"
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,22 @@ If you want to use the existing babel config from your project instead of the au

*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.*

*Note for Babel 8:* Babel 8 removed the root-level `moduleIds` and `getModuleId` options, which is how ember-cli-babel used to name your AMD modules. On Babel 8 they have to be passed to `@babel/plugin-transform-modules-amd` itself. `buildEmberPlugins` (below) already does this for you; if you configure the AMD transform by hand, name your modules like this:

```js
[
require.resolve("@babel/plugin-transform-modules-amd"),
{
noInterop: true,
moduleIds: true,
getModuleId: require("ember-cli-babel/lib/relative-module-paths")
.getRelativeModulePath,
},
],
```

Without it your modules are emitted as anonymous `define([...])` calls and the Ember loader will not find them.

Example usage:

```js
Expand Down Expand Up @@ -389,6 +405,8 @@ module.exports = function (api) {
],
plugins: [
// if you want external helpers
// On Babel 8, drop `regenerator` and `useESModules`: both options were
// removed, and @babel/runtime now resolves ESM helpers via its `exports`.
[
require.resolve("@babel/plugin-transform-runtime"),
{
Expand Down
23 changes: 11 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const {
_getExtensions,
_parentName,
_shouldHighlightCode,
_getHelpersPlugin,
_babelCoreMajorVersion,
} = require("./lib/babel-options-util");

const VersionChecker = require('ember-cli-version-checker');
Expand Down Expand Up @@ -64,7 +66,9 @@ module.exports = {
if (shouldUseBabelConfigFile) {
const babel = require('@babel/core');

let babelConfig = babel.loadPartialConfig({
// Babel 8's `loadPartialConfig` is callback-based; the explicit sync
// variant has been available since Babel 7.8.
let babelConfig = babel.loadPartialConfigSync({
root: this.parent.root,
rootMode: 'root',
envName: process.env.EMBER_ENV || process.env.BABEL_ENV || process.env.NODE_ENV || "development",
Expand Down Expand Up @@ -138,7 +142,11 @@ module.exports = {
plugins: [],
};

if (shouldCompileModules) {
// Babel 8 rejects these as unknown root options, so there the plugin-level
// values set by `_getModulesPlugin` are the only ones that apply. Babel 7
// still honors the root level, and `useBabelConfig` users depend on it
// because their own config supplies the module transform.
if (shouldCompileModules && _babelCoreMajorVersion() < 8) {
options.moduleIds = true;
options.getModuleId = require("./lib/relative-module-paths").getRelativeModulePath;
}
Expand Down Expand Up @@ -230,16 +238,7 @@ module.exports = {
},

_getHelpersPlugin() {
return [
[
require.resolve('@babel/plugin-transform-runtime'),
{
version: this._getHelperVersion(),
regenerator: false,
useESModules: true
}
]
]
return _getHelpersPlugin(this.project);
},

treeForAddon() {
Expand Down
48 changes: 34 additions & 14 deletions lib/babel-options-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ const defaultShouldIncludeHelpers = require("./default-should-include-helpers");
/**
* This util contains private functions required for generating babel options.
*/

// Resolved rather than assumed, since `@babel/core` is a peer dependency and
// several plugin options differ between Babel 7 and 8.
function _babelCoreMajorVersion() {
return semver.major(require("@babel/core/package.json").version);
}

function _getPresetEnv(config, project) {
let options = config.options;

Expand All @@ -31,14 +38,23 @@ function _getPresetEnv(config, project) {
}

function _getModulesPlugin() {
const resolvePath = require("./relative-module-paths")
.resolveRelativeModulePath;
const {
resolveRelativeModulePath: resolvePath,
getRelativeModulePath: getModuleId,
} = require("./relative-module-paths");

return [
[require.resolve("babel-plugin-module-resolver"), { resolvePath }],
[
require.resolve("@babel/plugin-transform-modules-amd"),
{ noInterop: true },
{
noInterop: true,
// Babel 8 dropped the root-level `moduleIds`/`getModuleId` options; the
// module transform reads them from its own options instead. Babel 7
// already prefers the plugin-level values, so this works on both.
moduleIds: true,
getModuleId,
},
],
];
}
Expand Down Expand Up @@ -274,16 +290,17 @@ function _getEmberDataPackagesPolyfill(config, parent) {
}
}
function _getHelpersPlugin(project) {
return [
[
require.resolve("@babel/plugin-transform-runtime"),
{
version: _getHelperVersion(project),
regenerator: false,
useESModules: true,
},
],
];
const options = { version: _getHelperVersion(project) };

if (_babelCoreMajorVersion() < 8) {
// Both options were removed in Babel 8: generators no longer depend on a
// `regeneratorRuntime` global, and @babel/runtime exposes its ESM helpers
// through package.json#exports.
options.regenerator = false;
options.useESModules = true;
}

return [[require.resolve("@babel/plugin-transform-runtime"), options]];
}
function _getHelperVersion(project) {
if (!APP_BABEL_RUNTIME_VERSION.has(project)) {
Expand Down Expand Up @@ -345,7 +362,9 @@ function _addDecoratorPlugins(plugins, options, config, parent, project) {
} else {
addPlugin(
plugins,
[require.resolve("@babel/plugin-proposal-decorators"), { legacy: true }],
// `legacy: true` was removed in Babel 8; `version: "legacy"` is the
// equivalent and is understood by Babel 7 as well.
[require.resolve("@babel/plugin-proposal-decorators"), { version: "legacy" }],
_buildClassFeaturePluginConstraints(
{
before: ["@babel/plugin-transform-class-properties"],
Expand Down Expand Up @@ -625,6 +644,7 @@ function _shouldIncludeHelpers(options, appInstance) {
}

module.exports = {
_babelCoreMajorVersion,
_addDecoratorPlugins,
_addTypeScriptPlugin,
_getAddonProvidedConfig,
Expand Down
18 changes: 14 additions & 4 deletions lib/ember-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,23 @@ function _getEmberDataPackagesPolyfill(appRoot, config) {

function _getModuleResolutionPlugins(config) {
if (!config.disableModuleResolution) {
const resolvePath = require("../lib/relative-module-paths")
.resolveRelativeModulePath;
const {
resolveRelativeModulePath: resolvePath,
getRelativeModulePath: getModuleId,
} = require("../lib/relative-module-paths");

return [
[require.resolve("babel-plugin-module-resolver"), { resolvePath }],
[
require.resolve("@babel/plugin-transform-modules-amd"),
{ noInterop: true },
{
noInterop: true,
// Babel 8 only reads `moduleIds`/`getModuleId` from the module
// transform's own options, not from the root config. Babel 7 prefers
// the plugin-level values too, so this is correct on both.
moduleIds: true,
getModuleId,
},
],
];
}
Expand All @@ -173,7 +183,7 @@ function _getProposalDecoratorsAndClassPlugins(config) {
* we need to compile it away.
*/
["@babel/plugin-transform-class-static-block"],
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-decorators", { version: "legacy" }],
["@babel/plugin-transform-class-properties"],
];
}
Expand Down
49 changes: 44 additions & 5 deletions node-tests/addon-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const {
_shouldHandleTypeScript,
_shouldIncludeHelpers,
_shouldCompileModules,
_getExtensions
_getExtensions,
_babelCoreMajorVersion
} = require("../lib/babel-options-util");

const { codeEquality } = require("code-equality-assertions/chai");
Expand Down Expand Up @@ -153,6 +154,18 @@ describe('ember-cli-babel', function() {
const result = output.read();

expect(Object.keys(result)).to.deep.equal(['foo.js']);

// Babel 8 emits the same inline helpers in a different order, so the
// exact-output snapshot below only holds on Babel 7. What this test is
// really about is that the static block is compiled away, which is
// asserted for both.
expect(result['foo.js']).to.include('_Second.bar = 1');
expect(result['foo.js']).to.not.include('static {');

if (_babelCoreMajorVersion() >= 8) {
return;
}

expect(result['foo.js']).to.equalCode(`function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
define("foo", [], function () {
"use strict";
Expand Down Expand Up @@ -1698,13 +1711,32 @@ define("foo", [], function () {
describe('buildBabelOptions', function() {
this.timeout(0);

// Module ids always live on the AMD transform's own options. Babel 7 also
// honors them at the root, but Babel 8 rejects unknown root options, so we
// only emit them there on 7.
function expectModuleIdsConfigured(result) {
let amdPlugin = result.plugins.find(
plugin => Array.isArray(plugin) && /plugin-transform-modules-amd/.test(plugin[0])
);

expect(amdPlugin, 'AMD transform is present').to.exist;
expect(amdPlugin[1].moduleIds).to.be.true;
expect(amdPlugin[1].getModuleId).to.be.a('function');

if (_babelCoreMajorVersion() < 8) {
expect(result.moduleIds).to.be.true;
} else {
expect('moduleIds' in result).to.be.false;
}
}

it('returns broccoli-babel-transpiler options by default', function() {
this.addon.parent = { ...this.addon.parent, name: 'foo' };

let result = this.addon.buildBabelOptions();

expect(result.annotation).to.equal('Babel: foo');
expect(result.moduleIds).to.be.true;
expectModuleIdsConfigured(result);
expect(result.babelrc).to.be.false;
expect(result.configFile).to.be.false;
});
Expand All @@ -1715,7 +1747,7 @@ define("foo", [], function () {
let result = this.addon.buildBabelOptions('broccoli');

expect(result.annotation).to.equal('Babel: foo');
expect(result.moduleIds).to.be.true;
expectModuleIdsConfigured(result);
expect(result.babelrc).to.be.false;
expect(result.configFile).to.be.false;
});
Expand All @@ -1728,7 +1760,7 @@ define("foo", [], function () {
});

expect(result.annotation).to.equal('hello!!!');
expect(result.moduleIds).to.be.true;
expectModuleIdsConfigured(result);
expect(result.babelrc).to.be.false;
expect(result.configFile).to.be.false;
});
Expand Down Expand Up @@ -2320,9 +2352,16 @@ describe('babel config file', function() {
}));

it("should transpile to amd modules based on babel config", co.wrap(function* () {
// On Babel 7 ember-cli-babel supplies `moduleIds`/`getModuleId` at the root
// of the Babel config. Babel 8 removed those root options, so a project
// bringing its own babel config has to pass them to the AMD transform.
let moduleIdOptions = _babelCoreMajorVersion() < 8 ? '' : `
moduleIds: true,
getModuleId: require("ember-cli-babel/lib/relative-module-paths").getRelativeModulePath,`;

yield setupForVersion(`[
require.resolve("@babel/plugin-transform-modules-amd"),
{ noInterop: true },
{ noInterop: true,${moduleIdOptions} },
]`);
input.write({
"foo.js": `export default {};`,
Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@
"test:node:debug": "mocha debug node-tests"
},
"dependencies": {
"@babel/helper-compilation-targets": "^7.20.7",
"@babel/plugin-proposal-decorators": "^7.20.13",
"@babel/plugin-transform-class-properties": "^7.16.5",
"@babel/plugin-transform-class-static-block": "^7.22.11",
"@babel/plugin-transform-modules-amd": "^7.20.11",
"@babel/plugin-transform-private-methods": "^7.16.5",
"@babel/plugin-transform-private-property-in-object": "^7.20.5",
"@babel/plugin-transform-runtime": "^7.13.9",
"@babel/plugin-transform-typescript": "^7.20.13",
"@babel/preset-env": "^7.20.2",
"@babel/helper-compilation-targets": "^7.20.7 || ^8.0.0",
"@babel/plugin-proposal-decorators": "^7.20.13 || ^8.0.0",
"@babel/plugin-transform-class-properties": "^7.16.5 || ^8.0.0",
"@babel/plugin-transform-class-static-block": "^7.22.11 || ^8.0.0",
"@babel/plugin-transform-modules-amd": "^7.20.11 || ^8.0.0",
"@babel/plugin-transform-private-methods": "^7.16.5 || ^8.0.0",
"@babel/plugin-transform-private-property-in-object": "^7.20.5 || ^8.0.0",
"@babel/plugin-transform-runtime": "^7.13.9 || ^8.0.0",
"@babel/plugin-transform-typescript": "^7.20.13 || ^8.0.0",
"@babel/preset-env": "^7.20.2 || ^8.0.0",
"@babel/runtime": "7.12.18",
"amd-name-resolver": "^1.3.1",
"babel-plugin-debug-macros": "^0.3.4",
Expand Down Expand Up @@ -108,7 +108,7 @@
"webpack": "^5.88.2"
},
"peerDependencies": {
"@babel/core": "^7.12.0"
"@babel/core": "^7.12.0 || ^8.0.0"
},
"packageManager": "pnpm@8.15.9",
"engines": {
Expand Down
Loading
Loading