diff --git a/packages/babel-preset-gatsby/src/__tests__/__snapshots__/index.js.snap b/packages/babel-preset-gatsby/src/__tests__/__snapshots__/index.js.snap index fc43ff044de22..4d2d9a97c65d1 100644 --- a/packages/babel-preset-gatsby/src/__tests__/__snapshots__/index.js.snap +++ b/packages/babel-preset-gatsby/src/__tests__/__snapshots__/index.js.snap @@ -258,6 +258,7 @@ Object { Object { "development": false, "pragma": "React.createElement", + "runtime": "classic", "useBuiltIns": true, }, ], @@ -527,6 +528,7 @@ Object { Object { "development": false, "pragma": "React.createElement", + "runtime": "classic", "useBuiltIns": true, }, ], @@ -790,6 +792,7 @@ Object { Object { "development": false, "pragma": "React.createElement", + "runtime": "classic", "useBuiltIns": true, }, ], @@ -1053,6 +1056,7 @@ Object { Object { "development": true, "pragma": "React.createElement", + "runtime": "classic", "useBuiltIns": true, }, ], diff --git a/packages/babel-preset-gatsby/src/__tests__/index.js b/packages/babel-preset-gatsby/src/__tests__/index.js index 45589154e4273..27ff3d6b3f889 100644 --- a/packages/babel-preset-gatsby/src/__tests__/index.js +++ b/packages/babel-preset-gatsby/src/__tests__/index.js @@ -26,4 +26,18 @@ describe(`babel-preset-gatsby`, () => { }), ]) }) + + it(`Allows to configure react runtime`, () => { + const { presets } = preset(null, { + reactRuntime: `automatic`, + }) + + expect(presets[1]).toEqual([ + expect.stringContaining(path.join(`@babel`, `preset-react`)), + expect.objectContaining({ + pragma: undefined, + runtime: `automatic`, + }), + ]) + }) }) diff --git a/packages/babel-preset-gatsby/src/index.js b/packages/babel-preset-gatsby/src/index.js index 5e5f4beca8bc1..1400891a2ce6a 100644 --- a/packages/babel-preset-gatsby/src/index.js +++ b/packages/babel-preset-gatsby/src/index.js @@ -77,8 +77,12 @@ export default function preset(_, options = {}) { resolve(`@babel/preset-react`), { useBuiltIns: true, - pragma: `React.createElement`, + pragma: + options.reactRuntime === `automatic` + ? undefined + : `React.createElement`, development: stage === `develop`, + runtime: options.reactRuntime || `classic`, }, ], ], diff --git a/packages/gatsby/src/redux/__tests__/__snapshots__/babelrc.ts.snap b/packages/gatsby/src/redux/__tests__/__snapshots__/babelrc.ts.snap index 0c7d3b172ab34..aa9553634ff2c 100644 --- a/packages/gatsby/src/redux/__tests__/__snapshots__/babelrc.ts.snap +++ b/packages/gatsby/src/redux/__tests__/__snapshots__/babelrc.ts.snap @@ -173,6 +173,7 @@ Array [ Array [ "/path/to/module/babel-preset-gatsby", Object { + "reactRuntime": undefined, "stage": "test", }, ], diff --git a/packages/gatsby/src/utils/__tests__/__snapshots__/webpack-utils.ts.snap b/packages/gatsby/src/utils/__tests__/__snapshots__/webpack-utils.ts.snap index 7dd6d27ff33dd..8cdf725c739ee 100644 --- a/packages/gatsby/src/utils/__tests__/__snapshots__/webpack-utils.ts.snap +++ b/packages/gatsby/src/utils/__tests__/__snapshots__/webpack-utils.ts.snap @@ -41,6 +41,7 @@ Object { "cacheDirectory": "/test/.cache/webpack/babel", "compact": false, "configFile": true, + "reactRuntime": "classic", "stage": "develop", }, }, diff --git a/packages/gatsby/src/utils/babel-loader-helpers.js b/packages/gatsby/src/utils/babel-loader-helpers.js index ce8f81af5194d..f9928d6ff8079 100644 --- a/packages/gatsby/src/utils/babel-loader-helpers.js +++ b/packages/gatsby/src/utils/babel-loader-helpers.js @@ -24,7 +24,7 @@ const getCustomOptions = stage => { const prepareOptions = (babel, options = {}, resolve = require.resolve) => { const pluginBabelConfig = loadCachedConfig() - const { stage } = options + const { stage, reactRuntime } = options // Required plugins/presets const requiredPlugins = [ @@ -67,6 +67,7 @@ const prepareOptions = (babel, options = {}, resolve = require.resolve) => { resolve(`babel-preset-gatsby`), { stage, + reactRuntime, }, ], { diff --git a/packages/gatsby/src/utils/babel-loader.js b/packages/gatsby/src/utils/babel-loader.js index 2d8b5f6b6e729..63a10c1931f58 100644 --- a/packages/gatsby/src/utils/babel-loader.js +++ b/packages/gatsby/src/utils/babel-loader.js @@ -24,10 +24,11 @@ const { module.exports = babelLoader.custom(babel => { const toReturn = { // Passed the loader options. - customOptions({ stage = `test`, ...options }) { + customOptions({ stage = `test`, reactRuntime = `classic`, ...options }) { return { custom: { stage, + reactRuntime, }, loader: { sourceType: `unambiguous`, diff --git a/packages/gatsby/src/utils/eslint-config.ts b/packages/gatsby/src/utils/eslint-config.ts index 648fe4f6ead2b..e5b5c64ca1f9a 100644 --- a/packages/gatsby/src/utils/eslint-config.ts +++ b/packages/gatsby/src/utils/eslint-config.ts @@ -1,7 +1,10 @@ import { printSchema, GraphQLSchema } from "graphql" import { CLIEngine } from "eslint" -export const eslintConfig = (schema: GraphQLSchema): CLIEngine.Options => { +export const eslintConfig = ( + schema: GraphQLSchema, + usingJsxRuntime: boolean +): CLIEngine.Options => { return { useEslintrc: false, resolvePluginsRelativeTo: __dirname, @@ -14,6 +17,12 @@ export const eslintConfig = (schema: GraphQLSchema): CLIEngine.Options => { extends: [require.resolve(`eslint-config-react-app`)], plugins: [`graphql`], rules: { + // New versions of react use a special jsx runtime that remove the requirement + // for having react in scope for jsx. Once the jsx runtime is backported to all + // versions of react we can make this always be `off`. + // I would also assume that eslint-config-react-app will switch their flag to `off` + // when jsx runtime is stable in all common versions of React. + "react/react-in-jsx-scope": usingJsxRuntime ? `off` : `error`, // Conditionally apply for reactRuntime? "import/no-webpack-loader-syntax": [0], "graphql/template-strings": [ `error`, diff --git a/packages/gatsby/src/utils/webpack-utils.ts b/packages/gatsby/src/utils/webpack-utils.ts index d31f79fe9fc5c..e1c909e429e3e 100644 --- a/packages/gatsby/src/utils/webpack-utils.ts +++ b/packages/gatsby/src/utils/webpack-utils.ts @@ -137,6 +137,7 @@ export const createWebpackUtils = ( const isSSR = stage.includes(`html`) + const jsxRuntimeExists = reactHasJsxRuntime() const makeExternalOnly = (original: RuleFactory) => ( options = {} ): RuleSetRule => { @@ -273,6 +274,7 @@ export const createWebpackUtils = ( return { options: { stage, + reactRuntime: jsxRuntimeExists ? `automatic` : `classic`, // TODO add proper cache keys cacheDirectory: path.join( program.directory, @@ -303,7 +305,7 @@ export const createWebpackUtils = ( }, eslint: (schema: GraphQLSchema) => { - const options = eslintConfig(schema) + const options = eslintConfig(schema, jsxRuntimeExists) return { options, @@ -710,3 +712,19 @@ export const createWebpackUtils = ( plugins, } } + +function reactHasJsxRuntime(): boolean { + try { + // React is shipping a new jsx runtime that is to be used with + // an option on @babel/preset-react called `runtime: automatic` + // Not every version of React has this jsx-runtime yet. Eventually, + // it will be backported to older versions of react and this check + // will become unnecessary. + return !!require.resolve(`react/jsx-runtime.js`) + } catch (e) { + // If the require.resolve throws, that means this version of React + // does not support the jsx runtime. + } + + return false +}