diff --git a/.gitignore b/.gitignore index 1a60afccbeee..1f02e940c27a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ npm-debug.log # coverage .nyc_output coverage + +.DS_Store diff --git a/bin/next-build b/bin/next-build index 6a964540ec12..fcd1347aa9ac 100755 --- a/bin/next-build +++ b/bin/next-build @@ -22,8 +22,9 @@ if (argv.help) { Usage $ next build - represents where the compiled .next folder should go. - If no directory is provided, .next will be created in the current directory + represents where the compiled dist folder should go. + If no directory is provided, the dist folder will be created in the current directory. + You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration, otherwise it will be created inside '.next' `) process.exit(0) } diff --git a/bin/next-dev b/bin/next-dev index d5055a3cf200..02dda15dec7f 100755 --- a/bin/next-dev +++ b/bin/next-dev @@ -29,8 +29,9 @@ if (argv.help) { Usage $ next dev -p - represents where the compiled .next folder should go. - If no directory is provided, .next will be created in the current directory + represents where the compiled folder should go. + If no directory is provided, the folder will be created in the current directory. + You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration. Options --port, -p A port number on which to start the application diff --git a/bin/next-start b/bin/next-start index 854a11e9b267..09d222315436 100755 --- a/bin/next-start +++ b/bin/next-start @@ -4,6 +4,7 @@ import { resolve } from 'path' import parseArgs from 'minimist' import Server from '../server' import { existsSync } from 'fs' +import getConfig from '../server/config' process.env.NODE_ENV = process.env.NODE_ENV || 'production' @@ -32,9 +33,10 @@ if (argv.help) { Usage $ next start -p - is the directory that contains the compiled .next folder + is the directory that contains the compiled dist folder created by running \`next build\`. If no directory is provided, the current directory will be assumed. + You can set a custom dist folder in config https://github.com/zeit/next.js#custom-configuration Options --port, -p A port number on which to start the application @@ -45,11 +47,12 @@ if (argv.help) { } const dir = resolve(argv._[0] || '.') +const dist = getConfig(dir).distDir const srv = new Server({ dir }) -if (!existsSync(resolve(dir, '.next', 'BUILD_ID'))) { - console.error(`> Could not find a valid build in the '.next' directory! Try building your app with 'next build' before starting the server.`) +if (!existsSync(resolve(dir, dist, 'BUILD_ID'))) { + console.error(`> Could not find a valid build in the '${dist}' directory! Try building your app with 'next build' before starting the server.`) process.exit(1) } diff --git a/readme.md b/readme.md index d5bb023b6fe5..3f5c607b1b4c 100644 --- a/readme.md +++ b/readme.md @@ -630,6 +630,17 @@ module.exports = { } ``` +#### Setting a custom build directory + +You can specify a name to use for a custom build directory. For example, the following config will create a `build` folder instead of a `.next` folder. If no configuration is specified then next will create a `.next` folder. + +```javascript +// next.config.js +module.exports = { + distDir: 'build' +} +``` + ### Customizing webpack config In order to extend our usage of `webpack`, you can define a function that extends its config via `next.config.js`. @@ -704,7 +715,7 @@ Then run `now` and enjoy! Next.js can be deployed to other hosting solutions too. Please have a look at the ['Deployment'](https://github.com/zeit/next.js/wiki/Deployment) section of the wiki. -Note: we recommend putting `.next` in `.npmignore` or `.gitignore`. Otherwise, use `files` or `now.files` to opt-into a whitelist of files you want to deploy (and obviously exclude `.next`) +Note: we recommend putting `.next`, or your custom dist folder (Please have a look at ['Custom Config'](You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration.)), in `.npmignore` or `.gitignore`. Otherwise, use `files` or `now.files` to opt-into a whitelist of files you want to deploy (and obviously exclude `.next` or your custom dist folder) ## FAQ diff --git a/server/build/clean.js b/server/build/clean.js index f0d0876a47db..f108bd2568e3 100644 --- a/server/build/clean.js +++ b/server/build/clean.js @@ -1,6 +1,8 @@ import { resolve } from 'path' import del from 'del' +import getConfig from '../config' export default function clean (dir) { - return del(resolve(dir, '.next')) + const dist = getConfig(dir).distDir + return del(resolve(dir, dist)) } diff --git a/server/build/index.js b/server/build/index.js index e13d36ff66f9..95195460e749 100644 --- a/server/build/index.js +++ b/server/build/index.js @@ -1,5 +1,6 @@ import { tmpdir } from 'os' import { join } from 'path' +import getConfig from '../config' import fs from 'mz/fs' import uuid from 'uuid' import del from 'del' @@ -13,8 +14,10 @@ export default async function build (dir) { try { await runCompiler(compiler) - await writeBuildStats(buildDir) - await writeBuildId(buildDir) + + // Pass in both the buildDir and the dir to retrieve config + await writeBuildStats(buildDir, dir) + await writeBuildId(buildDir, dir) } catch (err) { console.error(`> Failed to build on ${buildDir}`) throw err @@ -45,22 +48,24 @@ function runCompiler (compiler) { }) } -async function writeBuildStats (dir) { +async function writeBuildStats (buildDir, dir) { + const dist = getConfig(dir).distDir // Here we can't use hashes in webpack chunks. // That's because the "app.js" is not tied to a chunk. // It's created by merging a few assets. (commons.js and main.js) // So, we need to generate the hash ourself. const assetHashMap = { 'app.js': { - hash: await md5File(join(dir, '.next', 'app.js')) + hash: await md5File(join(buildDir, dist, 'app.js')) } } - const buildStatsPath = join(dir, '.next', 'build-stats.json') + const buildStatsPath = join(buildDir, dist, 'build-stats.json') await fs.writeFile(buildStatsPath, JSON.stringify(assetHashMap), 'utf8') } -async function writeBuildId (dir) { - const buildIdPath = join(dir, '.next', 'BUILD_ID') +async function writeBuildId (buildDir, dir) { + const dist = getConfig(dir).distDir + const buildIdPath = join(buildDir, dist, 'BUILD_ID') const buildId = uuid.v4() await fs.writeFile(buildIdPath, buildId, 'utf8') } diff --git a/server/build/replace.js b/server/build/replace.js index daf071647b65..1679ba25ad97 100644 --- a/server/build/replace.js +++ b/server/build/replace.js @@ -1,10 +1,13 @@ import mv from 'mv' import { join } from 'path' +import getConfig from '../config' export default async function replaceCurrentBuild (dir, buildDir) { - const _dir = join(dir, '.next') - const _buildDir = join(buildDir, '.next') - const oldDir = join(buildDir, '.next.old') + const dist = getConfig(dir).distDir + const buildDist = getConfig(buildDir).distDir + const _dir = join(dir, dist) + const _buildDir = join(buildDir, dist) + const oldDir = join(buildDir, `${buildDist}.old`) try { await move(_dir, oldDir) diff --git a/server/build/webpack.js b/server/build/webpack.js index 804434a32d9f..5e9785edcd6a 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -265,7 +265,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false, context: dir, entry, output: { - path: join(buildDir || dir, '.next'), + path: join(buildDir || dir, config.distDir), filename: '[name]', libraryTarget: 'commonjs2', publicPath: '/_webpack/', diff --git a/server/config.js b/server/config.js index 0c14009e40a1..5a70252cb941 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, + distDir: '.next' } export default function getConfig (dir) { diff --git a/server/index.js b/server/index.js index 17a45090cd73..75e4e7bf3d0d 100644 --- a/server/index.js +++ b/server/index.js @@ -27,7 +27,8 @@ export default class Server { this.hotReloader = dev ? new HotReloader(this.dir, { quiet }) : null this.http = null this.config = getConfig(this.dir) - this.buildStats = !dev ? require(join(this.dir, '.next', 'build-stats.json')) : null + this.dist = this.config.distDir + this.buildStats = !dev ? require(join(this.dir, this.dist, 'build-stats.json')) : null this.buildId = !dev ? this.readBuildId() : '-' this.renderOpts = { dev, @@ -92,25 +93,25 @@ export default class Server { '/_next/:hash/manifest.js': async (req, res, params) => { this.handleBuildHash('manifest.js', params.hash, res) - const p = join(this.dir, '.next/manifest.js') + const p = join(this.dir, `${this.dist}/manifest.js`) await this.serveStatic(req, res, p) }, '/_next/:hash/main.js': async (req, res, params) => { this.handleBuildHash('main.js', params.hash, res) - const p = join(this.dir, '.next/main.js') + const p = join(this.dir, `${this.dist}/main.js`) await this.serveStatic(req, res, p) }, '/_next/:hash/commons.js': async (req, res, params) => { this.handleBuildHash('commons.js', params.hash, res) - const p = join(this.dir, '.next/commons.js') + const p = join(this.dir, `${this.dist}/commons.js`) await this.serveStatic(req, res, p) }, '/_next/:hash/app.js': async (req, res, params) => { this.handleBuildHash('app.js', params.hash, res) - const p = join(this.dir, '.next/app.js') + const p = join(this.dir, `${this.dist}/app.js`) await this.serveStatic(req, res, p) }, @@ -291,7 +292,7 @@ export default class Server { } readBuildId () { - const buildIdPath = join(this.dir, '.next', 'BUILD_ID') + const buildIdPath = join(this.dir, this.dist, 'BUILD_ID') const buildId = fs.readFileSync(buildIdPath, 'utf8') return buildId.trim() } @@ -312,7 +313,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, this.dist, 'bundles', 'pages', page) const p = resolveFromList(id, errors.keys()) if (p) return errors.get(p)[0] } diff --git a/server/render.js b/server/render.js index 36c18994e369..c0447bdf96e9 100644 --- a/server/render.js +++ b/server/render.js @@ -3,6 +3,7 @@ import { createElement } from 'react' import { renderToString, renderToStaticMarkup } from 'react-dom/server' import send from 'send' import requireModule from './require' +import getConfig from './config' import resolvePath from './resolve' import readPage from './read-page' import { Router } from '../lib/router' @@ -42,9 +43,11 @@ async function doRender (req, res, pathname, query, { await ensurePage(page, { dir, hotReloader }) + const dist = getConfig(dir).distDir + let [Component, Document] = await Promise.all([ - requireModule(join(dir, '.next', 'dist', 'pages', page)), - requireModule(join(dir, '.next', 'dist', 'pages', '_document')) + requireModule(join(dir, dist, 'dist', 'pages', page)), + requireModule(join(dir, dist, 'dist', 'pages', '_document')) ]) Component = Component.default || Component Document = Document.default || Document @@ -56,8 +59,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, dist, 'bundles', 'pages', page)), + readPage(join(dir, dist, 'bundles', 'pages', '_error')) ]) // the response might be finshed on the getinitialprops call @@ -107,13 +110,15 @@ async function doRender (req, res, pathname, query, { } export async function renderJSON (req, res, page, { dir = process.cwd(), hotReloader } = {}) { + const dist = getConfig(dir).distDir await ensurePage(page, { dir, hotReloader }) - const pagePath = await resolvePath(join(dir, '.next', 'bundles', 'pages', page)) + const pagePath = await resolvePath(join(dir, dist, 'bundles', 'pages', 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 dist = getConfig(dir).distDir + const component = await readPage(join(dir, dist, 'bundles', 'pages', '_error')) sendJSON(res, { component, diff --git a/test/integration/dist-dir/next.config.js b/test/integration/dist-dir/next.config.js new file mode 100644 index 000000000000..db58ee17b506 --- /dev/null +++ b/test/integration/dist-dir/next.config.js @@ -0,0 +1,7 @@ +module.exports = { + onDemandEntries: { + // Make sure entries are not getting disposed. + maxInactiveAge: 1000 * 60 * 60 + }, + distDir: 'dist' +} diff --git a/test/integration/dist-dir/pages/index.js b/test/integration/dist-dir/pages/index.js new file mode 100644 index 000000000000..3d446a4e89b8 --- /dev/null +++ b/test/integration/dist-dir/pages/index.js @@ -0,0 +1,3 @@ +export default () => ( +
Hello World
+) diff --git a/test/integration/dist-dir/test/index.test.js b/test/integration/dist-dir/test/index.test.js new file mode 100644 index 000000000000..913f223f230f --- /dev/null +++ b/test/integration/dist-dir/test/index.test.js @@ -0,0 +1,49 @@ +/* global jasmine, describe, it, expect, beforeAll, afterAll */ + +import { join } from 'path' +import { existsSync } from 'fs' +import { + nextServer, + nextBuild, + startApp, + stopApp, + renderViaHTTP +} from 'next-test-utils' + +const appDir = join(__dirname, '../') +let appPort +let server +let app +jasmine.DEFAULT_TIMEOUT_INTERVAL = 40000 + +describe('Production Usage', () => { + beforeAll(async () => { + await nextBuild(appDir) + app = nextServer({ + dir: join(__dirname, '../'), + dev: false, + quiet: true + }) + + server = await startApp(app) + appPort = server.address().port + }) + afterAll(() => stopApp(server)) + + describe('With basic usage', () => { + it('should render the page', async () => { + const html = await renderViaHTTP(appPort, '/') + expect(html).toMatch(/Hello World/) + }) + }) + + describe('File locations', () => { + it('should build the app within the given `dist` directory', () => { + expect(existsSync(join(__dirname, '/../dist/app.js'))).toBeTruthy() + }) + + it('should not build the app within the default `.next` directory', () => { + expect(existsSync(join(__dirname, '/../.next/app.js'))).toBeFalsy() + }) + }) +})