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

perf(gatsby/babel): Optimize hook destructuring #22619

Merged
merged 12 commits into from
Apr 16, 2020
8 changes: 7 additions & 1 deletion packages/babel-preset-gatsby/.babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{
"presets": [["babel-preset-gatsby-package"]]
"presets": [["babel-preset-gatsby-package"]],
"overrides": [
{
"test": ["**/*.ts", "**/*.tsx"],
"plugins": [["@babel/plugin-transform-typescript", { "isTSX": true }]]
}
]
}
1 change: 1 addition & 0 deletions packages/babel-preset-gatsby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For more information on how to customize the Babel configuration of your Gatsby
- [`babel-plugin-transform-react-remove-prop-types`](https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types)
- [`@babel/plugin-proposal-nullish-coalescing-operator`](https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator)
- [`@babel/plugin-proposal-optional-chaining`](https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining)
- [`babel-plugin-optimize-hook-destructuring`](https://www.github.com/gatsbyjs/gatsby/packages/babel-plugin-optimize-hook-destructring)

## Usage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
exports[`babel-preset-gatsby should specify proper presets and plugins when stage is build-html 1`] = `
Object {
"plugins": Array [
Array [
"<PROJECT_ROOT>/packages/babel-preset-gatsby/src/optimize-hook-destructuring.ts",
Object {
"lib": true,
},
],
Array [
"<PROJECT_ROOT>/node_modules/@babel/plugin-proposal-class-properties/lib/index.js",
Object {
Expand Down Expand Up @@ -66,6 +72,12 @@ Object {
exports[`babel-preset-gatsby should specify proper presets and plugins when stage is build-javascript 1`] = `
Object {
"plugins": Array [
Array [
"<PROJECT_ROOT>/packages/babel-preset-gatsby/src/optimize-hook-destructuring.ts",
Object {
"lib": true,
},
],
Array [
"<PROJECT_ROOT>/node_modules/@babel/plugin-proposal-class-properties/lib/index.js",
Object {
Expand Down Expand Up @@ -133,6 +145,12 @@ Object {
exports[`babel-preset-gatsby should specify proper presets and plugins when stage is build-stage 1`] = `
Object {
"plugins": Array [
Array [
"<PROJECT_ROOT>/packages/babel-preset-gatsby/src/optimize-hook-destructuring.ts",
Object {
"lib": true,
},
],
Array [
"<PROJECT_ROOT>/node_modules/@babel/plugin-proposal-class-properties/lib/index.js",
Object {
Expand Down Expand Up @@ -194,6 +212,12 @@ Object {
exports[`babel-preset-gatsby should specify proper presets and plugins when stage is develop 1`] = `
Object {
"plugins": Array [
Array [
"<PROJECT_ROOT>/packages/babel-preset-gatsby/src/optimize-hook-destructuring.ts",
Object {
"lib": true,
},
],
Array [
"<PROJECT_ROOT>/node_modules/@babel/plugin-proposal-class-properties/lib/index.js",
Object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { transform } from "@babel/core"
import preset from "babel-preset-gatsby"
import plugin from "../optimize-hook-destructuring"

const trim = s => s.join(`\n`).trim().replace(/^\s+/gm, ``)

const babel = code =>
transform(code, {
filename: `noop.js`,
presets: [preset],
plugins: [plugin],
babelrc: false,
configFile: false,
sourceType: `module`,
compact: true,
caller: {
name: `tests`,
supportsStaticESM: true,
},
}).code

describe(`optimize-hook-destructuring`, () => {
it(`should transform Array-destructured hook return values use object destructuring`, () => {
const output = babel(
trim`
import { useState } from 'react';
const [count, setCount] = useState(0);
`
)

expect(output).toMatch(trim`
\"use strict\";var _react=require(\"react\");const{0:count,1:setCount}=(0,_react.useState)(0);
`)

expect(output).toMatchInlineSnapshot(
`"\\"use strict\\";var _react=require(\\"react\\");const{0:count,1:setCount}=(0,_react.useState)(0);"`
)
})
})
6 changes: 6 additions & 0 deletions packages/babel-preset-gatsby/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ module.exports = function preset(_, options = {}) {
],
],
plugins: [
[
resolve(`./optimize-hook-destructuring`),
{
lib: true,
},
],
[
resolve(`@babel/plugin-proposal-class-properties`),
{
Expand Down
71 changes: 71 additions & 0 deletions packages/babel-preset-gatsby/src/optimize-hook-destructuring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { NodePath, PluginObj, Visitor } from "@babel/core"
import * as BabelTypes from "@babel/types"
import { Program } from "@babel/types"

// matches any hook-like (the default)
const isHook = /^use[A-Z]/

// matches only built-in hooks provided by React et al
const isBuiltInHook = /^use(Callback|Context|DebugValue|Effect|ImperativeHandle|LayoutEffect|Memo|Reducer|Ref|State)$/

interface IState {
opts?: {
onlyBuiltIns?: boolean
lib?: boolean
}
}

export default function ({
types: t,
}: {
types: typeof BabelTypes
}): PluginObj<Program> {
const visitor: Visitor = {
CallExpression(path, state: IState): void {
const onlyBuiltIns = state.opts?.onlyBuiltIns || false

// if specified, options.lib is a list of libraries that provide hook functions
const libs =
state.opts?.lib === true ? [`react`, `preact/hooks`] : [state.opts!.lib]

// skip function calls that are not the init of a variable declaration:
if (!t.isVariableDeclarator(path.parent)) return

// skip function calls where the return value is not Array-destructured:
if (!t.isArrayPattern(path.parent.id)) return

// name of the (hook) function being called:
const hookName = (path.node.callee as BabelTypes.Identifier).name

if (libs) {
const binding = path.scope.getBinding(hookName)
// not an import
if (!binding || binding.kind !== `module`) return

const specifier = (binding.path.parent as BabelTypes.ImportDeclaration)
.source.value
// not a match
if (!libs.some(lib => lib === specifier)) return
}

// only match function calls with names that look like a hook
if (!(onlyBuiltIns ? isBuiltInHook : isHook).test(hookName)) return

path.parent.id = t.objectPattern(
path.parent.id.elements.map((element, i) =>
t.objectProperty(t.numericLiteral(i), element!)
)
)
},
}

return {
name: `optimize-hook-destructuring`,
visitor: {
// this is a workaround to run before preset-env destroys destructured assignments
Program<Program>(path: NodePath<Program>, state: any): void {
path.traverse(visitor, state)
},
},
}
}
1 change: 1 addition & 0 deletions packages/gatsby-dev-cli/src/__tests__/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ describe(`watching`, () => {
})

const monoRepoPackages = [
`babel-plugin-optimize-hook-destructuring`,
`babel-plugin-remove-graphql-queries`,
`babel-preset-gatsby`,
`babel-preset-gatsby-package`,
Expand Down