From 8ac4013c8d669d9689c7e9b8ebccc86c1592bac8 Mon Sep 17 00:00:00 2001 From: George Pantazis Date: Mon, 30 Jan 2017 09:33:32 -0800 Subject: [PATCH 1/2] Add configuration to specify the name of the `pages` directory. --- bin/next-build | 11 ++++++---- bin/next-dev | 11 ++++++---- bin/next-init | 13 +++++++----- lib/router/router.js | 4 ++-- .../build/loaders/hot-self-accept-loader.js | 5 ++++- server/build/plugins/json-pages-plugin.js | 11 +++++++++- server/build/plugins/watch-pages-plugin.js | 9 ++++---- server/build/webpack.js | 16 +++++++------- server/config.js | 3 ++- server/hot-reloader.js | 2 +- server/index.js | 4 ++-- server/on-demand-entry-handler.js | 4 +++- server/render.js | 21 +++++++++++++------ 13 files changed, 74 insertions(+), 40 deletions(-) diff --git a/bin/next-build b/bin/next-build index 6a964540ec124..26d542c3a01fd 100755 --- a/bin/next-build +++ b/bin/next-build @@ -2,6 +2,7 @@ import { resolve, join } from 'path' import { existsSync } from 'fs' import parseArgs from 'minimist' +import getConfig from '../server/config' import build from '../server/build' import { printAndExit } from '../lib/utils' @@ -29,18 +30,20 @@ if (argv.help) { } const dir = resolve(argv._[0] || '.') +const config = getConfig(dir) +const pagesDirectory = config.pagesDirectory // Check if pages dir exists and warn if not if (!existsSync(dir)) { printAndExit(`> No such directory exists as the project root: ${dir}`) } -if (!existsSync(join(dir, 'pages'))) { - if (existsSync(join(dir, '..', 'pages'))) { - printAndExit('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?') +if (!existsSync(join(dir, pagesDirectory))) { + if (existsSync(join(dir, '..', pagesDirectory))) { + printAndExit(`> No \`${pagesDirectory}\` directory found. Did you mean to run \`next\` in the parent (\`../\`) directory?`) } - printAndExit('> Couldn\'t find a `pages` directory. Please create one under the project root') + printAndExit(`> Couldn't find a \`${pagesDirectory}\` directory. Please create one under the project root`) } build(dir) diff --git a/bin/next-dev b/bin/next-dev index d5055a3cf2004..c434aba115961 100755 --- a/bin/next-dev +++ b/bin/next-dev @@ -4,6 +4,7 @@ import { resolve, join } from 'path' import parseArgs from 'minimist' import { existsSync, readFileSync } from 'fs' import Server from '../server' +import getConfig from '../server/config' import { printAndExit } from '../lib/utils' import pkgUp from 'pkg-up' @@ -40,18 +41,20 @@ if (argv.help) { } const dir = resolve(argv._[0] || '.') +const config = getConfig(dir) +const pagesDirectory = config.pagesDirectory // Check if pages dir exists and warn if not if (!existsSync(dir)) { printAndExit(`> No such directory exists as the project root: ${dir}`) } -if (!existsSync(join(dir, 'pages'))) { - if (existsSync(join(dir, '..', 'pages'))) { - printAndExit('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?') +if (!existsSync(join(dir, pagesDirectory))) { + if (existsSync(join(dir, '..', pagesDirectory))) { + printAndExit(`> No \`${pagesDirectory}\` directory found. Did you mean to run \`next\` in the parent (\`../\`) directory?`) } - printAndExit('> Couldn\'t find a `pages` directory. Please create one under the project root') + printAndExit(`> Couldn't find a \`${pagesDirectory}\` directory. Please create one under the project root`) } const srv = new Server({ dir, dev: true }) diff --git a/bin/next-init b/bin/next-init index e67817ba7d5d3..ccc646568d52b 100755 --- a/bin/next-init +++ b/bin/next-init @@ -3,6 +3,7 @@ import { resolve, join, basename } from 'path' import parseArgs from 'minimist' import { exists, writeFile, mkdir } from 'mz/fs' import mkdirp from 'mkdirp-then' +import getConfig from '../server/config' const argv = parseArgs(process.argv.slice(2), { alias: { @@ -29,9 +30,11 @@ if (argv.help) { } const dir = resolve(argv._[0] || '.') +const config = getConfig(dir) +const pagesDirectory = config.pagesDirectory -if (basename(dir) === 'pages') { - console.warn('Your root directory is named "pages". This looks suspicious. You probably want to go one directory up.') +if (basename(dir) === pagesDirectory) { + console.warn(`Your root directory is named "${pagesDirectory}". This looks suspicious. You probably want to go one directory up.`) process.exit(1) } @@ -49,9 +52,9 @@ exists(dir) await mkdir(join(dir, 'static')) } - if (!await exists(join(dir, 'pages'))) { - await mkdir(join(dir, 'pages')) - await writeFile(join(dir, 'pages', 'index.js'), basePage) + if (!await exists(join(dir, pagesDirectory))) { + await mkdir(join(dir, pagesDirectory)) + await writeFile(join(dir, pagesDirectory, 'index.js'), basePage) } }) .catch((err) => { diff --git a/lib/router/router.js b/lib/router/router.js index ca9484fc56073..5b6bdc73d3af0 100644 --- a/lib/router/router.js +++ b/lib/router/router.js @@ -339,8 +339,8 @@ export default class Router extends EventEmitter { } doFetchRoute (route) { - const { buildId } = window.__NEXT_DATA__ - const url = `/_next/${encodeURIComponent(buildId)}/pages${route}` + const { buildId, pagesDirectory } = window.__NEXT_DATA__ + const url = `/_next/${encodeURIComponent(buildId)}/${pagesDirectory}${route}` return fetch(url, { method: 'GET', diff --git a/server/build/loaders/hot-self-accept-loader.js b/server/build/loaders/hot-self-accept-loader.js index b33a31755f8bc..95ebabce2e1cd 100644 --- a/server/build/loaders/hot-self-accept-loader.js +++ b/server/build/loaders/hot-self-accept-loader.js @@ -1,4 +1,5 @@ import { resolve, relative } from 'path' +import getConfig from '../../config' module.exports = function (content, sourceMap) { this.cacheable() @@ -34,7 +35,9 @@ module.exports = function (content, sourceMap) { const nextPagesDir = resolve(__dirname, '..', '..', '..', 'pages') function getRoute (loaderContext) { - const pagesDir = resolve(loaderContext.options.context, 'pages') + const config = getConfig(loaderContext.options.context) + const pagesDirectory = config.pagesDirectory + const pagesDir = resolve(loaderContext.options.context, pagesDirectory) const { resourcePath } = loaderContext const dir = [pagesDir, nextPagesDir] .find((d) => resourcePath.indexOf(d) === 0) diff --git a/server/build/plugins/json-pages-plugin.js b/server/build/plugins/json-pages-plugin.js index 06e34851b5f4d..b7da1ffea9501 100644 --- a/server/build/plugins/json-pages-plugin.js +++ b/server/build/plugins/json-pages-plugin.js @@ -1,9 +1,18 @@ +import getConfig from '../../config' + export default class JsonPagesPlugin { + constructor (dir) { + this.config = getConfig(dir) + } + apply (compiler) { compiler.plugin('after-compile', (compilation, callback) => { + const regex = new RegExp(`^bundles/${this.config.pagesDirectory}.*.js$`) const pages = Object .keys(compilation.assets) - .filter((filename) => /^bundles[/\\]pages.*\.js$/.test(filename)) + .filter((filename) => { + return filename.match(regex) + }) pages.forEach((pageName) => { const page = compilation.assets[pageName] diff --git a/server/build/plugins/watch-pages-plugin.js b/server/build/plugins/watch-pages-plugin.js index c258c3293cfed..e10cc5d4063f9 100644 --- a/server/build/plugins/watch-pages-plugin.js +++ b/server/build/plugins/watch-pages-plugin.js @@ -1,8 +1,9 @@ -import { resolve, join } from 'path' +import { join } from 'path' +import getConfig from '../../config' export default class WatchPagesPlugin { constructor (dir) { - this.dir = resolve(dir, 'pages') + this.config = getConfig(dir) } apply (compiler) { @@ -10,7 +11,7 @@ export default class WatchPagesPlugin { compilation.plugin('optimize-assets', (assets, callback) => { // transpile pages/_document.js and descendants, // but don't need the bundle file - delete assets[join('bundles', 'pages', '_document.js')] + delete assets[join('bundles', this.config.pagesDirectory, '_document.js')] callback() }) }) @@ -19,7 +20,7 @@ export default class WatchPagesPlugin { // watch the pages directory compilation.contextDependencies = [ ...compilation.contextDependencies, - this.dir + this.config.pagesDirectory ] callback() }) diff --git a/server/build/webpack.js b/server/build/webpack.js index dae3c8eca75d6..b82c2ae5d44b7 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -19,9 +19,6 @@ const defaultPages = [ ] const nextPagesDir = join(__dirname, '..', '..', 'pages') const nextNodeModulesDir = join(__dirname, '..', '..', '..', 'node_modules') -const interpolateNames = new Map(defaultPages.map((p) => { - return [join(nextPagesDir, p), `dist/pages/${p}`] -})) const relativeResolve = rootModuleRelativePath(require) @@ -34,6 +31,9 @@ export default async function createCompiler (dir, { dev = false, quiet = false, ] : [] const mainJS = dev ? require.resolve('../../client/next-dev') : require.resolve('../../client/next') + const interpolateNames = new Map(defaultPages.map((p) => { + return [join(nextPagesDir, p), `dist/${config.pagesDirectory}/${p}`] + })) let minChunks @@ -45,8 +45,8 @@ export default async function createCompiler (dir, { dev = false, quiet = false, ] } - const pages = await glob('pages/**/*.js', { cwd: dir }) - const devPages = pages.filter((p) => p === 'pages/_document.js' || p === 'pages/_error.js') + const pages = await glob(`${config.pagesDirectory}/**/*.js`, { cwd: dir }) + const devPages = pages.filter((p) => p === `${config.pagesDirectory}/_document.js` || p === `${config.pagesDirectory}/_error.js`) // In the dev environment, on-demand-entry-handler will take care of // managing pages. @@ -61,7 +61,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false, } for (const p of defaultPages) { - const entryName = join('bundles', 'pages', p) + const entryName = join('bundles', config.pagesDirectory, p) if (!entries[entryName]) { entries[entryName] = [...defaultEntries, join(nextPagesDir, p) + '?entry'] } @@ -104,7 +104,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false, new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production') }), - new JsonPagesPlugin(), + new JsonPagesPlugin(dir), new CaseSensitivePathPlugin() ] @@ -158,7 +158,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false, test: /\.js(\?[^?]*)?$/, loader: 'hot-self-accept-loader', include: [ - join(dir, 'pages'), + join(dir, config.pagesDirectory), nextPagesDir ] }, { diff --git a/server/config.js b/server/config.js index 0c14009e40a18..c8d9acf0e6fbb 100644 --- a/server/config.js +++ b/server/config.js @@ -5,7 +5,8 @@ const cache = new Map() const defaultConfig = { webpack: null, - poweredByHeader: true + poweredByHeader: true, + pagesDirectory: 'pages' } export default function getConfig (dir) { diff --git a/server/hot-reloader.js b/server/hot-reloader.js index 15998aab6ed43..3cdd2480dec41 100644 --- a/server/hot-reloader.js +++ b/server/hot-reloader.js @@ -100,7 +100,7 @@ export default class HotReloader { // and to update error content const failed = failedChunkNames - const rootDir = join('bundles', 'pages') + const rootDir = join('bundles', this.config.pagesDirectory) for (const n of new Set([...added, ...removed, ...failed, ...succeeded])) { const route = toRoute(relative(rootDir, n)) diff --git a/server/index.js b/server/index.js index f08a9fec8ed95..eab92e838ea9b 100644 --- a/server/index.js +++ b/server/index.js @@ -102,7 +102,7 @@ export default class Server { await this.serveStatic(req, res, p) }, - '/_next/:buildId/pages/:path*': async (req, res, params) => { + [`/_next/:buildId/${this.config.pagesDirectory}/:path*`]: async (req, res, params) => { if (!this.handleBuildId(params.buildId, res)) { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ buildIdMismatch: true })) @@ -300,7 +300,7 @@ export default class Server { const errors = this.hotReloader.getCompilationErrors() if (!errors.size) return - const id = join(this.dir, '.next', 'bundles', 'pages', page) + const id = join(this.dir, '.next', 'bundles', this.config.pagesDirectory, page) const p = resolveFromList(id, errors.keys()) if (p) return errors.get(p)[0] } diff --git a/server/on-demand-entry-handler.js b/server/on-demand-entry-handler.js index 79c9160b58804..4ccfd8b0b213a 100644 --- a/server/on-demand-entry-handler.js +++ b/server/on-demand-entry-handler.js @@ -3,6 +3,7 @@ import { EventEmitter } from 'events' import { join } from 'path' import { parse } from 'url' import resolvePath from './resolve' +import getConfig from './config' import touch from 'touch' const ADDED = Symbol('added') @@ -18,6 +19,7 @@ export default function onDemandEntryHandler (devMiddleware, compiler, { const lastAccessPages = [''] const doneCallbacks = new EventEmitter() const invalidator = new Invalidator(devMiddleware) + const config = getConfig(dir) let touchedAPage = false compiler.plugin('make', function (compilation, done) { @@ -67,7 +69,7 @@ export default function onDemandEntryHandler (devMiddleware, compiler, { async ensurePage (page) { page = normalizePage(page) - const pagePath = join(dir, 'pages', page) + const pagePath = join(dir, config.pagesDirectory, page) const pathname = await resolvePath(pagePath) const name = join('bundles', pathname.substring(dir.length)) diff --git a/server/render.js b/server/render.js index fac5dffb61bc7..1ff97808d4b09 100644 --- a/server/render.js +++ b/server/render.js @@ -2,6 +2,7 @@ import { join } from 'path' import { createElement } from 'react' import { renderToString, renderToStaticMarkup } from 'react-dom/server' import send from 'send' +import getConfig from './config' import requireModule from './require' import resolvePath from './resolve' import readPage from './read-page' @@ -38,13 +39,16 @@ async function doRender (req, res, pathname, query, { dev = false, staticMarkup = false } = {}) { + const config = getConfig(dir) + const pagesDirectory = config.pagesDirectory + page = page || pathname await ensurePage(page, { dir, hotReloader }) let [Component, Document] = await Promise.all([ - requireModule(join(dir, '.next', 'dist', 'pages', page)), - requireModule(join(dir, '.next', 'dist', 'pages', '_document')) + requireModule(join(dir, '.next', 'dist', pagesDirectory, page)), + requireModule(join(dir, '.next', 'dist', pagesDirectory, '_document')) ]) Component = Component.default || Component Document = Document.default || Document @@ -56,8 +60,8 @@ async function doRender (req, res, pathname, query, { errorComponent ] = await Promise.all([ loadGetInitialProps(Component, ctx), - readPage(join(dir, '.next', 'bundles', 'pages', page)), - readPage(join(dir, '.next', 'bundles', 'pages', '_error')) + readPage(join(dir, '.next', 'bundles', pagesDirectory, page)), + readPage(join(dir, '.next', 'bundles', pagesDirectory, '_error')) ]) // the response might be finshed on the getinitialprops call @@ -96,6 +100,7 @@ async function doRender (req, res, pathname, query, { query, buildId, buildStats, + pagesDirectory, err: (err && dev) ? errorToJSON(err) : null }, dev, @@ -108,12 +113,16 @@ async function doRender (req, res, pathname, query, { export async function renderJSON (req, res, page, { dir = process.cwd(), hotReloader } = {}) { await ensurePage(page, { dir, hotReloader }) - const pagePath = await resolvePath(join(dir, '.next', 'bundles', 'pages', page)) + const config = getConfig(dir) + const pagesDirectory = config.pagesDirectory + const pagePath = await resolvePath(join(dir, '.next', 'bundles', pagesDirectory, page)) return serveStatic(req, res, pagePath) } export async function renderErrorJSON (err, req, res, { dir = process.cwd(), dev = false } = {}) { - const component = await readPage(join(dir, '.next', 'bundles', 'pages', '_error')) + const config = getConfig(dir) + const pagesDirectory = config.pagesDirectory + const component = await readPage(join(dir, '.next', 'bundles', pagesDirectory, '_error')) sendJSON(res, { component, From 009af6b7f6b11733e23c721b44bbc3499e96f0d9 Mon Sep 17 00:00:00 2001 From: George Pantazis Date: Fri, 10 Mar 2017 17:03:59 -0600 Subject: [PATCH 2/2] Adding integration test for custom folder --- .../custom-page-directory/foo/index.js | 9 +++++ .../custom-page-directory/next.config.js | 3 ++ .../custom-page-directory/test/index.test.js | 38 +++++++++++++++++++ .../custom-page-directory/test/rendering.js | 11 ++++++ 4 files changed, 61 insertions(+) create mode 100644 test/integration/custom-page-directory/foo/index.js create mode 100644 test/integration/custom-page-directory/next.config.js create mode 100644 test/integration/custom-page-directory/test/index.test.js create mode 100644 test/integration/custom-page-directory/test/rendering.js diff --git a/test/integration/custom-page-directory/foo/index.js b/test/integration/custom-page-directory/foo/index.js new file mode 100644 index 0000000000000..554d0ab97cf60 --- /dev/null +++ b/test/integration/custom-page-directory/foo/index.js @@ -0,0 +1,9 @@ +import React from 'react' + +export default class FooHomePage extends React.Component { + render () { + return
+

