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 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')
})

View File

@ -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)
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
}
}
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

View File

@ -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)

View File

@ -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)

View File

@ -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())

View File

@ -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')

View File

@ -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()
}

View File

@ -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: {

View File

@ -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: '<pre>{{ stack }}</pre>' // Will be loaded on ready
errorTemplate: parseTemplate('<pre>{{ stack }}</pre>') // 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

View File

@ -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)