Fully decouple builder

runBuild flag removed
This commit is contained in:
Pooya Parsa 2017-06-18 20:14:01 +04:30
parent 5521925668
commit 59d7e786aa
10 changed files with 104 additions and 128 deletions

View File

@ -5,8 +5,7 @@ process.env.DEBUG = 'nuxt:*'
const fs = require('fs') const fs = require('fs')
const parseArgs = require('minimist') const parseArgs = require('minimist')
const without = require('lodash').without const { Nuxt, Builder } = require('../')
const { Nuxt } = require('../')
const resolve = require('path').resolve const resolve = require('path').resolve
const debug = require('debug')('nuxt:build') const debug = require('debug')('nuxt:build')
debug.color = 2 // Force green color debug.color = 2 // Force green color
@ -53,7 +52,6 @@ if (typeof options.rootDir !== 'string') {
} }
// Create production build when calling `nuxt build` // Create production build when calling `nuxt build`
options.dev = false options.dev = false
options.runBuild = true // Force doing production build before init
// Analyze option // Analyze option
options.build = options.build || {} options.build = options.build || {}
@ -62,8 +60,10 @@ if (argv.analyze) {
} }
debug('Building...') debug('Building...')
const nuxt = module.exports = new Nuxt(options) const nuxt = new Nuxt(options)
nuxt.ready() const builder = new Builder(nuxt)
builder.build()
.then(() => { .then(() => {
debug('Building done') debug('Building done')
}) })

View File

