forked from parcel-bundler/parcel
/
babelrc.js
321 lines (273 loc) 路 9.12 KB
/
babelrc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
const semver = require('semver');
const logger = require('@parcel/logger');
const path = require('path');
const localRequire = require('../../utils/localRequire');
const installPackage = require('../../utils/installPackage');
const fs = require('../../utils/fs');
const micromatch = require('micromatch');
async function getBabelConfig(asset, isSource) {
let config = await getBabelRc(asset, isSource);
if (!config) {
return null;
}
// Ignore if the config is empty.
if (
(!config.plugins || config.plugins.length === 0) &&
(!config.presets || config.presets.length === 0)
) {
return null;
}
let plugins = await installPlugins(asset, config);
let babelVersion = await getBabelVersion(asset, plugins);
return {
babelVersion,
config
};
}
module.exports = getBabelConfig;
/**
* Finds a .babelrc for an asset. By default, .babelrc files inside node_modules are not used.
* However, there are some exceptions:
* - if `browserify.transforms` includes "babelify" in package.json (for legacy module compat)
* - the `source` field in package.json is used by the resolver
*/
async function getBabelRc(asset, isSource) {
// Support legacy browserify packages
let pkg = await asset.getPackage();
let browserify = pkg && pkg.browserify;
if (browserify && Array.isArray(browserify.transform)) {
// Look for babelify in the browserify transform list
let babelify = browserify.transform.find(
t => (Array.isArray(t) ? t[0] : t) === 'babelify'
);
// If specified as an array, override the config with the one specified
if (Array.isArray(babelify) && babelify[1]) {
return babelify[1];
}
// Otherwise, return the .babelrc if babelify was found
return babelify ? await findBabelRc(asset) : null;
}
// If this asset is not in node_modules, always use the .babelrc
if (isSource) {
return await findBabelRc(asset);
}
// Otherwise, don't load .babelrc for node_modules.
// See https://github.com/parcel-bundler/parcel/issues/13.
return null;
}
async function findBabelRc(asset) {
// TODO: use the babel API to do this config resolution and support all of its features.
// This is not currently possible because babel tries to actually load plugins and presets
// while resolving the config, but those plugins might not be installed yet.
let config = await asset.getConfig(['.babelrc', '.babelrc.js'], {
packageKey: 'babel'
});
if (!config) {
return null;
}
if (typeof config === 'function') {
// We cannot support function configs since there is no exposed method in babel
// to create the API that is passed to them...
throw new Error(
'Parcel does not support function configs in .babelrc.js yet.'
);
}
for (let key of ['extends', 'overrides', 'test', 'include', 'exclude']) {
if (config[key]) {
throw new Error(
`Parcel does not support babel 7 advanced configuration option "${key}" yet.`
);
}
}
// Support ignore/only config options.
if (shouldIgnore(asset, config)) {
return null;
}
// Support .babelignore
let ignoreConfig = await getIgnoreConfig(asset);
if (ignoreConfig && shouldIgnore(asset, ignoreConfig)) {
return null;
}
return config;
}
async function getIgnoreConfig(asset) {
let ignoreFile = await asset.getConfig(['.babelignore'], {
load: false
});
if (!ignoreFile) {
return null;
}
let data = await fs.readFile(ignoreFile, 'utf8');
let patterns = data
.split('\n')
.map(line => line.replace(/#.*$/, '').trim())
.filter(Boolean);
return {ignore: patterns};
}
function shouldIgnore(asset, config) {
if (config.ignore && matchesPatterns(config.ignore, asset.name)) {
return true;
}
if (config.only && !matchesPatterns(config.only, asset.name)) {
return true;
}
return false;
}
function matchesPatterns(patterns, path) {
return patterns.some(pattern => {
if (typeof pattern === 'function') {
return !!pattern(path);
}
if (typeof pattern === 'string') {
return micromatch.isMatch(path, '**/' + pattern + '/**');
}
return pattern.test(path);
});
}
async function getBabelVersion(asset, plugins) {
// Check the package.json to determine the babel version that is installed
let pkg = await asset.getPackage();
let babelLegacy = getDependency(pkg, 'babel-core');
let babelModern = getDependency(pkg, '@babel/core');
if (babelModern) {
return getMaxMajor(babelModern);
}
if (babelLegacy) {
return 6;
}
// No version was installed. This is either an old app where we didn't require a version to be installed,
// or a new app that just added a .babelrc without manually installing a version of babel core.
// We will attempt to infer a verison of babel and install it based on the dependencies of the plugins
// in the config. This should only happen once since we save babel core into package.json for subsequent runs.
let inferred = await inferBabelVersion(asset, plugins);
let name = inferred === 6 ? 'babel-core' : `@babel/core`;
await installPackage(name, asset.name);
return inferred;
}
function getDependency(pkg, dep) {
return (
(pkg.dependencies && pkg.dependencies[dep]) ||
(pkg.peerDependencies && pkg.peerDependencies[dep]) ||
(pkg.devDependencies && pkg.devDependencies[dep])
);
}
// Core babel packages we use to infer the major version of babel to use.
const CORE_DEPS = new Set([
'@babel/core',
'@babel/runtime',
'@babel/template',
'@babel/traverse',
'@babel/types',
'@babel/parser',
'@babel/cli',
'@babel/register',
'@babel/generator',
'babel-core',
'babel-runtime',
'babel-template',
'babel-traverse',
'babel-types',
'babylon',
'babel-cli',
'babel-register',
'babel-generator'
]);
async function inferBabelVersion(asset, plugins) {
// Attempt to determine version based on dependencies of plugins
let version;
for (let pkg of plugins) {
if (!pkg) {
continue;
}
for (let name of CORE_DEPS) {
let dep = getDependency(pkg, name);
if (dep) {
// Parse version range (ignore prerelease), and ensure it overlaps with the existing version (if any)
let range = new semver.Range(dep.replace(/-.*(\s|\|\||$)?/, ''));
if (version && !version.intersects(range)) {
throw new Error(
'Conflicting babel versions found in .babelrc. Make sure all of your plugins and presets depend on the same major version of babel.'
);
}
version = range;
break;
}
}
}
// Find the maximum major version allowed in the range and use that.
// e.g. if ^6 || ^7 were specified, use 7.
version = getMaxMajor(version);
if (!version) {
logger.warn(
`Could not infer babel version. Defaulting to babel 7. Please add either babel-core or @babel/core as a dependency.`
);
version = 7;
}
return version;
}
function getPluginName(p) {
return Array.isArray(p) ? p[0] : p;
}
function getMaxMajor(version) {
try {
let range = new semver.Range(version);
let sorted = range.set.sort((a, b) => a[0].semver.compare(b[0].semver));
return semver.major(sorted.pop()[0].semver.version);
} catch (err) {
return null;
}
}
async function installPlugins(asset, babelrc) {
let presets = (babelrc.presets || []).map(p =>
resolveModule('preset', getPluginName(p), asset.name)
);
let plugins = (babelrc.plugins || []).map(p =>
resolveModule('plugin', getPluginName(p), asset.name)
);
return await Promise.all([...presets, ...plugins]);
}
async function resolveModule(type, name, path) {
try {
name = standardizeName(type, name);
let [, pkg] = await localRequire.resolve(name, path);
return pkg;
} catch (err) {
return null;
}
}
// Copied from https://github.com/babel/babel/blob/3a399d1eb907df520f2b85bf9ddbc6533e256f6d/packages/babel-core/src/config/files/plugins.js#L61
const EXACT_RE = /^module:/;
const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-plugin-)/;
const BABEL_PRESET_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-preset-)/;
const BABEL_PLUGIN_ORG_RE = /^(@babel\/)(?!plugin-|[^/]+\/)/;
const BABEL_PRESET_ORG_RE = /^(@babel\/)(?!preset-|[^/]+\/)/;
const OTHER_PLUGIN_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-plugin(?:-|\/|$)|[^/]+\/)/;
const OTHER_PRESET_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-preset(?:-|\/|$)|[^/]+\/)/;
const OTHER_ORG_DEFAULT_RE = /^(@(?!babel$)[^/]+)$/;
function standardizeName(type, name) {
// Let absolute and relative paths through.
if (path.isAbsolute(name)) return name;
const isPreset = type === 'preset';
return (
name
// foo -> babel-preset-foo
.replace(
isPreset ? BABEL_PRESET_PREFIX_RE : BABEL_PLUGIN_PREFIX_RE,
`babel-${type}-`
)
// @babel/es2015 -> @babel/preset-es2015
.replace(
isPreset ? BABEL_PRESET_ORG_RE : BABEL_PLUGIN_ORG_RE,
`$1${type}-`
)
// @foo/mypreset -> @foo/babel-preset-mypreset
.replace(
isPreset ? OTHER_PRESET_ORG_RE : OTHER_PLUGIN_ORG_RE,
`$1babel-${type}-`
)
// @foo -> @foo/babel-preset
.replace(OTHER_ORG_DEFAULT_RE, `$1/babel-${type}`)
// module:mypreset -> mypreset
.replace(EXACT_RE, '')
);
}