Just a page in /foo

+
+ } +} diff --git a/test/integration/custom-page-directory/next.config.js b/test/integration/custom-page-directory/next.config.js new file mode 100644 index 0000000000000..1a594f991b619 --- /dev/null +++ b/test/integration/custom-page-directory/next.config.js @@ -0,0 +1,3 @@ +module.exports = { + pagesDirectory: 'foo' +} diff --git a/test/integration/custom-page-directory/test/index.test.js b/test/integration/custom-page-directory/test/index.test.js new file mode 100644 index 0000000000000..7d2d597c70db1 --- /dev/null +++ b/test/integration/custom-page-directory/test/index.test.js @@ -0,0 +1,38 @@ +/* global jasmine, describe, beforeAll, afterAll */ + +import { join } from 'path' +import { + nextServer, + renderViaAPI, + renderViaHTTP, + startApp, + stopApp +} from 'next-test-utils' + +// test suits +import rendering from './rendering' + +const context = {} +context.app = nextServer({ + dir: join(__dirname, '../'), + dev: true, + quiet: true +}) + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2 + +describe('Basic Features', () => { + beforeAll(async () => { + context.server = await startApp(context.app) + context.appPort = context.server.address().port + + // pre-build all pages at the start + await Promise.all([ + renderViaHTTP(context.appPort, '/') + ]) + }) + afterAll(() => stopApp(context.server)) + + rendering(context, 'Rendering via API', (p, q) => renderViaAPI(context.app, p, q)) + rendering(context, 'Rendering via HTTP', (p, q) => renderViaHTTP(context.appPort, p, q)) +}) diff --git a/test/integration/custom-page-directory/test/rendering.js b/test/integration/custom-page-directory/test/rendering.js new file mode 100644 index 0000000000000..b9d27685aacf4 --- /dev/null +++ b/test/integration/custom-page-directory/test/rendering.js @@ -0,0 +1,11 @@ +/* global describe, test, expect */ + +export default function ({ app }, suiteName, render) { + describe(suiteName, () => { + test('renders a stateless component', async () => { + const html = await render('/') + expect(html.includes('')).toBeTruthy() + expect(html.includes('Just a page in /foo')).toBeTruthy() + }) + }) +}