fusion-cli/build/loaders/babel-worker.js (168 lines of code) (raw):
/** Copyright (c) 2018 Uber Technologies, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* eslint-env node */
const PersistentDiskCache = require('../persistent-disk-cache.js');
const TranslationsExtractor = require('../babel-plugins/babel-plugin-i18n');
const path = require('path');
const babel = require('@babel/core');
const getBabelConfig = require('../get-babel-config.js');
let {getTransformDefault} = require('../get-webpack-config.js');
const loadFusionRC = require('../load-fusionrc.js');
module.exports = {
runTransformation,
};
const JS_EXT_PATTERN = /\.(mjs|js|jsx)$/;
let cache;
function getCache(cacheDir) {
if (!cache) {
cache = new PersistentDiskCache(cacheDir);
}
return cache;
}
async function runTransformation(
source /*: string */,
inputSourceMap /*: Object */,
cacheKey /*: string */,
filename /*: string */,
loaderOptions /*: Object*/,
rootContext /*: Object*/,
sourceMap /*: Object*/
) {
let newOptions = {
...getBabelConfigFromCache(
rootContext,
loaderOptions.configCacheKey,
loaderOptions.babelConfigData
),
overrides: [],
};
if (loaderOptions.overrides != undefined) {
for (let i = 0; i < loaderOptions.overrides.length; i++) {
let override = getBabelConfigFromCache(
rootContext,
loaderOptions.overrideCacheKey,
loaderOptions.overrides[i]
);
//$FlowFixMe
override.test = modulePath => {
if (!JS_EXT_PATTERN.test(modulePath)) {
return false;
}
const experimentalTransformTest = getExperimentalTransformTest(
rootContext
);
const defaultTransform = getTransformDefault(modulePath, rootContext);
const transform = experimentalTransformTest
? experimentalTransformTest(modulePath, defaultTransform)
: defaultTransform;
if (transform === 'none' || transform === 'spec') {
return false;
} else if (transform === 'all') {
return true;
} else {
throw new Error(
`Unexpected value from experimentalTransformTest ${transform}. Expected 'spec' | 'all' | 'none'`
);
}
};
newOptions.overrides.push(override);
}
}
const config = babel.loadPartialConfig({
...newOptions,
filename,
sourceRoot: rootContext,
sourceMap: sourceMap,
inputSourceMap: inputSourceMap || void 0,
sourceFileName: relative(rootContext, filename),
});
const options = config.options;
const cacheDir = path.join(
loaderOptions.dir,
'node_modules/.fusion_babel-cache'
);
const diskCache = getCache(cacheDir);
const result = await diskCache.get(cacheKey, () => {
let metadata = {};
let translationIds = new Set();
// Add the discovery plugin
// This only does side effects, so it is ok this doesn't affect cache key
// This plugin is here because webpack config -> loader options
// requires serialization. But we want to pass translationsIds directly.
options.plugins.unshift([TranslationsExtractor, {translationIds}]);
const transformed = transform(source, options);
if (translationIds.size > 0) {
metadata.translationIds = Array.from(translationIds.values());
}
if (!transformed) {
return null;
}
return {metadata, ...transformed};
});
return result;
}
function transform(source, options) {
let result;
try {
result = babel.transformSync(source, options);
} catch (err) {
throw err.message && err.codeFrame ? new LoaderError(err) : err;
}
if (!result) return null;
// We don't return the full result here because some entries are not
// really serializable. For a full list of properties see here:
// https://github.com/babel/babel/blob/master/packages/babel-core/src/transformation/index.js
// For discussion on this topic see here:
// https://github.com/babel/babel-loader/pull/629
const {code, map, sourceType} = result;
if (map && (!map.sourcesContent || !map.sourcesContent.length)) {
map.sourcesContent = [source];
}
return {code, map, sourceType};
}
class LoaderError extends Error {
/*::
hideStack: boolean
*/
constructor(err) {
super();
const {name, message, codeFrame, hideStack} = formatError(err);
this.name = 'BabelLoaderError';
this.message = `${name ? `${name}: ` : ''}${message}\n\n${codeFrame}\n`;
this.hideStack = hideStack;
Error.captureStackTrace(this, this.constructor);
}
}
const STRIP_FILENAME_RE = /^[^:]+: /;
function formatError(err) {
if (err instanceof SyntaxError) {
err.name = 'SyntaxError';
err.message = err.message.replace(STRIP_FILENAME_RE, '');
err.hideStack = true;
} else if (err instanceof TypeError) {
err.name = null;
err.message = err.message.replace(STRIP_FILENAME_RE, '');
err.hideStack = true;
}
return err;
}
function relative(root, file) {
const rootPath = root.replace(/\\/g, '/').split('/')[1];
const filePath = file.replace(/\\/g, '/').split('/')[1];
// If the file is in a completely different root folder
// use the absolute path of the file
if (rootPath && rootPath !== filePath) {
return file;
}
return path.relative(root, file);
}
let babelConfigCache = {};
function getBabelConfigFromCache(path, uid, data) {
if (babelConfigCache[uid]) return babelConfigCache[uid];
const customConfig = getCustomBabelConfig(path);
const config = getBabelConfig({
...data,
plugins: customConfig.plugins,
presets: customConfig.presets,
});
babelConfigCache[uid] = config;
return config;
}
let loaded = false,
savedConfig;
function getFusionRC(path) {
if (!loaded) {
const config = loadFusionRC(path, true);
savedConfig = config;
loaded = true;
}
return savedConfig;
}
function getExperimentalTransformTest(path) {
return getFusionRC(path).experimentalTransformTest;
}
function getCustomBabelConfig(path) {
return getFusionRC(path).babel || {};
}