@ -8,10 +8,9 @@ const debug = require('debug')('nuxt:build')
debug.color = 2 // force green color debug.color = 2 // force green color
const fs = require('fs') const fs = require('fs')
const parseArgs = require('minimist') const parseArgs = require('minimist')
const { Nuxt, Server } = require('../') const { Nuxt, Server, Builder } = require('../')
const chokidar = require('chokidar') const chokidar = require('chokidar')
const resolve = require('path').resolve const resolve = require('path').resolve
const without = require('lodash').without
const argv = parseArgs(process.argv.slice(2), { const argv = parseArgs(process.argv.slice(2), {
alias: { alias: {
@ -65,18 +64,23 @@ if (typeof options.rootDir !== 'string') {
options.dev = true options.dev = true
const nuxt = new Nuxt(options) 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 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 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)
builder.build().then(() => {
listenOnConfigChanges(nuxt, server) listenOnConfigChanges(nuxt, server)
})
function listenOnConfigChanges (nuxt, server) { function listenOnConfigChanges (nuxt, server) {
// Listen on nuxt.config.js changes // Listen on nuxt.config.js changes
const build = _.debounce(() => { const build = _.debounce(() => {
debug('[nuxt.config.js] changed') debug('[nuxt.config.js] changed')
delete require.cache[nuxtConfigFile] delete require.cache[nuxtConfigFile]
let options = {} var options = {}
if (fs.existsSync(nuxtConfigFile)) { if (fs.existsSync(nuxtConfigFile)) {
try { try {
options = require(nuxtConfigFile) options = require(nuxtConfigFile)
@ -84,13 +88,18 @@ function listenOnConfigChanges (nuxt, server) {
return console.error(e) // eslint-disable-line no-console return console.error(e) // eslint-disable-line no-console
} }
} }
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir options.rootDir = rootDir
}
nuxt.close() nuxt.close()
.then(() => { .then(() => {
debug('Rebuilding the app...') debug('Rebuilding the app...')
const nuxt = new Nuxt(options) const nuxt = new Nuxt(options)
const builder = new Builder(nuxt)
server.nuxt = nuxt server.nuxt = nuxt
return nuxt.ready() return builder.build()
}) })
.catch((error) => { .catch((error) => {
console.error('Error while rebuild the app:', error) // eslint-disable-line no-console console.error('Error while rebuild the app:', error) // eslint-disable-line no-console

View File

@ -7,7 +7,7 @@ const fs = require('fs')
const parseArgs = require('minimist') const parseArgs = require('minimist')
const debug = require('debug')('nuxt:generate') const debug = require('debug')('nuxt:generate')
const { Nuxt } = require('../') const { Nuxt, Builder, Generator } = require('../')
const resolve = require('path').resolve const resolve = require('path').resolve
const argv = parseArgs(process.argv.slice(2), { const argv = parseArgs(process.argv.slice(2), {
@ -49,11 +49,12 @@ if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir options.rootDir = rootDir
} }
options.dev = false // Force production mode (no webpack middleware called) options.dev = false // Force production mode (no webpack middleware called)
options.runBuild = true // Force doing production build before init
debug('Generating...') debug('Generating...')
const nuxt = module.exports = new Nuxt(options) const nuxt = new Nuxt(options)
nuxt.generate() const builder = new Builder(nuxt)
const generator = new Generator(nuxt, builder)
generator.generate()
.then(() => { .then(() => {
debug('Generate done') debug('Generate done')
process.exit(0) process.exit(0)

View File

@ -14,8 +14,15 @@ process.noDeprecation = true
// Require Core // Require Core
const Core = require('./dist/core.js') const Core = require('./dist/core.js')
Object.assign(exports, Core.default || Core)
// Require Builder // Require Builder
const Builder = require('./dist/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) module.exports = Object.assign(Core, Builder)

View File

@ -1,6 +1,6 @@
import _ from 'lodash' import _ from 'lodash'
import chokidar from 'chokidar' 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 hash from 'hash-sum'
import pify from 'pify' import pify from 'pify'
import webpack from 'webpack' import webpack from 'webpack'
@ -19,11 +19,6 @@ import serverWebpackConfig from './webpack/server.config.js'
const debug = Debug('nuxt:build') const debug = Debug('nuxt:build')
debug.color = 2 // Force green color 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) const glob = pify(Glob)
export default class Builder extends Tapable { export default class Builder extends Tapable {
@ -32,8 +27,6 @@ export default class Builder extends Tapable {
this.nuxt = nuxt this.nuxt = nuxt
this.options = nuxt.options this.options = nuxt.options
this._buildStatus = STATUS.INITIAL
// Fields that set on build // Fields that set on build
this.compiler = null this.compiler = null
this.webpackDevMiddleware = null this.webpackDevMiddleware = null
@ -46,11 +39,13 @@ export default class Builder extends Tapable {
modules: false, modules: false,
colors: true colors: true
} }
this._buildStatus = STATUS.INITIAL
} }
async build () { async build () {
// Avoid calling this method multiple times // Avoid calling build() method multiple times when dev:true
if (this._buildStatus === STATUS.BUILD_DONE) { if (this._buildStatus === STATUS.BUILD_DONE && this.options.dev) {
return this return this
} }
// If building // If building
@ -63,6 +58,9 @@ export default class Builder extends Tapable {
} }
this._buildStatus = STATUS.BUILDING this._buildStatus = STATUS.BUILDING
// Wait for nuxt ready
await this.nuxt.ready()
// Check if pages dir exists and warn if not // Check if pages dir exists and warn if not
this._nuxtPages = typeof this.options.build.createRoutes !== 'function' this._nuxtPages = typeof this.options.build.createRoutes !== 'function'
if (this._nuxtPages) { if (this._nuxtPages) {
@ -258,16 +256,27 @@ export default class Builder extends Tapable {
// Simulate webpack multi compiler interface // Simulate webpack multi compiler interface
// Separate compilers are simpler, safer and faster // Separate compilers are simpler, safer and faster
this.compiler = { cache: {}, compilers: [] } this.compiler = { compilers: [] }
compilersOptions.forEach(compilersOption => {
this.compiler.compilers.push(webpack(compilersOption))
})
this.compiler.plugin = (...args) => { this.compiler.plugin = (...args) => {
this.compiler.compilers.forEach(compiler => { this.compiler.compilers.forEach(compiler => {
compiler.plugin(...args) 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 // Access to compilers with name
this.compiler.compilers.forEach(compiler => { this.compiler.compilers.forEach(compiler => {
if (compiler.name) { 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 // Add dev Stuff
if (this.options.dev) { if (this.options.dev) {
this.webpackDev() this.webpackDev()
@ -297,8 +318,11 @@ export default class Builder extends Tapable {
}) })
} }
} else { } else {
// --- Production build --- // --- Production Build ---
compiler.run((err, stats) => { compiler.run((err, stats) => {
if (err) {
return reject(err)
}
if (err) return console.error(err) // eslint-disable-line no-console if (err) return console.error(err) // eslint-disable-line no-console
// Show build stats for production // Show build stats for production
console.log(stats.toString(this.webpackStats)) // eslint-disable-line no-console console.log(stats.toString(this.webpackStats)) // eslint-disable-line no-console
@ -312,26 +336,6 @@ export default class Builder extends Tapable {
} }
webpackDev () { 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...') debug('Adding webpack middleware...')
// Create webpack dev middleware // Create webpack dev middleware
@ -348,6 +352,12 @@ export default class Builder extends Tapable {
heartbeat: 2500 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() // Stop webpack middleware on nuxt.close()
this.nuxt.plugin('close', () => new Promise(resolve => { this.nuxt.plugin('close', () => new Promise(resolve => {
this.webpackDevMiddleware.close(() => resolve()) this.webpackDevMiddleware.close(() => resolve())

View File

@ -1,5 +1,5 @@
import fs from 'fs-extra' import fs from 'fs'
import pify from 'pify' import { copy, remove, writeFile, mkdirp } from 'fs-extra'
import _ from 'lodash' import _ from 'lodash'
import { resolve, join, dirname, sep } from 'path' import { resolve, join, dirname, sep } from 'path'
import { minify } from 'html-minifier' import { minify } from 'html-minifier'
@ -8,19 +8,16 @@ import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils'
import Debug from 'debug' import Debug from 'debug'
const debug = Debug('nuxt:generate') 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 { export default class Generator extends Tapable {
constructor (nuxt) { constructor (nuxt, builder) {
super() super()
this.nuxt = nuxt this.nuxt = nuxt
this.options = nuxt.options this.options = nuxt.options
this.builder = builder
} }
async generate () { async generate (doBuild = true) {
const s = Date.now() const s = Date.now()
let errors = [] let errors = []
let generateRoutes = [] let generateRoutes = []
@ -34,6 +31,11 @@ export default class Generator extends Tapable {
// Wait for nuxt be ready // Wait for nuxt be ready
await this.nuxt.ready() await this.nuxt.ready()
// Start build process
if (this.builder && doBuild) {
await this.builder.build()
}
// Clean destination folder // Clean destination folder
await remove(distPath) await remove(distPath)
debug('Destination folder cleaned') debug('Destination folder cleaned')

View File

@ -1,12 +1,7 @@
import Tapable from 'tappable' import Tapable from 'tappable'
import chalk from 'chalk'
import ModuleContainer from './module' import ModuleContainer from './module'
import Renderer from './renderer' import Renderer from './renderer'
import Options from './options' 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 { export default class Nuxt extends Tapable {
constructor (_options = {}) { constructor (_options = {}) {
@ -26,18 +21,6 @@ export default class Nuxt extends Tapable {
this.renderRoute = this.renderer.renderRoute.bind(this.renderer) this.renderRoute = this.renderer.renderRoute.bind(this.renderer)
this.renderAndGetWindow = this.renderer.renderAndGetWindow.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() this._ready = this.ready()
} }
@ -55,26 +38,6 @@ export default class Nuxt extends Tapable {
return this 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 () { errorHandler () {
// Silent // Silent
if (this.options.errorHandler === false) { if (this.options.errorHandler === false) {
@ -90,26 +53,10 @@ export default class Nuxt extends Tapable {
process.exit(1) 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) { async close (callback) {
// Call for close // Call for close
await this.applyPluginsAsync('close') await this.applyPluginsAsync('close')
// Remove all references
delete this._generator
delete this._builder
this.initialized = false
if (typeof callback === 'function') { if (typeof callback === 'function') {
await callback() await callback()
} }

View File

@ -45,17 +45,11 @@ export default function Options (_options) {
options.store = true options.store = true
} }
// runBuild can not be enabled for dev === true
if (options.dev === true) {
options.runBuild = false
}
return options return options
} }
const defaultOptions = { const defaultOptions = {
dev: (process.env.NODE_ENV !== 'production'), dev: (process.env.NODE_ENV !== 'production'),
runBuild: false,
buildDir: '.nuxt', buildDir: '.nuxt',
nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist
build: { build: {

View File

@ -33,12 +33,16 @@ export default class Renderer extends Tapable {
// Will be set by createRenderer // Will be set by createRenderer
this.bundleRenderer = null this.bundleRenderer = null
// Will be available on dev
this.webpackDevMiddleware = null
this.webpackHotMiddleware = null
// Renderer runtime resources // Renderer runtime resources
this.resources = { this.resources = {
clientManifest: null, clientManifest: null,
serverBundle: null, serverBundle: null,
appTemplate: null, appTemplate: null,
errorTemplate: '<pre>{{ stack }}</pre>' // Will be loaded on ready errorTemplate: parseTemplate('<pre>{{ stack }}</pre>') // Will be loaded on ready
} }
// Initialize // Initialize
@ -146,10 +150,6 @@ export default class Renderer extends Tapable {
// Promisify renderToString // Promisify renderToString
this.bundleRenderer.renderToString = pify(this.bundleRenderer.renderToString) this.bundleRenderer.renderToString = pify(this.bundleRenderer.renderToString)
if (!this.options.runBuild) {
this.nuxt.serverReady()
}
} }
async render (req, res) { async render (req, res) {
@ -170,9 +170,12 @@ export default class Renderer extends Tapable {
} }
// Call webpack middleware only in development // Call webpack middleware only in development
if (this.options.dev && this.nuxt.builder && this.nuxt.builder.webpackDevMiddleware) { if (this.webpackDevMiddleware) {
await this.nuxt.builder.webpackDevMiddleware(req, res) await this.webpackDevMiddleware(req, res)
await this.nuxt.builder.webpackHotMiddleware(req, res) }
if (this.webpackHotMiddleware) {
await this.webpackHotMiddleware(req, res)
} }
// Serve static/ files // Serve static/ files

View File

@ -1,6 +1,7 @@
import http from 'http' import http from 'http'
import connect from 'connect' import connect from 'connect'
import path from 'path' import path from 'path'
import chalk from 'chalk'
class Server { class Server {
constructor (nuxt) { constructor (nuxt) {
@ -32,6 +33,7 @@ class Server {
this.options.serverMiddleware.forEach(m => { this.options.serverMiddleware.forEach(m => {
this.useMiddleware(m) this.useMiddleware(m)
}) })
// Add default render middleware // Add default render middleware
this.useMiddleware(this.render.bind(this)) this.useMiddleware(this.render.bind(this))
@ -66,8 +68,9 @@ class Server {
this.nuxt.ready() this.nuxt.ready()
.then(() => { .then(() => {
this.server.listen(port, host, () => { this.server.listen(port, host, () => {
// Renderer calls showURL when server is really ready let _host = host === '0.0.0.0' ? 'localhost' : host
// this.nuxt.showURL(host, port) // 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) .catch(this.nuxt.errorHandler)