rsnext/packages/next/build/babel/plugins/optimize-hook-destructuring.ts
Guy Bedford 64850a8348
ncc Babel inlining (#18768)
This adds inlining for Babel and the Babel plugins used in next.

This is based to the PR at https://github.com/vercel/next.js/pull/18823.

The approach is to make one large bundle and then separate out the individual packages from that in order to avoid duplications.

In the first attempt the Babel bundle size was 10MB... using "resolutions" in the Yarn workspace to reduce the duplicated packages this was brought down to a 2.8MB bundle for Babel and all the used plugins which is exactly the expected file size here.

This will thus add a 2.8MB download size to the next package, but save downloading any babel dependencies separately, removing a large number of package dependencies from the overall install.
2020-11-05 14:23:01 +00:00

78 lines
2.3 KiB
TypeScript

import {
NodePath,
PluginObj,
types as BabelTypes,
} from 'next/dist/compiled/babel/core'
// 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)$/
export default function ({
types: t,
}: {
types: typeof BabelTypes
}): PluginObj<any> {
const visitor = {
CallExpression(path: NodePath<BabelTypes.CallExpression>, state: any) {
const onlyBuiltIns = state.opts.onlyBuiltIns
// if specified, options.lib is a list of libraries that provide hook functions
const libs =
state.opts.lib &&
(state.opts.lib === true
? ['react', 'preact/hooks']
: [].concat(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.reduce<Array<BabelTypes.ObjectProperty>>(
(patterns, element, i) => {
if (element === null) {
return patterns
}
return patterns.concat(
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(path, state) {
path.traverse(visitor, state)
},
},
}
}