2020-11-06 03:33:14 +01:00
|
|
|
import {
|
|
|
|
NodePath,
|
|
|
|
PluginObj,
|
|
|
|
types as BabelTypes,
|
|
|
|
} from 'next/dist/compiled/babel/core'
|
2020-01-25 05:25:11 +01:00
|
|
|
import { PageConfig } from 'next/types'
|
2020-06-14 14:49:46 +02:00
|
|
|
import { STRING_LITERAL_DROP_BUNDLE } from '../../../next-server/lib/constants'
|
2019-08-06 22:26:01 +02:00
|
|
|
|
2020-08-17 19:24:18 +02:00
|
|
|
const CONFIG_KEY = 'config'
|
|
|
|
|
2019-08-06 22:26:01 +02:00
|
|
|
// replace program path with just a variable with the drop identifier
|
2020-05-21 14:57:04 +02:00
|
|
|
function replaceBundle(path: any, t: typeof BabelTypes): void {
|
2019-06-10 02:16:14 +02:00
|
|
|
path.parentPath.replaceWith(
|
|
|
|
t.program(
|
|
|
|
[
|
|
|
|
t.variableDeclaration('const', [
|
|
|
|
t.variableDeclarator(
|
2020-06-14 14:49:46 +02:00
|
|
|
t.identifier(STRING_LITERAL_DROP_BUNDLE),
|
|
|
|
t.stringLiteral(`${STRING_LITERAL_DROP_BUNDLE} ${Date.now()}`)
|
2019-06-10 02:16:14 +02:00
|
|
|
),
|
|
|
|
]),
|
|
|
|
],
|
|
|
|
[]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-05-21 14:57:04 +02:00
|
|
|
function errorMessage(state: any, details: string): string {
|
2020-02-13 05:39:51 +01:00
|
|
|
const pageName =
|
|
|
|
(state.filename || '').split(state.cwd || '').pop() || 'unknown'
|
2020-05-27 23:51:11 +02:00
|
|
|
return `Invalid page config export found. ${details} in file ${pageName}. See: https://err.sh/vercel/next.js/invalid-page-config`
|
2020-02-13 05:39:51 +01:00
|
|
|
}
|
|
|
|
|
2019-07-01 23:13:52 +02:00
|
|
|
interface ConfigState {
|
|
|
|
bundleDropped?: boolean
|
|
|
|
}
|
|
|
|
|
2019-09-24 10:50:04 +02:00
|
|
|
// config to parsing pageConfig for client bundles
|
2019-06-10 02:16:14 +02:00
|
|
|
export default function nextPageConfig({
|
|
|
|
types: t,
|
|
|
|
}: {
|
|
|
|
types: typeof BabelTypes
|
2019-07-01 23:13:52 +02:00
|
|
|
}): PluginObj {
|
2019-06-10 02:16:14 +02:00
|
|
|
return {
|
|
|
|
visitor: {
|
|
|
|
Program: {
|
2019-07-01 23:13:52 +02:00
|
|
|
enter(path, state: ConfigState) {
|
2019-06-10 02:16:14 +02:00
|
|
|
path.traverse(
|
|
|
|
{
|
2020-08-17 19:24:18 +02:00
|
|
|
ExportDeclaration(exportPath, exportState) {
|
|
|
|
if (
|
|
|
|
BabelTypes.isExportNamedDeclaration(exportPath) &&
|
|
|
|
(exportPath.node as BabelTypes.ExportNamedDeclaration).specifiers?.some(
|
|
|
|
(specifier) => {
|
2020-11-06 03:33:14 +01:00
|
|
|
return (
|
|
|
|
(t.isIdentifier(specifier.exported)
|
|
|
|
? specifier.exported.name
|
|
|
|
: specifier.exported.value) === CONFIG_KEY
|
|
|
|
)
|
2020-08-17 19:24:18 +02:00
|
|
|
}
|
|
|
|
) &&
|
|
|
|
BabelTypes.isStringLiteral(
|
|
|
|
(exportPath.node as BabelTypes.ExportNamedDeclaration)
|
|
|
|
.source
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
throw new Error(
|
|
|
|
errorMessage(
|
|
|
|
exportState,
|
|
|
|
'Expected object but got export from'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
2019-06-10 02:16:14 +02:00
|
|
|
ExportNamedDeclaration(
|
2020-06-01 23:00:22 +02:00
|
|
|
exportPath: NodePath<BabelTypes.ExportNamedDeclaration>,
|
|
|
|
exportState: any
|
2019-06-10 02:16:14 +02:00
|
|
|
) {
|
2020-06-01 23:00:22 +02:00
|
|
|
if (
|
2020-08-17 19:24:18 +02:00
|
|
|
exportState.bundleDropped ||
|
|
|
|
(!exportPath.node.declaration &&
|
|
|
|
exportPath.node.specifiers.length === 0)
|
2020-06-01 23:00:22 +02:00
|
|
|
) {
|
2019-09-24 10:50:04 +02:00
|
|
|
return
|
|
|
|
}
|
2020-02-13 05:39:51 +01:00
|
|
|
|
|
|
|
const config: PageConfig = {}
|
2020-11-06 03:33:14 +01:00
|
|
|
const declarations: BabelTypes.VariableDeclarator[] = [
|
|
|
|
...((exportPath.node
|
|
|
|
.declaration as BabelTypes.VariableDeclaration)
|
|
|
|
?.declarations || []),
|
|
|
|
exportPath.scope.getBinding(CONFIG_KEY)?.path
|
|
|
|
.node as BabelTypes.VariableDeclarator,
|
2020-08-17 19:24:18 +02:00
|
|
|
].filter(Boolean)
|
2020-02-13 05:39:51 +01:00
|
|
|
|
2020-09-04 15:19:12 +02:00
|
|
|
for (const specifier of exportPath.node.specifiers) {
|
2020-11-06 03:33:14 +01:00
|
|
|
if (
|
|
|
|
(t.isIdentifier(specifier.exported)
|
|
|
|
? specifier.exported.name
|
|
|
|
: specifier.exported.value) === CONFIG_KEY
|
|
|
|
) {
|
2020-09-04 15:19:12 +02:00
|
|
|
// export {} from 'somewhere'
|
|
|
|
if (BabelTypes.isStringLiteral(exportPath.node.source)) {
|
2020-08-17 19:24:18 +02:00
|
|
|
throw new Error(
|
|
|
|
errorMessage(
|
|
|
|
exportState,
|
|
|
|
`Expected object but got import`
|
|
|
|
)
|
|
|
|
)
|
2020-09-04 15:19:12 +02:00
|
|
|
// import hello from 'world'
|
|
|
|
// export { hello as config }
|
|
|
|
} else if (
|
|
|
|
BabelTypes.isIdentifier(
|
|
|
|
(specifier as BabelTypes.ExportSpecifier).local
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
BabelTypes.isImportSpecifier(
|
|
|
|
exportPath.scope.getBinding(
|
|
|
|
(specifier as BabelTypes.ExportSpecifier).local.name
|
|
|
|
)?.path.node
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
throw new Error(
|
|
|
|
errorMessage(
|
|
|
|
exportState,
|
|
|
|
`Expected object but got import`
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2020-08-17 19:24:18 +02:00
|
|
|
}
|
2020-09-04 15:19:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const declaration of declarations) {
|
|
|
|
if (
|
|
|
|
!BabelTypes.isIdentifier(declaration.id, {
|
|
|
|
name: CONFIG_KEY,
|
|
|
|
})
|
|
|
|
) {
|
2019-09-24 10:50:04 +02:00
|
|
|
continue
|
|
|
|
}
|
2019-06-10 02:16:14 +02:00
|
|
|
|
2020-02-13 05:39:51 +01:00
|
|
|
if (!BabelTypes.isObjectExpression(declaration.init)) {
|
|
|
|
const got = declaration.init
|
|
|
|
? declaration.init.type
|
|
|
|
: 'undefined'
|
2019-07-15 22:54:35 +02:00
|
|
|
throw new Error(
|
2020-06-01 23:00:22 +02:00
|
|
|
errorMessage(
|
|
|
|
exportState,
|
|
|
|
`Expected object but got ${got}`
|
|
|
|
)
|
2019-07-15 22:54:35 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-06-10 02:16:14 +02:00
|
|
|
for (const prop of declaration.init.properties) {
|
2020-02-13 05:39:51 +01:00
|
|
|
if (BabelTypes.isSpreadElement(prop)) {
|
|
|
|
throw new Error(
|
2020-06-01 23:00:22 +02:00
|
|
|
errorMessage(
|
|
|
|
exportState,
|
|
|
|
`Property spread is not allowed`
|
|
|
|
)
|
2020-02-13 05:39:51 +01:00
|
|
|
)
|
|
|
|
}
|
2020-11-06 03:33:14 +01:00
|
|
|
const { name } = prop.key as BabelTypes.Identifier
|
2020-02-13 05:39:51 +01:00
|
|
|
if (BabelTypes.isIdentifier(prop.key, { name: 'amp' })) {
|
|
|
|
if (!BabelTypes.isObjectProperty(prop)) {
|
|
|
|
throw new Error(
|
2020-06-01 23:00:22 +02:00
|
|
|
errorMessage(
|
|
|
|
exportState,
|
|
|
|
`Invalid property "${name}"`
|
|
|
|
)
|
2020-02-13 05:39:51 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
!BabelTypes.isBooleanLiteral(prop.value) &&
|
|
|
|
!BabelTypes.isStringLiteral(prop.value)
|
|
|
|
) {
|
|
|
|
throw new Error(
|
2020-06-01 23:00:22 +02:00
|
|
|
errorMessage(
|
|
|
|
exportState,
|
|
|
|
`Invalid value for "${name}"`
|
|
|
|
)
|
2020-02-13 05:39:51 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
config.amp = prop.value.value as PageConfig['amp']
|
2019-07-01 23:13:52 +02:00
|
|
|
}
|
2019-06-10 02:16:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.amp === true) {
|
2020-06-01 23:00:22 +02:00
|
|
|
if (!exportState.file?.opts?.caller.isDev) {
|
2019-12-16 18:45:48 +01:00
|
|
|
// don't replace bundle in development so HMR can track
|
|
|
|
// dependencies and trigger reload when they are changed
|
2020-06-01 23:00:22 +02:00
|
|
|
replaceBundle(exportPath, t)
|
2019-12-16 18:45:48 +01:00
|
|
|
}
|
2020-06-01 23:00:22 +02:00
|
|
|
exportState.bundleDropped = true
|
2019-07-01 23:13:52 +02:00
|
|
|
return
|
|
|
|
}
|
2019-06-10 02:16:14 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
state
|
|
|
|
)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|