nx/packages/react/babel.ts
Jack Hsu 653cad2492
feat(react): add support for React Compiler in @nx/react/babel (#26826)
This PR adds a check in `@nx/react/babel` preset to enable React
Compiler when `babel-plugin-react-compiler` is installed.

This affects:

- React apps using Webpack + Babel
- React libs using Rollup + Babel
- React Native apps using Webpack + Babel

If SWC is used, there is no way to use React Compiler currently.

Also adds a [new
recipe](https://nx-dev-git-feat-react-compiler-nrwl.vercel.app/recipes/react/react-compiler)
showing how to enable the compiler using `@nx/react/babel`.

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2024-07-04 16:18:34 -04:00

108 lines
2.7 KiB
TypeScript

/*
* Babel preset to provide React support for Nx.
*/
interface NxReactBabelOptions {
decorators?: {
decoratorsBeforeExport?: boolean;
legacy?: boolean;
};
development?: boolean;
importSource?: string;
loose?: boolean;
reactCompiler?:
| boolean
| {
compilationMode?: string;
sources?: (filename: string) => boolean;
};
runtime?: string;
useBuiltIns?: boolean | string;
/** @deprecated Use `loose` option instead of `classProperties.loose`
*/
classProperties?: {
loose?: boolean;
};
}
module.exports = function (api: any, options: NxReactBabelOptions) {
api.assertVersion(7);
const env = api.env();
/**
* pagesDir is set when being transpiled by Next.js
*/
const isNextJs = api.caller((caller) => caller?.pagesDir);
const presets: Array<[string, unknown]> = [
[require.resolve('@nx/js/babel'), options],
];
const plugins: Array<[string, unknown]> = [];
/**
* Next.js already includes the preset-react, and including it
* the second time here results in having two instances of this preset.
*
* The plugin is duplicated as opposed to being merged because Next.js uses
* their own compiled version of the plugin, rather than one from node_modules.
* That affectively changes the "identity" of the plugin, and babel treats it as
* two separate instances.
*
* More on babel merging: https://babeljs.io/docs/en/options#merging
*/
if (!isNextJs) {
presets.push([
require.resolve('@babel/preset-react'),
getReactPresetOptions({
presetOptions: options,
env,
}),
]);
}
if (options.reactCompiler) {
try {
const reactCompilerPlugin = require.resolve(
'babel-plugin-react-compiler'
);
plugins.push([
reactCompilerPlugin,
typeof options.reactCompiler !== 'boolean' ? options.reactCompiler : {},
]);
} catch {
throw new Error(
`Could not find "babel-plugin-react-compiler". Did you install it?`
);
}
}
return {
presets,
plugins,
};
};
function getReactPresetOptions({
presetOptions,
env,
}: {
env: string;
presetOptions: NxReactBabelOptions;
}) {
const reactPresetOptions: Record<string, string | boolean> = {
runtime: presetOptions.runtime ?? 'automatic',
development: presetOptions.development ?? env !== 'production',
};
// JSX spread is transformed into object spread in `@babel/plugin-transform-react-jsx`
// `useBuiltIns` will be removed in Babel 8.
if (reactPresetOptions.runtime === 'automatic') {
reactPresetOptions.useBuiltIns = true;
}
if (presetOptions.importSource) {
reactPresetOptions.importSource = presetOptions.importSource;
}
return reactPresetOptions;
}