Skip to content

Commit

Permalink
Make pageExtensions configurable (#3787)
Browse files Browse the repository at this point in the history
* Make page require faster

* Add windows search/replace

* Use normalize instead of resolve

* Add remaining tests

* Use sep instead of /

* Add test files

* Make component require faster

* Add console.error

* Make pageExtensions configurable

* Remove resolve.js

* Add test for `.jsx`

* Also resolve `/nav/index` and the likes

* Normalize page paths

* Use config passed off by webpack
  • Loading branch information
timneutkens authored and arunoda committed Feb 14, 2018
1 parent 64379ee commit 903f15a
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 167 deletions.
2 changes: 1 addition & 1 deletion server/build/webpack.js
Expand Up @@ -115,7 +115,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
externals: externalsConfig(dir, isServer),
context: dir,
entry: async () => {
const pages = await getPages(dir, {dev, isServer})
const pages = await getPages(dir, {dev, isServer, pageExtensions: config.pageExtensions.join('|')})
totalPages = Object.keys(pages).length
const mainJS = require.resolve(`../../client/next${dev ? '-dev' : ''}`)
const clientConfig = !isServer ? {
Expand Down
28 changes: 16 additions & 12 deletions server/build/webpack/utils.js
Expand Up @@ -3,26 +3,28 @@ import glob from 'glob-promise'

const nextPagesDir = path.join(__dirname, '..', '..', '..', 'pages')

export async function getPages (dir, {dev, isServer}) {
const pageFiles = await getPagePaths(dir, {dev, isServer})
export async function getPages (dir, {dev, isServer, pageExtensions}) {
const pageFiles = await getPagePaths(dir, {dev, isServer, pageExtensions})

return getPageEntries(pageFiles, {isServer})
return getPageEntries(pageFiles, {isServer, pageExtensions})
}

async function getPagePaths (dir, {dev, isServer}) {
async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
let pages

if (dev) {
pages = await glob(isServer ? 'pages/+(_document|_error).+(js|jsx|ts|tsx)' : 'pages/_error.+(js|jsx|ts|tsx)', { cwd: dir })
// In development we only compile _document.js and _error.js when starting, since they're always needed. All other pages are compiled with on demand entries
pages = await glob(isServer ? `pages/+(_document|_error).+(${pageExtensions})` : `pages/_error.+(${pageExtensions})`, { cwd: dir })
} else {
pages = await glob(isServer ? 'pages/**/*.+(js|jsx|ts|tsx)' : 'pages/**/!(_document)*.+(js|jsx|ts|tsx)', { cwd: dir })
// 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, name) {
export function createEntry (filePath, {name, pageExtensions} = {}) {
const parsedPath = path.parse(filePath)
let entryName = name || filePath

Expand All @@ -33,7 +35,9 @@ export function createEntry (filePath, name) {
}

// Makes sure supported extensions are stripped off. The outputted file should always be `.js`
entryName = entryName.replace(/\.+(jsx|tsx|ts)/, '.js')
if (pageExtensions) {
entryName = entryName.replace(new RegExp(`\\.+(${pageExtensions})`), '.js')
}

return {
name: path.join('bundles', entryName),
Expand All @@ -42,23 +46,23 @@ export function createEntry (filePath, name) {
}

// Convert page paths into entries
export function getPageEntries (pagePaths, {isServer}) {
export function getPageEntries (pagePaths, {isServer = false, pageExtensions} = {}) {
const entries = {}

for (const filePath of pagePaths) {
const entry = createEntry(filePath)
const entry = createEntry(filePath, {pageExtensions})
entries[entry.name] = entry.files
}

const errorPagePath = path.join(nextPagesDir, '_error.js')
const errorPageEntry = createEntry(errorPagePath, 'pages/_error.js') // default error.js
const errorPageEntry = createEntry(errorPagePath, {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, 'pages/_document.js')
const documentPageEntry = createEntry(documentPagePath, {name: 'pages/_document.js'}) // default _document.js
if (!entries[documentPageEntry.name]) {
entries[documentPageEntry.name] = documentPageEntry.files
}
Expand Down
3 changes: 2 additions & 1 deletion server/config.js
Expand Up @@ -8,7 +8,8 @@ const defaultConfig = {
distDir: '.next',
assetPrefix: '',
configOrigin: 'default',
useFileSystemPublicRoutes: true
useFileSystemPublicRoutes: true,
pageExtensions: ['jsx', 'js'] // jsx before js because otherwise regex matching will match js first
}

export default function getConfig (dir, customConfig) {
Expand Down
1 change: 1 addition & 0 deletions server/hot-reloader.js
Expand Up @@ -228,6 +228,7 @@ export default class HotReloader {
dir: this.dir,
dev: true,
reload: this.reload.bind(this),
pageExtensions: this.config.pageExtensions,
...this.config.onDemandEntries
})

Expand Down
28 changes: 23 additions & 5 deletions server/on-demand-entry-handler.js
@@ -1,9 +1,10 @@
import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin'
import { EventEmitter } from 'events'
import { join, relative } from 'path'
import { join } from 'path'
import { parse } from 'url'
import touch from 'touch'
import resolvePath from './resolve'
import glob from 'glob-promise'
import {normalizePagePath, pageNotFoundError} from './require'
import {createEntry} from './build/webpack/utils'
import { MATCH_ROUTE_NAME, IS_BUNDLED_PAGE } from './utils'

Expand All @@ -15,6 +16,7 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
dir,
dev,
reload,
pageExtensions,
maxInactiveAge = 1000 * 60,
pagesBufferLength = 2
}) {
Expand Down Expand Up @@ -139,10 +141,26 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
async ensurePage (page) {
await this.waitUntilReloaded()
page = normalizePage(page)
let normalizedPagePath
try {
normalizedPagePath = normalizePagePath(page)
} catch (err) {
console.error(err)
throw pageNotFoundError(normalizedPagePath)
}

const extensions = pageExtensions.join('|')
const paths = await glob(`pages/{${normalizedPagePath}/index,${normalizedPagePath}}.+(${extensions})`, {cwd: dir})

if (paths.length === 0) {
throw pageNotFoundError(normalizedPagePath)
}

const relativePathToPage = paths[0]

const pathname = join(dir, relativePathToPage)

const pagePath = join(dir, 'pages', page)
const pathname = await resolvePath(pagePath)
const {name, files} = createEntry(relative(dir, pathname))
const {name, files} = createEntry(relativePathToPage, {pageExtensions: extensions})

await new Promise((resolve, reject) => {
const entryInfo = entries[page]
Expand Down
96 changes: 0 additions & 96 deletions server/resolve.js

This file was deleted.

52 changes: 0 additions & 52 deletions test/isolated/resolve.test.js

This file was deleted.

68 changes: 68 additions & 0 deletions test/isolated/webpack-utils.test.js
@@ -0,0 +1,68 @@
/* global describe, it, expect */

import {normalize} from 'path'
import {getPageEntries, createEntry} from '../../dist/server/build/webpack/utils'

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.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.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.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.files[0]).toBe('./pages/index.jsx')
})

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.files[0]).toBe('./pages/blog/index.js')
})
})

describe('getPageEntries', () => {
it('Should return paths', () => {
const pagePaths = ['pages/index.js']
const pageEntries = getPageEntries(pagePaths)
expect(pageEntries[normalize('bundles/pages/index.js')][0]).toBe('./pages/index.js')
})

it('Should include default _error', () => {
const pagePaths = ['pages/index.js']
const pageEntries = getPageEntries(pagePaths)
expect(pageEntries[normalize('bundles/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)
expect(pageEntries[normalize('bundles/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, {isServer: true})
expect(pageEntries[normalize('bundles/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, {isServer: true})
expect(pageEntries[normalize('bundles/pages/_document.js')][0]).toBe('./pages/_document.js')
})
})

0 comments on commit 903f15a

Please sign in to comment.