diff --git a/bin/nuxt-build b/bin/nuxt-build index 5d70258cef..0141ed65fc 100755 --- a/bin/nuxt-build +++ b/bin/nuxt-build @@ -5,8 +5,7 @@ process.env.DEBUG = 'nuxt:*' const fs = require('fs') const parseArgs = require('minimist') -const without = require('lodash').without -const { Nuxt } = require('../') +const { Nuxt, Builder } = require('../') const resolve = require('path').resolve const debug = require('debug')('nuxt:build') debug.color = 2 // Force green color @@ -53,7 +52,6 @@ if (typeof options.rootDir !== 'string') { } // Create production build when calling `nuxt build` options.dev = false -options.runBuild = true // Force doing production build before init // Analyze option options.build = options.build || {} @@ -62,8 +60,10 @@ if (argv.analyze) { } debug('Building...') -const nuxt = module.exports = new Nuxt(options) -nuxt.ready() +const nuxt = new Nuxt(options) +const builder = new Builder(nuxt) + +builder.build() .then(() => { debug('Building done') }) diff --git a/bin/nuxt-dev b/bin/nuxt-dev index 229a523355..b26fcbe623 100755 --- a/bin/nuxt-dev +++ b/bin/nuxt-dev @@ -8,10 +8,9 @@ const debug = require('debug')('nuxt:build') debug.color = 2 // force green color const fs = require('fs') const parseArgs = require('minimist') -const { Nuxt, Server } = require('../') +const { Nuxt, Server, Builder } = require('../') const chokidar = require('chokidar') const resolve = require('path').resolve -const without = require('lodash').without const argv = parseArgs(process.argv.slice(2), { alias: { @@ -65,18 +64,23 @@ if (typeof options.rootDir !== 'string') { options.dev = true const nuxt = new Nuxt(options) +const builder = new Builder(nuxt) + const port = argv.port || process.env.PORT || process.env.npm_package_config_nuxt_port const host = argv.hostname || process.env.HOST || process.env.npm_package_config_nuxt_host -const server = new Server(nuxt).listen(port, host) +const server = new Server(nuxt) +server.listen(port, host) -listenOnConfigChanges(nuxt, server) +builder.build().then(() => { + listenOnConfigChanges(nuxt, server) +}) function listenOnConfigChanges (nuxt, server) { // Listen on nuxt.config.js changes const build = _.debounce(() => { debug('[nuxt.config.js] changed') delete require.cache[nuxtConfigFile] - let options = {} + var options = {} if (fs.existsSync(nuxtConfigFile)) { try { options = require(nuxtConfigFile) @@ -84,13 +88,18 @@ function listenOnConfigChanges (nuxt, server) { return console.error(e) // eslint-disable-line no-console } } - options.rootDir = rootDir + + if (typeof options.rootDir !== 'string') { + options.rootDir = rootDir + } + nuxt.close() .then(() => { debug('Rebuilding the app...') const nuxt = new Nuxt(options) + const builder = new Builder(nuxt) server.nuxt = nuxt - return nuxt.ready() + return builder.build() }) .catch((error) => { console.error('Error while rebuild the app:', error) // eslint-disable-line no-console diff --git a/bin/nuxt-generate b/bin/nuxt-generate index ad7d4da2e0..fd97bc4fde 100755 --- a/bin/nuxt-generate +++ b/bin/nuxt-generate @@ -7,7 +7,7 @@ const fs = require('fs') const parseArgs = require('minimist') const debug = require('debug')('nuxt:generate') -const { Nuxt } = require('../') +const { Nuxt, Builder, Generator } = require('../') const resolve = require('path').resolve const argv = parseArgs(process.argv.slice(2), { @@ -49,11 +49,12 @@ if (typeof options.rootDir !== 'string') { options.rootDir = rootDir } options.dev = false // Force production mode (no webpack middleware called) -options.runBuild = true // Force doing production build before init debug('Generating...') -const nuxt = module.exports = new Nuxt(options) -nuxt.generate() +const nuxt = new Nuxt(options) +const builder = new Builder(nuxt) +const generator = new Generator(nuxt, builder) +generator.generate() .then(() => { debug('Generate done') process.exit(0) diff --git a/index.js b/index.js index a3e2b704ee..4f1b490c42 100644 --- a/index.js +++ b/index.js @@ -14,8 +14,15 @@ process.noDeprecation = true // Require Core const Core = require('./dist/core.js') +Object.assign(exports, Core.default || Core) // Require Builder const Builder = require('./dist/builder') +Object.assign(exports, Builder.default || Builder) + +// Use special env flag to specify app dir without modify builder +if (!process.env.NUXT_APP_DIR) { + process.env.NUXT_APP_DIR = path.resolve(__dirname, 'lib/app') +} module.exports = Object.assign(Core, Builder) diff --git a/lib/builder/builder.js b/lib/builder/builder.js index a86fd46568..21a55ae45c 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -1,6 +1,6 @@ import _ from 'lodash' import chokidar from 'chokidar' -import fs from 'fs-extra' +import fs, { remove, readFile, writeFile, mkdirp, utimes } from 'fs-extra' import hash from 'hash-sum' import pify from 'pify' import webpack from 'webpack' @@ -19,11 +19,6 @@ import serverWebpackConfig from './webpack/server.config.js' const debug = Debug('nuxt:build') debug.color = 2 // Force green color -const remove = pify(fs.remove) -const readFile = pify(fs.readFile) -const utimes = pify(fs.utimes) -const writeFile = pify(fs.writeFile) -const mkdirp = pify(fs.mkdirp) const glob = pify(Glob) export default class Builder extends Tapable { @@ -32,8 +27,6 @@ export default class Builder extends Tapable { this.nuxt = nuxt this.options = nuxt.options - this._buildStatus = STATUS.INITIAL - // Fields that set on build this.compiler = null this.webpackDevMiddleware = null @@ -46,11 +39,13 @@ export default class Builder extends Tapable { modules: false, colors: true } + + this._buildStatus = STATUS.INITIAL } async build () { - // Avoid calling this method multiple times - if (this._buildStatus === STATUS.BUILD_DONE) { + // Avoid calling build() method multiple times when dev:true + if (this._buildStatus === STATUS.BUILD_DONE && this.options.dev) { return this } // If building @@ -63,6 +58,9 @@ export default class Builder extends Tapable { } this._buildStatus = STATUS.BUILDING + // Wait for nuxt ready + await this.nuxt.ready() + // Check if pages dir exists and warn if not this._nuxtPages = typeof this.options.build.createRoutes !== 'function' if (this._nuxtPages) { @@ -258,16 +256,27 @@ export default class Builder extends Tapable { // Simulate webpack multi compiler interface // Separate compilers are simpler, safer and faster - this.compiler = { cache: {}, compilers: [] } - compilersOptions.forEach(compilersOption => { - this.compiler.compilers.push(webpack(compilersOption)) - }) + this.compiler = { compilers: [] } this.compiler.plugin = (...args) => { this.compiler.compilers.forEach(compiler => { compiler.plugin(...args) }) } + // Initialize shared FS and Cache + const sharedFS = this.options.dev && new MFS() + const sharedCache = {} + + // Initialize compilers + compilersOptions.forEach(compilersOption => { + const compiler = webpack(compilersOption) + if (sharedFS) { + compiler.outputFileSystem = sharedFS + } + compiler.cache = sharedCache + this.compiler.compilers.push(compiler) + }) + // Access to compilers with name this.compiler.compilers.forEach(compiler => { if (compiler.name) { @@ -275,6 +284,18 @@ export default class Builder extends Tapable { } }) + // Run after each compile + this.compiler.plugin('done', stats => { + // Don't reload failed builds + if (stats.hasErrors() || stats.hasWarnings()) { + return + } + // Reload renderer if available + if (this.nuxt.renderer) { + this.nuxt.renderer.loadResources(sharedFS || fs) + } + }) + // Add dev Stuff if (this.options.dev) { this.webpackDev() @@ -297,8 +318,11 @@ export default class Builder extends Tapable { }) } } else { - // --- Production build --- + // --- Production Build --- compiler.run((err, stats) => { + if (err) { + return reject(err) + } if (err) return console.error(err) // eslint-disable-line no-console // Show build stats for production console.log(stats.toString(this.webpackStats)) // eslint-disable-line no-console @@ -312,26 +336,6 @@ export default class Builder extends Tapable { } webpackDev () { - // Use shared MFS + Cache for faster builds - let mfs = new MFS() - this.compiler.compilers.forEach(compiler => { - compiler.outputFileSystem = mfs - compiler.cache = this.compiler.cache - }) - - // Run after each compile - this.compiler.plugin('done', stats => { - // Don't reload failed builds - if (stats.hasErrors() || stats.hasWarnings()) { - return - } - // Reload renderer if available - if (this.nuxt.renderer) { - this.nuxt.renderer.loadResources(mfs) - } - }) - - // Add dev Middleware debug('Adding webpack middleware...') // Create webpack dev middleware @@ -348,6 +352,12 @@ export default class Builder extends Tapable { heartbeat: 2500 })) + // Inject to renderer instance + if (this.nuxt.renderer) { + this.nuxt.renderer.webpackDevMiddleware = this.webpackDevMiddleware + this.nuxt.renderer.webpackHotMiddleware = this.webpackHotMiddleware + } + // Stop webpack middleware on nuxt.close() this.nuxt.plugin('close', () => new Promise(resolve => { this.webpackDevMiddleware.close(() => resolve()) diff --git a/lib/builder/generator.js b/lib/builder/generator.js index e9ac779805..2589134043 100644 --- a/lib/builder/generator.js +++ b/lib/builder/generator.js @@ -1,5 +1,5 @@ -import fs from 'fs-extra' -import pify from 'pify' +import fs from 'fs' +import { copy, remove, writeFile, mkdirp } from 'fs-extra' import _ from 'lodash' import { resolve, join, dirname, sep } from 'path' import { minify } from 'html-minifier' @@ -8,19 +8,16 @@ import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils' import Debug from 'debug' const debug = Debug('nuxt:generate') -const copy = pify(fs.copy) -const remove = pify(fs.remove) -const writeFile = pify(fs.writeFile) -const mkdirp = pify(fs.mkdirp) export default class Generator extends Tapable { - constructor (nuxt) { + constructor (nuxt, builder) { super() this.nuxt = nuxt this.options = nuxt.options + this.builder = builder } - async generate () { + async generate (doBuild = true) { const s = Date.now() let errors = [] let generateRoutes = [] @@ -34,6 +31,11 @@ export default class Generator extends Tapable { // Wait for nuxt be ready await this.nuxt.ready() + // Start build process + if (this.builder && doBuild) { + await this.builder.build() + } + // Clean destination folder await remove(distPath) debug('Destination folder cleaned') diff --git a/lib/core/nuxt.js b/lib/core/nuxt.js index b07016894c..a6f9ffd510 100644 --- a/lib/core/nuxt.js +++ b/lib/core/nuxt.js @@ -1,12 +1,7 @@ import Tapable from 'tappable' -import chalk from 'chalk' import ModuleContainer from './module' import Renderer from './renderer' import Options from './options' -import Core from './index' - -const defaultHost = process.env.HOST || process.env.npm_package_config_nuxt_host || 'localhost' -const defaultPort = process.env.PORT || process.env.npm_package_config_nuxt_port || '3000' export default class Nuxt extends Tapable { constructor (_options = {}) { @@ -26,18 +21,6 @@ export default class Nuxt extends Tapable { this.renderRoute = this.renderer.renderRoute.bind(this.renderer) this.renderAndGetWindow = this.renderer.renderAndGetWindow.bind(this.renderer) - // Builder is lazy loaded, so register plugin here - this.plugin('init', async () => { - // Call to build on dev - if (this.options.dev) { - this.builder.build().catch(this.errorHandler) - } - // If explicitly runBuild required - if (this.options.runBuild) { - await this.builder.build() - } - }) - this._ready = this.ready() } @@ -55,26 +38,6 @@ export default class Nuxt extends Tapable { return this } - get builder () { - if (this._builder) { - return this._builder - } - this._builder = new Core.Builder(this) - return this._builder - } - - get generator () { - if (this._generator) { - return this._generator - } - this._generator = new Core.Generator(this) - return this._generator - } - - generate () { - return this.generator.generate.apply(this.generator, arguments) - } - errorHandler () { // Silent if (this.options.errorHandler === false) { @@ -90,26 +53,10 @@ export default class Nuxt extends Tapable { process.exit(1) } - // Both Renderer & Server depend on this method - serverReady ({ host = defaultHost, port = defaultPort } = {}) { - let _host = host === '0.0.0.0' ? 'localhost' : host - - // eslint-disable-next-line no-console - console.log('\n' + chalk.bold(chalk.bgBlue.black(' OPEN ') + chalk.blue(` http://${_host}:${port}\n`))) - - return this.applyPluginsAsync('serverReady').catch(this.errorHandler) - } - async close (callback) { // Call for close await this.applyPluginsAsync('close') - // Remove all references - delete this._generator - delete this._builder - - this.initialized = false - if (typeof callback === 'function') { await callback() } diff --git a/lib/core/options.js b/lib/core/options.js index c4e735968e..498ec8c30d 100755 --- a/lib/core/options.js +++ b/lib/core/options.js @@ -45,17 +45,11 @@ export default function Options (_options) { options.store = true } - // runBuild can not be enabled for dev === true - if (options.dev === true) { - options.runBuild = false - } - return options } const defaultOptions = { dev: (process.env.NODE_ENV !== 'production'), - runBuild: false, buildDir: '.nuxt', nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist build: { diff --git a/lib/core/renderer.js b/lib/core/renderer.js index f9d7114910..b688b25b99 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -33,12 +33,16 @@ export default class Renderer extends Tapable { // Will be set by createRenderer this.bundleRenderer = null + // Will be available on dev + this.webpackDevMiddleware = null + this.webpackHotMiddleware = null + // Renderer runtime resources this.resources = { clientManifest: null, serverBundle: null, appTemplate: null, - errorTemplate: '
{{ stack }}' // Will be loaded on ready + errorTemplate: parseTemplate('
{{ stack }}') // Will be loaded on ready } // Initialize @@ -146,10 +150,6 @@ export default class Renderer extends Tapable { // Promisify renderToString this.bundleRenderer.renderToString = pify(this.bundleRenderer.renderToString) - - if (!this.options.runBuild) { - this.nuxt.serverReady() - } } async render (req, res) { @@ -170,9 +170,12 @@ export default class Renderer extends Tapable { } // Call webpack middleware only in development - if (this.options.dev && this.nuxt.builder && this.nuxt.builder.webpackDevMiddleware) { - await this.nuxt.builder.webpackDevMiddleware(req, res) - await this.nuxt.builder.webpackHotMiddleware(req, res) + if (this.webpackDevMiddleware) { + await this.webpackDevMiddleware(req, res) + } + + if (this.webpackHotMiddleware) { + await this.webpackHotMiddleware(req, res) } // Serve static/ files diff --git a/lib/core/server.js b/lib/core/server.js index dd27891aca..619f4c879f 100644 --- a/lib/core/server.js +++ b/lib/core/server.js @@ -1,6 +1,7 @@ import http from 'http' import connect from 'connect' import path from 'path' +import chalk from 'chalk' class Server { constructor (nuxt) { @@ -32,6 +33,7 @@ class Server { this.options.serverMiddleware.forEach(m => { this.useMiddleware(m) }) + // Add default render middleware this.useMiddleware(this.render.bind(this)) @@ -66,8 +68,9 @@ class Server { this.nuxt.ready() .then(() => { this.server.listen(port, host, () => { - // Renderer calls showURL when server is really ready - // this.nuxt.showURL(host, port) + let _host = host === '0.0.0.0' ? 'localhost' : host + // eslint-disable-next-line no-console + console.log('\n' + chalk.bold(chalk.bgBlue.black(' OPEN ') + chalk.blue(` http://${_host}:${port}\n`))) }) }) .catch(this.nuxt.errorHandler)