Skip to content

Commit

Permalink
Replace pages-plugin with loader (#5994)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
timneutkens committed Jan 8, 2019
1 parent 3a3347d commit 9ffd23e
Show file tree
Hide file tree
Showing 16 changed files with 202 additions and 512 deletions.
67 changes: 67 additions & 0 deletions packages/next/build/entries.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
58 changes: 5 additions & 53 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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: []}
Expand Down
36 changes: 8 additions & 28 deletions packages/next/build/webpack-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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'
}
}

Expand All @@ -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': [],
Expand All @@ -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']
}
Expand All @@ -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: {
Expand Down Expand Up @@ -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/],
Expand Down Expand Up @@ -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)
Expand Down
50 changes: 0 additions & 50 deletions packages/next/build/webpack/loaders/hot-self-accept-loader.js

This file was deleted.

29 changes: 29 additions & 0 deletions packages/next/build/webpack/loaders/next-client-pages-loader.ts
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions packages/next/build/webpack/loaders/noop-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = (source) => source
53 changes: 0 additions & 53 deletions packages/next/build/webpack/plugins/pages-plugin.js

This file was deleted.

0 comments on commit 9ffd23e

Please sign in to comment.