2022-03-09 16:22:54 +01:00
|
|
|
// TODO: rewrite against the stable edge functions standard
|
|
|
|
|
2022-02-06 02:28:42 +01:00
|
|
|
import { relative } from 'path'
|
2022-01-31 16:46:04 +01:00
|
|
|
import { sources, webpack5 } from 'next/dist/compiled/webpack/webpack'
|
2022-02-06 02:28:42 +01:00
|
|
|
import { normalizePagePath } from '../../../server/normalize-page-path'
|
2022-01-31 16:46:04 +01:00
|
|
|
import { FUNCTIONS_MANIFEST } from '../../../shared/lib/constants'
|
2022-02-06 02:28:42 +01:00
|
|
|
import { getPageFromPath } from '../../entries'
|
2022-03-02 16:09:36 +01:00
|
|
|
import { collectAssets, getEntrypointInfo, PerRoute } from './middleware-plugin'
|
2022-01-31 16:46:04 +01:00
|
|
|
|
|
|
|
const PLUGIN_NAME = 'FunctionsManifestPlugin'
|
|
|
|
export interface FunctionsManifest {
|
|
|
|
version: 1
|
|
|
|
pages: {
|
|
|
|
[page: string]: {
|
2022-02-06 02:28:42 +01:00
|
|
|
runtime?: string
|
2022-01-31 16:46:04 +01:00
|
|
|
env: string[]
|
|
|
|
files: string[]
|
|
|
|
name: string
|
|
|
|
page: string
|
|
|
|
regexp: string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-06 02:28:42 +01:00
|
|
|
function containsPath(outer: string, inner: string) {
|
|
|
|
const rel = relative(outer, inner)
|
|
|
|
return !rel.startsWith('../') && rel !== '..'
|
|
|
|
}
|
2022-01-31 16:46:04 +01:00
|
|
|
export default class FunctionsManifestPlugin {
|
|
|
|
dev: boolean
|
2022-02-06 02:28:42 +01:00
|
|
|
pagesDir: string
|
|
|
|
pageExtensions: string[]
|
2022-02-08 14:16:46 +01:00
|
|
|
isEdgeRuntime: boolean
|
2022-02-06 02:28:42 +01:00
|
|
|
pagesRuntime: Map<string, string>
|
2022-01-31 16:46:04 +01:00
|
|
|
|
|
|
|
constructor({
|
|
|
|
dev,
|
2022-02-06 02:28:42 +01:00
|
|
|
pagesDir,
|
|
|
|
pageExtensions,
|
2022-02-08 14:16:46 +01:00
|
|
|
isEdgeRuntime,
|
2022-01-31 16:46:04 +01:00
|
|
|
}: {
|
|
|
|
dev: boolean
|
2022-02-06 02:28:42 +01:00
|
|
|
pagesDir: string
|
|
|
|
pageExtensions: string[]
|
2022-02-08 14:16:46 +01:00
|
|
|
isEdgeRuntime: boolean
|
2022-01-31 16:46:04 +01:00
|
|
|
}) {
|
|
|
|
this.dev = dev
|
2022-02-06 02:28:42 +01:00
|
|
|
this.pagesDir = pagesDir
|
2022-02-08 14:16:46 +01:00
|
|
|
this.isEdgeRuntime = isEdgeRuntime
|
2022-02-06 02:28:42 +01:00
|
|
|
this.pageExtensions = pageExtensions
|
|
|
|
this.pagesRuntime = new Map()
|
2022-01-31 16:46:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
createAssets(
|
|
|
|
compilation: webpack5.Compilation,
|
|
|
|
assets: any,
|
2022-03-02 16:09:36 +01:00
|
|
|
perRoute: PerRoute,
|
2022-02-08 14:16:46 +01:00
|
|
|
isEdgeRuntime: boolean
|
2022-01-31 16:46:04 +01:00
|
|
|
) {
|
|
|
|
const functionsManifest: FunctionsManifest = {
|
|
|
|
version: 1,
|
|
|
|
pages: {},
|
|
|
|
}
|
|
|
|
|
2022-03-02 16:09:36 +01:00
|
|
|
const infos = getEntrypointInfo(compilation, perRoute, isEdgeRuntime)
|
2022-01-31 16:46:04 +01:00
|
|
|
infos.forEach((info) => {
|
2022-02-06 02:28:42 +01:00
|
|
|
const { page } = info
|
|
|
|
// TODO: use global default runtime instead of 'web'
|
|
|
|
const pageRuntime = this.pagesRuntime.get(page)
|
|
|
|
const isWebRuntime =
|
2022-02-08 14:16:46 +01:00
|
|
|
pageRuntime === 'edge' || (this.isEdgeRuntime && !pageRuntime)
|
2022-02-06 02:28:42 +01:00
|
|
|
functionsManifest.pages[page] = {
|
|
|
|
// Not assign if it's nodejs runtime, project configured node version is used instead
|
|
|
|
...(isWebRuntime && { runtime: 'web' }),
|
2022-01-31 16:46:04 +01:00
|
|
|
...info,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-02-08 14:16:46 +01:00
|
|
|
const assetPath = (this.isEdgeRuntime ? '' : 'server/') + FUNCTIONS_MANIFEST
|
2022-01-31 16:46:04 +01:00
|
|
|
assets[assetPath] = new sources.RawSource(
|
|
|
|
JSON.stringify(functionsManifest, null, 2)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
apply(compiler: webpack5.Compiler) {
|
2022-02-06 02:28:42 +01:00
|
|
|
const handler = (parser: webpack5.javascript.JavascriptParser) => {
|
|
|
|
parser.hooks.exportSpecifier.tap(
|
|
|
|
PLUGIN_NAME,
|
|
|
|
(statement: any, _identifierName: string, exportName: string) => {
|
|
|
|
const { resource } = parser.state.module
|
|
|
|
const isPagePath = containsPath(this.pagesDir, resource)
|
|
|
|
// Only parse exported config in pages
|
|
|
|
if (!isPagePath) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const { declaration } = statement
|
|
|
|
if (exportName === 'config') {
|
|
|
|
const varDecl = declaration.declarations[0]
|
|
|
|
const { properties } = varDecl.init
|
|
|
|
const prop = properties.find((p: any) => p.key.name === 'runtime')
|
|
|
|
if (!prop) return
|
|
|
|
const runtime = prop.value.value
|
|
|
|
if (!['nodejs', 'edge'].includes(runtime))
|
|
|
|
throw new Error(
|
|
|
|
`The runtime option can only be 'nodejs' or 'edge'`
|
|
|
|
)
|
|
|
|
|
|
|
|
// @ts-ignore buildInfo exists on Module
|
|
|
|
parser.state.module.buildInfo.NEXT_runtime = runtime
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
compiler.hooks.compilation.tap(
|
|
|
|
PLUGIN_NAME,
|
|
|
|
(
|
|
|
|
compilation: webpack5.Compilation,
|
|
|
|
{ normalModuleFactory: factory }: any
|
|
|
|
) => {
|
|
|
|
factory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, handler)
|
|
|
|
factory.hooks.parser.for('javascript/esm').tap(PLUGIN_NAME, handler)
|
|
|
|
|
|
|
|
compilation.hooks.seal.tap(PLUGIN_NAME, () => {
|
|
|
|
for (const entryData of compilation.entries.values()) {
|
|
|
|
for (const dependency of entryData.dependencies) {
|
|
|
|
// @ts-ignore TODO: webpack 5 types
|
|
|
|
const module = compilation.moduleGraph.getModule(dependency)
|
|
|
|
const outgoingConnections =
|
|
|
|
compilation.moduleGraph.getOutgoingConnectionsByModule(module)
|
|
|
|
if (!outgoingConnections) return
|
|
|
|
const entryModules = outgoingConnections.keys()
|
|
|
|
for (const mod of entryModules) {
|
|
|
|
const runtime = mod?.buildInfo?.NEXT_runtime
|
|
|
|
if (runtime) {
|
|
|
|
// @ts-ignore: TODO: webpack 5 types
|
|
|
|
const normalizedPagePath = normalizePagePath(mod.userRequest)
|
|
|
|
const pagePath = normalizedPagePath.replace(this.pagesDir, '')
|
|
|
|
const page = getPageFromPath(pagePath, this.pageExtensions)
|
|
|
|
this.pagesRuntime.set(page, runtime)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-01-31 16:46:04 +01:00
|
|
|
collectAssets(compiler, this.createAssets.bind(this), {
|
|
|
|
dev: this.dev,
|
|
|
|
pluginName: PLUGIN_NAME,
|
2022-02-08 14:16:46 +01:00
|
|
|
isEdgeRuntime: this.isEdgeRuntime,
|
2022-01-31 16:46:04 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|