-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathbabelrc.ts
More file actions
377 lines (320 loc) · 10.6 KB
/
babelrc.ts
File metadata and controls
377 lines (320 loc) · 10.6 KB
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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
import { safeLocalRequire } from '@jgbjs/shared';
import { warning } from '@jgbjs/shared/lib/Logger';
import { localResolve } from '@jgbjs/shared/lib/utils';
import BabelAsset from 'BabelAsset';
import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
const micromatch = require('micromatch');
const semver = require('semver');
async function loadPlugin(name: string, assetName: string) {
const plugin = await safeLocalRequire(name, assetName, () => require(name));
if (plugin && plugin.default) {
return plugin.default;
}
return plugin;
}
export default async function getBabelConfig(
asset: BabelAsset,
isSource: boolean
) {
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
};
}
/**
* 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/jgb-cli-bundler/jgb-cli/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(
'jgb-cli does not support function configs in .babelrc.js yet.'
);
}
for (let key of ['extends', 'overrides', 'test', 'include', 'exclude']) {
if (config[key]) {
throw new Error(
`jgb-cli does not support babel 7 advanced configuration option "${key}" yet.`
);
}
}
const { source, target, lib } = asset.options;
// 但两个都指定值且不相同时
const isDifferenceTarget = source && target && source !== target;
// 移除node_modules相关的忽略
config.ignore = generateIgnore(config.ignore || [], isDifferenceTarget);
// Support ignore/only config options.
if (shouldIgnore(asset, config)) {
return null;
}
// Support .babelignore
let ignoreConfig = await getIgnoreConfig(asset, isDifferenceTarget);
if (ignoreConfig && shouldIgnore(asset, ignoreConfig)) {
return null;
}
if (isDifferenceTarget) {
config.plugins = [].concat(config.plugins, [
[
// require('babel-plugin-transform-miniprogram'),
'babel-plugin-transform-miniprogram',
{
source,
target,
lib
}
]
]);
}
return config;
}
async function getIgnoreConfig(asset, isDifferenceTarget = false) {
let ignoreFile = await asset.getConfig(['.babelignore'], {
load: false
});
if (!ignoreFile) {
return null;
}
let data = await promisify(fs.readFile)(ignoreFile, 'utf8');
let patterns = data
.split('\n')
.map(line => line.replace(/#.*$/, '').trim())
.filter(Boolean);
if (isDifferenceTarget) {
// 移除node_modules相关的忽略
patterns = patterns.filter(l => !l.includes('node_modules'));
}
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: BabelAsset, 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`;
warning(`should install package [${name}]`);
// 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)) {
warning(
'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) {
// warning(
// `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) {
if (typeof name === 'function') {
return name;
}
if (typeof name.default === 'function') {
return name.default;
}
try {
name = standardizeName(type, name);
let [, pkg] = await localResolve(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, '')
);
}
function generateIgnore(ignores = [] as string[], isDifferenceTarget) {
// if (isDifferenceTarget) {
// // 移除node_modules相关的忽略
// return ignores.filter(ig => !ig.includes('node_modules'));
// }
return [...new Set(ignores.concat('node_modules'))];
}