-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Automatically transpile dependencies with babel-preset-env (#559)
- Loading branch information
1 parent
e3fcfa0
commit 665e6b1
Showing
34 changed files
with
473 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,201 @@ | ||
const babel = require('babel-core'); | ||
const presetEnv = require('babel-preset-env'); | ||
const getTargetEngines = require('../utils/getTargetEngines'); | ||
const localRequire = require('../utils/localRequire'); | ||
const path = require('path'); | ||
|
||
module.exports = async function(asset) { | ||
if (!await shouldTransform(asset)) { | ||
const NODE_MODULES = `${path.sep}node_modules${path.sep}`; | ||
const ENV_PLUGINS = require('babel-preset-env/data/plugins'); | ||
const ENV_PRESETS = { | ||
es2015: true, | ||
es2016: true, | ||
es2017: true, | ||
latest: true, | ||
env: true | ||
}; | ||
|
||
async function babelTransform(asset) { | ||
let config = await getConfig(asset); | ||
if (!config) { | ||
return; | ||
} | ||
|
||
await asset.parseIfNeeded(); | ||
|
||
let config = { | ||
code: false, | ||
filename: asset.name | ||
}; | ||
// If this is an internally generated config, use our internal babel-core, | ||
// otherwise require a local version from the package we're compiling. | ||
let babel = config.internal | ||
? require('babel-core') | ||
: await localRequire('babel-core', asset.name); | ||
|
||
if (asset.isES6Module) { | ||
config.babelrc = false; | ||
config.plugins = [ | ||
require('babel-plugin-transform-es2015-modules-commonjs') | ||
]; | ||
// TODO: support other versions of babel | ||
if (parseInt(babel.version, 10) !== 6) { | ||
throw new Error(`Unsupported babel version: ${babel.version}`); | ||
} | ||
|
||
let res = babel.transformFromAst(asset.ast, asset.contents, config); | ||
if (!res.ignored) { | ||
asset.ast = res.ast; | ||
asset.isAstDirty = true; | ||
} | ||
}; | ||
} | ||
|
||
async function shouldTransform(asset) { | ||
module.exports = babelTransform; | ||
|
||
async function getConfig(asset) { | ||
let config = await getBabelConfig(asset); | ||
if (config) { | ||
config.code = false; | ||
config.filename = asset.name; | ||
config.babelrc = false; | ||
|
||
// Hide the internal property from babel | ||
let internal = config.internal; | ||
delete config.internal; | ||
Object.defineProperty(config, 'internal', { | ||
value: internal | ||
}); | ||
} | ||
|
||
return config; | ||
} | ||
|
||
babelTransform.getConfig = getConfig; | ||
|
||
async function getBabelConfig(asset) { | ||
// If asset is marked as an ES6 modules, this is a second pass after dependencies are extracted. | ||
// Just compile modules to CommonJS. | ||
if (asset.isES6Module) { | ||
return true; | ||
return { | ||
internal: true, | ||
plugins: [require('babel-plugin-transform-es2015-modules-commonjs')] | ||
}; | ||
} | ||
|
||
if (asset.babelConfig) { | ||
return asset.babelConfig; | ||
} | ||
|
||
let babelrc = await getBabelRc(asset); | ||
let envConfig = await getEnvConfig(asset, !!babelrc); | ||
|
||
// Merge the babel-preset-env config and the babelrc if needed | ||
if (babelrc) { | ||
if (envConfig) { | ||
// Filter out presets that are already applied by babel-preset-env | ||
if (Array.isArray(babelrc.presets)) { | ||
babelrc.presets = babelrc.presets.filter(preset => { | ||
preset = Array.isArray(preset) ? preset[0] : preset; | ||
return !ENV_PRESETS[preset]; | ||
}); | ||
} | ||
|
||
// Filter out plugins that are already applied by babel-preset-env | ||
if (Array.isArray(babelrc.plugins)) { | ||
babelrc.plugins = babelrc.plugins.filter(plugin => { | ||
plugin = Array.isArray(plugin) ? plugin[0] : plugin; | ||
return !ENV_PLUGINS[plugin]; | ||
}); | ||
} | ||
|
||
// Add plugins generated by babel-preset-env to get to the app's target engines. | ||
babelrc.plugins = (babelrc.plugins || []).concat(envConfig.plugins); | ||
} | ||
|
||
return babelrc; | ||
} | ||
|
||
// If there is a babel-preset-env config, and it isn't empty use that | ||
if (envConfig && envConfig.plugins.length > 0) { | ||
return envConfig; | ||
} | ||
|
||
// Otherwise, don't run babel at all | ||
return null; | ||
} | ||
|
||
/** | ||
* 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) | ||
*/ | ||
async function getBabelRc(asset) { | ||
// Support legacy browserify packages | ||
let browserify = asset.package && asset.package.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 (asset.ast) { | ||
return !!asset.babelConfig; | ||
// If this asset is not in node_modules, always use the .babelrc | ||
if (!asset.name.includes(NODE_MODULES)) { | ||
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) { | ||
if (asset.package && asset.package.babel) { | ||
return true; | ||
return asset.package.babel; | ||
} | ||
|
||
return await asset.getConfig(['.babelrc', '.babelrc.js']); | ||
} | ||
|
||
/** | ||
* Generates a babel-preset-env config for an asset. | ||
* This is done by finding the source module's target engines, and the app's | ||
* target engines, and doing a diff to include only the necessary plugins. | ||
*/ | ||
async function getEnvConfig(asset, isSourceModule) { | ||
// Load the target engines for the app and generate a babel-preset-env config | ||
let targetEngines = await getTargetEngines(asset, true); | ||
let targetEnv = await getEnvPlugins(targetEngines); | ||
if (!targetEnv) { | ||
return null; | ||
} | ||
|
||
// If this is the app module, the source and target will be the same, so just compile everything. | ||
// Otherwise, load the source engines and generate a babel-present-env config. | ||
if (asset.name.includes(NODE_MODULES) && !isSourceModule) { | ||
let sourceEngines = await getTargetEngines(asset, false); | ||
let sourceEnv = (await getEnvPlugins(sourceEngines)) || targetEnv; | ||
|
||
// Do a diff of the returned plugins. We only need to process the remaining plugins to get to the app target. | ||
let sourcePlugins = new Set(sourceEnv.map(p => p[0])); | ||
targetEnv = targetEnv.filter(plugin => { | ||
return !sourcePlugins.has(plugin[0]); | ||
}); | ||
} | ||
|
||
return {plugins: targetEnv, internal: true}; | ||
} | ||
|
||
const envCache = new Map(); | ||
|
||
async function getEnvPlugins(targets) { | ||
if (!targets) { | ||
return null; | ||
} | ||
|
||
let key = JSON.stringify(targets); | ||
if (envCache.has(key)) { | ||
return envCache.get(key); | ||
} | ||
|
||
let babelrc = await asset.getConfig(['.babelrc', '.babelrc.js']); | ||
return !!babelrc; | ||
let plugins = presetEnv.default({}, {targets, modules: false}).plugins; | ||
envCache.set(key, plugins); | ||
return plugins; | ||
} |
Oops, something went wrong.