Files
react-native/jest/preprocessor.js
T
Rubén Norte 249b8d21c4 Fix inline requires for ESM in react-native-github
Summary:
This fixes how ES modules are handled in Jest tests in the react-native codebase.

They caused some problems before because `import` statements weren't inlined as `require` calls were, so there were some errors in tests when migrating from CommonJS to ESM.

This changes the transform that Jest uses to inline import statements the same way, so we can migrate everything without issues in tests.

Changelog: [Internal]

Reviewed By: kacieb

Differential Revision: D28899692

fbshipit-source-id: 027690f57ca3b5613c261a1089c0635af76662b2
2021-06-04 13:05:11 -07:00

192 lines
6.0 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
/* eslint-env node */
'use strict';
const babelRegisterOnly = require('metro-babel-register');
const nullthrows = require('nullthrows');
const createCacheKeyFunction = require('@jest/create-cache-key-function')
.default;
const t = require('@babel/types');
const {statements} = require('@babel/template').default;
const importDefault = '__importDefault__';
const importAll = '__importAll__';
// prelude
const importPrelude = statements(`
function ${importDefault}(moduleId) {
const exports = require(moduleId);
if (exports && exports.__esModule) {
return exports.default;
}
return exports;
};
function ${importAll}(moduleId) {
const exports = require(moduleId);
if (exports && exports.__esModule) {
return exports;
}
return Object.assign({}, exports, {default: exports});
};
`);
const {
transformSync: babelTransformSync,
transformFromAstSync: babelTransformFromAstSync,
} = require('@babel/core');
const generate = require('@babel/generator').default;
const nodeFiles = new RegExp(
[
'/metro(?:-[^/]*)?/', // metro, metro-core, metro-source-map, metro-etc.
].join('|'),
);
const nodeOptions = babelRegisterOnly.config([nodeFiles]);
babelRegisterOnly([]);
const transformer = require('metro-react-native-babel-transformer');
module.exports = {
process(src /*: string */, file /*: string */) /*: string */ {
if (nodeFiles.test(file)) {
// node specific transforms only
return babelTransformSync(src, {
filename: file,
sourceType: 'script',
...nodeOptions,
ast: false,
}).code;
}
let {ast} = transformer.transform({
filename: file,
options: {
ast: true, // needed for open source (?) https://github.com/facebook/react-native/commit/f8d6b97140cffe8d18b2558f94570c8d1b410d5c#r28647044
dev: true,
enableBabelRuntime: false,
experimentalImportSupport: true,
globalPrefix: '',
hot: false,
inlineRequires: true,
minify: false,
platform: '',
projectRoot: '',
publicPath: '/assets',
retainLines: true,
sourceType: 'unambiguous', // b7 required. detects module vs script mode
},
src,
plugins: [
[require('@babel/plugin-transform-block-scoping')],
// the flow strip types plugin must go BEFORE class properties!
// there'll be a test case that fails if you don't.
[require('@babel/plugin-transform-flow-strip-types')],
[
require('@babel/plugin-proposal-class-properties'),
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
{loose: true},
],
[require('@babel/plugin-transform-computed-properties')],
[require('@babel/plugin-transform-destructuring')],
[require('@babel/plugin-transform-function-name')],
[require('@babel/plugin-transform-literals')],
[require('@babel/plugin-transform-parameters')],
[require('@babel/plugin-transform-shorthand-properties')],
[require('@babel/plugin-transform-react-jsx')],
[require('@babel/plugin-transform-regenerator')],
[require('@babel/plugin-transform-sticky-regex')],
[require('@babel/plugin-transform-unicode-regex')],
[require('@babel/plugin-transform-classes')],
[require('@babel/plugin-transform-arrow-functions')],
[require('@babel/plugin-transform-spread')],
[require('@babel/plugin-proposal-object-rest-spread')],
[
require('@babel/plugin-transform-template-literals'),
{loose: true}, // dont 'a'.concat('b'), just use 'a'+'b'
],
[require('@babel/plugin-transform-exponentiation-operator')],
[require('@babel/plugin-transform-object-assign')],
[require('@babel/plugin-transform-for-of'), {loose: true}],
[require('@babel/plugin-transform-react-display-name')],
[require('@babel/plugin-transform-react-jsx-source')],
],
});
// We're not using @babel/plugin-transform-modules-commonjs so
// we need to add 'use strict' manually
const directives = ast.program.directives;
if (
ast.program.sourceType === 'module' &&
(directives == null ||
directives.findIndex(d => d.value.value === 'use strict') === -1)
) {
ast.program.directives = [
...(directives || []),
t.directive(t.directiveLiteral('use strict')),
];
}
// Postprocess the transformed module to handle ESM and inline requires.
// We need to do this in a separate pass to avoid issues tracking references.
const babelTransformResult = babelTransformFromAstSync(ast, src, {
ast: true,
retainLines: true,
plugins: [
[
require('metro-transform-plugins').importExportPlugin,
{importDefault, importAll},
],
[
require('babel-preset-fbjs/plugins/inline-requires.js'),
{inlineableCalls: [importDefault, importAll]},
],
],
sourceType: 'module',
});
ast = nullthrows(babelTransformResult.ast);
// Inject import helpers *after* running the inline-requires transform,
// because otherwise it will assume they are user code and bail out of
// inlining calls to them.
ast.program.body.unshift(...importPrelude());
return generate(
ast,
// $FlowFixMe[prop-missing] Error found when improving flow typing for libs
{
code: true,
comments: false,
compact: false,
filename: file,
retainLines: true,
sourceFileName: file,
sourceMaps: true,
},
src,
).code;
},
getCacheKey: (createCacheKeyFunction([
__filename,
require.resolve('metro-react-native-babel-transformer'),
require.resolve('@babel/core/package.json'),
]) /*: any */),
};