2020-03-29 01:18:22 +01:00
|
|
|
import devalue from 'next/dist/compiled/devalue'
|
2020-08-03 14:26:23 +02:00
|
|
|
import webpack, { Compiler, compilation as CompilationType } from 'webpack'
|
2019-11-02 02:00:56 +01:00
|
|
|
import { RawSource } from 'webpack-sources'
|
2019-03-12 05:01:50 +01:00
|
|
|
import {
|
|
|
|
BUILD_MANIFEST,
|
2019-08-08 19:14:33 +02:00
|
|
|
CLIENT_STATIC_FILES_PATH,
|
2019-05-29 13:57:26 +02:00
|
|
|
CLIENT_STATIC_FILES_RUNTIME_MAIN,
|
2019-11-02 02:00:56 +01:00
|
|
|
CLIENT_STATIC_FILES_RUNTIME_POLYFILLS,
|
2020-04-19 19:58:31 +02:00
|
|
|
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH,
|
2020-06-20 21:59:47 +02:00
|
|
|
CLIENT_STATIC_FILES_RUNTIME_AMP,
|
2019-09-04 16:00:54 +02:00
|
|
|
} from '../../../next-server/lib/constants'
|
2020-02-29 23:09:42 +01:00
|
|
|
import { BuildManifest } from '../../../next-server/server/get-page-files'
|
2020-06-04 19:32:45 +02:00
|
|
|
import getRouteFromEntrypoint from '../../../next-server/server/get-route-from-entrypoint'
|
2020-06-14 14:49:46 +02:00
|
|
|
import { ampFirstEntryNamesMap } from './next-drop-client-page-plugin'
|
2019-08-08 19:14:33 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const isWebpack5 = parseInt(webpack.version!) === 5
|
|
|
|
|
2019-08-08 19:14:33 +02:00
|
|
|
// This function takes the asset map generated in BuildManifestPlugin and creates a
|
|
|
|
// reduced version to send to the client.
|
2020-06-08 20:11:00 +02:00
|
|
|
function generateClientManifest(
|
2020-02-29 23:09:42 +01:00
|
|
|
assetMap: BuildManifest,
|
2019-08-08 19:14:33 +02:00
|
|
|
isModern: boolean
|
2020-06-08 20:11:00 +02:00
|
|
|
): string {
|
2019-08-08 19:14:33 +02:00
|
|
|
const clientManifest: { [s: string]: string[] } = {}
|
|
|
|
const appDependencies = new Set(assetMap.pages['/_app'])
|
2019-08-20 21:32:03 +02:00
|
|
|
|
2019-08-08 19:14:33 +02:00
|
|
|
Object.entries(assetMap.pages).forEach(([page, dependencies]) => {
|
2020-06-08 20:11:00 +02:00
|
|
|
if (page === '/_app') return
|
2019-08-08 19:14:33 +02:00
|
|
|
// Filter out dependencies in the _app entry, because those will have already
|
|
|
|
// been loaded by the client prior to a navigation event
|
|
|
|
const filteredDeps = dependencies.filter(
|
2020-05-18 21:24:37 +02:00
|
|
|
(dep) =>
|
2020-01-27 21:32:45 +01:00
|
|
|
!appDependencies.has(dep) &&
|
|
|
|
(!dep.endsWith('.js') || dep.endsWith('.module.js') === isModern)
|
2019-08-08 19:14:33 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// The manifest can omit the page if it has no requirements
|
|
|
|
if (filteredDeps.length) {
|
|
|
|
clientManifest[page] = filteredDeps
|
|
|
|
}
|
|
|
|
})
|
2019-08-20 08:28:09 +02:00
|
|
|
return devalue(clientManifest)
|
2019-08-08 19:14:33 +02:00
|
|
|
}
|
|
|
|
|
2020-06-11 10:57:24 +02:00
|
|
|
function isJsFile(file: string): boolean {
|
2020-06-26 06:26:09 +02:00
|
|
|
// We don't want to include `.hot-update.js` files into the initial page
|
|
|
|
return !file.endsWith('.hot-update.js') && file.endsWith('.js')
|
2020-06-11 10:57:24 +02:00
|
|
|
}
|
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
function getFilesArray(files: any) {
|
|
|
|
if (!files) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
if (isWebpack5) {
|
|
|
|
return Array.from(files)
|
|
|
|
}
|
|
|
|
|
|
|
|
return files
|
|
|
|
}
|
|
|
|
|
2018-04-12 09:47:42 +02:00
|
|
|
// This plugin creates a build-manifest.json for all assets that are being output
|
|
|
|
// It has a mapping of "entry" filename to real filename. Because the real filename can be hashed in production
|
|
|
|
export default class BuildManifestPlugin {
|
2019-08-08 19:14:33 +02:00
|
|
|
private buildId: string
|
|
|
|
private modern: boolean
|
|
|
|
|
2020-06-02 16:13:11 +02:00
|
|
|
constructor(options: { buildId: string; modern: boolean }) {
|
2019-08-08 19:14:33 +02:00
|
|
|
this.buildId = options.buildId
|
|
|
|
this.modern = options.modern
|
|
|
|
}
|
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
createAssets(compilation: any, assets: any) {
|
|
|
|
const namedChunks: Map<string, CompilationType.Chunk> =
|
|
|
|
compilation.namedChunks
|
|
|
|
const assetMap: BuildManifest = {
|
|
|
|
polyfillFiles: [],
|
|
|
|
devFiles: [],
|
|
|
|
ampDevFiles: [],
|
|
|
|
lowPriorityFiles: [],
|
|
|
|
pages: { '/_app': [] },
|
|
|
|
ampFirstPages: [],
|
|
|
|
}
|
2020-06-14 14:49:46 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const ampFirstEntryNames = ampFirstEntryNamesMap.get(compilation)
|
|
|
|
if (ampFirstEntryNames) {
|
|
|
|
for (const entryName of ampFirstEntryNames) {
|
|
|
|
const pagePath = getRouteFromEntrypoint(entryName)
|
|
|
|
if (!pagePath) {
|
|
|
|
continue
|
2020-02-29 23:09:42 +01:00
|
|
|
}
|
2018-04-12 09:47:42 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
assetMap.ampFirstPages.push(pagePath)
|
|
|
|
}
|
|
|
|
}
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const mainJsChunk = namedChunks.get(CLIENT_STATIC_FILES_RUNTIME_MAIN)
|
2020-06-08 20:11:00 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const mainJsFiles: string[] = getFilesArray(mainJsChunk?.files).filter(
|
|
|
|
isJsFile
|
|
|
|
)
|
2019-11-02 02:00:56 +01:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const polyfillChunk = namedChunks.get(CLIENT_STATIC_FILES_RUNTIME_POLYFILLS)
|
2020-04-19 19:58:31 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
// Create a separate entry for polyfills
|
|
|
|
assetMap.polyfillFiles = getFilesArray(polyfillChunk?.files).filter(
|
|
|
|
isJsFile
|
|
|
|
)
|
2020-06-20 21:59:47 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const reactRefreshChunk = namedChunks.get(
|
|
|
|
CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH
|
|
|
|
)
|
|
|
|
assetMap.devFiles = getFilesArray(reactRefreshChunk?.files).filter(isJsFile)
|
2020-06-20 21:59:47 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
for (const entrypoint of compilation.entrypoints.values()) {
|
|
|
|
const isAmpRuntime = entrypoint.name === CLIENT_STATIC_FILES_RUNTIME_AMP
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
if (isAmpRuntime) {
|
|
|
|
for (const file of entrypoint.getFiles()) {
|
|
|
|
if (!(isJsFile(file) || file.endsWith('.css'))) {
|
2018-07-24 11:24:40 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
assetMap.ampDevFiles.push(file.replace(/\\/g, '/'))
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const pagePath = getRouteFromEntrypoint(entrypoint.name)
|
2019-10-30 02:27:41 +01:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
if (!pagePath) {
|
|
|
|
continue
|
|
|
|
}
|
2019-10-30 02:27:41 +01:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const filesForEntry: string[] = []
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
// getFiles() - helper function to read the files for an entrypoint from stats object
|
|
|
|
for (const file of entrypoint.getFiles()) {
|
|
|
|
if (!(isJsFile(file) || file.endsWith('.css'))) {
|
|
|
|
continue
|
2019-03-12 05:01:50 +01:00
|
|
|
}
|
2018-07-24 11:24:40 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
filesForEntry.push(file.replace(/\\/g, '/'))
|
|
|
|
}
|
2019-08-08 19:14:33 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
assetMap.pages[pagePath] = [...mainJsFiles, ...filesForEntry]
|
|
|
|
}
|
2020-03-02 18:14:40 +01:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
// Add the runtime build manifest file (generated later in this file)
|
|
|
|
// as a dependency for the app. If the flag is false, the file won't be
|
|
|
|
// downloaded by the client.
|
|
|
|
assetMap.lowPriorityFiles.push(
|
|
|
|
`${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_buildManifest.js`
|
|
|
|
)
|
|
|
|
if (this.modern) {
|
|
|
|
assetMap.lowPriorityFiles.push(
|
|
|
|
`${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_buildManifest.module.js`
|
|
|
|
)
|
|
|
|
}
|
2019-03-11 22:34:41 +01:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
// Add the runtime ssg manifest file as a lazy-loaded file dependency.
|
|
|
|
// We also stub this file out for development mode (when it is not
|
|
|
|
// generated).
|
|
|
|
const srcEmptySsgManifest = `self.__SSG_MANIFEST=new Set;self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()`
|
2019-08-08 19:14:33 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
const ssgManifestPath = `${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_ssgManifest.js`
|
|
|
|
assetMap.lowPriorityFiles.push(ssgManifestPath)
|
|
|
|
assets[ssgManifestPath] = new RawSource(srcEmptySsgManifest)
|
2020-06-02 16:13:11 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
if (this.modern) {
|
|
|
|
const ssgManifestPathModern = `${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_ssgManifest.module.js`
|
|
|
|
assetMap.lowPriorityFiles.push(ssgManifestPathModern)
|
|
|
|
assets[ssgManifestPathModern] = new RawSource(srcEmptySsgManifest)
|
|
|
|
}
|
2020-06-02 16:13:11 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
assetMap.pages = Object.keys(assetMap.pages)
|
|
|
|
.sort()
|
|
|
|
// eslint-disable-next-line
|
|
|
|
.reduce((a, c) => ((a[c] = assetMap.pages[c]), a), {} as any)
|
2019-08-08 19:14:33 +02:00
|
|
|
|
2020-08-03 14:26:23 +02:00
|
|
|
assets[BUILD_MANIFEST] = new RawSource(JSON.stringify(assetMap, null, 2))
|
|
|
|
|
|
|
|
const clientManifestPath = `${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_buildManifest.js`
|
|
|
|
|
|
|
|
assets[clientManifestPath] = new RawSource(
|
|
|
|
`self.__BUILD_MANIFEST = ${generateClientManifest(
|
|
|
|
assetMap,
|
|
|
|
false
|
|
|
|
)};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()`
|
2019-03-12 05:01:50 +01:00
|
|
|
)
|
2020-08-03 14:26:23 +02:00
|
|
|
|
|
|
|
if (this.modern) {
|
|
|
|
const modernClientManifestPath = `${CLIENT_STATIC_FILES_PATH}/${this.buildId}/_buildManifest.module.js`
|
|
|
|
|
|
|
|
assets[modernClientManifestPath] = new RawSource(
|
|
|
|
`self.__BUILD_MANIFEST = ${generateClientManifest(
|
|
|
|
assetMap,
|
|
|
|
true
|
|
|
|
)};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return assets
|
|
|
|
}
|
|
|
|
|
|
|
|
apply(compiler: Compiler) {
|
|
|
|
if (isWebpack5) {
|
|
|
|
compiler.hooks.make.tap('NextJsBuildManifest', (compilation) => {
|
|
|
|
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
|
|
|
|
compilation.hooks.processAssets.tap(
|
|
|
|
{
|
|
|
|
name: 'NextJsBuildManifest',
|
|
|
|
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
|
|
|
|
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
|
|
|
|
},
|
|
|
|
(assets: any) => {
|
|
|
|
this.createAssets(compilation, assets)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
compiler.hooks.emit.tap('NextJsBuildManifest', (compilation: any) => {
|
|
|
|
this.createAssets(compilation, compilation.assets)
|
|
|
|
})
|
2018-04-12 09:47:42 +02:00
|
|
|
}
|
|
|
|
}
|