1 | 'use strict';
|
2 |
|
3 | const Filter = require('broccoli-persistent-filter');
|
4 | const clone = require('clone');
|
5 | const fs = require('fs');
|
6 | const stringify = require('json-stable-stringify');
|
7 | const mergeTrees = require('broccoli-merge-trees');
|
8 | const funnel = require('broccoli-funnel');
|
9 | const crypto = require('crypto');
|
10 | const hashForDep = require('hash-for-dep');
|
11 | const transformString = require('./lib/parallel-api').transformString;
|
12 | const transformIsParallelizable = require('./lib/parallel-api').transformIsParallelizable;
|
13 |
|
14 | function getExtensionsRegex(extensions) {
|
15 | return extensions.map(extension => {
|
16 | return new RegExp('\.' + extension + '$');
|
17 | });
|
18 | }
|
19 |
|
20 | function replaceExtensions(extensionsRegex, name) {
|
21 | for (let i = 0, l = extensionsRegex.length; i < l; i++) {
|
22 | name = name.replace(extensionsRegex[i], '');
|
23 | }
|
24 |
|
25 | return name;
|
26 | }
|
27 |
|
28 | module.exports = Babel;
|
29 | function Babel(inputTree, _options) {
|
30 | if (!(this instanceof Babel)) {
|
31 | return new Babel(inputTree, _options);
|
32 | }
|
33 |
|
34 | let options = _options || {};
|
35 | options.persist = 'persist' in options ? options.persist : true;
|
36 | options.async = true;
|
37 | Filter.call(this, inputTree, options);
|
38 |
|
39 | delete options.persist;
|
40 | delete options.async;
|
41 | delete options.annotation;
|
42 | delete options.description;
|
43 |
|
44 | this.console = options.console || console;
|
45 | this.throwUnlessParallelizable = options.throwUnlessParallelizable;
|
46 |
|
47 | delete options.console;
|
48 | delete options.throwUnlessParallelizable;
|
49 |
|
50 | this.options = options;
|
51 | this.extensions = this.options.filterExtensions || ['js'];
|
52 | this.extensionsRegex = getExtensionsRegex(this.extensions);
|
53 | this.name = 'broccoli-babel-transpiler';
|
54 |
|
55 | if (this.options.helperWhiteList) {
|
56 | this.helperWhiteList = this.options.helperWhiteList;
|
57 | }
|
58 |
|
59 |
|
60 |
|
61 | delete this.options.helperWhiteList;
|
62 |
|
63 | if (this.options.browserPolyfill) {
|
64 | let babelCorePath = require.resolve('babel-core');
|
65 | babelCorePath = babelCorePath.replace(/\/babel-core\/.*$/, '/babel-core');
|
66 |
|
67 | let polyfill = funnel(babelCorePath, { files: ['browser-polyfill.js'] });
|
68 | this.inputTree = mergeTrees([polyfill, inputTree]);
|
69 | } else {
|
70 | this.inputTree = inputTree;
|
71 | }
|
72 | delete this.options.browserPolyfill;
|
73 |
|
74 | let result = transformIsParallelizable(options);
|
75 | let isParallelizable = result.isParallelizable;
|
76 | let errors = result.errors;
|
77 |
|
78 | if ((this.throwUnlessParallelizable || process.env.THROW_UNLESS_PARALLELIZABLE) && isParallelizable === false) {
|
79 | try {
|
80 | throw new Error(this.toString() +
|
81 | ' was configured to `throwUnlessParallelizable` and was unable to parallelize a plugin. \nplugins:\n' + joinCount(errors) + '\nPlease see: https://github.com/babel/broccoli-babel-transpiler#parallel-transpilation for more details');
|
82 | } catch(e) {
|
83 | debugger;
|
84 |
|
85 | let result = transformIsParallelizable(options);
|
86 | throw e;
|
87 | }
|
88 | }
|
89 | }
|
90 |
|
91 | function joinCount(list) {
|
92 | let summary = '';
|
93 |
|
94 | for (let i = 0; i < list.length; i++) {
|
95 | summary += `${i + 1}: ${list[i]}\n`
|
96 | }
|
97 |
|
98 | return summary;
|
99 | }
|
100 |
|
101 | Babel.prototype = Object.create(Filter.prototype);
|
102 | Babel.prototype.constructor = Babel;
|
103 | Babel.prototype.targetExtension = 'js';
|
104 |
|
105 | Babel.prototype.baseDir = function() {
|
106 | return __dirname;
|
107 | };
|
108 |
|
109 | Babel.prototype.transform = function(string, options) {
|
110 | return transformString(string, options);
|
111 | };
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | Babel.prototype.optionsHash = function() {
|
120 | let options = this.options;
|
121 | let hash = {};
|
122 | let key, value;
|
123 |
|
124 | if (!this._optionsHash) {
|
125 | for (key in options) {
|
126 | value = options[key];
|
127 | hash[key] = (typeof value === 'function') ? (value + '') : value;
|
128 | }
|
129 |
|
130 | if (options.plugins) {
|
131 | hash.plugins = [];
|
132 |
|
133 | let cacheableItems = options.plugins.slice();
|
134 |
|
135 | for (let i = 0; i < cacheableItems.length; i++) {
|
136 | let item = cacheableItems[i];
|
137 |
|
138 | let type = typeof item;
|
139 | let augmentsCacheKey = false;
|
140 | let providesBaseDir = false;
|
141 | let requiresBaseDir = true;
|
142 |
|
143 | if (type === 'function') {
|
144 | augmentsCacheKey = typeof item.cacheKey === 'function';
|
145 | providesBaseDir = typeof item.baseDir === 'function';
|
146 |
|
147 | if (augmentsCacheKey) {
|
148 | hash.plugins.push(item.cacheKey());
|
149 | }
|
150 |
|
151 | if (providesBaseDir) {
|
152 | let depHash = hashForDep(item.baseDir());
|
153 |
|
154 | hash.plugins.push(depHash);
|
155 | }
|
156 |
|
157 | if (!providesBaseDir && requiresBaseDir){
|
158 |
|
159 |
|
160 | this.console.warn('broccoli-babel-transpiler is opting out of caching due to a plugin that does not provide a caching strategy: `' + item + '`.');
|
161 | hash.plugins.push((new Date).getTime() + '|' + Math.random());
|
162 | break;
|
163 | }
|
164 | } else if (Array.isArray(item)) {
|
165 | item.forEach(part => cacheableItems.push(part));
|
166 | continue;
|
167 | } else if (type !== 'object' || item === null) {
|
168 |
|
169 | hash.plugins.push(item);
|
170 | continue;
|
171 | } else if (type === 'object' && (typeof item.baseDir === 'function')) {
|
172 | hash.plugins.push(hashForDep(item.baseDir()));
|
173 |
|
174 | if (typeof item.cacheKey === 'function') {
|
175 | hash.plugins.push(item.cacheKey());
|
176 | }
|
177 | } else if (type === 'object') {
|
178 |
|
179 | Object.keys(item).forEach(key => {
|
180 | cacheableItems.push(key);
|
181 | cacheableItems.push(item[key]);
|
182 | });
|
183 | continue;
|
184 | } else {
|
185 | this.console.warn('broccoli-babel-transpiler is opting out of caching due to an non-cacheable item: `' + item + '` (' + type + ').');
|
186 | hash.plugins.push((new Date).getTime() + '|' + Math.random());
|
187 | break;
|
188 | }
|
189 | }
|
190 | }
|
191 |
|
192 | this._optionsHash = crypto.createHash('md5').update(stringify(hash), 'utf8').digest('hex');
|
193 | }
|
194 |
|
195 | return this._optionsHash;
|
196 | };
|
197 |
|
198 | Babel.prototype.cacheKeyProcessString = function(string, relativePath) {
|
199 | return this.optionsHash() + Filter.prototype.cacheKeyProcessString.call(this, string, relativePath);
|
200 | };
|
201 |
|
202 | Babel.prototype.processString = function(string, relativePath) {
|
203 | let options = this.copyOptions();
|
204 |
|
205 | options.filename = options.sourceMapTarget = options.sourceFileName = relativePath;
|
206 |
|
207 | if (options.moduleId === true) {
|
208 | options.moduleId = replaceExtensions(this.extensionsRegex, options.filename);
|
209 | }
|
210 |
|
211 | return this.transform(string, options)
|
212 | .then(transpiled => {
|
213 |
|
214 | if (this.helperWhiteList) {
|
215 | let invalidHelpers = transpiled.metadata.usedHelpers.filter(helper => {
|
216 | return this.helperWhiteList.indexOf(helper) === -1;
|
217 | });
|
218 |
|
219 | validateHelpers(invalidHelpers, relativePath);
|
220 | }
|
221 |
|
222 | return transpiled.code;
|
223 | });
|
224 | };
|
225 |
|
226 | Babel.prototype.copyOptions = function() {
|
227 | let cloned = clone(this.options);
|
228 | if (cloned.filterExtensions) {
|
229 | delete cloned.filterExtensions;
|
230 | }
|
231 | if (cloned.targetExtension) {
|
232 | delete cloned.targetExtension;
|
233 | }
|
234 | return cloned;
|
235 | };
|
236 |
|
237 | function validateHelpers(invalidHelpers, relativePath) {
|
238 | if (invalidHelpers.length > 0) {
|
239 | let message = relativePath + ' was transformed and relies on `' + invalidHelpers[0] + '`, which was not included in the helper whitelist. Either add this helper to the whitelist or refactor to not be dependent on this runtime helper.';
|
240 |
|
241 | if (invalidHelpers.length > 1) {
|
242 | let helpers = invalidHelpers.map((item, i) => {
|
243 | if (i === invalidHelpers.length - 1) {
|
244 | return '& `' + item;
|
245 | } else if (i === invalidHelpers.length - 2) {
|
246 | return item + '`, ';
|
247 | }
|
248 |
|
249 | return item + '`, `';
|
250 | }).join('');
|
251 |
|
252 | message = relativePath + ' was transformed and relies on `' + helpers + '`, which were not included in the helper whitelist. Either add these helpers to the whitelist or refactor to not be dependent on these runtime helpers.';
|
253 | }
|
254 |
|
255 | throw new Error(message);
|
256 | }
|
257 | }
|