diff --git a/lib/constants.js b/lib/constants.js index 20050772..6888a5f2 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -6,3 +6,9 @@ export const PAGES_MANIFEST = 'pages-manifest.json' export const BUILD_MANIFEST = 'build-manifest.json' export const SERVER_DIRECTORY = 'server' export const CONFIG_FILE = 'next.config.js' +export const BUILD_ID_FILE = 'BUILD_ID' +export const BLOCKED_PAGES = [ + '/_document', + '/_app', + '/_error' +] diff --git a/server/build/index.js b/server/build/index.js index 4734ec84..809240b6 100644 --- a/server/build/index.js +++ b/server/build/index.js @@ -3,7 +3,7 @@ import promisify from '../lib/promisify' import fs from 'fs' import webpack from 'webpack' import loadConfig from '../config' -import { PHASE_PRODUCTION_BUILD } from '../../lib/constants' +import { PHASE_PRODUCTION_BUILD, BUILD_ID_FILE } from '../../lib/constants' import getBaseWebpackConfig from './webpack' const access = promisify(fs.access) @@ -12,6 +12,7 @@ const writeFile = promisify(fs.writeFile) export default async function build (dir, conf = null) { const config = loadConfig(PHASE_PRODUCTION_BUILD, dir, conf) const buildId = await config.generateBuildId() // defaults to a uuid + const distDir = join(dir, config.distDir) try { await access(dir, (fs.constants || fs).W_OK) @@ -28,7 +29,7 @@ export default async function build (dir, conf = null) { await runCompiler(configs) - await writeBuildId(dir, buildId, config) + await writeBuildId(distDir, buildId) } catch (err) { console.error(`> Failed to build`) throw err @@ -55,7 +56,7 @@ function runCompiler (compiler) { }) } -async function writeBuildId (dir, buildId, config) { - const buildIdPath = join(dir, config.distDir, 'BUILD_ID') +async function writeBuildId (distDir, buildId) { + const buildIdPath = join(distDir, BUILD_ID_FILE) await writeFile(buildIdPath, buildId, 'utf8') } diff --git a/server/export.js b/server/export.js index e2ff94a3..2e3985ab 100644 --- a/server/export.js +++ b/server/export.js @@ -5,7 +5,7 @@ import walk from 'walk' import { extname, resolve, join, dirname, sep } from 'path' import { existsSync, readFileSync, writeFileSync } from 'fs' import loadConfig from './config' -import {PHASE_EXPORT, SERVER_DIRECTORY, PAGES_MANIFEST, CONFIG_FILE} from '../lib/constants' +import {PHASE_EXPORT, SERVER_DIRECTORY, PAGES_MANIFEST, CONFIG_FILE, BUILD_ID_FILE} from '../lib/constants' import { renderToHTML } from './render' import { getAvailableChunks } from './utils' import { setAssetPrefix } from '../lib/asset' @@ -14,16 +14,16 @@ import * as envConfig from '../lib/runtime-config' export default async function (dir, options, configuration) { dir = resolve(dir) const nextConfig = configuration || loadConfig(PHASE_EXPORT, dir) - const nextDir = join(dir, nextConfig.distDir) + const distDir = join(dir, nextConfig.distDir) - log(`> using build directory: ${nextDir}`) + log(`> using build directory: ${distDir}`) - if (!existsSync(nextDir)) { - throw new Error(`Build directory ${nextDir} does not exist. Make sure you run "next build" before running "next start" or "next export".`) + if (!existsSync(distDir)) { + throw new Error(`Build directory ${distDir} does not exist. Make sure you run "next build" before running "next start" or "next export".`) } - const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8') - const pagesManifest = require(join(nextDir, SERVER_DIRECTORY, PAGES_MANIFEST)) + const buildId = readFileSync(join(distDir, BUILD_ID_FILE), 'utf8') + const pagesManifest = require(join(distDir, SERVER_DIRECTORY, PAGES_MANIFEST)) const pages = Object.keys(pagesManifest) const defaultPathMap = {} @@ -52,26 +52,26 @@ export default async function (dir, options, configuration) { } // Copy .next/static directory - if (existsSync(join(nextDir, 'static'))) { + if (existsSync(join(distDir, 'static'))) { log(' copying "static build" directory') await cp( - join(nextDir, 'static'), + join(distDir, 'static'), join(outDir, '_next', 'static') ) } // Copy dynamic import chunks - if (existsSync(join(nextDir, 'chunks'))) { + if (existsSync(join(distDir, 'chunks'))) { log(' copying dynamic import chunks') await mkdirp(join(outDir, '_next', 'webpack')) await cp( - join(nextDir, 'chunks'), + join(distDir, 'chunks'), join(outDir, '_next', 'webpack', 'chunks') ) } - await copyPages(nextDir, outDir, buildId) + await copyPages(distDir, outDir, buildId) // Get the exportPathMap from the config file if (typeof nextConfig.exportPathMap !== 'function') { @@ -84,14 +84,14 @@ export default async function (dir, options, configuration) { // Start the rendering process const renderOpts = { dir, - dist: nextConfig.distDir, buildId, nextExport: true, assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''), + distDir, dev: false, staticMarkup: false, hotReloader: null, - availableChunks: getAvailableChunks(dir, nextConfig.distDir) + availableChunks: getAvailableChunks(distDir) } const {serverRuntimeConfig, publicRuntimeConfig} = nextConfig @@ -152,10 +152,10 @@ export default async function (dir, options, configuration) { } } -function copyPages (nextDir, outDir, buildId) { +function copyPages (distDir, outDir, buildId) { // TODO: do some proper error handling return new Promise((resolve, reject) => { - const nextBundlesDir = join(nextDir, 'bundles', 'pages') + const nextBundlesDir = join(distDir, 'bundles', 'pages') const walker = walk.walk(nextBundlesDir, { followLinks: false }) walker.on('file', (root, stat, next) => { diff --git a/server/index.js b/server/index.js index 828635bb..01dd8ee4 100644 --- a/server/index.js +++ b/server/index.js @@ -15,7 +15,7 @@ import { import Router from './router' import { getAvailableChunks, isInternalUrl } from './utils' import loadConfig from './config' -import {PHASE_PRODUCTION_SERVER, PHASE_DEVELOPMENT_SERVER} from '../lib/constants' +import {PHASE_PRODUCTION_SERVER, PHASE_DEVELOPMENT_SERVER, BLOCKED_PAGES, BUILD_ID_FILE} from '../lib/constants' import * as asset from '../lib/asset' import * as envConfig from '../lib/runtime-config' import { isResSent } from '../lib/utils' @@ -26,12 +26,6 @@ import pkg from '../../package' const access = promisify(fs.access) -const blockedPages = { - '/_document': true, - '/_app': true, - '/_error': true -} - export default class Server { constructor ({ dir = '.', dev = false, staticMarkup = false, quiet = false, conf = null } = {}) { this.dir = resolve(dir) @@ -41,7 +35,7 @@ export default class Server { this.http = null const phase = dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER this.nextConfig = loadConfig(phase, this.dir, conf) - this.dist = this.nextConfig.distDir + this.distDir = join(dir, this.nextConfig.distDir) this.hotReloader = dev ? this.getHotReloader(this.dir, { quiet, config: this.nextConfig }) : null @@ -49,19 +43,18 @@ export default class Server { // publicRuntimeConfig gets it's default in client/index.js const {serverRuntimeConfig = {}, publicRuntimeConfig, assetPrefix, generateEtags} = this.nextConfig - if (!dev && !fs.existsSync(resolve(dir, this.dist, 'BUILD_ID'))) { - console.error(`> Could not find a valid build in the '${this.dist}' directory! Try building your app with 'next build' before starting the server.`) + if (!dev && !fs.existsSync(resolve(this.distDir, BUILD_ID_FILE))) { + console.error(`> Could not find a valid build in the '${this.distDir}' directory! Try building your app with 'next build' before starting the server.`) process.exit(1) } this.buildId = !dev ? this.readBuildId() : '-' this.renderOpts = { dev, staticMarkup, - dir: this.dir, - dist: this.dist, + distDir: this.distDir, hotReloader: this.hotReloader, buildId: this.buildId, - availableChunks: dev ? {} : getAvailableChunks(this.dir, this.dist), + availableChunks: dev ? {} : getAvailableChunks(this.distDir), generateEtags } @@ -159,13 +152,13 @@ export default class Server { if (!this.dev) { res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') } - const p = join(this.dir, this.dist, 'chunks', params.name) + const p = join(this.distDir, 'chunks', params.name) await this.serveStatic(req, res, p) }, // This is to support, webpack dynamic import support with HMR '/_next/webpack/:id': async (req, res, params) => { - const p = join(this.dir, this.dist, 'chunks', params.id) + const p = join(this.distDir, 'chunks', params.id) await this.serveStatic(req, res, p) }, @@ -181,7 +174,7 @@ export default class Server { } } - const path = join(this.dir, this.dist, 'bundles', 'pages', `${page}.js.map`) + const path = join(this.distDir, 'bundles', 'pages', `${page}.js.map`) await serveStatic(req, res, path) }, @@ -207,7 +200,7 @@ export default class Server { } } - const p = join(this.dir, this.dist, 'bundles', 'pages', `${page}.js`) + const p = join(this.distDir, 'bundles', 'pages', `${page}.js`) // [production] If the page is not exists, we need to send a proper Next.js style 404 // Otherwise, it'll affect the multi-zones feature. @@ -230,7 +223,7 @@ export default class Server { res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') } } - const p = join(this.dir, this.dist, 'static', ...(params.path || [])) + const p = join(this.distDir, 'static', ...(params.path || [])) await this.serveStatic(req, res, p) }, @@ -315,7 +308,7 @@ export default class Server { return this.handleRequest(req, res, parsedUrl) } - if (blockedPages[pathname]) { + if (BLOCKED_PAGES.indexOf(pathname) !== -1) { return await this.render404(req, res, parsedUrl) } @@ -408,7 +401,7 @@ export default class Server { isServeableUrl (path) { const resolved = resolve(path) if ( - resolved.indexOf(join(this.dir, this.dist) + sep) !== 0 && + resolved.indexOf(join(this.distDir) + sep) !== 0 && resolved.indexOf(join(this.dir, 'static') + sep) !== 0 ) { // Seems like the user is trying to traverse the filesystem. @@ -419,7 +412,7 @@ export default class Server { } readBuildId () { - const buildIdPath = join(this.dir, this.dist, 'BUILD_ID') + const buildIdPath = join(this.distDir, BUILD_ID_FILE) const buildId = fs.readFileSync(buildIdPath, 'utf8') return buildId.trim() } diff --git a/server/render.js b/server/render.js index 3b0ef23d..c09d9c38 100644 --- a/server/render.js +++ b/server/render.js @@ -42,8 +42,8 @@ async function doRender (req, res, pathname, query, { assetPrefix, runtimeConfig, availableChunks, - dist, - dir = process.cwd(), + distDir, + dir, dev = false, staticMarkup = false, nextExport = false @@ -56,11 +56,11 @@ async function doRender (req, res, pathname, query, { await ensurePage(page, { dir, hotReloader }) } - const documentPath = join(dir, dist, SERVER_DIRECTORY, 'bundles', 'pages', '_document') - const appPath = join(dir, dist, SERVER_DIRECTORY, 'bundles', 'pages', '_app') - const buildManifest = require(join(dir, dist, BUILD_MANIFEST)) + const documentPath = join(distDir, SERVER_DIRECTORY, 'bundles', 'pages', '_document') + const appPath = join(distDir, SERVER_DIRECTORY, 'bundles', 'pages', '_app') + const buildManifest = require(join(distDir, BUILD_MANIFEST)) let [Component, Document, App] = await Promise.all([ - requirePage(page, {dir, dist}), + requirePage(page, {distDir}), require(documentPath), require(appPath) ]) @@ -105,7 +105,7 @@ async function doRender (req, res, pathname, query, { } finally { head = Head.rewind() || defaultHead() } - const chunks = loadChunks({ dev, dir, dist, availableChunks }) + const chunks = loadChunks({ dev, distDir, availableChunks }) return { html, head, errorHtml, chunks, buildManifest } } @@ -230,7 +230,7 @@ async function ensurePage (page, { dir, hotReloader }) { await hotReloader.ensurePage(page) } -function loadChunks ({ dev, dir, dist, availableChunks }) { +function loadChunks ({ dev, distDir, availableChunks }) { const flushedChunks = flushChunks() const response = { names: [], @@ -238,7 +238,7 @@ function loadChunks ({ dev, dir, dist, availableChunks }) { } if (dev) { - availableChunks = getAvailableChunks(dir, dist) + availableChunks = getAvailableChunks(distDir) } for (var chunk of flushedChunks) { diff --git a/server/require.js b/server/require.js index 816fe2be..f85f7ade 100644 --- a/server/require.js +++ b/server/require.js @@ -27,8 +27,8 @@ export function normalizePagePath (page) { return page } -export function getPagePath (page, {dir, dist}) { - const serverBuildPath = join(dir, dist, SERVER_DIRECTORY) +export function getPagePath (page, {distDir}) { + const serverBuildPath = join(distDir, SERVER_DIRECTORY) const pagesManifest = require(join(serverBuildPath, PAGES_MANIFEST)) try { @@ -45,7 +45,7 @@ export function getPagePath (page, {dir, dist}) { return join(serverBuildPath, pagesManifest[page]) } -export default async function requirePage (page, {dir, dist}) { - const pagePath = getPagePath(page, {dir, dist}) +export default async function requirePage (page, {distDir}) { + const pagePath = getPagePath(page, {distDir}) return require(pagePath) } diff --git a/server/utils.js b/server/utils.js index 3e546e8b..a233aeb8 100644 --- a/server/utils.js +++ b/server/utils.js @@ -4,8 +4,8 @@ import { readdirSync, existsSync } from 'fs' export const IS_BUNDLED_PAGE = /^bundles[/\\]pages.*\.js$/ export const MATCH_ROUTE_NAME = /^bundles[/\\]pages[/\\](.*)\.js$/ -export function getAvailableChunks (dir, dist) { - const chunksDir = join(dir, dist, 'chunks') +export function getAvailableChunks (distDir) { + const chunksDir = join(distDir, 'chunks') if (!existsSync(chunksDir)) return {} const chunksMap = {} diff --git a/test/integration/dist-dir/test/index.test.js b/test/integration/dist-dir/test/index.test.js index 2e7b99e1..984ebf1b 100644 --- a/test/integration/dist-dir/test/index.test.js +++ b/test/integration/dist-dir/test/index.test.js @@ -2,6 +2,7 @@ import { join } from 'path' import { existsSync } from 'fs' +import {BUILD_ID_FILE} from 'next/constants' import { nextServer, nextBuild, @@ -39,10 +40,10 @@ describe('Production Usage', () => { describe('File locations', () => { it('should build the app within the given `dist` directory', () => { - expect(existsSync(join(__dirname, '/../dist/BUILD_ID'))).toBeTruthy() + expect(existsSync(join(__dirname, `/../dist/${BUILD_ID_FILE}`))).toBeTruthy() }) it('should not build the app within the default `.next` directory', () => { - expect(existsSync(join(__dirname, '/../.next/BUILD_ID'))).toBeFalsy() + expect(existsSync(join(__dirname, `/../.next/${BUILD_ID_FILE}`))).toBeFalsy() }) }) }) diff --git a/test/isolated/require-page.test.js b/test/isolated/require-page.test.js index de7c63ab..1c200d0c 100644 --- a/test/isolated/require-page.test.js +++ b/test/isolated/require-page.test.js @@ -5,7 +5,8 @@ import {SERVER_DIRECTORY} from 'next/constants' import requirePage, {getPagePath, normalizePagePath, pageNotFoundError} from '../../dist/server/require' const sep = '/' -const pathToBundles = join(__dirname, '_resolvedata', SERVER_DIRECTORY, 'bundles', 'pages') +const distDir = join(__dirname, '_resolvedata') +const pathToBundles = join(distDir, SERVER_DIRECTORY, 'bundles', 'pages') describe('pageNotFoundError', () => { it('Should throw error with ENOENT code', () => { @@ -41,39 +42,39 @@ describe('normalizePagePath', () => { describe('getPagePath', () => { it('Should append /index to the / page', () => { - const pagePath = getPagePath('/', {dir: __dirname, dist: '_resolvedata'}) + const pagePath = getPagePath('/', {distDir}) expect(pagePath).toBe(join(pathToBundles, `${sep}index.js`)) }) it('Should prepend / when a page does not have it', () => { - const pagePath = getPagePath('_error', {dir: __dirname, dist: '_resolvedata'}) + const pagePath = getPagePath('_error', {distDir}) expect(pagePath).toBe(join(pathToBundles, `${sep}_error.js`)) }) it('Should throw with paths containing ../', () => { - expect(() => getPagePath('/../../package.json', {dir: __dirname, dist: '_resolvedata'})).toThrow() + expect(() => getPagePath('/../../package.json', {distDir})).toThrow() }) }) describe('requirePage', () => { it('Should require /index.js when using /', async () => { - const page = await requirePage('/', {dir: __dirname, dist: '_resolvedata'}) + const page = await requirePage('/', {distDir}) expect(page.test).toBe('hello') }) it('Should require /index.js when using /index', async () => { - const page = await requirePage('/index', {dir: __dirname, dist: '_resolvedata'}) + const page = await requirePage('/index', {distDir}) expect(page.test).toBe('hello') }) it('Should require /world.js when using /world', async () => { - const page = await requirePage('/world', {dir: __dirname, dist: '_resolvedata'}) + const page = await requirePage('/world', {distDir}) expect(page.test).toBe('world') }) it('Should throw when using /../../test.js', async () => { try { - await requirePage('/../../test', {dir: __dirname, dist: '_resolvedata'}) + await requirePage('/../../test', {distDir}) } catch (err) { expect(err.code).toBe('ENOENT') } @@ -81,7 +82,7 @@ describe('requirePage', () => { it('Should throw when using non existent pages like /non-existent.js', async () => { try { - await requirePage('/non-existent', {dir: __dirname, dist: '_resolvedata'}) + await requirePage('/non-existent', {distDir}) } catch (err) { expect(err.code).toBe('ENOENT') } @@ -89,7 +90,7 @@ describe('requirePage', () => { it('Should bubble up errors in the child component', async () => { try { - await requirePage('/non-existent-child', {dir: __dirname, dist: '_resolvedata'}) + await requirePage('/non-existent-child', {distDir}) } catch (err) { expect(err.code).toBe('MODULE_NOT_FOUND') }