UNPKG

18.2 kBJavaScriptView Raw
1const VersionChecker = require("ember-cli-version-checker");
2const resolvePackagePath = require("resolve-package-path");
3const clone = require("clone");
4const semver = require("semver");
5
6const APP_BABEL_RUNTIME_VERSION = new WeakMap();
7
8const findApp = require("./find-app");
9const defaultShouldIncludeHelpers = require("./default-should-include-helpers");
10
11/**
12 * This util contains private functions required for generating babel options.
13 */
14function _getPresetEnv(config, project) {
15 let options = config.options;
16
17 let targets = project && project.targets;
18 let presetOptions = Object.assign({}, options, {
19 modules: false,
20 targets,
21 });
22
23 // delete any properties added to `options.babel` that
24 // are invalid for @babel/preset-env
25 delete presetOptions.sourceMaps;
26 delete presetOptions.generatorOpts;
27 delete presetOptions.plugins;
28 delete presetOptions.postTransformPlugins;
29
30 return [require.resolve("@babel/preset-env"), presetOptions];
31}
32
33function _getModulesPlugin() {
34 const resolvePath = require("./relative-module-paths")
35 .resolveRelativeModulePath;
36
37 return [
38 [require.resolve("babel-plugin-module-resolver"), { resolvePath }],
39 [
40 require.resolve("@babel/plugin-transform-modules-amd"),
41 { noInterop: true },
42 ],
43 ];
44}
45
46function _shouldHighlightCode(parent) {
47 let checker = new VersionChecker(parent).for("broccoli-middleware", "npm");
48 return checker.gte("2.1.0");
49}
50
51function _getDebugMacroPlugins(config, project) {
52 let addonOptions = config["ember-cli-babel"] || {};
53
54 let disableDebugTooling = addonOptions.disableDebugTooling;
55 if (disableDebugTooling) {
56 return;
57 }
58
59 const isProduction = process.env.EMBER_ENV === "production";
60 const isDebug = !isProduction;
61
62 const emberDebugOptions = {
63 flags: [
64 {
65 source: "@glimmer/env",
66 flags: { DEBUG: isDebug, CI: !!process.env.CI },
67 },
68 ],
69
70 debugTools: {
71 isDebug,
72 source: "@ember/debug",
73 assertPredicateIndex: 1,
74 },
75 };
76
77 const emberApplicationDeprecationsOptions = {
78 // deprecated import path https://github.com/emberjs/ember.js/pull/17926#issuecomment-484987305
79 externalizeHelpers: {
80 global: "Ember",
81 },
82
83 debugTools: {
84 isDebug,
85 source: "@ember/application/deprecations",
86 assertPredicateIndex: 1,
87 },
88 };
89
90 let useModulesVersion;
91
92 if (_emberVersionRequiresModulesAPIPolyfill(project)) {
93 useModulesVersion = false;
94 } else if (addonOptions.compileModules === false) {
95 // we have to use the global form when not compiling modules, because it is often used
96 // in the context of an `app.import` where there is no wrapped in an AMD module
97 //
98 // However, you can opt out of this behavior by explicitly specifying `disableDebugTooling`
99 useModulesVersion = disableDebugTooling === false;
100 } else {
101 useModulesVersion = true;
102 }
103
104 if (useModulesVersion) {
105 emberDebugOptions.externalizeHelpers = {
106 module: "@ember/debug",
107 };
108 emberApplicationDeprecationsOptions.externalizeHelpers = {
109 module: "@ember/application/deprecations",
110 };
111 } else {
112 emberDebugOptions.externalizeHelpers = {
113 global: "Ember",
114 };
115 emberApplicationDeprecationsOptions.externalizeHelpers = {
116 global: "Ember",
117 };
118 }
119
120 return [
121 [
122 require.resolve("babel-plugin-debug-macros"),
123 emberDebugOptions,
124 "@ember/debug stripping",
125 ],
126 [
127 require.resolve("babel-plugin-debug-macros"),
128 emberApplicationDeprecationsOptions,
129 "@ember/application/deprecations stripping",
130 ],
131 ];
132}
133
134function _emberVersionRequiresModulesAPIPolyfill(project) {
135 let checker = new VersionChecker(project).for("ember-source", "npm");
136 if (!checker.exists()) {
137 return true;
138 }
139 let result = checker.lt("3.27.0-alpha.1");
140
141 return result;
142}
143
144function _emberDataVersionRequiresPackagesPolyfill(project) {
145 let checker = new VersionChecker(project);
146 let dep = checker.for("ember-data");
147 let hasEmberData = dep.exists();
148
149 if (hasEmberData) {
150 if (!dep.version) {
151 throw new Error("EmberData missing version");
152 }
153 return semver.lt(dep.version, "3.12.0-alpha.0");
154 }
155 return false;
156}
157function _getEmberModulesAPIPolyfill(config, parent, project) {
158 let addonOptions = config["ember-cli-babel"] || {};
159
160 if (addonOptions.disableEmberModulesAPIPolyfill) {
161 return;
162 }
163
164 let useModulesVersion;
165
166 if (_emberVersionRequiresModulesAPIPolyfill(project)) {
167 useModulesVersion = false;
168 } else if (addonOptions.compileModules === false) {
169 // we have to use the global form when not compiling modules, because it is often used
170 // in the context of an `app.import` where there is no wrapped in an AMD module
171 //
172 // However, you can opt out of this behavior by explicitly specifying `disableEmberModulesAPIPolyfill`
173 useModulesVersion = addonOptions.disableEmberModulesAPIPolyfill === false;
174 } else {
175 useModulesVersion = true;
176 }
177
178 // we have to use the global form when not compiling modules, because it is often used
179 // in the context of an `app.import` where there is no wrapped in an AMD module
180 if (!useModulesVersion) {
181 const ignore = _getEmberModulesAPIIgnore(parent, project);
182
183 return [
184 [require.resolve("babel-plugin-ember-modules-api-polyfill"), { ignore }],
185 ];
186 }
187}
188
189function _getEmberModulesAPIIgnore(parent, project) {
190 const ignore = {
191 "@ember/debug": ["assert", "deprecate", "warn"],
192 "@ember/application/deprecations": ["deprecate"],
193 };
194
195 if (_shouldIgnoreEmberString(parent, project)) {
196 ignore["@ember/string"] = [
197 "fmt",
198 "loc",
199 "w",
200 "decamelize",
201 "dasherize",
202 "camelize",
203 "classify",
204 "underscore",
205 "capitalize",
206 "setStrings",
207 "getStrings",
208 "getString",
209 ];
210 }
211
212 if (_shouldIgnoreJQuery(parent, project)) {
213 ignore["jquery"] = ["default"];
214 }
215
216 return ignore;
217}
218
219function _isProjectName(dependency, project) {
220 return project.name && project.name() === dependency;
221}
222
223function _isTransitiveDependency(dependency, parent, project) {
224 return (
225 !(dependency in parent.dependencies()) &&
226 !(dependency in project.dependencies())
227 );
228}
229
230function _shouldIgnoreEmberString(parent, project) {
231 let packageName = "@ember/string";
232 if (_isProjectName(packageName, project)) {
233 return true;
234 }
235 if (_isTransitiveDependency(packageName, parent, project)) {
236 return false;
237 }
238
239 let checker = new VersionChecker(parent).for(packageName, "npm");
240
241 return checker.exists();
242}
243
244function _shouldIgnoreJQuery(parent, project) {
245 let packageName = "@ember/jquery";
246 if (_isProjectName(packageName, project)) {
247 return true;
248 }
249 if (_isTransitiveDependency(packageName, parent, project)) {
250 return false;
251 }
252
253 let checker = new VersionChecker(parent).for(packageName, "npm");
254
255 return checker.gte("0.6.0");
256}
257
258function _getEmberDataPackagesPolyfill(config, parent) {
259 let addonOptions = config["ember-cli-babel"] || {};
260
261 if (addonOptions.disableEmberDataPackagesPolyfill) {
262 return;
263 }
264 // Don't convert ember-data itself or any @ember-data packages!
265 if (
266 typeof parent.name === "string" &&
267 (parent.name === "ember-data" || parent.name.startsWith("@ember-data/"))
268 ) {
269 return;
270 }
271
272 if (_emberDataVersionRequiresPackagesPolyfill(parent)) {
273 return [[require.resolve("babel-plugin-ember-data-packages-polyfill")]];
274 }
275}
276function _getHelpersPlugin(project) {
277 return [
278 [
279 require.resolve("@babel/plugin-transform-runtime"),
280 {
281 version: _getHelperVersion(project),
282 regenerator: false,
283 useESModules: true,
284 },
285 ],
286 ];
287}
288function _getHelperVersion(project) {
289 if (!APP_BABEL_RUNTIME_VERSION.has(project)) {
290 let checker = new VersionChecker(project);
291 APP_BABEL_RUNTIME_VERSION.set(
292 project,
293 checker.for("@babel/runtime", "npm").version
294 );
295 }
296
297 return APP_BABEL_RUNTIME_VERSION.get(project);
298}
299
300function _buildClassFeaturePluginConstraints(constraints, config, parent, project) {
301 // With versions of ember-cli-typescript < 4.0, class feature plugins like
302 // @babel/plugin-proposal-class-properties were run before the TS transform.
303 if (!_shouldHandleTypeScript(config, parent, project)) {
304 constraints.before = constraints.before || [];
305 constraints.before.push("@babel/plugin-transform-typescript");
306 }
307
308 return constraints;
309}
310
311function _addDecoratorPlugins(plugins, options, config, parent, project) {
312 const { hasPlugin, addPlugin } = require("ember-cli-babel-plugin-helpers");
313
314 if (hasPlugin(plugins, "@babel/plugin-transform-class-static-block")) {
315 if (parent === project) {
316 project.ui.writeWarnLine(
317 `${_parentName(
318 parent
319 )} has added the static class block plugin to its build, but ember-cli-babel provides these by default now! You can remove the transforms, or the addon that provided them.`
320 );
321 }
322 } else {
323 addPlugin(
324 plugins,
325 [require.resolve("@babel/plugin-transform-class-static-block"), { legacy: true }],
326 _buildClassFeaturePluginConstraints(
327 {
328 before: ["@babel/plugin-proposal-decorators"],
329 },
330 config,
331 parent,
332 project
333 )
334 );
335 }
336
337 if (hasPlugin(plugins, "@babel/plugin-proposal-decorators")) {
338 if (parent === project) {
339 project.ui.writeWarnLine(
340 `${_parentName(
341 parent
342 )} has added the decorators plugin to its build, but ember-cli-babel provides these by default now! You can remove the transforms, or the addon that provided them, such as @ember-decorators/babel-transforms. Ember supports the stage 1 decorator spec and transforms, so if you were using stage 2, you'll need to ensure that your decorators are compatible, or convert them to stage 1.`
343 );
344 }
345 } else {
346 addPlugin(
347 plugins,
348 [require.resolve("@babel/plugin-proposal-decorators"), { legacy: true }],
349 _buildClassFeaturePluginConstraints(
350 {
351 before: ["@babel/plugin-proposal-class-properties"],
352 },
353 config,
354 parent,
355 project
356 )
357 );
358 }
359
360 if (hasPlugin(plugins, "@babel/plugin-proposal-class-properties")) {
361 if (parent === project) {
362 project.ui.writeWarnLine(
363 `${_parentName(
364 parent
365 )} has added the class-properties plugin to its build, but ember-cli-babel provides these by default now! You can remove the transforms, or the addon that provided them, such as @ember-decorators/babel-transforms.`
366 );
367 }
368 } else {
369 _addClassProperties(addPlugin, plugins, options, config, parent, project);
370 }
371
372 if (hasPlugin(plugins, "babel-plugin-filter-imports")) {
373 let checker = new VersionChecker(parent).for(
374 "babel-plugin-filter-imports",
375 "npm"
376 );
377
378 if (checker.lt("3.0.0")) {
379 addPlugin(
380 plugins,
381 require.resolve("./dedupe-internal-decorators-plugin"),
382 {
383 after: ["babel-plugin-filter-imports"],
384 }
385 );
386 }
387 }
388
389 return plugins;
390}
391
392function _addClassProperties(addPlugin, plugins, options, config, parent, project) {
393 addPlugin(
394 plugins,
395 [
396 require.resolve("@babel/plugin-proposal-class-properties"),
397 { loose: options.loose || false }
398 ],
399 _buildClassFeaturePluginConstraints(
400 {
401 after: ["@babel/plugin-proposal-decorators"],
402 },
403 config,
404 parent,
405 project
406 )
407 );
408 addPlugin(
409 plugins,
410 [
411 require.resolve("@babel/plugin-proposal-private-methods"),
412 { loose: options.loose || false }
413 ],
414 _buildClassFeaturePluginConstraints(
415 {
416 after: ["@babel/plugin-proposal-decorators"],
417 },
418 config,
419 parent,
420 project
421 )
422 );
423 addPlugin(
424 plugins,
425 [
426 require.resolve("@babel/plugin-proposal-private-property-in-object"),
427 { loose: options.loose || false }
428 ],
429 _buildClassFeaturePluginConstraints(
430 {
431 after: ["@babel/plugin-proposal-decorators"],
432 },
433 config,
434 parent,
435 project
436 )
437 );
438}
439
440function _addTypeScriptPlugin(plugins, parent, project) {
441 const { hasPlugin, addPlugin } = require("ember-cli-babel-plugin-helpers");
442
443 if (hasPlugin(plugins, "@babel/plugin-transform-typescript")) {
444 if (parent === project) {
445 project.ui.writeWarnLine(
446 `${_parentName(
447 parent
448 )} has added the TypeScript transform plugin to its build, but ember-cli-babel provides this by default now when ember-cli-typescript >= 4.0 is installed! You can remove the transform, or the addon that provided it.`
449 );
450 }
451 } else {
452 addPlugin(
453 plugins,
454 [
455 require.resolve("@babel/plugin-transform-typescript"),
456 { allowDeclareFields: true },
457 ],
458 {
459 before: [
460 "@babel/plugin-proposal-class-properties",
461 "@babel/plugin-proposal-private-methods",
462 "@babel/plugin-proposal-decorators",
463 ],
464 }
465 );
466 }
467 return plugins;
468}
469
470function _parentName(parent) {
471 let parentName;
472
473 if (parent) {
474 if (typeof parent.name === "function") {
475 parentName = parent.name();
476 } else {
477 parentName = parent.name;
478 }
479 }
480
481 return parentName;
482}
483
484function _getExtensions(config, parent, project) {
485 let shouldHandleTypeScript = _shouldHandleTypeScript(config, parent, project);
486 let emberCLIBabelConfig = config["ember-cli-babel"] || {};
487 return (
488 emberCLIBabelConfig.extensions ||
489 (shouldHandleTypeScript ? ["js", "ts"] : ["js"])
490 );
491}
492
493function _shouldIncludeDecoratorPlugins(config) {
494 let customOptions = config["ember-cli-babel"] || {};
495
496 return customOptions.disableDecoratorTransforms !== true;
497}
498
499/**
500 * Returns whether we should handle TypeScript (based on the existence of
501 * `ember-cli-typescript` as a depenency). It's worth noting that we parse
502 * the `package.json` deps/devDeps directly (rather than using `addons` on
503 * the parent) because it's possible for `ember-cli-typescript` not to exist
504 * on the addons array, even if it is a dependency.
505 *
506 * Some more context:
507 *
508 * `ember-cli-typescript` returns a stable cache key so its possible for it to
509 * be deduped as part of `ember-engines`. The reason this is important is because
510 * `ember-engines` dedupe is _stateful_ so it's possible for `ember-cli-typescript`
511 * to not be part of the addons array when `ember-cli-babel` is running.
512 *
513 * For more info on `ember-engines` dedupe logic:
514 * https://github.com/ember-engines/ember-engines/blob/master/packages/ember-engines/lib/utils/deeply-non-duplicated-addon.js#L35
515 *
516 * @name _shouldHandleTypeScript
517 * @returns {boolean}
518 */
519function _shouldHandleTypeScript(config, parent, project) {
520 let emberCLIBabelConfig = config["ember-cli-babel"] || {};
521
522 if (typeof emberCLIBabelConfig.enableTypeScriptTransform === "boolean") {
523 return emberCLIBabelConfig.enableTypeScriptTransform;
524 }
525
526 let pkg = parent.pkg;
527
528 if (!pkg) {
529 return false;
530 }
531
532 let dependencies;
533
534 // consider `dependencies` and `devDependencies` if the parent is the project
535 // (`ember-cli` uses both in this case), otherwise only care about `dependencies`
536 if (parent === project) {
537 dependencies = Object.assign({}, pkg.dependencies, pkg.devDependencies);
538 } else {
539 dependencies = pkg.dependencies || {};
540 }
541
542 let tsDependency = dependencies["ember-cli-typescript"];
543
544 if (tsDependency !== undefined) {
545 let tsPkgPath = resolvePackagePath("ember-cli-typescript", parent.root);
546
547 if (tsPkgPath === null) {
548 return false;
549 }
550
551 let tsPkg = require(tsPkgPath);
552 return semver.gte(tsPkg.version, "4.0.0-alpha.1");
553 }
554
555 return false;
556}
557
558function _getAddonProvidedConfig(addonOptions) {
559 let options = clone(addonOptions.babel || {});
560
561 let plugins = options.plugins || [];
562 let postTransformPlugins = options.postTransformPlugins || [];
563
564 return {
565 options,
566 plugins,
567 postTransformPlugins,
568 };
569}
570
571function _shouldCompileModules(options, project) {
572 let addonOptions = options["ember-cli-babel"];
573
574 if (addonOptions && "compileModules" in addonOptions) {
575 return addonOptions.compileModules;
576 } else {
577 return semver.gt(project.emberCLIVersion(), "2.12.0-alpha.1");
578 }
579}
580
581function _getAppOptions(appInstance) {
582 let app = findApp(appInstance);
583
584 return (app && app.options) || {};
585}
586
587function _shouldIncludeHelpers(options, appInstance) {
588 let parent = appInstance.parent;
589 let project = appInstance.project;
590 let appOptions = _getAppOptions(appInstance);
591 let customOptions = appOptions["ember-cli-babel"];
592
593 let shouldIncludeHelpers = false;
594
595 if (!_shouldCompileModules(options, project)) {
596 // we cannot use external helpers if we are not transpiling modules
597 return false;
598 } else if (customOptions && "includeExternalHelpers" in customOptions) {
599 shouldIncludeHelpers = customOptions.includeExternalHelpers === true;
600 } else {
601 // Check the project to see if we should include helpers based on heuristics.
602 shouldIncludeHelpers = defaultShouldIncludeHelpers(project);
603 }
604
605 let appEmberCliBabel = project.addons.find(
606 (a) => a.name === "ember-cli-babel"
607 );
608 let appEmberCliBabelVersion =
609 appEmberCliBabel && appEmberCliBabel.pkg && appEmberCliBabel.pkg.version;
610
611 if (
612 appEmberCliBabelVersion &&
613 semver.gte(appEmberCliBabelVersion, "7.3.0-beta.1")
614 ) {
615 return shouldIncludeHelpers;
616 } else if (shouldIncludeHelpers) {
617 project.ui.writeWarnLine(
618 `${_parentName(
619 parent
620 )} 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.`
621 );
622 }
623
624 return false;
625}
626
627module.exports = {
628 _addDecoratorPlugins,
629 _addTypeScriptPlugin,
630 _getAddonProvidedConfig,
631 _shouldCompileModules,
632 _shouldIncludeHelpers,
633 _shouldHandleTypeScript,
634 _shouldIncludeDecoratorPlugins,
635 _getExtensions,
636 _parentName,
637 _getHelpersPlugin,
638 _getDebugMacroPlugins,
639 _getEmberModulesAPIPolyfill,
640 _getEmberDataPackagesPolyfill,
641 _getModulesPlugin,
642 _getPresetEnv,
643 _shouldHighlightCode,
644};