Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): Support React 17's new JSX Transform #26652

Merged
merged 17 commits into from
Aug 28, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ Object {
Object {
"development": false,
"pragma": "React.createElement",
"runtime": "classic",
"useBuiltIns": true,
},
],
Expand Down Expand Up @@ -527,6 +528,7 @@ Object {
Object {
"development": false,
"pragma": "React.createElement",
"runtime": "classic",
"useBuiltIns": true,
},
],
Expand Down Expand Up @@ -790,6 +792,7 @@ Object {
Object {
"development": false,
"pragma": "React.createElement",
"runtime": "classic",
"useBuiltIns": true,
},
],
Expand Down Expand Up @@ -1053,6 +1056,7 @@ Object {
Object {
"development": true,
"pragma": "React.createElement",
"runtime": "classic",
"useBuiltIns": true,
},
],
Expand Down
14 changes: 14 additions & 0 deletions packages/babel-preset-gatsby/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
}),
])
})
})
6 changes: 5 additions & 1 deletion packages/babel-preset-gatsby/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
},
],
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ Array [
Array [
"/path/to/module/babel-preset-gatsby",
Object {
"reactRuntime": undefined,
"stage": "test",
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Object {
"cacheDirectory": "<TEMP_DIR>/test/.cache/webpack/babel",
"compact": false,
"configFile": true,
"reactRuntime": "classic",
"stage": "develop",
},
},
Expand Down
3 changes: 2 additions & 1 deletion packages/gatsby/src/utils/babel-loader-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -67,6 +67,7 @@ const prepareOptions = (babel, options = {}, resolve = require.resolve) => {
resolve(`babel-preset-gatsby`),
{
stage,
reactRuntime,
},
],
{
Expand Down
3 changes: 2 additions & 1 deletion packages/gatsby/src/utils/babel-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
11 changes: 10 additions & 1 deletion packages/gatsby/src/utils/eslint-config.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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`,
Expand Down
20 changes: 19 additions & 1 deletion packages/gatsby/src/utils/webpack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const createWebpackUtils = (

const isSSR = stage.includes(`html`)

const jsxRuntimeExists = reactHasJsxRuntime()
const makeExternalOnly = (original: RuleFactory) => (
options = {}
): RuleSetRule => {
Expand Down Expand Up @@ -273,6 +274,7 @@ export const createWebpackUtils = (
return {
options: {
stage,
reactRuntime: jsxRuntimeExists ? `automatic` : `classic`,
// TODO add proper cache keys
cacheDirectory: path.join(
program.directory,
Expand Down Expand Up @@ -303,7 +305,7 @@ export const createWebpackUtils = (
},

eslint: (schema: GraphQLSchema) => {
const options = eslintConfig(schema)
const options = eslintConfig(schema, jsxRuntimeExists)

return {
options,
Expand Down Expand Up @@ -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
}