UNPKG

10.7 kBJavaScriptView Raw
1'use strict';
2
3const {
4 _shouldCompileModules,
5 _shouldIncludeHelpers,
6 _shouldHandleTypeScript,
7 _getExtensions,
8 _parentName,
9 _shouldHighlightCode,
10} = require("./lib/babel-options-util");
11
12const VersionChecker = require('ember-cli-version-checker');
13const getBabelOptions = require('./lib/get-babel-options');
14const findApp = require('./lib/find-app');
15const emberPlugins = require('./lib/ember-plugins');
16const cacheKeyForTree = require('calculate-cache-key-for-tree');
17
18const APP_BABEL_RUNTIME_VERSION = new WeakMap();
19const PROJECTS_WITH_VALID_EMBER_CLI = new WeakSet();
20
21let count = 0;
22
23module.exports = {
24 name: 'ember-cli-babel',
25 configKey: 'ember-cli-babel',
26 // Note: This is not used internally for this addon, this is added for users to import this function for getting the ember specific
27 // babel plugins. Eg: adding ember specific babel plugins in their babel.config.js.
28 buildEmberPlugins: emberPlugins,
29
30 init() {
31 this._super.init && this._super.init.apply(this, arguments);
32
33 if (!PROJECTS_WITH_VALID_EMBER_CLI.has(this.project)) {
34 let checker = new VersionChecker(this);
35 let dep = checker.for('ember-cli', 'npm');
36
37 if (dep.lt('2.13.0')) {
38 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}`);
39 }
40
41 PROJECTS_WITH_VALID_EMBER_CLI.add(this.project);
42 }
43 },
44
45 buildBabelOptions(configOrType, _config) {
46 let resultType;
47
48 if (typeof configOrType !== 'string') {
49 _config = configOrType;
50 resultType = 'broccoli';
51 } else if (configOrType === 'broccoli') {
52 resultType = 'broccoli';
53 } else if (configOrType === 'babel') {
54 resultType = 'babel';
55 }
56
57 let config = _config || this._getAddonOptions();
58
59 const customAddonConfig = config['ember-cli-babel'];
60 const shouldUseBabelConfigFile = customAddonConfig && customAddonConfig.useBabelConfig;
61
62 let options;
63
64 if (shouldUseBabelConfigFile) {
65 const babel = require('@babel/core');
66
67 let babelConfig = babel.loadPartialConfig({
68 root: this.parent.root,
69 rootMode: 'root',
70 envName: process.env.EMBER_ENV || process.env.BABEL_ENV || process.env.NODE_ENV || "development",
71 });
72
73 if (babelConfig.config === undefined) {
74 // should contain the file that we used for the config,
75 // if it is undefined then we didn't find any config and
76 // should error
77
78 throw new Error(
79 "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."
80 );
81 }
82
83 // If the babel config file is found, then pass the path into the options for the transpiler
84 // parse and leverage the same.
85 options = { configFile: babelConfig.config };
86 } else {
87 options = getBabelOptions(config, this);
88 }
89
90 if (resultType === 'babel') {
91 return options;
92 } else {
93 // legacy codepath
94 return Object.assign({}, this._buildBroccoliBabelTranspilerOptions(config), options);
95 }
96 },
97
98 _debugTree() {
99 if (!this._cachedDebugTree) {
100 this._cachedDebugTree = require('broccoli-debug').buildDebugCallback(`ember-cli-babel:${_parentName(this.parent)}`);
101 }
102
103 return this._cachedDebugTree.apply(null, arguments);
104 },
105
106 getSupportedExtensions(config = {}) {
107 return _getExtensions(config, this.parent, this.project);
108 },
109
110 _buildBroccoliBabelTranspilerOptions(config = {}) {
111 let emberCLIBabelConfig = config["ember-cli-babel"];
112
113 let providedAnnotation;
114 let throwUnlessParallelizable;
115 let sourceMaps = false;
116 let generatorOpts = {};
117 let shouldCompileModules = _shouldCompileModules(config, this.project);
118
119 if (emberCLIBabelConfig) {
120 providedAnnotation = emberCLIBabelConfig.annotation;
121 throwUnlessParallelizable = emberCLIBabelConfig.throwUnlessParallelizable;
122 }
123
124 if (config.babel && "sourceMaps" in config.babel) {
125 sourceMaps = config.babel.sourceMaps;
126 }
127
128 if (config.babel && "generatorOpts" in config.babel) {
129 generatorOpts = config.babel.generatorOpts;
130 }
131
132 let options = {
133 annotation: providedAnnotation || `Babel: ${_parentName(this.parent)}`,
134 sourceMaps,
135 generatorOpts,
136 throwUnlessParallelizable,
137 filterExtensions: this.getSupportedExtensions(config),
138 plugins: [],
139 };
140
141 if (shouldCompileModules) {
142 options.moduleIds = true;
143 options.getModuleId = require("./lib/relative-module-paths").getRelativeModulePath;
144 }
145
146 options.highlightCode = _shouldHighlightCode(this.parent);
147 options.babelrc = false;
148 options.configFile = false;
149
150 return options;
151 },
152
153 transpileTree(inputTree, _config) {
154 let config = _config || this._getAddonOptions();
155 let description = `000${++count}`.slice(-3);
156 let postDebugTree = this._debugTree(inputTree, `${description}:input`);
157 let {
158 // Separate Babel options from `broccoli-babel-transpiler` options.
159 babelrc,
160 configFile,
161 getModuleId,
162 highlightCode,
163 moduleIds,
164 plugins,
165 sourceMaps,
166 ...transpilerOptions
167 } = this._buildBroccoliBabelTranspilerOptions(config);
168 let options = {
169 ...transpilerOptions,
170 // `broccoli-babel-transpiler` now expects all Babel options to be
171 // present under a `babel` key.
172 babel: {
173 babelrc,
174 configFile,
175 getModuleId,
176 highlightCode,
177 moduleIds,
178 plugins,
179 sourceMaps,
180 ...this.buildBabelOptions('babel', config),
181 },
182 };
183
184 // Remove any undefined options so that they don't override the default
185 // Or error when broccoli-babel-transpiler checks serializability
186 Object.keys(options.babel).forEach(key => {
187 if (options.babel[key] === undefined) {
188 delete options.babel[key];
189 }
190 });
191
192 let output;
193
194 const customAddonConfig = config['ember-cli-babel'];
195 const shouldUseBabelConfigFile = customAddonConfig && customAddonConfig.useBabelConfig;
196
197 if (!shouldUseBabelConfigFile && this._shouldDoNothing(options)) {
198 output = postDebugTree;
199 } else {
200 let BabelTranspiler = require('broccoli-babel-transpiler');
201 let transpilationInput = postDebugTree;
202
203 if (_shouldHandleTypeScript(config, this.parent, this.project)) {
204 let Funnel = require('broccoli-funnel');
205 let inputWithoutDeclarations = new Funnel(transpilationInput, { exclude: ['**/*.d.ts'] });
206 transpilationInput = this._debugTree(inputWithoutDeclarations, `${description}:filtered-input`);
207 }
208
209 output = new BabelTranspiler(transpilationInput, options);
210 }
211
212 return this._debugTree(output, `${description}:output`);
213 },
214
215 setupPreprocessorRegistry(type, registry) {
216 registry.add('js', {
217 name: 'ember-cli-babel',
218 ext: _getExtensions(this._getAddonOptions(), this.parent, this.project),
219 toTree: (tree) => this.transpileTree(tree)
220 });
221 },
222
223 _getHelperVersion() {
224 if (!APP_BABEL_RUNTIME_VERSION.has(this.project)) {
225 let checker = new VersionChecker(this.project);
226 APP_BABEL_RUNTIME_VERSION.set(this.project, checker.for('@babel/runtime', 'npm').version);
227 }
228
229 return APP_BABEL_RUNTIME_VERSION.get(this.project);
230 },
231
232 _getHelpersPlugin() {
233 return [
234 [
235 require.resolve('@babel/plugin-transform-runtime'),
236 {
237 version: this._getHelperVersion(),
238 regenerator: false,
239 useESModules: true
240 }
241 ]
242 ]
243 },
244
245 treeForAddon() {
246 // Helpers are a global config, so only the root application should bother
247 // generating and including the file.
248 let isRootBabel = this.parent === this.project;
249 let shouldIncludeHelpers = isRootBabel && _shouldIncludeHelpers(this._getAppOptions(), this);
250
251 if (!shouldIncludeHelpers) { return; }
252
253 const path = require('path');
254 const Funnel = require('broccoli-funnel');
255 const UnwatchedDir = require('broccoli-source').UnwatchedDir;
256
257 const babelHelpersPath = path.dirname(require.resolve('@babel/runtime/package.json'));
258
259 let babelHelpersTree = new Funnel(new UnwatchedDir(babelHelpersPath), {
260 srcDir: 'helpers/esm',
261 destDir: '@babel/runtime/helpers/esm'
262 });
263
264 const transpiledHelpers = this.transpileTree(babelHelpersTree, {
265 'ember-cli-babel': {
266 // prevents the helpers from being double transpiled, and including themselves
267 disablePresetEnv: true
268 }
269 });
270
271 return new Funnel(transpiledHelpers, {
272 destDir: this.moduleName(),
273 });
274 },
275
276 cacheKeyForTree(treeType) {
277 if (treeType === 'addon') {
278 let isRootBabel = this.parent === this.project;
279 let shouldIncludeHelpers = isRootBabel && _shouldIncludeHelpers(this._getAppOptions(), this);
280
281 return cacheKeyForTree('addon', this, [shouldIncludeHelpers]);
282 }
283
284 return cacheKeyForTree(treeType, this);
285 },
286
287 isPluginRequired(pluginName) {
288 let targets = this._getTargets();
289
290 // if no targets are setup, assume that all plugins are required
291 if (!targets) { return true; }
292
293 const isPluginRequired = require('@babel/helper-compilation-targets').isRequired;
294 return isPluginRequired(pluginName, targets);
295 },
296
297 _getAddonOptions() {
298 let parentOptions = this.parent && this.parent.options;
299 let appOptions = this.app && this.app.options;
300
301 if (parentOptions) {
302 let customAddonOptions = parentOptions['ember-cli-babel'];
303
304 if (customAddonOptions && 'includeExternalHelpers' in customAddonOptions) {
305 throw new Error('includeExternalHelpers is not supported in addon configurations, it is an app-wide configuration option');
306 }
307 }
308
309 return parentOptions || appOptions || {};
310 },
311
312 _getAppOptions() {
313 let app = findApp(this);
314
315 return (app && app.options) || {};
316 },
317
318 _getTargets() {
319 let targets = this.project && this.project.targets;
320
321 let parser = require('@babel/helper-compilation-targets').default;
322 if (typeof targets === 'object' && targets !== null) {
323 return parser(targets);
324 } else {
325 return targets;
326 }
327 },
328
329 /*
330 * Used to discover if the addon's current configuration will compile modules
331 * or not.
332 *
333 * @public
334 * @method shouldCompileModules
335 */
336 shouldCompileModules() {
337 return _shouldCompileModules(this._getAddonOptions(), this.project);
338 },
339
340 // detect if running babel would do nothing... and do nothing instead
341 _shouldDoNothing(options) {
342 return !options.babel.sourceMaps && !options.babel.plugins.length;
343 }
344};