diff --git a/src/helpers/config.js b/src/helpers/config.js index 0b1589d985..ba1490d9b9 100644 --- a/src/helpers/config.js +++ b/src/helpers/config.js @@ -150,6 +150,7 @@ exports.configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) => `${publish}/*.json`, `${publish}/BUILD_ID`, `${publish}/static/chunks/webpack-middleware*.js`, + `!${publish}/server/**/*.js.nft.json`, ...ignore.map((path) => `!${slash(path)}`), ) diff --git a/src/helpers/files.js b/src/helpers/files.js index 9d8e8a3f24..72bda85c75 100644 --- a/src/helpers/files.js +++ b/src/helpers/files.js @@ -1,52 +1,55 @@ // @ts-check +const { cpus } = require('os') + const { existsSync, readJson, move, cpSync, copy, writeJson } = require('fs-extra') const pLimit = require('p-limit') const { join } = require('pathe') +const slash = require('slash') +const glob = require('tiny-glob') -const TEST_ROUTE = /\/\[[^/]+?](?=\/|$)/ +const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/ const isDynamicRoute = (route) => TEST_ROUTE.test(route) exports.moveStaticPages = async ({ netlifyConfig, target, i18n, failBuild }) => { - const root = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless') - const pagesManifestPath = join(root, 'pages-manifest.json') - if (!existsSync(pagesManifestPath)) { - failBuild(`Could not find pages manifest at ${pagesManifestPath}`) - } + console.log('Moving static page files to serve from CDN...') + const root = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless', 'pages') + const files = [] const moveFile = async (file) => { const source = join(root, file) - // Trim the initial "pages" - const filePath = file.slice(6) - files.push(filePath) - const dest = join(netlifyConfig.build.publish, filePath) + files.push(file) + const dest = join(netlifyConfig.build.publish, file) await move(source, dest) } + // Move all static files, except error documents and nft manifests + const pages = await glob('**/!(500|404|*.js.nft).{html,json}', { + cwd: root, + dot: true, + }) - const pagesManifest = await readJson(pagesManifestPath) - // Arbitrary limit of 10 concurrent file moves - const limit = pLimit(10) - const promises = Object.entries(pagesManifest).map(async ([route, filePath]) => { - if ( - isDynamicRoute(route) || - !(filePath.endsWith('.html') || filePath.endsWith('.json')) || - filePath.endsWith('/404.html') || - filePath.endsWith('/500.html') - ) { + // Limit concurrent file moves to number of cpus or 2 if there is only 1 + const limit = pLimit(Math.max(2, cpus().length)) + const promises = pages.map(async (rawPath) => { + const filePath = slash(rawPath) + if (isDynamicRoute(filePath)) { return } return limit(moveFile, filePath) }) await Promise.all(promises) - console.log(`Moved ${files.length} page files`) + console.log(`Moved ${files.length} files`) // Write the manifest for use in the serverless functions await writeJson(join(netlifyConfig.build.publish, 'static-manifest.json'), files) if (i18n?.defaultLocale) { // Copy the default locale into the root - await copy(join(netlifyConfig.build.publish, i18n.defaultLocale), `${netlifyConfig.build.publish}/`) + const defaultLocaleDir = join(netlifyConfig.build.publish, i18n.defaultLocale) + if (existsSync(defaultLocaleDir)) { + await copy(defaultLocaleDir, `${netlifyConfig.build.publish}/`) + } } } diff --git a/src/index.js b/src/index.js index 388f007a41..c61b51a583 100644 --- a/src/index.js +++ b/src/index.js @@ -2,8 +2,6 @@ const { join, relative } = require('path') -const { copy, existsSync } = require('fs-extra') - const { ODB_FUNCTION_NAME } = require('./constants') const { restoreCache, saveCache } = require('./helpers/cache') const { getNextConfig, configureHandlerFunctions, generateRedirects } = require('./helpers/config') diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index c61322a691..5c693ff14e 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -68,6 +68,62 @@ exports.resolvePages = () => { }" `; +exports[`onBuild() generates static files manifest 1`] = ` +Array [ + "en/getStaticProps/1.html", + "en/getStaticProps/1.json", + "en/getStaticProps/2.html", + "en/getStaticProps/2.json", + "en/getStaticProps/static.html", + "en/getStaticProps/static.json", + "en/getStaticProps/with-revalidate.html", + "en/getStaticProps/with-revalidate.json", + "en/getStaticProps/withFallback/3.html", + "en/getStaticProps/withFallback/3.json", + "en/getStaticProps/withFallback/4.html", + "en/getStaticProps/withFallback/4.json", + "en/getStaticProps/withFallback/my/path/1.html", + "en/getStaticProps/withFallback/my/path/1.json", + "en/getStaticProps/withFallback/my/path/2.html", + "en/getStaticProps/withFallback/my/path/2.json", + "en/getStaticProps/withFallbackBlocking/3.html", + "en/getStaticProps/withFallbackBlocking/3.json", + "en/getStaticProps/withFallbackBlocking/4.html", + "en/getStaticProps/withFallbackBlocking/4.json", + "en/getStaticProps/withRevalidate/1.html", + "en/getStaticProps/withRevalidate/1.json", + "en/getStaticProps/withRevalidate/2.html", + "en/getStaticProps/withRevalidate/2.json", + "en/getStaticProps/withRevalidate/withFallback/1.html", + "en/getStaticProps/withRevalidate/withFallback/1.json", + "en/getStaticProps/withRevalidate/withFallback/2.html", + "en/getStaticProps/withRevalidate/withFallback/2.json", + "en/image.html", + "en/middle.html", + "en/previewTest.html", + "en/previewTest.json", + "en/static.html", + "es/getStaticProps/static.html", + "es/getStaticProps/static.json", + "es/getStaticProps/with-revalidate.html", + "es/getStaticProps/with-revalidate.json", + "es/image.html", + "es/middle.html", + "es/previewTest.html", + "es/previewTest.json", + "es/static.html", + "fr/getStaticProps/static.html", + "fr/getStaticProps/static.json", + "fr/getStaticProps/with-revalidate.html", + "fr/getStaticProps/with-revalidate.json", + "fr/image.html", + "fr/middle.html", + "fr/previewTest.html", + "fr/previewTest.json", + "fr/static.html", +] +`; + exports[`onBuild() writes correct redirects to netlifyConfig 1`] = ` Array [ Object { diff --git a/test/index.js b/test/index.js index e6f77c44c3..2dc0a1f412 100644 --- a/test/index.js +++ b/test/index.js @@ -203,6 +203,49 @@ describe('onBuild()', () => { expect(existsSync(path.resolve('.next/BUILD_ID'))).toBeTruthy() }) + test('generates static files manifest', async () => { + await moveNextDist() + process.env.EXPERIMENTAL_MOVE_STATIC_PAGES = 'true' + await plugin.onBuild(defaultArgs) + expect(existsSync(path.resolve('.next/static-manifest.json'))).toBeTruthy() + const data = JSON.parse(readFileSync(path.resolve('.next/static-manifest.json'), 'utf8')) + expect(data).toMatchSnapshot() + delete process.env.EXPERIMENTAL_MOVE_STATIC_PAGES + }) + + test('moves static files to root', async () => { + await moveNextDist() + process.env.EXPERIMENTAL_MOVE_STATIC_PAGES = 'true' + await plugin.onBuild(defaultArgs) + const data = JSON.parse(readFileSync(path.resolve('.next/static-manifest.json'), 'utf8')) + + data.forEach((file) => { + expect(existsSync(path.resolve(path.join('.next', file)))).toBeTruthy() + expect(existsSync(path.resolve(path.join('.next', 'server', 'pages', file)))).toBeFalsy() + }) + + delete process.env.EXPERIMENTAL_MOVE_STATIC_PAGES + }) + + test('copies default locale files to top level', async () => { + await moveNextDist() + process.env.EXPERIMENTAL_MOVE_STATIC_PAGES = 'true' + await plugin.onBuild(defaultArgs) + const data = JSON.parse(readFileSync(path.resolve('.next/static-manifest.json'), 'utf8')) + + const locale = 'en/' + + data.forEach((file) => { + if (!file.startsWith(locale)) { + return + } + const trimmed = file.substring(locale.length) + expect(existsSync(path.resolve(path.join('.next', trimmed)))).toBeTruthy() + }) + + delete process.env.EXPERIMENTAL_MOVE_STATIC_PAGES + }) + test('sets correct config', async () => { await moveNextDist() @@ -213,6 +256,7 @@ describe('onBuild()', () => { '.next/*.json', '.next/BUILD_ID', '.next/static/chunks/webpack-middleware*.js', + '!.next/server/**/*.js.nft.json', '!../node_modules/next/dist/compiled/@ampproject/toolbox-optimizer/**/*', `!node_modules/next/dist/server/lib/squoosh/**/*.wasm`, `!node_modules/next/dist/next-server/server/lib/squoosh/**/*.wasm`,