1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Compile pages to .next/static directory (#4828)

* Compile pages to .next/static/<buildid>/pages/<page>

* Fix test

* Export class instead of using exports

* Use constant for static directory

* Add comment about what the middleware does
This commit is contained in:
Tim Neutkens 2018-07-25 13:45:42 +02:00 committed by GitHub
parent c090a57e77
commit 475b426ed1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 134 additions and 178 deletions

View file

@ -128,7 +128,7 @@ export default async function getBaseWebpackConfig (dir: string, {dev = false, i
.filter((p) => !!p)
const outputPath = path.join(dir, config.distDir, isServer ? SERVER_DIRECTORY : '')
const pagesEntries = await getPages(dir, {nextPagesDir: DEFAULT_PAGES_DIR, dev, isServer, pageExtensions: config.pageExtensions.join('|')})
const pagesEntries = await getPages(dir, {nextPagesDir: DEFAULT_PAGES_DIR, dev, buildId, isServer, pageExtensions: config.pageExtensions.join('|')})
const totalPages = Object.keys(pagesEntries).length
const clientEntries = !isServer ? {
// Backwards compatibility

View file

@ -3,7 +3,7 @@ import { RawSource } from 'webpack-sources'
import {PAGES_MANIFEST, ROUTE_NAME_REGEX} from '../../../lib/constants'
// This plugin creates a pages-manifest.json from page entrypoints.
// This is used for mapping paths like `/` to `.next/dist/bundles/pages/index.js` when doing SSR
// This is used for mapping paths like `/` to `.next/server/static/<buildid>/pages/index.js` when doing SSR
// It's also used by next export to provide defaultPathMap
export default class PagesManifestPlugin {
apply (compiler: any) {

View file

@ -33,7 +33,7 @@ function buildManifest (compiler, compilation) {
return manifest
}
class ReactLoadablePlugin {
export class ReactLoadablePlugin {
constructor (opts = {}) {
this.filename = opts.filename
}
@ -54,5 +54,3 @@ class ReactLoadablePlugin {
})
}
}
exports.ReactLoadablePlugin = ReactLoadablePlugin

View file

@ -1,13 +1,14 @@
import path from 'path'
import promisify from '../../lib/promisify'
import globModule from 'glob'
import {CLIENT_STATIC_FILES_PATH} from '../../lib/constants'
const glob = promisify(globModule)
export async function getPages (dir, {nextPagesDir, dev, isServer, pageExtensions}) {
export async function getPages (dir, {nextPagesDir, dev, buildId, isServer, pageExtensions}) {
const pageFiles = await getPagePaths(dir, {dev, isServer, pageExtensions})
return getPageEntries(pageFiles, {nextPagesDir, isServer, pageExtensions})
return getPageEntries(pageFiles, {nextPagesDir, buildId, isServer, pageExtensions})
}
export async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
@ -25,7 +26,7 @@ export async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
}
// Convert page path into single entry
export function createEntry (filePath, {name, pageExtensions} = {}) {
export function createEntry (filePath, {buildId = '', name, pageExtensions} = {}) {
const parsedPath = path.parse(filePath)
let entryName = name || filePath
@ -41,35 +42,35 @@ export function createEntry (filePath, {name, pageExtensions} = {}) {
}
return {
name: path.join('bundles', entryName),
name: path.join(CLIENT_STATIC_FILES_PATH, buildId, entryName),
files: [parsedPath.root ? filePath : `./${filePath}`] // The entry always has to be an array.
}
}
// Convert page paths into entries
export function getPageEntries (pagePaths, {nextPagesDir, isServer = false, pageExtensions} = {}) {
export function getPageEntries (pagePaths, {nextPagesDir, buildId, isServer = false, pageExtensions} = {}) {
const entries = {}
for (const filePath of pagePaths) {
const entry = createEntry(filePath, {pageExtensions})
const entry = createEntry(filePath, {pageExtensions, buildId})
entries[entry.name] = entry.files
}
const appPagePath = path.join(nextPagesDir, '_app.js')
const appPageEntry = createEntry(appPagePath, {name: 'pages/_app.js'}) // default app.js
const appPageEntry = createEntry(appPagePath, {buildId, name: 'pages/_app.js'}) // default app.js
if (!entries[appPageEntry.name]) {
entries[appPageEntry.name] = appPageEntry.files
}
const errorPagePath = path.join(nextPagesDir, '_error.js')
const errorPageEntry = createEntry(errorPagePath, {name: 'pages/_error.js'}) // default error.js
const errorPageEntry = createEntry(errorPagePath, {buildId, name: 'pages/_error.js'}) // default error.js
if (!entries[errorPageEntry.name]) {
entries[errorPageEntry.name] = errorPageEntry.files
}
if (isServer) {
const documentPagePath = path.join(nextPagesDir, '_document.js')
const documentPageEntry = createEntry(documentPagePath, {name: 'pages/_document.js'}) // default _document.js
const documentPageEntry = createEntry(documentPagePath, {buildId, name: 'pages/_document.js'}) // default _document.js
if (!entries[documentPageEntry.name]) {
entries[documentPageEntry.name] = documentPageEntry.files
}

View file

@ -14,9 +14,12 @@ export const BLOCKED_PAGES = [
'/_app',
'/_error'
]
export const IS_BUNDLED_PAGE_REGEX = /^bundles[/\\]pages.*\.js$/
export const ROUTE_NAME_REGEX = /^bundles[/\\]pages[/\\](.*)\.js$/
// matches static/<buildid>/pages/<page>.js
export const IS_BUNDLED_PAGE_REGEX = /^static[/\\][^/\\]+[/\\]pages.*\.js$/
// matches static/<buildid>/pages/:page*.js
export const ROUTE_NAME_REGEX = /^static[/\\][^/\\]+[/\\]pages[/\\](.*)\.js$/
export const NEXT_PROJECT_ROOT = join(__dirname, '..', '..')
export const NEXT_PROJECT_ROOT_DIST = join(NEXT_PROJECT_ROOT, 'dist')
export const NEXT_PROJECT_ROOT_NODE_MODULES = join(NEXT_PROJECT_ROOT, 'node_modules')
export const DEFAULT_PAGES_DIR = join(NEXT_PROJECT_ROOT_DIST, 'pages')
export const CLIENT_STATIC_FILES_PATH = 'static'

View file

@ -69,7 +69,7 @@ export default class PageLoader {
const scriptRoute = route === '/' ? '/index.js' : `${route}.js`
const script = document.createElement('script')
const url = `${this.assetPrefix}/_next/${encodeURIComponent(this.buildId)}/page${scriptRoute}`
const url = `${this.assetPrefix}/_next/static/${encodeURIComponent(this.buildId)}/pages${scriptRoute}`
script.src = url
script.onerror = () => {
const error = new Error(`Error when loading route: ${route}`)

View file

@ -99,7 +99,6 @@
"unfetch": "3.0.0",
"url": "0.11.0",
"uuid": "3.1.0",
"walk": "2.3.9",
"webpack": "4.16.1",
"webpack-dev-middleware": "3.1.3",
"webpack-hot-middleware": "2.22.2",

View file

@ -80,9 +80,9 @@ export class Head extends Component {
return <head {...this.props}>
{(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))}
{page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} as='script' nonce={this.props.nonce} />}
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_app.js`} as='script' nonce={this.props.nonce} />
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error.js`} as='script' nonce={this.props.nonce} />
{page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}`} as='script' nonce={this.props.nonce} />}
<link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_app.js`} as='script' nonce={this.props.nonce} />
<link rel='preload' href={`${assetPrefix}/_next/static/${buildId}/pages/_error.js`} as='script' nonce={this.props.nonce} />
{this.getPreloadDynamicChunks()}
{this.getPreloadMainLinks()}
{styles || null}
@ -168,9 +168,9 @@ export class NextScript extends Component {
})`: ''}
`
}} />}
{page !== '/_error' && <script async id={`__NEXT_PAGE__${pathname}`} src={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} nonce={this.props.nonce} />}
<script async id={`__NEXT_PAGE__/_app`} src={`${assetPrefix}/_next/${buildId}/page/_app.js`} nonce={this.props.nonce} />
<script async id={`__NEXT_PAGE__/_error`} src={`${assetPrefix}/_next/${buildId}/page/_error.js`} nonce={this.props.nonce} />
{page !== '/_error' && <script async id={`__NEXT_PAGE__${pathname}`} src={`${assetPrefix}/_next/static/${buildId}/pages${pagePathname}`} nonce={this.props.nonce} />}
<script async id={`__NEXT_PAGE__/_app`} src={`${assetPrefix}/_next/static/${buildId}/pages/_app.js`} nonce={this.props.nonce} />
<script async id={`__NEXT_PAGE__/_error`} src={`${assetPrefix}/_next/static/${buildId}/pages/_error.js`} nonce={this.props.nonce} />
{staticMarkup ? null : this.getDynamicChunks()}
{staticMarkup ? null : this.getScripts()}
</Fragment>

View file

@ -1,11 +1,10 @@
import del from 'del'
import cp from 'recursive-copy'
import mkdirp from 'mkdirp-then'
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, BUILD_ID_FILE} from '../lib/constants'
import {PHASE_EXPORT, SERVER_DIRECTORY, PAGES_MANIFEST, CONFIG_FILE, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH} from '../lib/constants'
import { renderToHTML } from './render'
import { setAssetPrefix } from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
@ -51,11 +50,11 @@ export default async function (dir, options, configuration) {
}
// Copy .next/static directory
if (existsSync(join(distDir, 'static'))) {
if (existsSync(join(distDir, CLIENT_STATIC_FILES_PATH))) {
log(' copying "static build" directory')
await cp(
join(distDir, 'static'),
join(outDir, '_next', 'static')
join(distDir, CLIENT_STATIC_FILES_PATH),
join(outDir, '_next', CLIENT_STATIC_FILES_PATH)
)
}
@ -70,8 +69,6 @@ export default async function (dir, options, configuration) {
)
}
await copyPages(distDir, outDir, buildId)
// Get the exportPathMap from the config file
if (typeof nextConfig.exportPathMap !== 'function') {
console.log(`> No "exportPathMap" found in "${CONFIG_FILE}". Generating map from "./pages"`)
@ -149,40 +146,3 @@ export default async function (dir, options, configuration) {
console.log(message)
}
}
function copyPages (distDir, outDir, buildId) {
// TODO: do some proper error handling
return new Promise((resolve, reject) => {
const nextBundlesDir = join(distDir, 'bundles', 'pages')
const walker = walk.walk(nextBundlesDir, { followLinks: false })
walker.on('file', (root, stat, next) => {
const filename = stat.name
const fullFilePath = `${root}${sep}${filename}`
const relativeFilePath = fullFilePath.replace(nextBundlesDir, '')
// We should not expose this page to the client side since
// it has no use in the client side.
if (relativeFilePath === `${sep}_document.js`) {
next()
return
}
let destFilePath = null
if (relativeFilePath === `${sep}index.js`) {
destFilePath = join(outDir, '_next', buildId, 'page', relativeFilePath)
} else if (/index\.js$/.test(filename)) {
const newRelativeFilePath = relativeFilePath.replace(`${sep}index.js`, '.js')
destFilePath = join(outDir, '_next', buildId, 'page', newRelativeFilePath)
} else {
destFilePath = join(outDir, '_next', buildId, 'page', relativeFilePath)
}
cp(fullFilePath, destFilePath)
.then(next)
.catch(reject)
})
walker.on('end', resolve)
})
}

View file

@ -8,7 +8,12 @@ import getBaseWebpackConfig from '../build/webpack'
import {
addCorsSupport
} from './utils'
import {IS_BUNDLED_PAGE_REGEX, ROUTE_NAME_REGEX} from '../lib/constants'
import {IS_BUNDLED_PAGE_REGEX, ROUTE_NAME_REGEX, BLOCKED_PAGES, CLIENT_STATIC_FILES_PATH} from '../lib/constants'
import pathMatch from './lib/path-match'
import {renderScriptError} from './render'
const route = pathMatch()
const matchNextPageBundleRequest = route('/_next/static/:buildId/pages/:path*.js(.map)?')
// Recursively look up the issuer till it ends up at the root
function findEntryModule (issuer) {
@ -46,7 +51,7 @@ function erroredPages (compilation, options = {enhanceName: (name) => name}) {
}
export default class HotReloader {
constructor (dir, { quiet, config, buildId } = {}) {
constructor (dir, { config, buildId } = {}) {
this.buildId = buildId
this.dir = dir
this.middlewares = []
@ -63,7 +68,7 @@ export default class HotReloader {
this.config = config
}
async run (req, res) {
async run (req, res, parsedUrl) {
// Usually CORS support is not needed for the hot-reloader (this is dev only feature)
// With when the app runs for multi-zones support behind a proxy,
// the current page is trying to access this URL via assetPrefix.
@ -73,6 +78,42 @@ export default class HotReloader {
return
}
// When a request comes in that is a page bundle, e.g. /_next/static/<buildid>/pages/index.js
// we have to compile the page using on-demand-entries, this middleware will handle doing that
// by adding the page to on-demand-entries, waiting till it's done
// and then the bundle will be served like usual by the actual route in server/index.js
const handlePageBundleRequest = async (req, res, parsedUrl) => {
const {pathname} = parsedUrl
const params = matchNextPageBundleRequest(pathname)
if (!params) {
return {}
}
if (params.buildId !== this.buildId) {
return
}
const page = `/${params.path.join('/')}`
if (BLOCKED_PAGES.indexOf(page) === -1) {
try {
await this.ensurePage(page)
} catch (error) {
await renderScriptError(req, res, page, error)
return {finished: true}
}
const errors = await this.getCompilationErrors(page)
if (errors.length > 0) {
await renderScriptError(req, res, page, errors[0])
return {finished: true}
}
}
return {}
}
const {finished} = await handlePageBundleRequest(req, res, parsedUrl)
for (const fn of this.middlewares) {
await new Promise((resolve, reject) => {
fn(req, res, (err) => {
@ -81,6 +122,8 @@ export default class HotReloader {
})
})
}
return {finished}
}
async clean () {
@ -121,8 +164,8 @@ export default class HotReloader {
await this.clean()
const configs = await Promise.all([
getBaseWebpackConfig(this.dir, { dev: true, isServer: false, config: this.config }),
getBaseWebpackConfig(this.dir, { dev: true, isServer: true, config: this.config })
getBaseWebpackConfig(this.dir, { dev: true, isServer: false, config: this.config, buildId: this.buildId }),
getBaseWebpackConfig(this.dir, { dev: true, isServer: true, config: this.config, buildId: this.buildId })
])
const compiler = webpack(configs)
@ -158,7 +201,7 @@ export default class HotReloader {
// We only watch `_document` for changes on the server compilation
// the rest of the files will be triggered by the client compilation
const documentChunk = compilation.chunks.find(c => c.name === normalize('bundles/pages/_document.js'))
const documentChunk = compilation.chunks.find(c => c.name === normalize(`static/${this.buildId}/pages/_document.js`))
// If the document chunk can't be found we do nothing
if (!documentChunk) {
console.warn('_document.js chunk not found')
@ -209,7 +252,7 @@ export default class HotReloader {
// and to update error content
const failed = failedChunkNames
const rootDir = join('bundles', 'pages')
const rootDir = join(CLIENT_STATIC_FILES_PATH, this.buildId, 'pages')
for (const n of new Set([...added, ...succeeded, ...removed, ...failed])) {
const route = toRoute(relative(rootDir, n))
@ -274,6 +317,7 @@ export default class HotReloader {
const onDemandEntries = onDemandEntryHandler(webpackDevMiddleware, multiCompiler.compilers, {
dir: this.dir,
buildId: this.buildId,
dev: true,
reload: this.reload.bind(this),
pageExtensions: this.config.pageExtensions,

View file

@ -4,18 +4,16 @@ import { parse as parseUrl } from 'url'
import { parse as parseQs } from 'querystring'
import fs from 'fs'
import http, { STATUS_CODES } from 'http'
import promisify from '../lib/promisify'
import {
renderToHTML,
renderErrorToHTML,
sendHTML,
serveStatic,
renderScriptError
serveStatic
} from './render'
import Router from './router'
import { isInternalUrl } from './utils'
import loadConfig from './config'
import {PHASE_PRODUCTION_SERVER, PHASE_DEVELOPMENT_SERVER, BLOCKED_PAGES, BUILD_ID_FILE} from '../lib/constants'
import {PHASE_PRODUCTION_SERVER, PHASE_DEVELOPMENT_SERVER, BLOCKED_PAGES, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH} from '../lib/constants'
import * as asset from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
import { isResSent } from '../lib/utils'
@ -23,8 +21,6 @@ import { isResSent } from '../lib/utils'
// We need to go up one more level since we are in the `dist` directory
import pkg from '../../package'
const access = promisify(fs.access)
export default class Server {
constructor ({ dir = '.', dev = false, staticMarkup = false, quiet = false, conf = null } = {}) {
this.dir = resolve(dir)
@ -44,8 +40,8 @@ export default class Server {
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.hotReloader = dev ? this.getHotReloader(this.dir, { quiet, config: this.nextConfig, buildId: this.buildId }) : null
this.buildId = this.readBuildId(dev)
this.hotReloader = dev ? this.getHotReloader(this.dir, { config: this.nextConfig, buildId: this.buildId }) : null
this.renderOpts = {
dev,
staticMarkup,
@ -128,69 +124,19 @@ export default class Server {
async defineRoutes () {
const routes = {
'/_next/:buildId/page/:path*.js.map': async (req, res, params) => {
const paths = params.path || ['']
const page = `/${paths.join('/')}`
if (this.dev) {
try {
await this.hotReloader.ensurePage(page)
} catch (err) {
await this.render404(req, res)
}
}
const path = join(this.distDir, 'bundles', 'pages', `${page}.js.map`)
await serveStatic(req, res, path)
},
'/_next/:buildId/page/:path*.js': async (req, res, params) => {
const paths = params.path || ['']
const page = `/${paths.join('/')}`
if (!this.handleBuildId(params.buildId, res)) {
const error = new Error('INVALID_BUILD_ID')
return await renderScriptError(req, res, page, error)
}
if (this.dev && page !== '/_error' && page !== '/_app') {
try {
await this.hotReloader.ensurePage(page)
} catch (error) {
return await renderScriptError(req, res, page, error)
}
const compilationErr = await this.getCompilationError(page)
if (compilationErr) {
return await renderScriptError(req, res, page, compilationErr)
}
}
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.
try {
await access(p, (fs.constants || fs).R_OK)
} catch (err) {
return await renderScriptError(req, res, page, { code: 'ENOENT' })
}
await this.serveStatic(req, res, p)
},
'/_next/static/:path*': async (req, res, params) => {
// The commons folder holds commonschunk files
// The chunks folder holds dynamic entries
// The buildId folder holds pages and potentially other assets. As buildId changes per build it can be long-term cached.
// In development they don't have a hash, and shouldn't be cached by the browser.
if (params.path[0] === 'commons' || params.path[0] === 'chunks') {
if (params.path[0] === 'commons' || params.path[0] === 'chunks' || params.path[0] === this.buildId) {
if (this.dev) {
res.setHeader('Cache-Control', 'no-store, must-revalidate')
} else {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
}
}
const p = join(this.distDir, 'static', ...(params.path || []))
const p = join(this.distDir, CLIENT_STATIC_FILES_PATH, ...(params.path || []))
await this.serveStatic(req, res, p)
},
@ -269,7 +215,10 @@ export default class Server {
async run (req, res, parsedUrl) {
if (this.hotReloader) {
await this.hotReloader.run(req, res)
const {finished} = await this.hotReloader.run(req, res, parsedUrl)
if (finished) {
return
}
}
const fn = this.router.match(req, res, parsedUrl)
@ -392,7 +341,10 @@ export default class Server {
return true
}
readBuildId () {
readBuildId (dev) {
if (dev) {
return 'development'
}
const buildIdPath = join(this.distDir, BUILD_ID_FILE)
const buildId = fs.readFileSync(buildIdPath, 'utf8')
return buildId.trim()

View file

@ -17,6 +17,7 @@ const glob = promisify(globModule)
const access = promisify(fs.access)
export default function onDemandEntryHandler (devMiddleware, compilers, {
buildId,
dir,
dev,
reload,
@ -163,7 +164,7 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
const pathname = join(dir, relativePathToPage)
const {name, files} = createEntry(relativePathToPage, {pageExtensions: extensions})
const {name, files} = createEntry(relativePathToPage, {buildId, pageExtensions: extensions})
await new Promise((resolve, reject) => {
const entryInfo = entries[page]

View file

@ -10,10 +10,10 @@ import { loadGetInitialProps, isResSent } from '../lib/utils'
import Head, { defaultHead } from '../lib/head'
import ErrorDebug from '../lib/error-debug'
import Loadable from 'react-loadable'
import { BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY } from '../lib/constants'
import { BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH } from '../lib/constants'
// Based on https://github.com/jamiebuilds/react-loadable/pull/132
function getBundles (manifest, moduleIds) {
function getDynamicImportBundles (manifest, moduleIds) {
return moduleIds.reduce((bundles, moduleId) => {
if (typeof manifest[moduleId] === 'undefined') {
return bundles
@ -62,8 +62,8 @@ async function doRender (req, res, pathname, query, {
await ensurePage(page, { dir, hotReloader })
}
const documentPath = join(distDir, SERVER_DIRECTORY, 'bundles', 'pages', '_document')
const appPath = join(distDir, SERVER_DIRECTORY, 'bundles', 'pages', '_app')
const documentPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_document')
const appPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_app')
let [buildManifest, reactLoadableManifest, Component, Document, App] = await Promise.all([
require(join(distDir, BUILD_MANIFEST)),
require(join(distDir, REACT_LOADABLE_MANIFEST)),
@ -144,7 +144,7 @@ async function doRender (req, res, pathname, query, {
}
const docProps = await loadGetInitialProps(Document, { ...ctx, renderPage })
const dynamicImports = getBundles(reactLoadableManifest, reactLoadableModules)
const dynamicImports = getDynamicImportBundles(reactLoadableManifest, reactLoadableModules)
if (isResSent(res)) return

View file

@ -30,22 +30,22 @@ describe('On Demand Entries', () => {
})
it('should compile pages for JSON page requests', async () => {
const pageContent = await renderViaHTTP(context.appPort, '/_next/-/page/about.js')
const pageContent = await renderViaHTTP(context.appPort, '/_next/static/development/pages/about.js')
expect(pageContent.includes('About Page')).toBeTruthy()
})
it('should dispose inactive pages', async () => {
const indexPagePath = resolve(__dirname, '../.next/bundles/pages/index.js')
const indexPagePath = resolve(__dirname, '../.next/static/development/pages/index.js')
expect(existsSync(indexPagePath)).toBeTruthy()
// Render two pages after the index, since the server keeps at least two pages
await renderViaHTTP(context.appPort, '/about')
await renderViaHTTP(context.appPort, '/_next/on-demand-entries-ping', {page: '/about'})
const aboutPagePath = resolve(__dirname, '../.next/bundles/pages/about.js')
const aboutPagePath = resolve(__dirname, '../.next/static/development/pages/about.js')
await renderViaHTTP(context.appPort, '/third')
await renderViaHTTP(context.appPort, '/_next/on-demand-entries-ping', {page: '/third'})
const thirdPagePath = resolve(__dirname, '../.next/bundles/pages/third.js')
const thirdPagePath = resolve(__dirname, '../.next/static/development/pages/third.js')
// Wait maximum of jasmine.DEFAULT_TIMEOUT_INTERVAL checking
// for disposing /about

View file

@ -63,7 +63,7 @@ describe('Production Usage', () => {
const resources = []
// test a regular page
resources.push(`${url}${buildId}/page/index.js`)
resources.push(`${url}static/${buildId}/pages/index.js`)
// test dynamic chunk
resources.push(url + reactLoadableManifest['../../components/hello1'][0].publicPath)

View file

@ -1,6 +1,6 @@
{
"/index": "bundles/pages/index.js",
"/world": "bundles/pages/world.js",
"/_error": "bundles/pages/_error.js",
"/non-existent-child": "bundles/pages/non-existent-child.js"
"/index": "static/development/pages/index.js",
"/world": "static/development/pages/world.js",
"/_error": "static/development/pages/_error.js",
"/non-existent-child": "static/development/pages/non-existent-child.js"
}

View file

@ -1,12 +1,12 @@
/* global describe, it, expect */
import { join } from 'path'
import {SERVER_DIRECTORY} from 'next/constants'
import {SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH} from 'next/constants'
import requirePage, {getPagePath, normalizePagePath, pageNotFoundError} from '../../dist/server/require'
const sep = '/'
const distDir = join(__dirname, '_resolvedata')
const pathToBundles = join(distDir, SERVER_DIRECTORY, 'bundles', 'pages')
const pathToBundles = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, 'development', 'pages')
describe('pageNotFoundError', () => {
it('Should throw error with ENOENT code', () => {

View file

@ -3,46 +3,54 @@
import {normalize, join} from 'path'
import {getPageEntries, createEntry} from '../../dist/build/webpack/utils'
const buildId = 'development'
describe('createEntry', () => {
it('Should turn a path into a page entry', () => {
const entry = createEntry('pages/index.js')
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
expect(entry.name).toBe(normalize(`static/pages/index.js`))
expect(entry.files[0]).toBe('./pages/index.js')
})
it('Should have a custom name', () => {
const entry = createEntry('pages/index.js', {name: 'something-else.js'})
expect(entry.name).toBe(normalize('bundles/something-else.js'))
expect(entry.name).toBe(normalize(`static/something-else.js`))
expect(entry.files[0]).toBe('./pages/index.js')
})
it('Should allow custom extension like .ts to be turned into .js', () => {
const entry = createEntry('pages/index.ts', {pageExtensions: ['js', 'ts'].join('|')})
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
expect(entry.name).toBe(normalize('static/pages/index.js'))
expect(entry.files[0]).toBe('./pages/index.ts')
})
it('Should allow custom extension like .jsx to be turned into .js', () => {
const entry = createEntry('pages/index.jsx', {pageExtensions: ['jsx', 'js'].join('|')})
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
expect(entry.name).toBe(normalize('static/pages/index.js'))
expect(entry.files[0]).toBe('./pages/index.jsx')
})
it('Should allow custom extension like .tsx to be turned into .js', () => {
const entry = createEntry('pages/index.tsx', {pageExtensions: ['tsx', 'ts'].join('|')})
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
expect(entry.name).toBe(normalize('static/pages/index.js'))
expect(entry.files[0]).toBe('./pages/index.tsx')
})
it('Should allow custom extension like .tsx to be turned into .js with another order', () => {
const entry = createEntry('pages/index.tsx', {pageExtensions: ['ts', 'tsx'].join('|')})
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
expect(entry.name).toBe(normalize('static/pages/index.js'))
expect(entry.files[0]).toBe('./pages/index.tsx')
})
it('Should turn pages/blog/index.js into pages/blog.js', () => {
const entry = createEntry('pages/blog/index.js')
expect(entry.name).toBe(normalize('bundles/pages/blog.js'))
expect(entry.name).toBe(normalize('static/pages/blog.js'))
expect(entry.files[0]).toBe('./pages/blog/index.js')
})
it('Should add buildId when provided', () => {
const entry = createEntry('pages/blog/index.js', {buildId})
expect(entry.name).toBe(normalize(`static/${buildId}/pages/blog.js`))
expect(entry.files[0]).toBe('./pages/blog/index.js')
})
})
@ -53,30 +61,30 @@ describe('getPageEntries', () => {
it('Should return paths', () => {
const pagePaths = ['pages/index.js']
const pageEntries = getPageEntries(pagePaths, {nextPagesDir})
expect(pageEntries[normalize('bundles/pages/index.js')][0]).toBe('./pages/index.js')
expect(pageEntries[normalize('static/pages/index.js')][0]).toBe('./pages/index.js')
})
it('Should include default _error', () => {
const pagePaths = ['pages/index.js']
const pageEntries = getPageEntries(pagePaths, {nextPagesDir})
expect(pageEntries[normalize('bundles/pages/_error.js')][0]).toMatch(/dist[/\\]pages[/\\]_error\.js/)
expect(pageEntries[normalize('static/pages/_error.js')][0]).toMatch(/dist[/\\]pages[/\\]_error\.js/)
})
it('Should not include default _error when _error.js is inside the pages directory', () => {
const pagePaths = ['pages/index.js', 'pages/_error.js']
const pageEntries = getPageEntries(pagePaths, {nextPagesDir})
expect(pageEntries[normalize('bundles/pages/_error.js')][0]).toBe('./pages/_error.js')
expect(pageEntries[normalize('static/pages/_error.js')][0]).toBe('./pages/_error.js')
})
it('Should include default _document when isServer is true', () => {
const pagePaths = ['pages/index.js']
const pageEntries = getPageEntries(pagePaths, {nextPagesDir, isServer: true})
expect(pageEntries[normalize('bundles/pages/_document.js')][0]).toMatch(/dist[/\\]pages[/\\]_document\.js/)
expect(pageEntries[normalize('static/pages/_document.js')][0]).toMatch(/dist[/\\]pages[/\\]_document\.js/)
})
it('Should not include default _document when _document.js is inside the pages directory', () => {
const pagePaths = ['pages/index.js', 'pages/_document.js']
const pageEntries = getPageEntries(pagePaths, {nextPagesDir, isServer: true})
expect(pageEntries[normalize('bundles/pages/_document.js')][0]).toBe('./pages/_document.js')
expect(pageEntries[normalize('static/pages/_document.js')][0]).toBe('./pages/_document.js')
})
})

View file

@ -3352,10 +3352,6 @@ foreach@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
foreachasync@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6"
foreground-child@^1.5.3, foreground-child@^1.5.6:
version "1.5.6"
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9"
@ -7651,12 +7647,6 @@ vm-browserify@0.0.4:
dependencies:
indexof "0.0.1"
walk@2.3.9:
version "2.3.9"
resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.9.tgz#31b4db6678f2ae01c39ea9fb8725a9031e558a7b"
dependencies:
foreachasync "^3.0.0"
walkdir@^0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.11.tgz#a16d025eb931bd03b52f308caed0f40fcebe9532"