mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Replace pages-plugin with loader (#5994)
* Remove unused argument * Replace pages-plugin with loader * Add loader-utils types * Remove logs * Bring back previous deposal behavior * Remove console.log * Remove webpack/utils as it’s no longer in use * Remove hot-self-accept-loader * Error Recovery tests * Make hotSelfAccept a noop default loader * Fix windows deleted/added * Remove logging * Remove unused variables * Remove log * Simplify entrypoint generation * Don’t return the function * Fix _app test * Remove code that’s always true * Move aliases to constants * Use alias * Join pages alias in reduce * Default pages differently * Loop over pages instead of manually defining * Move entry generation into common function * Update packages/next/build/webpack/loaders/next-client-pages-loader.ts Co-Authored-By: timneutkens <tim@timneutkens.nl> * Update packages/next/build/webpack/loaders/next-client-pages-loader.ts
This commit is contained in:
parent
3a3347dc5f
commit
9ffd23eeef
67
packages/next/build/entries.ts
Normal file
67
packages/next/build/entries.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import {join} from 'path'
|
||||
import {stringify} from 'querystring'
|
||||
import {PAGES_DIR_ALIAS, DOT_NEXT_ALIAS} from '../lib/constants'
|
||||
import {ServerlessLoaderQuery} from './webpack/loaders/next-serverless-loader'
|
||||
|
||||
type PagesMapping = {
|
||||
[page: string]: string
|
||||
}
|
||||
|
||||
export function createPagesMapping(pagePaths: string[], extensions: string[]): PagesMapping {
|
||||
const pages: PagesMapping = pagePaths.reduce((result: PagesMapping, pagePath): PagesMapping => {
|
||||
const page = `/${pagePath.replace(new RegExp(`\\.+(${extensions.join('|')})$`), '').replace(/\\/g, '/')}`.replace(/\/index$/, '')
|
||||
result[page === '' ? '/' : page] = join(PAGES_DIR_ALIAS, pagePath).replace(/\\/g, '/')
|
||||
return result
|
||||
}, {})
|
||||
|
||||
pages['/_app'] = pages['/_app'] || 'next/dist/pages/_app'
|
||||
pages['/_error'] = pages['/_error'] || 'next/dist/pages/_error'
|
||||
pages['/_document'] = pages['/_document'] || 'next/dist/pages/_document'
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
type WebpackEntrypoints = {
|
||||
[bundle: string]: string|string[]
|
||||
}
|
||||
|
||||
type Entrypoints = {
|
||||
client: WebpackEntrypoints
|
||||
server: WebpackEntrypoints
|
||||
}
|
||||
|
||||
export function createEntrypoints(pages: PagesMapping, target: 'server'|'serverless', buildId: string, config: any): Entrypoints {
|
||||
const client: WebpackEntrypoints = {}
|
||||
const server: WebpackEntrypoints = {}
|
||||
|
||||
const defaultServerlessOptions = {
|
||||
absoluteAppPath: pages['/_app'],
|
||||
absoluteDocumentPath: pages['/_document'],
|
||||
absoluteErrorPath: pages['/_error'],
|
||||
distDir: DOT_NEXT_ALIAS,
|
||||
buildId,
|
||||
assetPrefix: config.assetPrefix,
|
||||
generateEtags: config.generateEtags
|
||||
}
|
||||
|
||||
Object.keys(pages).forEach((page) => {
|
||||
const absolutePagePath = pages[page]
|
||||
const bundleFile = page === '/' ? '/index.js' : `${page}.js`
|
||||
const bundlePath = join('static', buildId, 'pages', bundleFile)
|
||||
if(target === 'serverless') {
|
||||
const serverlessLoaderOptions: ServerlessLoaderQuery = {page, absolutePagePath, ...defaultServerlessOptions}
|
||||
server[join('pages', bundleFile)] = `next-serverless-loader?${stringify(serverlessLoaderOptions)}!`
|
||||
} else if(target === 'server') {
|
||||
server[bundlePath] = [absolutePagePath]
|
||||
}
|
||||
if (page === '/_document') {
|
||||
return
|
||||
}
|
||||
client[bundlePath] = `next-client-pages-loader?${stringify({page, absolutePagePath})}!`
|
||||
})
|
||||
|
||||
return {
|
||||
client,
|
||||
server
|
||||
}
|
||||
}
|
|
@ -9,8 +9,7 @@ import {isWriteable} from './is-writeable'
|
|||
import {runCompiler, CompilerResult} from './compiler'
|
||||
import globModule from 'glob'
|
||||
import {promisify} from 'util'
|
||||
import {stringify} from 'querystring'
|
||||
import {ServerlessLoaderQuery} from './webpack/loaders/next-serverless-loader'
|
||||
import {createPagesMapping, createEntrypoints} from './entries'
|
||||
|
||||
const glob = promisify(globModule)
|
||||
|
||||
|
@ -29,58 +28,11 @@ export default async function build (dir: string, conf = null): Promise<void> {
|
|||
const pagesDir = join(dir, 'pages')
|
||||
|
||||
const pagePaths = await collectPages(pagesDir, config.pageExtensions)
|
||||
type Result = {[page: string]: string}
|
||||
const pages: Result = pagePaths.reduce((result: Result, pagePath): Result => {
|
||||
let page = `/${pagePath.replace(new RegExp(`\\.+(${config.pageExtensions.join('|')})$`), '').replace(/\\/g, '/')}`.replace(/\/index$/, '')
|
||||
page = page === '' ? '/' : page
|
||||
result[page] = pagePath
|
||||
return result
|
||||
}, {})
|
||||
|
||||
let entrypoints
|
||||
if (config.target === 'serverless') {
|
||||
const serverlessEntrypoints: any = {}
|
||||
// Because on Windows absolute paths in the generated code can break because of numbers, eg 1 in the path,
|
||||
// we have to use a private alias
|
||||
const pagesDirAlias = 'private-next-pages'
|
||||
const dotNextDirAlias = 'private-dot-next'
|
||||
const absoluteAppPath = pages['/_app'] ? join(pagesDirAlias, pages['/_app']).replace(/\\/g, '/') : 'next/dist/pages/_app'
|
||||
const absoluteDocumentPath = pages['/_document'] ? join(pagesDirAlias, pages['/_document']).replace(/\\/g, '/') : 'next/dist/pages/_document'
|
||||
const absoluteErrorPath = pages['/_error'] ? join(pagesDirAlias, pages['/_error']).replace(/\\/g, '/') : 'next/dist/pages/_error'
|
||||
|
||||
const defaultOptions = {
|
||||
absoluteAppPath,
|
||||
absoluteDocumentPath,
|
||||
absoluteErrorPath,
|
||||
distDir: dotNextDirAlias,
|
||||
buildId,
|
||||
assetPrefix: config.assetPrefix,
|
||||
generateEtags: config.generateEtags
|
||||
}
|
||||
|
||||
Object.keys(pages).forEach(async (page) => {
|
||||
if (page === '/_app' || page === '/_document') {
|
||||
return
|
||||
}
|
||||
|
||||
const absolutePagePath = join(pagesDirAlias, pages[page]).replace(/\\/g, '/')
|
||||
const bundleFile = page === '/' ? '/index.js' : `${page}.js`
|
||||
const serverlessLoaderOptions: ServerlessLoaderQuery = {page, absolutePagePath, ...defaultOptions}
|
||||
serverlessEntrypoints[join('pages', bundleFile)] = `next-serverless-loader?${stringify(serverlessLoaderOptions)}!`
|
||||
})
|
||||
|
||||
const errorPage = join('pages', '/_error.js')
|
||||
if (!serverlessEntrypoints[errorPage]) {
|
||||
const serverlessLoaderOptions: ServerlessLoaderQuery = {page: '/_error', absolutePagePath: 'next/dist/pages/_error', ...defaultOptions}
|
||||
serverlessEntrypoints[errorPage] = `next-serverless-loader?${stringify(serverlessLoaderOptions)}!`
|
||||
}
|
||||
|
||||
entrypoints = serverlessEntrypoints
|
||||
}
|
||||
|
||||
const pages = createPagesMapping(pagePaths, config.pageExtensions)
|
||||
const entrypoints = createEntrypoints(pages, config.target, buildId, config)
|
||||
const configs: any = await Promise.all([
|
||||
getBaseWebpackConfig(dir, { buildId, isServer: false, config, target: config.target }),
|
||||
getBaseWebpackConfig(dir, { buildId, isServer: true, config, target: config.target, entrypoints })
|
||||
getBaseWebpackConfig(dir, { buildId, isServer: false, config, target: config.target, entrypoints: entrypoints.client }),
|
||||
getBaseWebpackConfig(dir, { buildId, isServer: true, config, target: config.target, entrypoints: entrypoints.server })
|
||||
])
|
||||
|
||||
let result: CompilerResult = {warnings: [], errors: []}
|
||||
|
|
|
@ -4,8 +4,6 @@ import resolve from 'resolve'
|
|||
import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin'
|
||||
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin'
|
||||
import WebpackBar from 'webpackbar'
|
||||
import {getPages} from './webpack/utils'
|
||||
import PagesPlugin from './webpack/plugins/pages-plugin'
|
||||
import NextJsSsrImportPlugin from './webpack/plugins/nextjs-ssr-import'
|
||||
import NextJsSSRModuleCachePlugin from './webpack/plugins/nextjs-ssr-module-cache'
|
||||
import NextJsRequireCacheHotReloader from './webpack/plugins/nextjs-require-cache-hot-reloader'
|
||||
|
@ -15,7 +13,7 @@ import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin'
|
|||
import ChunkNamesPlugin from './webpack/plugins/chunk-names-plugin'
|
||||
import { ReactLoadablePlugin } from './webpack/plugins/react-loadable-plugin'
|
||||
import {SERVER_DIRECTORY, REACT_LOADABLE_MANIFEST, CLIENT_STATIC_FILES_RUNTIME_WEBPACK, CLIENT_STATIC_FILES_RUNTIME_MAIN} from 'next-server/constants'
|
||||
import {NEXT_PROJECT_ROOT, NEXT_PROJECT_ROOT_NODE_MODULES, NEXT_PROJECT_ROOT_DIST_CLIENT, DEFAULT_PAGES_DIR} from '../lib/constants'
|
||||
import {NEXT_PROJECT_ROOT, NEXT_PROJECT_ROOT_NODE_MODULES, NEXT_PROJECT_ROOT_DIST_CLIENT, DEFAULT_PAGES_DIR, PAGES_DIR_ALIAS, DOT_NEXT_ALIAS} from '../lib/constants'
|
||||
import AutoDllPlugin from 'autodll-webpack-plugin'
|
||||
import TerserPlugin from 'terser-webpack-plugin'
|
||||
import AssetsSizePlugin from './webpack/plugins/assets-size-plugin'
|
||||
|
@ -139,22 +137,15 @@ function optimizationConfig ({ dev, isServer, totalPages, target }) {
|
|||
return config
|
||||
}
|
||||
|
||||
export default async function getBaseWebpackConfig (dir, {dev = false, isServer = false, buildId, config, target = 'server', entrypoints = false}) {
|
||||
export default async function getBaseWebpackConfig (dir, {dev = false, isServer = false, buildId, config, target = 'server', entrypoints}) {
|
||||
const defaultLoaders = {
|
||||
babel: {
|
||||
loader: 'next-babel-loader',
|
||||
options: {dev, isServer, cwd: dir}
|
||||
},
|
||||
// Backwards compat
|
||||
hotSelfAccept: {
|
||||
loader: 'hot-self-accept-loader',
|
||||
options: {
|
||||
include: [
|
||||
path.join(dir, 'pages')
|
||||
],
|
||||
// All pages are javascript files. So we apply hot-self-accept-loader here to facilitate hot reloading of pages.
|
||||
// This makes sure plugins just have to implement `pageExtensions` instead of also implementing the loader
|
||||
extensions: new RegExp(`\\.+(${config.pageExtensions.join('|')})$`)
|
||||
}
|
||||
loader: 'noop-loader'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,8 +157,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
|||
const distDir = path.join(dir, config.distDir)
|
||||
const outputDir = target === 'serverless' ? 'serverless' : SERVER_DIRECTORY
|
||||
const outputPath = path.join(distDir, isServer ? outputDir : '')
|
||||
const pagesEntries = await getPages(dir, {nextPagesDir: DEFAULT_PAGES_DIR, dev, buildId, isServer, pageExtensions: config.pageExtensions.join('|')})
|
||||
const totalPages = Object.keys(pagesEntries).length
|
||||
const totalPages = Object.keys(entrypoints).length
|
||||
const clientEntries = !isServer ? {
|
||||
// Backwards compatibility
|
||||
'main.js': [],
|
||||
|
@ -186,8 +176,8 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
|||
],
|
||||
alias: {
|
||||
next: NEXT_PROJECT_ROOT,
|
||||
'private-next-pages': path.join(dir, 'pages'),
|
||||
'private-dot-next': distDir
|
||||
[PAGES_DIR_ALIAS]: path.join(dir, 'pages'),
|
||||
[DOT_NEXT_ALIAS]: distDir
|
||||
},
|
||||
mainFields: isServer ? ['main'] : ['browser', 'module', 'main']
|
||||
}
|
||||
|
@ -205,13 +195,9 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
|||
context: dir,
|
||||
// Kept as function to be backwards compatible
|
||||
entry: async () => {
|
||||
if (entrypoints) {
|
||||
return entrypoints
|
||||
}
|
||||
return {
|
||||
...clientEntries,
|
||||
// Only _error and _document when in development. The rest is handled by on-demand-entries
|
||||
...pagesEntries
|
||||
...entrypoints
|
||||
}
|
||||
},
|
||||
output: {
|
||||
|
@ -244,11 +230,6 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
|||
},
|
||||
module: {
|
||||
rules: [
|
||||
dev && !isServer && {
|
||||
test: defaultLoaders.hotSelfAccept.options.extensions,
|
||||
include: defaultLoaders.hotSelfAccept.options.include,
|
||||
use: defaultLoaders.hotSelfAccept
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
include: [dir, NEXT_PROJECT_ROOT_DIST_CLIENT, DEFAULT_PAGES_DIR, /next-server[\\/]dist[\\/]lib/],
|
||||
|
@ -309,7 +290,6 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
|||
}),
|
||||
target !== 'serverless' && isServer && new PagesManifestPlugin(),
|
||||
!isServer && new BuildManifestPlugin(),
|
||||
!isServer && new PagesPlugin(),
|
||||
isServer && new NextJsSsrImportPlugin(),
|
||||
target !== 'serverless' && isServer && new NextJsSSRModuleCachePlugin({outputPath}),
|
||||
!isServer && !dev && new AssetsSizePlugin(buildId, distDir)
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import { relative } from 'path'
|
||||
import loaderUtils from 'loader-utils'
|
||||
|
||||
module.exports = function (content, sourceMap) {
|
||||
this.cacheable()
|
||||
|
||||
const options = loaderUtils.getOptions(this)
|
||||
if (!options.extensions) {
|
||||
throw new Error('extensions is not provided to hot-self-accept-loader. Please upgrade all next-plugins to the latest version.')
|
||||
}
|
||||
|
||||
if (!options.include) {
|
||||
throw new Error('include option is not provided to hot-self-accept-loader. Please upgrade all next-plugins to the latest version.')
|
||||
}
|
||||
|
||||
const route = getRoute(this.resourcePath, options)
|
||||
|
||||
// Webpack has a built in system to prevent default from colliding, giving it a random letter per export.
|
||||
// We can safely check if Component is undefined since all other pages imported into the entrypoint don't have __webpack_exports__.default
|
||||
this.callback(null, `${content}
|
||||
(function (Component, route) {
|
||||
if(!Component) return
|
||||
if (!module.hot) return
|
||||
module.hot.accept()
|
||||
Component.__route = route
|
||||
|
||||
if (module.hot.status() === 'idle') return
|
||||
|
||||
var components = next.router.components
|
||||
for (var r in components) {
|
||||
if (!components.hasOwnProperty(r)) continue
|
||||
|
||||
if (components[r].Component.__route === route) {
|
||||
next.router.update(r, Component)
|
||||
}
|
||||
}
|
||||
})(typeof __webpack_exports__ !== 'undefined' ? __webpack_exports__.default : (module.exports.default || module.exports), ${JSON.stringify(route)})
|
||||
`, sourceMap)
|
||||
}
|
||||
|
||||
function getRoute (resourcePath, options) {
|
||||
const dir = options.include.find((d) => resourcePath.indexOf(d) === 0)
|
||||
|
||||
if (!dir) {
|
||||
throw new Error(`'hot-self-accept-loader' was called on a file that isn't a page.`)
|
||||
}
|
||||
|
||||
const path = relative(dir, resourcePath).replace(options.extensions, '.js')
|
||||
return '/' + path.replace(/((^|\/)index)?\.js$/, '')
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import {loader} from 'webpack'
|
||||
import loaderUtils from 'loader-utils'
|
||||
|
||||
export type ClientPagesLoaderOptions = {
|
||||
absolutePagePath: string,
|
||||
page: string
|
||||
}
|
||||
|
||||
const nextClientPagesLoader: loader.Loader = function () {
|
||||
const {absolutePagePath, page}: any = loaderUtils.getOptions(this)
|
||||
const stringifiedAbsolutePagePath = JSON.stringify(absolutePagePath)
|
||||
const stringifiedPage = JSON.stringify(page)
|
||||
|
||||
return `
|
||||
(window.__NEXT_P=window.__NEXT_P||[]).push([${stringifiedPage}, function() {
|
||||
var page = require(${stringifiedAbsolutePagePath})
|
||||
if(module.hot) {
|
||||
module.hot.accept(${stringifiedAbsolutePagePath}, function() {
|
||||
if(!next.router.components[${stringifiedPage}]) return
|
||||
var updatedPage = require(${stringifiedAbsolutePagePath})
|
||||
next.router.update(${stringifiedPage}, updatedPage.default || updatedPage)
|
||||
})
|
||||
}
|
||||
return { page: page.default || page }
|
||||
}]);
|
||||
`
|
||||
}
|
||||
|
||||
export default nextClientPagesLoader
|
1
packages/next/build/webpack/loaders/noop-loader.js
Normal file
1
packages/next/build/webpack/loaders/noop-loader.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = (source) => source
|
|
@ -1,53 +0,0 @@
|
|||
import { ConcatSource } from 'webpack-sources'
|
||||
import {
|
||||
IS_BUNDLED_PAGE_REGEX,
|
||||
ROUTE_NAME_REGEX
|
||||
} from 'next-server/constants'
|
||||
|
||||
export default class PagesPlugin {
|
||||
apply (compiler) {
|
||||
compiler.hooks.compilation.tap('PagesPlugin', (compilation) => {
|
||||
// This hook is triggered right before a module gets wrapped into it's initializing function,
|
||||
// For example when you look at the source of a bundle you'll see an object holding `'pages/_app.js': function(module, etc, etc)`
|
||||
// This hook triggers right before that code is added and wraps the module into `__NEXT_REGISTER_PAGE` when the module is a page
|
||||
// The reason we're doing this is that we don't want to execute the page code which has potential side effects before switching to a route
|
||||
compilation.moduleTemplates.javascript.hooks.render.tap('PagesPluginRenderPageRegister', (moduleSourcePostModule, module, options) => {
|
||||
const {chunk} = options
|
||||
|
||||
// check if the current module is the entry module, we only want to wrap the topmost module
|
||||
if (chunk.entryModule !== module) {
|
||||
return moduleSourcePostModule
|
||||
}
|
||||
|
||||
// Check if the chunk is a page
|
||||
if (!IS_BUNDLED_PAGE_REGEX.test(chunk.name)) {
|
||||
return moduleSourcePostModule
|
||||
}
|
||||
|
||||
// Match the route the chunk belongs to
|
||||
let routeName = ROUTE_NAME_REGEX.exec(chunk.name)[1]
|
||||
|
||||
// We need to convert \ into / when we are in windows
|
||||
// to get the proper route name
|
||||
// Here we need to do windows check because it's possible
|
||||
// to have "\" in the filename in unix.
|
||||
// Anyway if someone did that, he'll be having issues here.
|
||||
// But that's something we cannot avoid.
|
||||
if (/^win/.test(process.platform)) {
|
||||
routeName = routeName.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
routeName = `/${routeName.replace(/(^|\/)index$/, '')}`
|
||||
|
||||
const source = new ConcatSource(
|
||||
`(window.__NEXT_P=window.__NEXT_P||[]).push(['${routeName}', function() {\n`,
|
||||
moduleSourcePostModule,
|
||||
'\nreturn { page: module.exports.default }',
|
||||
'}]);'
|
||||
)
|
||||
|
||||
return source
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import path from 'path'
|
||||
import promisify from '../../lib/promisify'
|
||||
import globModule from 'glob'
|
||||
import {CLIENT_STATIC_FILES_PATH} from 'next-server/constants'
|
||||
|
||||
const glob = promisify(globModule)
|
||||
|
||||
export async function getPages (dir, {nextPagesDir, dev, buildId, isServer, pageExtensions}) {
|
||||
const pageFiles = await getPagePaths(dir, {dev, isServer, pageExtensions})
|
||||
|
||||
return getPageEntries(pageFiles, {nextPagesDir, buildId, isServer, pageExtensions})
|
||||
}
|
||||
|
||||
export async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
|
||||
let pages
|
||||
|
||||
if (dev) {
|
||||
// In development we only compile _document.js, _error.js and _app.js when starting, since they're always needed. All other pages are compiled with on demand entries
|
||||
pages = await glob(isServer ? `pages/+(_document|_app|_error).+(${pageExtensions})` : `pages/+(_app|_error).+(${pageExtensions})`, { cwd: dir })
|
||||
} else {
|
||||
// In production get all pages from the pages directory
|
||||
pages = await glob(isServer ? `pages/**/*.+(${pageExtensions})` : `pages/**/!(_document)*.+(${pageExtensions})`, { cwd: dir })
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
// Convert page path into single entry
|
||||
export function createEntry (filePath, {buildId = '', name, pageExtensions} = {}) {
|
||||
const parsedPath = path.parse(filePath)
|
||||
let entryName = name || filePath
|
||||
|
||||
// This makes sure we compile `pages/blog/index.js` to `pages/blog.js`.
|
||||
// Excludes `pages/index.js` from this rule since we do want `/` to route to `pages/index.js`
|
||||
if (parsedPath.dir !== 'pages' && parsedPath.name === 'index') {
|
||||
entryName = `${parsedPath.dir}.js`
|
||||
}
|
||||
|
||||
// Makes sure supported extensions are stripped off. The outputted file should always be `.js`
|
||||
if (pageExtensions) {
|
||||
entryName = entryName.replace(new RegExp(`\\.+(${pageExtensions})$`), '.js')
|
||||
}
|
||||
|
||||
return {
|
||||
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, buildId, isServer = false, pageExtensions} = {}) {
|
||||
const entries = {}
|
||||
|
||||
for (const filePath of pagePaths) {
|
||||
const entry = createEntry(filePath, {pageExtensions, buildId})
|
||||
entries[entry.name] = entry.files
|
||||
}
|
||||
|
||||
const appPagePath = path.join(nextPagesDir, '_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, {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, {buildId, name: 'pages/_document.js'}) // default _document.js
|
||||
if (!entries[documentPageEntry.name]) {
|
||||
entries[documentPageEntry.name] = documentPageEntry.files
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
|
@ -1,65 +1,5 @@
|
|||
import 'event-source-polyfill'
|
||||
import connect from './dev-error-overlay/hot-dev-client'
|
||||
import Router from 'next/router'
|
||||
|
||||
const handlers = {
|
||||
reload (route) {
|
||||
// If the App component changes we have to reload the current route, this is handled by hot-self-accept-loader
|
||||
// So we just return
|
||||
if (route === '/_app') {
|
||||
return
|
||||
}
|
||||
|
||||
if (route === '/_error') {
|
||||
for (const r of Object.keys(Router.components)) {
|
||||
const { err } = Router.components[r]
|
||||
if (err) {
|
||||
// reload all error routes
|
||||
// which are expected to be errors of '/_error' routes
|
||||
Router.reload(r)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Since _document is server only we need to reload the full page when it changes.
|
||||
if (route === '/_document') {
|
||||
window.location.reload()
|
||||
return
|
||||
}
|
||||
|
||||
Router.reload(route)
|
||||
},
|
||||
|
||||
change (route) {
|
||||
// If the App component changes we have to reload the current route, this is handled by hot-self-accept-loader
|
||||
// So we just return
|
||||
if (route === '/_app') {
|
||||
return
|
||||
}
|
||||
|
||||
const { err, Component } = Router.components[route] || {}
|
||||
|
||||
if (err) {
|
||||
// reload to recover from runtime errors
|
||||
Router.reload(route)
|
||||
}
|
||||
|
||||
if (Router.route !== route) {
|
||||
// If this is a not a change for a currently viewing page.
|
||||
// We don't need to worry about it.
|
||||
return
|
||||
}
|
||||
|
||||
if (!Component) {
|
||||
// This only happens when we create a new page without a default export.
|
||||
// If you removed a default export from a exising viewing page, this has no effect.
|
||||
console.warn(`Hard reloading due to no default component in page: ${route}`)
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ({assetPrefix}) => {
|
||||
const options = {
|
||||
path: `${assetPrefix}/_next/webpack-hmr`
|
||||
|
@ -68,13 +8,24 @@ export default ({assetPrefix}) => {
|
|||
const devClient = connect(options)
|
||||
|
||||
devClient.subscribeToHmrEvent((obj) => {
|
||||
const fn = handlers[obj.action]
|
||||
if (fn) {
|
||||
const data = obj.data || []
|
||||
fn(...data)
|
||||
} else {
|
||||
throw new Error('Unexpected action ' + obj.action)
|
||||
if (obj.action === 'reloadPage') {
|
||||
return window.location.reload()
|
||||
}
|
||||
if (obj.action === 'removedPage') {
|
||||
const [page] = obj.data
|
||||
if (page === window.next.router.pathname) {
|
||||
return window.location.reload()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (obj.action === 'addedPage') {
|
||||
const [page] = obj.data
|
||||
if (page === window.next.router.pathname && typeof window.next.router.components[page] === 'undefined') {
|
||||
return window.location.reload()
|
||||
}
|
||||
return
|
||||
}
|
||||
throw new Error('Unexpected action ' + obj.action)
|
||||
})
|
||||
|
||||
return devClient
|
||||
|
|
|
@ -5,3 +5,8 @@ export const NEXT_PROJECT_ROOT_NODE_MODULES = join(NEXT_PROJECT_ROOT, 'node_modu
|
|||
export const DEFAULT_PAGES_DIR = join(NEXT_PROJECT_ROOT_DIST, 'pages')
|
||||
export const NEXT_PROJECT_ROOT_DIST_CLIENT = join(NEXT_PROJECT_ROOT_DIST, 'client')
|
||||
export const NEXT_PROJECT_ROOT_DIST_SERVER = join(NEXT_PROJECT_ROOT_DIST, 'server')
|
||||
|
||||
// Because on Windows absolute paths in the generated code can break because of numbers, eg 1 in the path,
|
||||
// we have to use a private alias
|
||||
export const PAGES_DIR_ALIAS = 'private-next-pages'
|
||||
export const DOT_NEXT_ALIAS = 'private-dot-next'
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
"@babel/runtime": "7.1.2",
|
||||
"@babel/runtime-corejs2": "7.1.2",
|
||||
"@babel/template": "7.1.2",
|
||||
"@types/loader-utils": "1.1.3",
|
||||
"@types/node-fetch": "2.1.4",
|
||||
"@types/rimraf": "2.0.2",
|
||||
"ansi-html": "0.0.7",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { join, relative, sep, normalize } from 'path'
|
||||
import { join, normalize } from 'path'
|
||||
import WebpackDevMiddleware from 'webpack-dev-middleware'
|
||||
import WebpackHotMiddleware from 'webpack-hot-middleware'
|
||||
import errorOverlayMiddleware from './lib/error-overlay-middleware'
|
||||
|
@ -7,8 +7,13 @@ import onDemandEntryHandler, {normalizePage} from './on-demand-entry-handler'
|
|||
import webpack from 'webpack'
|
||||
import WebSocket from 'ws'
|
||||
import getBaseWebpackConfig from '../build/webpack-config'
|
||||
import {IS_BUNDLED_PAGE_REGEX, ROUTE_NAME_REGEX, BLOCKED_PAGES, CLIENT_STATIC_FILES_PATH} from 'next-server/constants'
|
||||
import {IS_BUNDLED_PAGE_REGEX, ROUTE_NAME_REGEX, BLOCKED_PAGES} from 'next-server/constants'
|
||||
import {route} from 'next-server/dist/server/router'
|
||||
import globModule from 'glob'
|
||||
import {promisify} from 'util'
|
||||
import {createPagesMapping, createEntrypoints} from '../build/entries'
|
||||
|
||||
const glob = promisify(globModule)
|
||||
|
||||
export async function renderScriptError (res, error) {
|
||||
// Asks CDNs and others to not to cache the errored page
|
||||
|
@ -92,10 +97,6 @@ export default class HotReloader {
|
|||
this.webpackHotMiddleware = null
|
||||
this.initialized = false
|
||||
this.stats = null
|
||||
this.compilationErrors = null
|
||||
this.prevChunkNames = null
|
||||
this.prevFailedChunkNames = null
|
||||
this.prevChunkHashes = null
|
||||
this.serverPrevDocumentHash = null
|
||||
|
||||
this.config = config
|
||||
|
@ -115,7 +116,7 @@ export default class HotReloader {
|
|||
// 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 handlePageBundleRequest = async (res, parsedUrl) => {
|
||||
const {pathname} = parsedUrl
|
||||
const params = matchNextPageBundleRequest(pathname)
|
||||
if (!params) {
|
||||
|
@ -145,7 +146,7 @@ export default class HotReloader {
|
|||
return {}
|
||||
}
|
||||
|
||||
const {finished} = await handlePageBundleRequest(req, res, parsedUrl)
|
||||
const {finished} = await handlePageBundleRequest(res, parsedUrl)
|
||||
|
||||
for (const fn of this.middlewares) {
|
||||
await new Promise((resolve, reject) => {
|
||||
|
@ -169,6 +170,16 @@ export default class HotReloader {
|
|||
}))
|
||||
}
|
||||
|
||||
async getWebpackConfig () {
|
||||
const pagePaths = await glob(`+(_app|_document|_error).+(${this.config.pageExtensions.join('|')})`, {cwd: join(this.dir, 'pages')})
|
||||
const pages = createPagesMapping(pagePaths, this.config.pageExtensions)
|
||||
const entrypoints = createEntrypoints(pages, 'server', this.buildId, this.config)
|
||||
return Promise.all([
|
||||
getBaseWebpackConfig(this.dir, { dev: true, isServer: false, config: this.config, buildId: this.buildId, entrypoints: entrypoints.client }),
|
||||
getBaseWebpackConfig(this.dir, { dev: true, isServer: true, config: this.config, buildId: this.buildId, entrypoints: entrypoints.server })
|
||||
])
|
||||
}
|
||||
|
||||
async start () {
|
||||
await this.clean()
|
||||
|
||||
|
@ -188,10 +199,7 @@ export default class HotReloader {
|
|||
})
|
||||
})
|
||||
|
||||
const configs = await Promise.all([
|
||||
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 configs = await this.getWebpackConfig()
|
||||
this.addWsPort(configs)
|
||||
|
||||
const multiCompiler = webpack(configs)
|
||||
|
@ -220,10 +228,7 @@ export default class HotReloader {
|
|||
|
||||
await this.clean()
|
||||
|
||||
const configs = await Promise.all([
|
||||
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 configs = await this.getWebpackConfig()
|
||||
this.addWsPort(configs)
|
||||
|
||||
const compiler = webpack(configs)
|
||||
|
@ -280,8 +285,7 @@ export default class HotReloader {
|
|||
}
|
||||
|
||||
// Notify reload to reload the page, as _document.js was changed (different hash)
|
||||
this.send('reload', '/_document')
|
||||
|
||||
this.send('reloadPage')
|
||||
this.serverPrevDocumentHash = documentChunk.hash
|
||||
})
|
||||
|
||||
|
@ -293,61 +297,32 @@ export default class HotReloader {
|
|||
.filter(name => IS_BUNDLED_PAGE_REGEX.test(name))
|
||||
)
|
||||
|
||||
const failedChunkNames = new Set(Object.keys(erroredPages(compilation)))
|
||||
|
||||
const chunkHashes = new Map(
|
||||
compilation.chunks
|
||||
.filter(c => IS_BUNDLED_PAGE_REGEX.test(c.name))
|
||||
.map((c) => [c.name, c.hash])
|
||||
)
|
||||
|
||||
if (this.initialized) {
|
||||
// detect chunks which have to be replaced with a new template
|
||||
// e.g, pages/index.js <-> pages/_error.js
|
||||
const added = diff(chunkNames, this.prevChunkNames)
|
||||
const removed = diff(this.prevChunkNames, chunkNames)
|
||||
const succeeded = diff(this.prevFailedChunkNames, failedChunkNames)
|
||||
const addedPages = diff(chunkNames, this.prevChunkNames)
|
||||
const removedPages = diff(this.prevChunkNames, chunkNames)
|
||||
|
||||
// reload all failed chunks to replace the templace to the error ones,
|
||||
// and to update error content
|
||||
const failed = failedChunkNames
|
||||
|
||||
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))
|
||||
this.send('reload', route)
|
||||
if (addedPages.size > 0) {
|
||||
for (const addedPage of addedPages) {
|
||||
let page = '/' + ROUTE_NAME_REGEX.exec(addedPage)[1].replace(/\\/g, '/')
|
||||
page = page === '/index' ? '/' : page
|
||||
this.send('addedPage', page)
|
||||
}
|
||||
}
|
||||
|
||||
let changedPageRoutes = []
|
||||
|
||||
for (const [n, hash] of chunkHashes) {
|
||||
if (!this.prevChunkHashes.has(n)) continue
|
||||
if (this.prevChunkHashes.get(n) === hash) continue
|
||||
|
||||
const route = toRoute(relative(rootDir, n))
|
||||
|
||||
changedPageRoutes.push(route)
|
||||
}
|
||||
|
||||
// This means `/_app` is most likely included in the list, or a page was added/deleted in this compilation run.
|
||||
// This means we should filter out `/_app` because `/_app` will be re-rendered with the page reload.
|
||||
if (added.size !== 0 || removed.size !== 0 || changedPageRoutes.length > 1) {
|
||||
changedPageRoutes = changedPageRoutes.filter((route) => route !== '/_app' && route !== '/_document')
|
||||
}
|
||||
|
||||
for (const changedPageRoute of changedPageRoutes) {
|
||||
// notify change to recover from runtime errors
|
||||
this.send('change', changedPageRoute)
|
||||
if (removedPages.size > 0) {
|
||||
for (const removedPage of removedPages) {
|
||||
let page = '/' + ROUTE_NAME_REGEX.exec(removedPage)[1].replace(/\\/g, '/')
|
||||
page = page === '/index' ? '/' : page
|
||||
this.send('removedPage', page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.initialized = true
|
||||
this.stats = stats
|
||||
this.compilationErrors = null
|
||||
this.prevChunkNames = chunkNames
|
||||
this.prevFailedChunkNames = failedChunkNames
|
||||
this.prevChunkHashes = chunkHashes
|
||||
})
|
||||
|
||||
// We don’t watch .git/ .next/ and node_modules for changes
|
||||
|
@ -381,7 +356,6 @@ export default class HotReloader {
|
|||
const onDemandEntries = onDemandEntryHandler(webpackDevMiddleware, multiCompiler, {
|
||||
dir: this.dir,
|
||||
buildId: this.buildId,
|
||||
dev: true,
|
||||
reload: this.reload.bind(this),
|
||||
pageExtensions: this.config.pageExtensions,
|
||||
wsPort: this.wsPort,
|
||||
|
@ -433,7 +407,7 @@ export default class HotReloader {
|
|||
|
||||
async ensurePage (page) {
|
||||
// Make sure we don't re-build or dispose prebuilt pages
|
||||
if (page === '/_error' || page === '/_document' || page === '/_app') {
|
||||
if (BLOCKED_PAGES.indexOf(page) !== -1) {
|
||||
return
|
||||
}
|
||||
await this.onDemandEntries.ensurePage(page)
|
||||
|
@ -443,8 +417,3 @@ export default class HotReloader {
|
|||
function diff (a, b) {
|
||||
return new Set([...a].filter((v) => !b.has(v)))
|
||||
}
|
||||
|
||||
function toRoute (file) {
|
||||
const f = sep === '\\' ? file.replace(/\\/g, '/') : file
|
||||
return ('/' + f).replace(/(\/index)?\.js$/, '') || '/'
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import promisify from '../lib/promisify'
|
|||
import globModule from 'glob'
|
||||
import {pageNotFoundError} from 'next-server/dist/server/require'
|
||||
import {normalizePagePath} from 'next-server/dist/server/normalize-page-path'
|
||||
import {createEntry} from '../build/webpack/utils'
|
||||
import { ROUTE_NAME_REGEX, IS_BUNDLED_PAGE_REGEX } from 'next-server/constants'
|
||||
import {stringify} from 'querystring'
|
||||
|
||||
const ADDED = Symbol('added')
|
||||
const BUILDING = Symbol('building')
|
||||
|
@ -30,7 +30,6 @@ function addEntry (compilation, context, name, entry) {
|
|||
export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
||||
buildId,
|
||||
dir,
|
||||
dev,
|
||||
reload,
|
||||
pageExtensions,
|
||||
maxInactiveAge,
|
||||
|
@ -51,20 +50,17 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
|||
invalidator.startBuilding()
|
||||
|
||||
const allEntries = Object.keys(entries).map(async (page) => {
|
||||
const { name, entry } = entries[page]
|
||||
const files = Array.isArray(entry) ? entry : [entry]
|
||||
// Is just one item. But it's passed as an array.
|
||||
for (const file of files) {
|
||||
try {
|
||||
await access(join(dir, file), (fs.constants || fs).W_OK)
|
||||
} catch (err) {
|
||||
console.warn('Page was removed', page)
|
||||
delete entries[page]
|
||||
return
|
||||
}
|
||||
const { name, absolutePagePath } = entries[page]
|
||||
try {
|
||||
await access(absolutePagePath, (fs.constants || fs).W_OK)
|
||||
} catch (err) {
|
||||
console.warn('Page was removed', page)
|
||||
delete entries[page]
|
||||
return
|
||||
}
|
||||
|
||||
entries[page].status = BUILDING
|
||||
return addEntry(compilation, compiler.context, name, entry)
|
||||
return addEntry(compilation, compiler.context, name, [compiler.name === 'client' ? `next-client-pages-loader?${stringify({page, absolutePagePath})}!` : absolutePagePath])
|
||||
})
|
||||
|
||||
return Promise.all(allEntries)
|
||||
|
@ -178,17 +174,19 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
|||
}
|
||||
|
||||
const extensions = pageExtensions.join('|')
|
||||
const paths = await glob(`pages/{${normalizedPagePath}/index,${normalizedPagePath}}.+(${extensions})`, {cwd: dir})
|
||||
const pagesDir = join(dir, 'pages')
|
||||
const paths = await glob(`{${normalizedPagePath.slice(1)}/index,${normalizedPagePath.slice(1)}}.+(${extensions})`, {cwd: pagesDir})
|
||||
|
||||
if (paths.length === 0) {
|
||||
throw pageNotFoundError(normalizedPagePath)
|
||||
}
|
||||
|
||||
const relativePathToPage = paths[0]
|
||||
|
||||
const pathname = join(dir, relativePathToPage)
|
||||
|
||||
const {name, files} = createEntry(relativePathToPage, {buildId, pageExtensions: extensions})
|
||||
const pagePath = paths[0]
|
||||
let pageUrl = `/${pagePath.replace(new RegExp(`\\.+(${extensions})$`), '').replace(/\\/g, '/')}`.replace(/\/index$/, '')
|
||||
pageUrl = pageUrl === '' ? '/' : pageUrl
|
||||
const bundleFile = pageUrl === '/' ? '/index.js' : `${pageUrl}.js`
|
||||
const name = join('static', buildId, 'pages', bundleFile)
|
||||
const absolutePagePath = join(pagesDir, pagePath)
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const entryInfo = entries[page]
|
||||
|
@ -207,7 +205,7 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
|||
|
||||
console.log(`> Building page: ${page}`)
|
||||
|
||||
entries[page] = { name, entry: files, pathname, status: ADDED }
|
||||
entries[page] = { name, absolutePagePath, status: ADDED }
|
||||
doneCallbacks.once(page, handleCallback)
|
||||
|
||||
invalidator.invalidate()
|
||||
|
|
|
@ -3,7 +3,7 @@ import webdriver from 'next-webdriver'
|
|||
import { join } from 'path'
|
||||
import { check, File, waitFor, getReactErrorOverlayContent, getBrowserBodyText } from 'next-test-utils'
|
||||
|
||||
export default (context) => {
|
||||
export default (context, renderViaHTTP) => {
|
||||
describe('Error Recovery', () => {
|
||||
it('should recover from 404 after a page has been added', async () => {
|
||||
let browser
|
||||
|
@ -110,6 +110,8 @@ export default (context) => {
|
|||
const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
|
||||
let browser
|
||||
try {
|
||||
await renderViaHTTP('/hmr/about')
|
||||
|
||||
aboutPage.replace('</div>', 'div')
|
||||
|
||||
browser = await webdriver(context.appPort, '/hmr/contact')
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/* eslint-env jest */
|
||||
|
||||
import {normalize, join} from 'path'
|
||||
import {getPageEntries, createEntry} from 'next/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(`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(`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('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('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('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('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('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')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getPageEntries', () => {
|
||||
const nextPagesDir = join(__dirname, '..', '..', 'dist', 'pages')
|
||||
|
||||
it('Should return paths', () => {
|
||||
const pagePaths = ['pages/index.js']
|
||||
const pageEntries = getPageEntries(pagePaths, {nextPagesDir})
|
||||
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('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('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('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('static/pages/_document.js')][0]).toBe('./pages/_document.js')
|
||||
})
|
||||
})
|
10
yarn.lock
10
yarn.lock
|
@ -1411,6 +1411,14 @@
|
|||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/loader-utils@1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/loader-utils/-/loader-utils-1.1.3.tgz#82b9163f2ead596c68a8c03e450fbd6e089df401"
|
||||
integrity sha512-euKGFr2oCB3ASBwG39CYJMR3N9T0nanVqXdiH7Zu/Nqddt6SmFRxytq/i2w9LQYNQekEtGBz+pE3qG6fQTNvRg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/webpack" "*"
|
||||
|
||||
"@types/mime@*":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
|
||||
|
@ -1502,7 +1510,7 @@
|
|||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@types/webpack@4.4.22":
|
||||
"@types/webpack@*", "@types/webpack@4.4.22":
|
||||
version "4.4.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.22.tgz#c4a5ea8b74a31b579537515bcfe86d2b2a34382c"
|
||||
integrity sha512-PxAAzli3krZX9rCeONSR5Z9v4CR/2HPsKsiVRFNDo9OZefN+dTemteMHZnYkddOu4bqoYqJTJ724gLy0ZySXOw==
|
||||
|
|
Loading…
Reference in a new issue