2019-12-07 04:03:55 +01:00
|
|
|
import { NodePath, PluginObj } from '@babel/core'
|
2019-12-05 22:43:11 +01:00
|
|
|
import * as BabelTypes from '@babel/types'
|
|
|
|
|
|
|
|
const pageComponentVar = '__NEXT_COMP'
|
|
|
|
const prerenderId = '__NEXT_SPR'
|
|
|
|
|
2019-12-05 23:22:41 +01:00
|
|
|
export const EXPORT_NAME_GET_STATIC_PROPS = 'unstable_getStaticProps'
|
|
|
|
export const EXPORT_NAME_GET_STATIC_PATHS = 'unstable_getStaticPaths'
|
2019-12-05 22:43:11 +01:00
|
|
|
|
2019-12-07 04:03:55 +01:00
|
|
|
const ssgExports = new Set([
|
|
|
|
EXPORT_NAME_GET_STATIC_PROPS,
|
|
|
|
EXPORT_NAME_GET_STATIC_PATHS,
|
|
|
|
])
|
|
|
|
|
2019-12-07 06:52:24 +01:00
|
|
|
type PluginState = {
|
|
|
|
refs: Set<NodePath<BabelTypes.Identifier>>
|
|
|
|
isPrerender: boolean
|
|
|
|
done: boolean
|
|
|
|
}
|
2019-12-07 04:03:55 +01:00
|
|
|
|
|
|
|
function decorateSsgExport(
|
|
|
|
t: typeof BabelTypes,
|
|
|
|
path: NodePath<BabelTypes.Program>,
|
|
|
|
state: PluginState
|
|
|
|
) {
|
|
|
|
path.traverse({
|
|
|
|
ExportDefaultDeclaration(path) {
|
|
|
|
if (state.done) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
state.done = true
|
|
|
|
|
|
|
|
const prev = path.node.declaration
|
|
|
|
if (prev.type.endsWith('Declaration')) {
|
|
|
|
prev.type = prev.type.replace(/Declaration$/, 'Expression') as any
|
|
|
|
}
|
|
|
|
|
|
|
|
// @ts-ignore invalid return type
|
|
|
|
const [pageCompPath] = path.replaceWithMultiple([
|
|
|
|
t.variableDeclaration('const', [
|
|
|
|
t.variableDeclarator(t.identifier(pageComponentVar), prev as any),
|
|
|
|
]),
|
|
|
|
t.assignmentExpression(
|
|
|
|
'=',
|
|
|
|
t.memberExpression(
|
|
|
|
t.identifier(pageComponentVar),
|
|
|
|
t.identifier(prerenderId)
|
|
|
|
),
|
|
|
|
t.booleanLiteral(true)
|
|
|
|
),
|
|
|
|
t.exportDefaultDeclaration(t.identifier(pageComponentVar)),
|
|
|
|
])
|
|
|
|
path.scope.registerDeclaration(pageCompPath)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-12-05 23:22:41 +01:00
|
|
|
export default function nextTransformSsg({
|
2019-12-05 22:43:11 +01:00
|
|
|
types: t,
|
|
|
|
}: {
|
|
|
|
types: typeof BabelTypes
|
2019-12-07 04:03:55 +01:00
|
|
|
}): PluginObj<PluginState> {
|
|
|
|
function getIdentifier(
|
|
|
|
path: NodePath<
|
|
|
|
| BabelTypes.FunctionDeclaration
|
|
|
|
| BabelTypes.FunctionExpression
|
|
|
|
| BabelTypes.ArrowFunctionExpression
|
|
|
|
>
|
|
|
|
): NodePath<BabelTypes.Identifier> | null {
|
|
|
|
const parentPath = path.parentPath
|
|
|
|
if (parentPath.type === 'VariableDeclarator') {
|
|
|
|
const pp = parentPath as NodePath<BabelTypes.VariableDeclarator>
|
|
|
|
const name = pp.get('id')
|
|
|
|
return name.node.type === 'Identifier'
|
|
|
|
? (name as NodePath<BabelTypes.Identifier>)
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parentPath.type === 'AssignmentExpression') {
|
|
|
|
const pp = parentPath as NodePath<BabelTypes.AssignmentExpression>
|
|
|
|
const name = pp.get('left')
|
|
|
|
return name.node.type === 'Identifier'
|
|
|
|
? (name as NodePath<BabelTypes.Identifier>)
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
|
|
|
|
if (path.node.type === 'ArrowFunctionExpression') {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
return path.node.id && path.node.id.type === 'Identifier'
|
|
|
|
? (path.get('id') as NodePath<BabelTypes.Identifier>)
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
|
|
|
|
function isIdentifierReferenced(
|
|
|
|
ident: NodePath<BabelTypes.Identifier>
|
|
|
|
): boolean {
|
|
|
|
const b = ident.scope.getBinding(ident.node.name)
|
|
|
|
return b != null && b.referenced
|
|
|
|
}
|
|
|
|
|
|
|
|
function markFunction(
|
|
|
|
path: NodePath<
|
|
|
|
| BabelTypes.FunctionDeclaration
|
|
|
|
| BabelTypes.FunctionExpression
|
|
|
|
| BabelTypes.ArrowFunctionExpression
|
|
|
|
>,
|
|
|
|
state: PluginState
|
|
|
|
) {
|
|
|
|
const ident = getIdentifier(path)
|
|
|
|
if (ident && ident.node && isIdentifierReferenced(ident)) {
|
2019-12-07 06:52:24 +01:00
|
|
|
state.refs.add(ident)
|
2019-12-07 04:03:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-07 04:57:14 +01:00
|
|
|
function markImport(
|
|
|
|
path: NodePath<
|
|
|
|
| BabelTypes.ImportSpecifier
|
|
|
|
| BabelTypes.ImportDefaultSpecifier
|
|
|
|
| BabelTypes.ImportNamespaceSpecifier
|
|
|
|
>,
|
|
|
|
state: PluginState
|
|
|
|
) {
|
|
|
|
const local = path.get('local')
|
|
|
|
if (isIdentifierReferenced(local)) {
|
2019-12-07 06:52:24 +01:00
|
|
|
state.refs.add(local)
|
2019-12-07 04:57:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-05 22:43:11 +01:00
|
|
|
return {
|
|
|
|
visitor: {
|
|
|
|
Program: {
|
2019-12-07 04:03:55 +01:00
|
|
|
enter(_, state) {
|
2019-12-07 06:52:24 +01:00
|
|
|
state.refs = new Set<NodePath<BabelTypes.Identifier>>()
|
2019-12-07 04:03:55 +01:00
|
|
|
state.isPrerender = false
|
|
|
|
state.done = false
|
|
|
|
},
|
|
|
|
exit(path, state) {
|
2019-12-07 07:03:29 +01:00
|
|
|
if (!state.isPrerender) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-07 04:03:55 +01:00
|
|
|
const refs = state.refs
|
|
|
|
let count: number
|
|
|
|
|
|
|
|
function sweepFunction(
|
|
|
|
path: NodePath<
|
|
|
|
| BabelTypes.FunctionDeclaration
|
|
|
|
| BabelTypes.FunctionExpression
|
|
|
|
| BabelTypes.ArrowFunctionExpression
|
|
|
|
>
|
|
|
|
) {
|
|
|
|
const ident = getIdentifier(path)
|
|
|
|
if (
|
|
|
|
ident &&
|
|
|
|
ident.node &&
|
2019-12-07 06:52:24 +01:00
|
|
|
refs.has(ident) &&
|
2019-12-07 04:03:55 +01:00
|
|
|
!isIdentifierReferenced(ident)
|
|
|
|
) {
|
|
|
|
++count
|
2019-12-05 23:22:41 +01:00
|
|
|
|
|
|
|
if (
|
2019-12-07 04:03:55 +01:00
|
|
|
t.isAssignmentExpression(path.parentPath) ||
|
|
|
|
t.isVariableDeclarator(path.parentPath)
|
2019-12-05 23:22:41 +01:00
|
|
|
) {
|
2019-12-07 04:03:55 +01:00
|
|
|
path.parentPath.remove()
|
|
|
|
} else {
|
2019-12-05 23:22:41 +01:00
|
|
|
path.remove()
|
|
|
|
}
|
2019-12-07 04:03:55 +01:00
|
|
|
}
|
|
|
|
}
|
2019-12-06 00:05:50 +01:00
|
|
|
|
2019-12-07 04:57:14 +01:00
|
|
|
function sweepImport(
|
|
|
|
path: NodePath<
|
|
|
|
| BabelTypes.ImportSpecifier
|
|
|
|
| BabelTypes.ImportDefaultSpecifier
|
|
|
|
| BabelTypes.ImportNamespaceSpecifier
|
|
|
|
>
|
|
|
|
) {
|
|
|
|
const local = path.get('local')
|
2019-12-07 06:52:24 +01:00
|
|
|
if (refs.has(local) && !isIdentifierReferenced(local)) {
|
2019-12-07 04:57:14 +01:00
|
|
|
++count
|
|
|
|
path.remove()
|
|
|
|
if (
|
|
|
|
(path.parent as BabelTypes.ImportDeclaration).specifiers
|
|
|
|
.length === 0
|
|
|
|
) {
|
|
|
|
path.parentPath.remove()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-07 04:03:55 +01:00
|
|
|
do {
|
|
|
|
;(path.scope as any).crawl()
|
|
|
|
count = 0
|
2019-12-06 00:05:50 +01:00
|
|
|
|
2019-12-07 04:03:55 +01:00
|
|
|
path.traverse({
|
|
|
|
// eslint-disable-next-line no-loop-func
|
|
|
|
VariableDeclarator(path) {
|
|
|
|
if (path.node.id.type !== 'Identifier') {
|
|
|
|
return
|
2019-12-05 22:43:11 +01:00
|
|
|
}
|
2019-12-05 23:43:18 +01:00
|
|
|
|
2019-12-07 04:03:55 +01:00
|
|
|
const local = path.get('id') as NodePath<BabelTypes.Identifier>
|
2019-12-07 06:52:24 +01:00
|
|
|
if (refs.has(local) && !isIdentifierReferenced(local)) {
|
2019-12-07 04:03:55 +01:00
|
|
|
++count
|
|
|
|
path.remove()
|
2019-12-05 23:43:18 +01:00
|
|
|
}
|
2019-12-07 04:03:55 +01:00
|
|
|
},
|
|
|
|
FunctionDeclaration: sweepFunction,
|
|
|
|
FunctionExpression: sweepFunction,
|
|
|
|
ArrowFunctionExpression: sweepFunction,
|
2019-12-07 04:57:14 +01:00
|
|
|
ImportSpecifier: sweepImport,
|
|
|
|
ImportDefaultSpecifier: sweepImport,
|
|
|
|
ImportNamespaceSpecifier: sweepImport,
|
2019-12-07 04:03:55 +01:00
|
|
|
})
|
|
|
|
} while (count)
|
2019-12-05 23:43:18 +01:00
|
|
|
|
2019-12-07 07:03:29 +01:00
|
|
|
decorateSsgExport(t, path, state)
|
2019-12-07 04:03:55 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
VariableDeclarator(path, state) {
|
|
|
|
if (path.node.id.type !== 'Identifier') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const local = path.get('id') as NodePath<BabelTypes.Identifier>
|
|
|
|
if (isIdentifierReferenced(local)) {
|
2019-12-07 06:52:24 +01:00
|
|
|
state.refs.add(local)
|
2019-12-07 04:03:55 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
FunctionDeclaration: markFunction,
|
|
|
|
FunctionExpression: markFunction,
|
|
|
|
ArrowFunctionExpression: markFunction,
|
2019-12-07 04:57:14 +01:00
|
|
|
ImportSpecifier: markImport,
|
|
|
|
ImportDefaultSpecifier: markImport,
|
|
|
|
ImportNamespaceSpecifier: markImport,
|
2019-12-07 04:03:55 +01:00
|
|
|
ExportNamedDeclaration(path, state) {
|
|
|
|
const specifiers = path.get('specifiers')
|
|
|
|
if (specifiers.length) {
|
|
|
|
specifiers.forEach(s => {
|
|
|
|
if (ssgExports.has(s.node.exported.name)) {
|
|
|
|
state.isPrerender = true
|
|
|
|
s.remove()
|
|
|
|
}
|
|
|
|
})
|
2019-12-05 22:43:11 +01:00
|
|
|
|
2019-12-07 04:03:55 +01:00
|
|
|
if (path.node.specifiers.length < 1) {
|
|
|
|
path.remove()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const decl = path.get('declaration')
|
|
|
|
if (decl == null || decl.node == null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (decl.node.type) {
|
|
|
|
case 'FunctionDeclaration': {
|
|
|
|
const name = decl.node.id!.name
|
|
|
|
if (ssgExports.has(name)) {
|
|
|
|
state.isPrerender = true
|
|
|
|
path.remove()
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'VariableDeclaration': {
|
|
|
|
const inner = decl.get('declarations') as NodePath<
|
|
|
|
BabelTypes.VariableDeclarator
|
|
|
|
>[]
|
|
|
|
inner.forEach(d => {
|
|
|
|
if (d.node.id.type !== 'Identifier') {
|
2019-12-05 23:22:41 +01:00
|
|
|
return
|
|
|
|
}
|
2019-12-07 04:03:55 +01:00
|
|
|
const name = d.node.id.name
|
|
|
|
if (ssgExports.has(name)) {
|
|
|
|
state.isPrerender = true
|
|
|
|
d.remove()
|
2019-12-05 23:22:41 +01:00
|
|
|
}
|
2019-12-07 04:03:55 +01:00
|
|
|
})
|
|
|
|
break
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-12-05 22:43:11 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|