diff --git a/README.md b/README.md index 44d7074586..39089af289 100644 --- a/README.md +++ b/README.md @@ -168,12 +168,19 @@ You can start by using one of our starter templates: ## Using nuxt.js programmatically ```js -const Nuxt = require('nuxt') +const { Nuxt, Builder } = require('nuxt') -// Launch nuxt build with given options +// Import and set nuxt.js options let config = require('./nuxt.config.js') +config.dev = !(process.env.NODE_ENV === 'production') + let nuxt = new Nuxt(config) +// Start build process (only in development) +if (config.dev) { + new Builder(nuxt).build() +} + // You can use nuxt.render(req, res) or nuxt.renderRoute(route, context) ``` diff --git a/build/start.js b/build/start.js index aa5a931879..ad8d8df4ad 100755 --- a/build/start.js +++ b/build/start.js @@ -1,5 +1,7 @@ #!/usr/bin/env node +const now = Date.now() + const { readFileSync, readJSONSync, writeFileSync, copySync, removeSync } = require('fs-extra') const { resolve, relative } = require('path') @@ -38,7 +40,7 @@ requires = requires.filter(r => excludes.indexOf(r) === -1) let dependencies = {} requires.forEach(r => { if (!packageJSON.dependencies[r]) { - console.warn('cannot resolve dependency version for ' + r) + console.warn('Cannot resolve dependency version for ' + r) return } dependencies[r] = packageJSON.dependencies[r] @@ -81,6 +83,7 @@ const extraFiles = [ 'bin/nuxt-build', 'bin/nuxt-generate', 'bin/nuxt-dev', + 'bin/nuxt', 'dist/nuxt.js', 'dist/nuxt.js.map' ] @@ -92,4 +95,9 @@ extraFiles.forEach(file => { const startIndexjs = resolve(startDir, 'index.js') writeFileSync(startIndexjs, String(readFileSync(startIndexjs)).replace('./dist/nuxt', './dist/core')) -console.log('generated ' + packageJSON.name + '@' + packageJSON.version) +// Patch bin/nuxt-start +const binStart = resolve(startDir, 'bin/nuxt-start') +writeFileSync(binStart, String(readFileSync(binStart)).replace(/nuxt start/g, 'nuxt-start')) + +const ms = Date.now() - now +console.log(`Generated ${packageJSON.name}@${packageJSON.version} in ${ms}ms`) diff --git a/examples/custom-server/server.js b/examples/custom-server/server.js index 0e82f3973c..04ade773c1 100644 --- a/examples/custom-server/server.js +++ b/examples/custom-server/server.js @@ -4,13 +4,13 @@ const { Nuxt, Builder } = require('nuxt') const host = process.env.HOST || '127.0.0.1' const port = process.env.PORT || 3000 -// Import and Set Nuxt.js options +// Import and set Nuxt.js options let config = require('./nuxt.config.js') config.dev = !(process.env.NODE_ENV === 'production') const nuxt = new Nuxt(config) -// Start build process if +// Start build process in dev mode if (config.dev) { const builder = new Builder(nuxt) builder.build() diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 25c347ceeb..e8de41e192 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -372,8 +372,19 @@ export default class Builder extends Tapable { return reject(err) } if (err) return console.error(err) // eslint-disable-line no-console + + // Hide internal assets and source maps in stats + const hiddenAssets = [ + /.map$/, + /index\..+\.html$/, + /vue-ssr-client-manifest.json/ + ] + const statsJson = stats.toJson(this.webpackStats, true) + statsJson.assets = statsJson.assets.filter(asset => hiddenAssets.every(e => !e.test(asset.name))) + // Show build stats for production - console.log(stats.toString(this.webpackStats)) // eslint-disable-line no-console + console.log(stats.constructor.jsonToString(statsJson, true))// eslint-disable-line no-console + /* istanbul ignore if */ if (stats.hasErrors()) { return reject(new Error('Webpack build exited with errors')) diff --git a/lib/builder/generator.js b/lib/builder/generator.js index ed58526cea..8333e06cac 100644 --- a/lib/builder/generator.js +++ b/lib/builder/generator.js @@ -35,7 +35,7 @@ export default class Generator extends Tapable { await this.builder.build() } - await this.nuxt.applyPluginsAsync('generate', this) + await this.nuxt.applyPluginsAsync('generator', this) // Initialize dist directory if (init) { diff --git a/lib/builder/index.js b/lib/builder/index.js index 34547d0c2a..2f96f7f4e8 100755 --- a/lib/builder/index.js +++ b/lib/builder/index.js @@ -5,3 +5,8 @@ export default { Builder, Generator } + +export { + Builder, + Generator +} diff --git a/lib/builder/webpack/base.config.js b/lib/builder/webpack/base.config.js index b0db0ddade..2ff83f6882 100644 --- a/lib/builder/webpack/base.config.js +++ b/lib/builder/webpack/base.config.js @@ -51,17 +51,19 @@ export default function webpackBaseConfig ({ isClient, isServer }) { '~~': join(this.options.rootDir), '@': join(this.options.srcDir), '@@': join(this.options.rootDir), - 'static': join(this.options.srcDir, 'static'), // use in template with - 'assets': join(this.options.srcDir, 'assets') // use in template with + 'static': join(this.options.srcDir, 'static'), + '~static': join(this.options.srcDir, 'static'), + 'assets': join(this.options.srcDir, 'assets'), + '~assets': join(this.options.srcDir, 'assets') }, modules: [ - join(this.options.rootDir, 'node_modules'), + this.options.modulesDir, nodeModulesDir ] }, resolveLoader: { modules: [ - join(this.options.rootDir, 'node_modules'), + this.options.modulesDir, nodeModulesDir ] }, @@ -78,7 +80,7 @@ export default function webpackBaseConfig ({ isClient, isServer }) { loader: 'babel-loader', exclude: /node_modules/, query: defaults(this.options.build.babel, { - presets: ['vue-app'], + presets: [require.resolve('babel-preset-vue-app')], babelrc: false, cacheDirectory: !!this.options.dev }) diff --git a/lib/builder/webpack/server.config.js b/lib/builder/webpack/server.config.js index 01a2fbf1d0..9d4b1eb949 100644 --- a/lib/builder/webpack/server.config.js +++ b/lib/builder/webpack/server.config.js @@ -3,6 +3,7 @@ import VueSSRServerPlugin from 'vue-server-renderer/server-plugin' import nodeExternals from 'webpack-node-externals' import { each } from 'lodash' import { resolve } from 'path' +import { existsSync } from 'fs' import base from './base.config.js' /* @@ -31,16 +32,10 @@ export default function webpackServerConfig () { libraryTarget: 'commonjs2' }), performance: { - hints: false + hints: false, + maxAssetSize: Infinity }, - externals: [ - // https://webpack.js.org/configuration/externals/#externals - // https://github.com/liady/webpack-node-externals - nodeExternals({ - // load non-javascript files with extensions, presumably via loaders - whitelist: [/\.(?!(?:js|json)$).{1,5}$/i] - }) - ], + externals: [], plugins: (config.plugins || []).concat([ new VueSSRServerPlugin({ filename: 'server-bundle.json' @@ -54,6 +49,16 @@ export default function webpackServerConfig () { ]) }) + // https://webpack.js.org/configuration/externals/#externals + // https://github.com/liady/webpack-node-externals + if (existsSync(this.options.modulesDir)) { + config.externals.push(nodeExternals({ + // load non-javascript files with extensions, presumably via loaders + whitelist: [/\.(?!(?:js|json)$).{1,5}$/i], + modulesDir: this.options.modulesDir + })) + } + // -------------------------------------- // Production specific config // -------------------------------------- diff --git a/lib/builder/webpack/vue-loader.config.js b/lib/builder/webpack/vue-loader.config.js index 0b08bc108e..659eded04b 100644 --- a/lib/builder/webpack/vue-loader.config.js +++ b/lib/builder/webpack/vue-loader.config.js @@ -3,7 +3,7 @@ import { extractStyles, styleLoader } from './helpers' export default function ({ isClient }) { let babelOptions = JSON.stringify(defaults(this.options.build.babel, { - presets: ['vue-app'], + presets: [require.resolve('babel-preset-vue-app')], babelrc: false, cacheDirectory: !!this.options.dev })) diff --git a/lib/common/index.js b/lib/common/index.js index 46498a8c10..26a5ba474a 100755 --- a/lib/common/index.js +++ b/lib/common/index.js @@ -1,5 +1,12 @@ import * as Utils from './utils' +import Options from './options' export default { - Utils + Utils, + Options +} + +export { + Utils, + Options } diff --git a/lib/core/options.js b/lib/common/options.js similarity index 88% rename from lib/core/options.js rename to lib/common/options.js index 2d58aabd3a..1464c9862e 100755 --- a/lib/core/options.js +++ b/lib/common/options.js @@ -22,11 +22,13 @@ export default function Options (_options) { } // Apply defaults - _.defaultsDeep(options, defaultOptions) + _.defaultsDeep(options, Options.defaults) // Resolve dirs - options.rootDir = (typeof options.rootDir === 'string' && options.rootDir ? options.rootDir : process.cwd()) - options.srcDir = (typeof options.srcDir === 'string' && options.srcDir ? resolve(options.rootDir, options.srcDir) : options.rootDir) + const hasValue = v => typeof v === 'string' && v + options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd() + options.srcDir = hasValue(options.srcDir) ? resolve(options.rootDir, options.srcDir) : options.rootDir + options.modulesDir = resolve(options.rootDir, hasValue(options.modulesDir) ? options.modulesDir : 'node_modules') options.buildDir = join(options.rootDir, options.buildDir) // If app.html is defined, set the template path to the user template @@ -38,7 +40,7 @@ export default function Options (_options) { // Ignore publicPath on dev /* istanbul ignore if */ if (options.dev && isUrl(options.build.publicPath)) { - options.build.publicPath = defaultOptions.build.publicPath + options.build.publicPath = Options.defaults.build.publicPath } // If store defined, update store options to true unless explicitly disabled @@ -52,7 +54,7 @@ export default function Options (_options) { mode = mode() } if (typeof mode === 'string') { - mode = Modes[mode] + mode = Options.modes[mode] } // Apply mode @@ -61,7 +63,7 @@ export default function Options (_options) { return options } -const Modes = { +Options.modes = { universal: { build: { ssr: true @@ -88,7 +90,7 @@ const Modes = { } } -export const defaultOptions = { +Options.defaults = { mode: 'universal', dev: process.env.NODE_ENV !== 'production', buildDir: '.nuxt', diff --git a/lib/core/index.js b/lib/core/index.js index 4bf8a61bf0..2af3372c2d 100755 --- a/lib/core/index.js +++ b/lib/core/index.js @@ -1,13 +1,20 @@ -import Options from './options' -import ModuleContainer from './module' +import { Options, Utils } from 'common' +import Module from './module' import Nuxt from './nuxt' import Renderer from './renderer' -import * as Utils from 'utils' export default { - Options, - ModuleContainer, Nuxt, + Module, Renderer, + Options, + Utils +} + +export { + Nuxt, + Module, + Renderer, + Options, Utils } diff --git a/lib/core/module.js b/lib/core/module.js index 63f835fc6c..726227b8c7 100755 --- a/lib/core/module.js +++ b/lib/core/module.js @@ -18,7 +18,7 @@ export default class ModuleContainer extends Tapable { async _ready () { await sequence(this.options.modules, this.addModule.bind(this)) - await this.nuxt.applyPluginsAsync('module', this) + await this.applyPluginsAsync('ready', this) } addVendor (vendor) { diff --git a/lib/core/nuxt.js b/lib/core/nuxt.js index 92d103e997..7ef1bd4482 100644 --- a/lib/core/nuxt.js +++ b/lib/core/nuxt.js @@ -1,8 +1,8 @@ import Tapable from 'tappable' import chalk from 'chalk' +import { Options } from 'common' import ModuleContainer from './module' import Renderer from './renderer' -import Options from './options' import Debug from 'debug' import enableDestroy from 'server-destroy' import Module from 'module' @@ -74,7 +74,7 @@ export default class Nuxt extends Tapable { }) })) - resolve() + resolve(this.applyPluginsAsync('listen', { server, port, host })) }) // Add server.destroy(cb) method diff --git a/lib/core/renderer.js b/lib/core/renderer.js index f9cd1353bf..bd3d350ccf 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -11,9 +11,9 @@ import { join, resolve } from 'path' import fs from 'fs-extra' import { createBundleRenderer } from 'vue-server-renderer' import { encodeHtml, getContext, setAnsiColors, isUrl } from 'utils' -import { defaultOptions } from './options' import Debug from 'debug' import connect from 'connect' +import { Options } from 'common' const debug = Debug('nuxt:render') debug.color = 4 // Force blue color @@ -46,14 +46,12 @@ export default class Renderer extends Tapable { spaTemplate: null, errorTemplate: parseTemplate('
{{ stack }}
') // Will be loaded on ready } - - // Bind middleware to this context - this.nuxtMiddleware = this.nuxtMiddleware.bind(this) - this.errorMiddleware = this.errorMiddleware.bind(this) } async _ready () { - // Setup all middleWare + await this.nuxt.applyPluginsAsync('renderer', this) + + // Setup nuxt middleware await this.setupMiddleware() // Load error template @@ -67,12 +65,12 @@ export default class Renderer extends Tapable { await this.loadResources() } - await this.nuxt.applyPluginsAsync('renderer', this) + // Call ready plugin + await this.applyPluginsAsync('ready', this) } async loadResources (_fs = fs) { let distPath = resolve(this.options.buildDir, 'dist') - let updated = [] resourceMap.forEach(({ key, fileName, transform }) => { @@ -96,7 +94,6 @@ export default class Renderer extends Tapable { this.resources[key] = data updated.push(key) }) - if (updated.length > 0) { // debug('Updated', updated.join(', '), isServer) this.createRenderer() @@ -197,7 +194,7 @@ export default class Renderer extends Tapable { if (!this.options.dev) { const distDir = resolve(this.options.buildDir, 'dist') this.useMiddleware({ - path: isUrl(this.options.build.publicPath) ? defaultOptions.build.publicPath : this.options.build.publicPath, + path: isUrl(this.options.build.publicPath) ? Options.defaults.build.publicPath : this.options.build.publicPath, handler: serveStatic(distDir, { index: false, // Don't serve index.html template maxAge: (this.options.dev ? 0 : '1y') // 1 year in production @@ -211,10 +208,10 @@ export default class Renderer extends Tapable { }) // Finally use nuxtMiddleware - this.useMiddleware(this.nuxtMiddleware) + this.useMiddleware(this.nuxtMiddleware.bind(this)) // Error middleware for errors that occurred in middleware that declared above - this.useMiddleware(this.errorMiddleware) + this.useMiddleware(this.errorMiddleware.bind(this)) } async nuxtMiddleware (req, res, next) { @@ -269,7 +266,7 @@ export default class Renderer extends Tapable { } } - async errorMiddleware (err, req, res, next, context) { + errorMiddleware (err, req, res, next, context) { /* istanbul ignore if */ if (context && context.redirected) { console.error(err) // eslint-disable-line no-console diff --git a/start/README.md b/start/README.md index ea4d75d2f8..731052c4e4 100644 --- a/start/README.md +++ b/start/README.md @@ -1,3 +1,42 @@ -# Nuxt-Start +# nuxt-start -WIP - Serve Nuxt.js Application for production \ No newline at end of file +> Start Nuxt.js Application in production mode. + +## Installation + +```bash +npm install --save nuxt-start +```` + +Add/Update your "start" script into your `package.json`: + +```json +{ + "scripts": { + "start": "nuxt-start" + } +} +``` + +## Usage + +```bash +nuxt-start -p -H -c +``` + +## Programmatic Usage + +```js +const { Nuxt } = require('nuxt-start') + +// Require nuxt config +const config = require('./nuxt.config.js') + +// Create a new nuxt instance +const nuxt = new Nuxt(config) + +// Start nuxt.js server +nuxt.listen(3000) // nuxt.listen(port, host) + +// Or use `nuxt.render` as an express middleware +``` diff --git a/test/fixtures/module/modules/tapable/index.js b/test/fixtures/module/modules/tapable/index.js index 48fc69466f..fa5bd572ef 100644 --- a/test/fixtures/module/modules/tapable/index.js +++ b/test/fixtures/module/modules/tapable/index.js @@ -2,7 +2,7 @@ module.exports = function () { let ctr = 1 // Add hook for module - this.nuxt.plugin('module', moduleContainer => { + this.plugin('ready', moduleContainer => { this.nuxt.__module_hook = moduleContainer && ctr++ }) diff --git a/test/ssr.test.js b/test/ssr.test.js index c3be1a519f..e45201fb04 100755 --- a/test/ssr.test.js +++ b/test/ssr.test.js @@ -62,10 +62,9 @@ test('unique responses with component', async t => { await uniqueTest(t, '/component') }) -test.todo('unique responses with async components (wait Vue 2.4)') -// test('unique responses with async components', async t => { -// await uniqueTest(t, '/asyncComponent') -// }) +test('unique responses with async components', async t => { + await uniqueTest(t, '/asyncComponent') +}) test('unique responses with asyncData()', async t => { await uniqueTest(t, '/asyncData')