Skip to content

Commit

Permalink
Optimize hook destructuring (#22619)
Browse files Browse the repository at this point in the history
* Add optimize-hook-destructing package

* feat(gatsby): Optimize hook destructuring to improve performance

* fix lint

* fix builds and clean up tests

* Undo specific package

* update test

* yarn lint:code

* format

* fix type errors

* use interface
  • Loading branch information
blainekasten authored Apr 16, 2020
1 parent 015f821 commit b3343ab
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 1 deletion.
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

0 comments on commit b3343ab

Please sign in to comment.