Merge pull request #1 from nuxt/dev

cathcing up
This commit is contained in:
James Lee 2017-07-18 23:59:11 -07:00 committed by GitHub
commit aed0a217ab
157 changed files with 5463 additions and 3330 deletions

3
.gitignore vendored
View File

@ -23,3 +23,6 @@ coverage
# Intellij idea # Intellij idea
*.iml *.iml
.idea .idea
# Macos
.DS_Store

View File

@ -2,9 +2,7 @@ language: node_js
node_js: node_js:
- "8.0" - "8.0"
- "7.2" - "7.2"
- "6.9" - "6.11"
before_install:
- if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
install: install:
- yarn install - yarn install
- yarn run build - yarn run build

View File

@ -15,7 +15,7 @@
</p> </p>
> Nuxt.js is a framework for server-rendered Vue applications (inspired by [Next.js](https://github.com/zeit/next.js)) > Nuxt.js is a Versatile Vue.js Framework
## 🚧 Under active development, [1.0](https://github.com/nuxt/nuxt.js/projects/1) will be released soon :fire: ## 🚧 Under active development, [1.0](https://github.com/nuxt/nuxt.js/projects/1) will be released soon :fire:
@ -98,6 +98,7 @@ Support us with a monthly donation and help us continue our activities. [[Become
- 📘 Documentation: [https://nuxtjs.org](https://nuxtjs.org) - 📘 Documentation: [https://nuxtjs.org](https://nuxtjs.org)
- 🎬 Video: [1 minute demo](https://www.youtube.com/watch?v=kmf-p-pTi40) - 🎬 Video: [1 minute demo](https://www.youtube.com/watch?v=kmf-p-pTi40)
- 🐦 Twitter: [@nuxt_js](https://twitter.com/nuxt_js) - 🐦 Twitter: [@nuxt_js](https://twitter.com/nuxt_js)
- 👥 [Nuxt.js Community](https://github.com/nuxt-community)
- 👉 [Play with Nuxt.js online](https://glitch.com/edit/#!/nuxt-hello-world) - 👉 [Play with Nuxt.js online](https://glitch.com/edit/#!/nuxt-hello-world)
## Getting started ## Getting started
@ -170,13 +171,8 @@ const Nuxt = require('nuxt')
// Launch nuxt build with given options // Launch nuxt build with given options
let config = require('./nuxt.config.js') let config = require('./nuxt.config.js')
let nuxt = new Nuxt(config) let nuxt = new Nuxt(config)
nuxt.build()
.then(() => { // You can use nuxt.render(req, res) or nuxt.renderRoute(route, context)
// You can use nuxt.render(req, res) or nuxt.renderRoute(route, context)
})
.catch((e) => {
// An error happened during the build
})
``` ```
Learn more: https://nuxtjs.org/api/nuxt Learn more: https://nuxtjs.org/api/nuxt

View File

@ -1,9 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
var join = require('path').join const join = require('path').join
var defaultCommand = 'dev' const defaultCommand = 'dev'
var commands = new Set([ const commands = new Set([
defaultCommand, defaultCommand,
'init', 'init',
'build', 'build',
@ -19,6 +19,13 @@ if (commands.has(cmd)) {
cmd = defaultCommand cmd = defaultCommand
} }
var bin = join(__dirname, 'nuxt-' + cmd) const bin = join(__dirname, 'nuxt-' + cmd)
// Console error unhandled promises
process.on('unhandledRejection', function (err) {
/* eslint-disable no-console */
console.error(err)
console.error('[nuxt] Unhandled promise rejection: ' + err)
})
require(bin) require(bin)

View File

@ -1,61 +1,71 @@
#!/usr/bin/env node #!/usr/bin/env node
// Show logs // Show logs
process.env.DEBUG = 'nuxt:*' process.env.DEBUG = process.env.DEBUG || 'nuxt:*'
var fs = require('fs') const fs = require('fs')
var without = require('lodash').without const parseArgs = require('minimist')
var Nuxt = require('../') const { Nuxt, Builder } = require('../')
var resolve = require('path').resolve const resolve = require('path').resolve
const debug = require('debug')('nuxt:build')
debug.color = 2 // Force green color
// --analyze option const argv = parseArgs(process.argv.slice(2), {
var analyzeBuild = false alias: {
if (process.argv.indexOf('--analyze') !== -1 || process.argv.indexOf('-a') !== -1) { h: 'help',
analyzeBuild = true c: 'config-file',
process.argv = without(process.argv, '--analyze', '-a') a: 'analyze'
},
boolean: ['h', 'a'],
string: ['c'],
default: {
c: 'nuxt.config.js'
}
})
if (argv.help) {
console.log(`
Description
Compiles the application for production deployment
Usage
$ nuxt build <dir>
Options
--analyze, -a Launch webpack-bundle-analyzer to optimize your bundles.
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--help, -h Displays this message
`)
process.exit(0)
} }
var nuxtConfigFileName = 'nuxt.config.js' const rootDir = resolve(argv._[0] || '.')
const nuxtConfigFile = resolve(rootDir, argv['config-file'])
// --config-file option
var indexOfConfig = false
if (process.argv.indexOf('--config-file') !== -1) {
indexOfConfig = process.argv.indexOf('--config-file')
} else if (process.argv.indexOf('-c') !== -1) {
indexOfConfig = process.argv.indexOf('-c')
}
if (indexOfConfig !== false) {
nuxtConfigFileName = process.argv.slice(indexOfConfig)[1]
process.argv = without(process.argv, '--config-file', '-c', nuxtConfigFileName)
}
// Root directory parameter
var rootDir = resolve(process.argv.slice(2)[0] || '.')
var nuxtConfigFilePath = resolve(rootDir, nuxtConfigFileName)
var options = {} var options = {}
if (fs.existsSync(nuxtConfigFilePath)) { if (fs.existsSync(nuxtConfigFile)) {
options = require(nuxtConfigFilePath) options = require(nuxtConfigFile)
} else { } else if (argv['config-file'] !== 'nuxt.config.js') {
console.log(`Could not locate ${nuxtConfigFilePath}`) // eslint-disable-line no-console console.error(`> Could not load config file ${argv['config-file']}`)
process.exit(1)
} }
if (typeof options.rootDir !== 'string') { if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir options.rootDir = rootDir
} }
options.dev = false // Create production build when calling `nuxt build` // Create production build when calling `nuxt build`
options.dev = false
// Analyze option
options.build = options.build || {} options.build = options.build || {}
if (analyzeBuild) { if (argv.analyze) {
options.build.analyze = analyzeBuild options.build.analyze = true
} }
console.log('[nuxt] Building...') // eslint-disable-line no-console debug('Building...')
var nuxt = module.exports = new Nuxt(options) const nuxt = new Nuxt(options)
nuxt.build() const builder = new Builder(nuxt)
builder.build()
.then(() => { .then(() => {
console.log('[nuxt] Building done') // eslint-disable-line no-console debug('Building done')
}) })
.catch((err) => { .catch((err) => {
console.error(err) // eslint-disable-line no-console console.error(err) // eslint-disable-line no-console

View File

@ -1,80 +1,113 @@
#!/usr/bin/env node #!/usr/bin/env node
// Show logs // Show logs
process.env.DEBUG = 'nuxt:*' process.env.DEBUG = process.env.DEBUG || 'nuxt:*'
var _ = require('lodash') const _ = require('lodash')
var debug = require('debug')('nuxt:build') const debug = require('debug')('nuxt:build')
debug.color = 2 // force green color debug.color = 2 // force green color
var fs = require('fs') const fs = require('fs')
var Nuxt = require('../') const parseArgs = require('minimist')
var chokidar = require('chokidar') const { Nuxt, Builder } = require('../')
var resolve = require('path').resolve const chokidar = require('chokidar')
var without = require('lodash').without const resolve = require('path').resolve
var nuxtConfigFileName = 'nuxt.config.js' const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
H: 'hostname',
p: 'port',
c: 'config-file'
},
boolean: ['h'],
string: ['H', 'c'],
default: {
c: 'nuxt.config.js'
}
})
// --config-file option if (argv.hostname === '') {
var indexOfConfig = false console.error(`> Provided hostname argument has no value`)
if (process.argv.indexOf('--config-file') !== -1) { process.exit(1)
indexOfConfig = process.argv.indexOf('--config-file')
} else if (process.argv.indexOf('-c') !== -1) {
indexOfConfig = process.argv.indexOf('-c')
} }
if (indexOfConfig !== false) { if (argv.help) {
nuxtConfigFileName = process.argv.slice(indexOfConfig)[1] console.log(`
process.argv = without(process.argv, '--config-file', '-c', nuxtConfigFileName) Description
Starts the application in development mode (hot-code reloading, error
reporting, etc)
Usage
$ nuxt dev <dir> -p <port number> -H <hostname>
Options
--port, -p A port number on which to start the application
--hostname, -H Hostname on which to start the application
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--help, -h Displays this message
`)
process.exit(0)
} }
var rootDir = resolve(process.argv.slice(2)[0] || '.') const rootDir = resolve(argv._[0] || '.')
var nuxtConfigFile = resolve(rootDir, nuxtConfigFileName) const nuxtConfigFile = resolve(rootDir, argv['config-file'])
var options = {} // Load config once for chokidar
if (fs.existsSync(nuxtConfigFile)) { const nuxtConfig = loadNuxtConfig()
options = require(nuxtConfigFile) _.defaultsDeep(nuxtConfig, { watchers: { chokidar: { ignoreInitial: true } } })
}
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir
}
options.dev = true // Add hot reloading and watching changes
var nuxt = module.exports = new Nuxt(options) // Start dev
var port = process.env.PORT || process.env.npm_package_config_nuxt_port let dev = startDev()
var host = process.env.HOST || process.env.npm_package_config_nuxt_host
var server = nuxt.server = new nuxt.Server(nuxt).listen(port, host)
listenOnConfigChanges(nuxt, server) // Start watching for nuxt.config.js changes
chokidar
function listenOnConfigChanges(nuxt, server) { .watch(nuxtConfigFile, nuxtConfig.watchers.chokidar)
// Listen on nuxt.config.js changes .on('all', _.debounce(() => {
var build = _.debounce(() => {
debug('[nuxt.config.js] changed') debug('[nuxt.config.js] changed')
delete require.cache[nuxtConfigFile] debug('Rebuilding the app...')
var options = {} dev = dev.then(startDev)
if (fs.existsSync(nuxtConfigFile)) { }), 2500)
try {
options = require(nuxtConfigFile) function startDev (oldNuxt) {
} catch (e) { // Get latest environment variables
return console.error(e) // eslint-disable-line no-console 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
}
options.rootDir = rootDir // Load options
nuxt.close() let options = {}
.then(() => { try {
nuxt.renderer = null options = loadNuxtConfig()
debug('Rebuilding the app...') } catch (err) {
return new Nuxt(options).build() console.error(err)
}) return // Wait for next reload
.then((nuxt) => { }
server.nuxt = nuxt
}) // Create nuxt and builder instance
.catch((error) => { const nuxt = new Nuxt(options)
console.error('Error while rebuild the app:', error) // eslint-disable-line no-console const builder = new Builder(nuxt)
process.exit(1)
}) return Promise.resolve()
}, 200) .then(() => builder.build()) // 1- Start build
var nuxtConfigFile = resolve(rootDir, nuxtConfigFileName) .then(() => oldNuxt ? oldNuxt.close() : Promise.resolve()) // 2- Close old nuxt after successful build
chokidar.watch(nuxtConfigFile, Object.assign({}, nuxt.options.watchers.chokidar, {ignoreInitial: true})) .then(() => nuxt.listen(port, host)) // 3- Start listening
.on('all', build) .then(() => nuxt) // 4- Pass new nuxt to watch chain
}
function loadNuxtConfig () {
let options = {}
if (fs.existsSync(nuxtConfigFile)) {
delete require.cache[nuxtConfigFile]
options = require(nuxtConfigFile)
} else if (argv['config-file'] !== 'nuxt.config.js') {
console.error(`> Could not load config file ${argv['config-file']}`)
process.exit(1)
}
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir
}
// Force development mode for add hot reloading and watching changes
options.dev = true
return options
} }

View File

@ -1,29 +1,63 @@
#!/usr/bin/env node #!/usr/bin/env node
// Show logs // Show logs
process.env.DEBUG = 'nuxt:*' process.env.DEBUG = process.env.DEBUG || 'nuxt:*'
var fs = require('fs') const fs = require('fs')
var Nuxt = require('../') const parseArgs = require('minimist')
var resolve = require('path').resolve const debug = require('debug')('nuxt:generate')
var rootDir = resolve(process.argv.slice(2)[0] || '.') const { Nuxt, Builder, Generator } = require('../')
var nuxtConfigFile = resolve(rootDir, 'nuxt.config.js') const resolve = require('path').resolve
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
c: 'config-file'
},
boolean: ['h'],
string: ['c'],
default: {
c: 'nuxt.config.js'
}
})
if (argv.help) {
console.log(`
Description
Generate a static web application (server-rendered)
Usage
$ nuxt generate <dir>
Options
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--help, -h Displays this message
`)
process.exit(0)
}
const rootDir = resolve(argv._[0] || '.')
const nuxtConfigFile = resolve(rootDir, argv['config-file'])
var options = {} var options = {}
if (fs.existsSync(nuxtConfigFile)) { if (fs.existsSync(nuxtConfigFile)) {
options = require(nuxtConfigFile) options = require(nuxtConfigFile)
} else if (argv['config-file'] !== 'nuxt.config.js') {
console.error(`> Could not load config file ${argv['config-file']}`)
process.exit(1)
} }
if (typeof options.rootDir !== 'string') { 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)
console.log('[nuxt] Generating...') // eslint-disable-line no-console debug('Generating...')
var 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(() => {
console.log('[nuxt] Generate done') // eslint-disable-line no-console debug('Generate done')
process.exit(0)
}) })
.catch((err) => { .catch((err) => {
console.error(err) // eslint-disable-line no-console console.error(err) // eslint-disable-line no-console

View File

@ -1,22 +1,72 @@
#!/usr/bin/env node #!/usr/bin/env node
var fs = require('fs') const fs = require('fs')
var Nuxt = require('../') const parseArgs = require('minimist')
var resolve = require('path').resolve const { Nuxt } = require('../')
const { join, resolve } = require('path')
var rootDir = resolve(process.argv.slice(2)[0] || '.') const argv = parseArgs(process.argv.slice(2), {
var nuxtConfigFile = resolve(rootDir, 'nuxt.config.js') alias: {
h: 'help',
H: 'hostname',
p: 'port',
c: 'config-file'
},
boolean: ['h'],
string: ['H', 'c'],
default: {
c: 'nuxt.config.js'
}
})
if (argv.hostname === '') {
console.error(`> Provided hostname argument has no value`)
process.exit(1)
}
if (argv.help) {
console.log(`
Description
Starts the application in production mode.
The application should be compiled with \`nuxt build\` first.
Usage
$ nuxt start <dir> -p <port number> -H <hostname>
Options
--port, -p A port number on which to start the application
--hostname, -H Hostname on which to start the application
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--help, -h Displays this message
`)
process.exit(0)
}
const rootDir = resolve(argv._[0] || '.')
const nuxtConfigFile = resolve(rootDir, argv['config-file'])
let options = {}
var options = {}
if (fs.existsSync(nuxtConfigFile)) { if (fs.existsSync(nuxtConfigFile)) {
options = require(nuxtConfigFile) options = require(nuxtConfigFile)
} else if (argv['config-file'] !== 'nuxt.config.js') {
console.error(`> Could not load config file ${argv['config-file']}`)
process.exit(1)
} }
if (typeof options.rootDir !== 'string') { if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir options.rootDir = rootDir
} }
options.dev = false // Force production mode (no webpack middleware called)
var nuxt = module.exports = new Nuxt(options) // Force production mode (no webpack middleware called)
var port = process.env.PORT || process.env.npm_package_config_nuxt_port options.dev = false
var host = process.env.HOST || process.env.npm_package_config_nuxt_host
var server = nuxt.server = new nuxt.Server(nuxt).listen(port, host) // Check if project is built for production
const distDir = join(options.rootDir, options.buildDir || '.nuxt', 'dist')
if (!fs.existsSync(join(distDir, 'server-bundle.json'))) {
console.error('> No build files found, please run `nuxt build` before launching `nuxt start`') // eslint-disable-line no-console
process.exit(1)
}
const nuxt = new Nuxt(options)
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
nuxt.listen(port, host)

109
build/rollup.config.js Executable file
View File

@ -0,0 +1,109 @@
// Some parts brought from https://github.com/vuejs/vue/blob/dev/build/config.js
const { resolve } = require('path')
const rollupBabel = require('rollup-plugin-babel')
const rollupAlias = require('rollup-plugin-alias')
const rollupCommonJS = require('rollup-plugin-commonjs')
const rollupReplace = require('rollup-plugin-replace')
const rollupNodeResolve = require('rollup-plugin-node-resolve')
const packageJson = require('../package.json')
const dependencies = Object.keys(packageJson.dependencies)
const version = packageJson.version || process.env.VERSION
// -----------------------------
// Banner
// -----------------------------
const banner =
'/*!\n' +
' * Nuxt.js v' + version + '\n' +
' * Released under the MIT License.\n' +
' */'
// -----------------------------
// Aliases
// -----------------------------
const rootDir = resolve(__dirname, '..')
const libDir = resolve(rootDir, 'lib')
const distDir = resolve(rootDir, 'dist')
const aliases = {
core: resolve(libDir, 'core/index.js'),
builder: resolve(libDir, 'builder/index.js'),
common: resolve(libDir, 'common/index.js'),
utils: resolve(libDir, 'common/utils.js'),
app: resolve(libDir, 'app')
}
// -----------------------------
// Builds
// -----------------------------
const builds = {
nuxt: {
entry: resolve(libDir, 'index.js'),
dest: resolve(distDir, 'nuxt.js')
},
core: {
entry: resolve(libDir, 'core/index.js'),
dest: resolve(distDir, 'core.js')
}
}
// -----------------------------
// Default config
// -----------------------------
function genConfig (opts) {
const config = {
entry: opts.entry,
dest: opts.dest,
external: ['fs', 'path', 'http'].concat(dependencies, opts.external),
format: opts.format || 'cjs',
banner: opts.banner || banner,
moduleName: opts.moduleName || 'Nuxt',
sourceMap: true,
plugins: [
rollupAlias(Object.assign({
resolve: ['.js', '.json', '.jsx', '.ts']
}, aliases, opts.alias)),
rollupNodeResolve({ main: true, jsnext: true }),
rollupCommonJS(),
rollupBabel(Object.assign({
exclude: 'node_modules/**',
plugins: [
['transform-runtime', { 'helpers': false, 'polyfill': false }],
'transform-async-to-generator',
'array-includes'
],
presets: [
'babel-preset-es2015-rollup'
],
'env': {
'test': {
'plugins': [ 'istanbul' ]
}
}
}, opts.babel)),
rollupReplace({
__VERSION__: version
})
].concat(opts.plugins || [])
}
if (opts.env) {
config.plugins.push(rollupReplace({
'process.env.NODE_ENV': JSON.stringify(opts.env)
}))
}
return config
}
if (process.env.TARGET) {
module.exports = genConfig(builds[process.env.TARGET])
} else {
exports.getBuild = name => genConfig(builds[name])
exports.getAllBuilds = () => Object.keys(builds).map(name => genConfig(builds[name]))
}

92
build/start.js Executable file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env node
const { readFileSync, readJSONSync, writeFileSync, copySync, removeSync } = require('fs-extra')
const { resolve, relative } = require('path')
// Dirs
const rootDir = resolve(__dirname, '..')
const startDir = resolve(rootDir, 'start')
// Read main package.json
const packageJSON = readJSONSync(resolve(rootDir, 'package.json'))
// Required and Excluded packages for start
let requires = [
'source-map-support'
]
const excludes = [
'path',
'fs'
].concat(Object.keys(packageJSON.devDependencies))
// Parse dist/core.js for all external dependencies
const requireRegex = /require\('([-\w]+)'\)/g
const rawCore = readFileSync(resolve(rootDir, 'dist/core.js'))
let match = requireRegex.exec(rawCore)
while (match) {
requires.push(match[1])
match = requireRegex.exec(rawCore)
}
// Apply Excludes
requires = requires.filter(r => excludes.indexOf(r) === -1)
// Resolve version constrains
let dependencies = {}
requires.forEach(r => {
if (!packageJSON.dependencies[r]) {
console.warn('cannot resolve dependency version for ' + r)
return
}
dependencies[r] = packageJSON.dependencies[r]
})
// Drop fields
let drops = ['devDependencies', 'scripts', 'nyc', 'types']
drops.forEach(k => {
delete packageJSON[k]
})
// Update dependencies
packageJSON.dependencies = dependencies
// Update package meta
packageJSON.name = 'nuxt-start'
packageJSON.description = 'runtime-only build for nuxt'
// Update package.json
writeFileSync(resolve(startDir, 'package.json'), JSON.stringify(packageJSON, null, 2))
// Copy required files
const excludeFiles = [
'README.md',
'.gitignore'
]
packageJSON.files.forEach(file => {
if (excludeFiles.indexOf(file) !== -1) {
return
}
let src = resolve(rootDir, file)
let dst = resolve(startDir, file)
// console.log(relative(rootDir, src), '~>', relative(rootDir, dst))
removeSync(dst)
copySync(src, dst)
})
// Remove extras
const extraFiles = [
'bin/nuxt-build',
'bin/nuxt-generate',
'bin/nuxt-dev',
'dist/nuxt.js',
'dist/nuxt.js.map'
]
extraFiles.forEach(file => {
removeSync(resolve(startDir, file))
})
// Patch index.js
const startIndexjs = resolve(startDir, 'index.js')
writeFileSync(startIndexjs, String(readFileSync(startIndexjs)).replace('./dist/nuxt', './dist/core'))
console.log('generated ' + packageJSON.name + '@' + packageJSON.version)

View File

@ -0,0 +1,69 @@
body {
font-family: "Roboto", "Helvetica Neue", "Hiragino Sans GB", "LiHei Pro", Arial, serif;
text-rendering: optimizelegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 400;
font-size: 16px;
word-spacing: 1px;
color: #666;
margin: 0;
}
img {
border: none;
}
a {
color: #666;
text-decoration: none;
transition: color 0.2s ease, border-color 0.2s ease;
}
.header {
letter-spacing: 5px;
margin: 50px auto 15px;
text-align: center;
}
.header a {
font-size: 15px;
color: #444;
}
.links {
text-align: center;
font-family: "Roboto", "Helvetica Neue", "Hiragino Sans GB", "LiHei Pro", Arial, serif;
color: #999;
font-size: 24px;
margin: 0;
}
.links a {
cursor: pointer;
padding: 2px;
margin: 0 3px;
}
.links img {
width: 15px;
height: 15px;
}
.header,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Montserrat", "Helvetica Neue", "Hiragino Sans GB", "LiHei Pro", Arial, sans-serif;
font-weight: 400;
color: #444;
}
.main {
max-width: 600px;
margin: 50px auto;
padding: 0 30px 50px;
position: relative;
}
@media screen and (max-width: 420px) {
.header {
margin: 40px auto 10px;
}
.header a {
font-size: 14px;
}
}

View File

@ -0,0 +1,57 @@
.main > ul {
list-style-type: none;
padding: 0;
padding-top: 4px;
}
.main > ul > li {
position: relative;
padding: 30px 0 30px;
border-bottom: 1px solid #e6e6e6;
}
.main > ul > li:first-child {
margin-top: -30px;
}
.main h2,
.main h3 {
letter-spacing: 1px;
margin: 0;
text-transform: uppercase;
}
.main h2 {
font-size: 20px;
letter-spacing: 1px;
margin-left: 120px;
}
.main h2 a {
color: #444;
}
.main h2 a:hover {
color: #f33;
}
.main h3 {
font-size: 13px;
color: #999;
position: absolute;
left: 0;
top: 33px;
}
@media screen and (max-width: 420px) {
.main h2 {
font-size: 16px;
margin-left: 0;
}
.main h2 a:hover {
color: #f66;
}
.main h3 {
font-size: 11px;
position: static;
margin-bottom: 10px;
}
.main ul li {
padding: 18px 0 20px;
}
.main ul li:first-child {
margin-top: -35px;
}
}

View File

@ -0,0 +1,307 @@
.gutter pre {
color: #999;
}
pre {
color: #525252;
}
pre .function .keyword,
pre .constant {
color: #0092db;
}
pre .keyword,
pre .attribute {
color: #e96900;
}
pre .number,
pre .literal {
color: #ae81ff;
}
pre .tag,
pre .tag .title,
pre .change,
pre .winutils,
pre .flow,
pre .lisp .title,
pre .clojure .built_in,
pre .nginx .title,
pre .tex .special {
color: #2973b7;
}
pre .class .title {
color: #fff;
}
pre .symbol,
pre .symbol .string,
pre .value,
pre .regexp {
color: #42b983;
}
pre .title {
color: #a6e22e;
}
pre .tag .value,
pre .string,
pre .subst,
pre .haskell .type,
pre .preprocessor,
pre .ruby .class .parent,
pre .built_in,
pre .sql .aggregate,
pre .django .template_tag,
pre .django .variable,
pre .smalltalk .class,
pre .javadoc,
pre .django .filter .argument,
pre .smalltalk .localvars,
pre .smalltalk .array,
pre .attr_selector,
pre .pseudo,
pre .addition,
pre .stream,
pre .envvar,
pre .apache .tag,
pre .apache .cbracket,
pre .tex .command,
pre .prompt {
color: #42b983;
}
pre .comment,
pre .java .annotation,
pre .python .decorator,
pre .template_comment,
pre .pi,
pre .doctype,
pre .deletion,
pre .shebang,
pre .apache .sqbracket,
pre .tex .formula {
color: #b3b3b3;
}
pre .coffeescript .javascript,
pre .javascript .xml,
pre .tex .formula,
pre .xml .javascript,
pre .xml .vbscript,
pre .xml .css,
pre .xml .cdata {
opacity: 0.5;
}
.main .post {
position: relative;
padding-bottom: 30px;
margin-bottom: 30px;
border-bottom: 1px solid #e6e6e6;
}
.main .post h1,
.main .post h2 {
text-transform: uppercase;
letter-spacing: 0px;
}
.main .post h1 a:hover,
.main .post h2 a:hover {
border-bottom: 3px solid #666;
}
.main .post h1 {
font-size: 32px;
margin: 0 0 45px;
letter-spacing: 1px;
}
.main .post h2 {
font-size: 24px;
margin: 60px 0 30px;
position: relative;
}
.main .post h2:before {
content: '';
border-left: 5px solid #41b883;
position: absolute;
left: -15px;
height: 75%;
top: 12%;
}
.main .post h3 {
margin: 30px 0 15px;
}
.main .post .date {
font-family: "Montserrat", "Helvetica Neue", "Hiragino Sans GB", "LiHei Pro", Arial, sans-serif;
font-size: 13px;
color: #999;
margin: 0 0 30px;
letter-spacing: 1px;
position: initial;
text-transform: none;
}
.main .post .content {
text-align: left;
line-height: 1.8em;
}
.main .post .content p,
.main .post .content ul,
.main .post .content ol {
margin: 1em 0 1.5em;
}
.main .post .content strong {
font-weight: 600;
color: #444;
}
.main .post .content ol {
padding-left: 1.6em;
}
.main .post .content ul {
padding-left: 15px;
list-style-type: none;
}
.main .post .content ul li:before {
position: absolute;
font-weight: 600;
content: " · ";
margin: 0;
left: 0;
}
.main .post .content a {
color: #41b883;
border-bottom: 2px solid transparent;
}
.main .post .content a:hover {
color: #41b883;
border-bottom-color: #41b883;
}
.main .post .content .highlight,
.main .post .content .highlight table {
margin: 0;
width: 100%;
}
.main .post .content .highlight {
overflow-x: auto;
}
.main .post .content .highlight table,
.main .post .content .highlight tr,
.main .post .content .highlight td {
padding: 0;
border-collapse: collapse;
}
.main .post .content code {
font-family: "Roboto Mono", "Menlo", "Consolas", monospace;
font-size: 13px;
background-color: #f6f6f6;
padding: 3px 10px;
margin: 0 5px;
border-radius: 2px;
}
.main .post .content pre {
font-family: "Roboto Mono", "Menlo", "Consolas", monospace;
font-size: 13px;
overflow-x: auto;
text-align: left;
padding: 15px 25px;
background-color: #f6f6f6;
line-height: 1.5em;
}
.main .post .content .code pre {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
.main .post .content .gutter pre {
padding: 15px 0 15px 15px;
color: #75715e;
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
.main .post .content blockquote {
margin: 2em 0;
padding-left: 30px;
border-left: 5px solid #e6e6e6;
}
.main .post .content blockquote p {
font-size: 17px;
font-style: italic;
line-height: 1.8em;
color: #999;
}
.main .post img {
display: block;
max-width: 100%;
}
.blog-nav {
position: fixed;
bottom: 20px;
height: 20px;
line-height: 20px;
font-family: "Montserrat", "Helvetica Neue", "Hiragino Sans GB", "LiHei Pro", Arial, sans-serif;
font-size: 15px;
color: #999;
text-decoration: none;
cursor: pointer;
letter-spacing: 1px;
border-bottom: 3px solid transparent;
}
.blog-nav:hover {
color: #333;
border-bottom-color: #333;
}
#newer {
left: 40px;
}
#older {
right: 40px;
}
.show-comments {
font-family: "Montserrat", "Helvetica Neue", "Hiragino Sans GB", "LiHei Pro", Arial, sans-serif;
text-align: center;
}
.show-comments a {
color: #999;
cursor: pointer;
}
.show-comments a:hover {
color: #666;
}
@media screen and (max-width: 900px) {
.main .post {
padding-bottom: 80px;
}
.blog-nav {
position: absolute;
bottom: 30px;
}
#newer {
left: 0;
}
#older {
right: 0;
}
}
@media screen and (max-width: 420px) {
.main {
margin-top: 32px;
}
.main .post h1 {
font-size: 24px;
margin: 0 0 30px;
}
.main .post h2 {
font-size: 20px;
margin: 30px 0 15px;
}
.main .post h3 {
font-size: 16px;
line-height: 1.3em;
}
.main .post .date {
font-size: 12px;
margin: 0 0 20px;
}
.main .post .content {
font-size: 15px;
}
.main .post .content pre {
font-size: 12px;
}
.main .post .content blockquote p {
font-size: 16px;
}
.blog-nav {
font-size: 14px;
color: #444;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,15 @@
<template>
<div class="wrapper">
<div class="header">
<nuxt-link to="/">NUXT BLOG</nuxt-link>
</div>
<p class="links">
<a href="https://twitter.com/nuxt_js" target="_blank"><img src="~assets/img/twitter.png"></a>
<a href="https://github.com/nuxt/nuxt.js/tree/dev/examples/async-component-injection" target="_blank"><img src="~assets/img/github.png"></a>
</p>
<div class="main">
<nuxt/>
</div>
</div>
</template>

View File

@ -0,0 +1,22 @@
module.exports = {
head: {
title: 'Nuxt Blog',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' }
],
link: [
{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' },
{ rel: 'stylesheet', href: 'http://fonts.googleapis.com/css?family=Montserrat|Roboto:400,400italic,600|Roboto+Mono', type: 'text/css' }
]
},
css: [
'@/assets/css/common.css'
],
generate: {
routes: [
'/deep-dive-into-ocean',
'/welcome-to-my-blog'
]
}
}

View File

@ -1,11 +1,11 @@
{ {
"name": "offline-config-nuxt", "name": "components-injection-nuxt",
"dependencies": {
"nuxt": "latest"
},
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt",
"build": "nuxt build", "build": "nuxt build",
"start": "nuxt start" "start": "nuxt"
},
"dependencies": {
"nuxt": "latest"
} }
} }

View File

@ -0,0 +1,21 @@
<template>
<div class="post">
<component :is="component"/>
</div>
</template>
<script>
// See https://vuejs.org/v2/guide/components.html#Advanced-Async-Components
const getPost = (slug) => ({
component: import(`@/posts/${slug}`),
error: require('@/posts/404')
})
export default {
beforeCreate () {
this.component = () => getPost(this.$route.params.slug)
}
}
</script>
<style src="@/assets/css/post.css"/>

View File

@ -0,0 +1,22 @@
<template>
<ul>
<li v-for="(post, index) in posts" :key="index">
<h3>{{ post.date }}</h3>
<h2><nuxt-link :to="post.link">{{ post.title }}</nuxt-link></h2>
</li>
<li style="border:none;text-align: center;font-size: 14px;">Design from <a href="http://blog.evanyou.me" target="_blank">EvanYou.me</a></li>
</ul>
</template>
<script>
export default {
data: () => ({
posts: [
{ date: 'Jul 10, 2017', title: 'Deep dive into the Ocean', link: '/deep-dive-into-ocean' },
{ date: 'Jul 08, 2017', title: 'Welcome to my blog', link: '/welcome-to-my-blog' }
]
})
}
</script>
<style src="@/assets/css/index.css"/>

View File

@ -0,0 +1,3 @@
<template>
<h1 style="text-align: center;">Article not found</h1>
</template>

View File

@ -0,0 +1,17 @@
<template>
<div>
<h3 class="date">Jul 10, 2017</h3>
<h1>Deep dive into the Ocean</h1>
<div class="content">
<img src="~assets/img/swimmer.jpg">
<h2>Subtitle #1</h2>
<p>Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.</p>
<h2>Another subtitle</h2>
<ul>
<li>Vue.js</li>
<li>Nuxt.js</li>
<li>= <3</li>
</ul>
</div>
</div>
</template>

View File

@ -0,0 +1,10 @@
<template>
<div>
<h3 class="date">Jul 08, 2017</h3>
<h1>Welcome to my blog</h1>
<div class="content">
<h2>What is Lorem Ipsum?</h2>
<p><b>Lorem Ipsum</b> is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
</div>
</div>
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,8 +1,7 @@
{ {
"name": "nuxt-async-data", "name": "nuxt-async-data",
"description": "",
"dependencies": { "dependencies": {
"axios": "^0.15.2", "axios": "latest",
"nuxt": "latest" "nuxt": "latest"
}, },
"scripts": { "scripts": {

View File

@ -9,14 +9,9 @@
<script> <script>
export default { export default {
asyncData ({ req }, callback) { asyncData: ({ req }) => ({
setTimeout(function () { userAgent: (req ? req.headers['user-agent'] : navigator.userAgent)
// callback(err, data) })
callback(null, {
userAgent: (req ? req.headers['user-agent'] : navigator.userAgent)
})
}, 100)
}
} }
</script> </script>

View File

@ -0,0 +1,36 @@
const express = require('express')
// Create express router
const router = express.Router()
// Transform req & res to have the same API as express
// So we can use res.status() & res.json()
var app = express()
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request)
Object.setPrototypeOf(res, app.response)
req.res = res
res.req = req
next()
})
// Add POST - /api/login
router.post('/login', (req, res) => {
if (req.body.username === 'demo' && req.body.password === 'demo') {
req.session.authUser = { username: 'demo' }
return res.json({ username: 'demo' })
}
res.status(401).json({ message: 'Bad credentials' })
})
// Add POST - /api/logout
router.post('/logout', (req, res) => {
delete req.session.authUser
res.json({ ok: true })
})
// Export the server middleware
module.exports = {
path: '/api',
handler: router
}

View File

@ -1,7 +1,5 @@
export default function ({ store, redirect, error }) { export default function ({ store, error }) {
// If user not connected, redirect to /
if (!store.state.authUser) { if (!store.state.authUser) {
// return redirect('/')
error({ error({
message: 'You are not connected', message: 'You are not connected',
statusCode: 403 statusCode: 403

View File

@ -1,3 +1,6 @@
const bodyParser = require('body-parser')
const session = require('express-session')
module.exports = { module.exports = {
head: { head: {
title: 'Auth Routes', title: 'Auth Routes',
@ -9,5 +12,24 @@ module.exports = {
}, },
build: { build: {
vendor: ['axios'] vendor: ['axios']
} },
/*
** Add server middleware
** Nuxt.js uses `connect` module as server
** So most of express middleware works with nuxt.js server middleware
*/
serverMiddleware: [
// body-parser middleware
bodyParser.json(),
// session middleware
session({
secret: 'super-secret-key',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 60000 }
}),
// Api middleware
// We add /api/login & /api/logout routes
'~/api'
]
} }

View File

@ -1,17 +1,15 @@
{ {
"name": "auth-routes", "name": "auth-routes",
"description": "",
"dependencies": { "dependencies": {
"axios": "^0.16.1", "axios": "^0.16.1",
"body-parser": "^1.17.2", "body-parser": "^1.17.2",
"cross-env": "^5.0.0",
"express": "^4.15.3", "express": "^4.15.3",
"express-session": "^1.15.3", "express-session": "^1.15.3",
"nuxt": "^1.0.0-alpha1" "nuxt": "latest"
}, },
"scripts": { "scripts": {
"dev": "node server.js", "dev": "nuxt",
"build": "nuxt build", "build": "nuxt build",
"start": "cross-env NODE_ENV=production node server.js" "start": "nuxt start"
} }
} }

View File

@ -28,22 +28,25 @@ export default {
} }
}, },
methods: { methods: {
login () { async login () {
this.$store.dispatch('login', { try {
username: this.formUsername, await this.$store.dispatch('login', {
password: this.formPassword username: this.formUsername,
}) password: this.formPassword
.then(() => { })
this.formUsername = '' this.formUsername = ''
this.formPassword = '' this.formPassword = ''
this.formError = null this.formError = null
}) } catch(e) {
.catch((e) => {
this.formError = e.message this.formError = e.message
}) }
}, },
logout () { async logout () {
this.$store.dispatch('logout') try {
await this.$store.dispatch('logout')
} catch (e) {
this.formError = e.message
}
} }
} }
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<h1>Super secret page</h1> <h1>Super secret page</h1>
<p>If you try to access this URL not connected, you will be redirected to the home page (server-side or client-side)</p> <p>If you try to access this URL not connected, you will see the error page telling your that you are not connected.</p>
<nuxt-link to="/">Back to the home page</nuxt-link> <nuxt-link to="/">Back to the home page</nuxt-link>
</div> </div>
</template> </template>

View File

@ -1,56 +0,0 @@
const Nuxt = require('nuxt')
const bodyParser = require('body-parser')
const session = require('express-session')
const app = require('express')()
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || '3000'
// Body parser, to access req.body
app.use(bodyParser.json())
// Sessions to create req.session
app.use(session({
secret: 'super-secret-key',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 60000 }
}))
// POST /api/login to log in the user and add him to the req.session.authUser
app.post('/api/login', function (req, res) {
if (req.body.username === 'demo' && req.body.password === 'demo') {
req.session.authUser = { username: 'demo' }
return res.json({ username: 'demo' })
}
res.status(401).json({ message: 'Bad credentials' })
})
// POST /api/logout to log out the user and remove it from the req.session
app.post('/api/logout', function (req, res) {
delete req.session.authUser
res.json({ ok: true })
})
// Import and Set Nuxt.js options
let config = require('./nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
// Init Nuxt.js
new Nuxt(config)
.then((nuxt) => {
// nuxt middlware
app.use(nuxt.render)
// Build only in dev mode
if (config.dev) {
nuxt.build()
.catch((error) => {
console.error(error) // eslint-disable-line no-console
process.exit(1)
})
}
// Listen the server
app.listen(port, host)
console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console
})

View File

@ -11,31 +11,27 @@ export const mutations = {
} }
export const actions = { export const actions = {
// nuxtServerInit is called by Nuxt.js before server-rendering every page
nuxtServerInit ({ commit }, { req }) { nuxtServerInit ({ commit }, { req }) {
if (req.session && req.session.authUser) { if (req.session && req.session.authUser) {
commit('SET_USER', req.session.authUser) commit('SET_USER', req.session.authUser)
} }
}, },
login ({ commit }, { username, password }) { async login ({ commit }, { username, password }) {
return axios.post('/api/login', { try {
username, const { data } = await axios.post('/api/login', { username, password })
password commit('SET_USER', data)
}) } catch (error) {
.then((res) => { if (error.response && error.response.status === 401) {
commit('SET_USER', res.data)
})
.catch((error) => {
if (error.response.status === 401) {
throw new Error('Bad credentials') throw new Error('Bad credentials')
} }
}) throw error
}
}, },
logout ({ commit }) { async logout ({ commit }) {
return axios.post('/api/logout') await axios.post('/api/logout')
.then(() => { commit('SET_USER', null)
commit('SET_USER', null)
})
} }
} }

View File

@ -1,6 +1,6 @@
module.exports = { module.exports = {
build: { render: {
ssr: { bundleRenderer: {
cache: require('lru-cache')({ cache: require('lru-cache')({
max: 1000, max: 1000,
maxAge: 1000 * 60 * 15 maxAge: 1000 * 60 * 15

View File

@ -7,21 +7,13 @@ module.exports = {
app: 'app.[chunkhash].js' // default: nuxt.bundle.[chunkhash].js app: 'app.[chunkhash].js' // default: nuxt.bundle.[chunkhash].js
}, },
vendor: ['lodash'], vendor: ['lodash'],
// Loaders config (Webpack 2)
loaders: [
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader',
options: {
limit: 100000, // 100KO
name: 'img/[name].[ext]?[hash]'
}
}
],
extend (config, { dev }) { extend (config, { dev }) {
if (dev) { if (dev) {
config.devtool = (dev ? 'eval-source-map' : false) config.devtool = (dev ? 'eval-source-map' : false)
} }
const urlLoader = config.module.rules.find((loader) => loader.loader === 'url-loader')
// Increase limit to 100KO
urlLoader.query.limit = 100000
} }
} }
} }

View File

@ -5,6 +5,9 @@
</template> </template>
<style> <style>
body {
overflow: hidden;
}
.dark { .dark {
position: absolute; position: absolute;
top: 0; top: 0;

View File

@ -1,4 +1,4 @@
<template lang="html"> <template>
<div class="loading-page" v-if="loading"> <div class="loading-page" v-if="loading">
<p>Loading...</p> <p>Loading...</p>
</div> </div>

View File

@ -1,3 +1,3 @@
module.exports = { module.exports = {
loading: 'components/loading.vue' loading: '~/components/loading.vue'
} }

View File

@ -1,8 +1,7 @@
{ {
"name": "nuxt-custom-routes", "name": "nuxt-custom-routes",
"description": "",
"dependencies": { "dependencies": {
"axios": "^0.15.2", "axios": "latest",
"nuxt": "latest" "nuxt": "latest"
}, },
"scripts": { "scripts": {

View File

@ -13,11 +13,9 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
asyncData () { async asyncData () {
return axios.get('https://jsonplaceholder.typicode.com/users') const { data } = await axios.get('https://jsonplaceholder.typicode.com/users')
.then((res) => { return { users: data }
return { users: res.data }
})
} }
} }
</script> </script>

View File

@ -14,12 +14,13 @@ export default {
validate ({ params }) { validate ({ params }) {
return !isNaN(+params.id) return !isNaN(+params.id)
}, },
asyncData ({ params, error }) { async asyncData ({ params, error }) {
return axios.get(`https://jsonplaceholder.typicode.com/users/${+params.id}`) try {
.then((res) => res.data) const { data } = await axios.get(`https://jsonplaceholder.typicode.com/users/${+params.id}`)
.catch(() => { return data
} catch (e) {
error({ message: 'User not found', statusCode: 404 }) error({ message: 'User not found', statusCode: 404 })
}) }
} }
} }
</script> </script>

View File

@ -0,0 +1,5 @@
# Dynamic Components with Nuxt.js
Demo: https://nuxt-chat.now.sh
Video: https://www.youtube.com/watch?v=HzDea5-PFaw

View File

@ -0,0 +1,20 @@
<template>
<pre>{{ data }}</pre>
</template>
<script>
export default {
props: {
data: String
}
}
</script>
<style scoped>
pre {
background: #222;
color: #eee;
margin: 0;
padding: 20px;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<img v-if="loaded" :src="data" alt="image" />
<svg v-else width="60px" height="60px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><defs><filter id="uil-ring-shadow" x="-100%" y="-100%" width="300%" height="300%"><feOffset result="offOut" in="SourceGraphic" dx="0" dy="0"></feOffset><feGaussianBlur result="blurOut" in="offOut" stdDeviation="0"></feGaussianBlur><feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend></filter></defs><path d="M10,50c0,0,0,0.5,0.1,1.4c0,0.5,0.1,1,0.2,1.7c0,0.3,0.1,0.7,0.1,1.1c0.1,0.4,0.1,0.8,0.2,1.2c0.2,0.8,0.3,1.8,0.5,2.8 c0.3,1,0.6,2.1,0.9,3.2c0.3,1.1,0.9,2.3,1.4,3.5c0.5,1.2,1.2,2.4,1.8,3.7c0.3,0.6,0.8,1.2,1.2,1.9c0.4,0.6,0.8,1.3,1.3,1.9 c1,1.2,1.9,2.6,3.1,3.7c2.2,2.5,5,4.7,7.9,6.7c3,2,6.5,3.4,10.1,4.6c3.6,1.1,7.5,1.5,11.2,1.6c4-0.1,7.7-0.6,11.3-1.6 c3.6-1.2,7-2.6,10-4.6c3-2,5.8-4.2,7.9-6.7c1.2-1.2,2.1-2.5,3.1-3.7c0.5-0.6,0.9-1.3,1.3-1.9c0.4-0.6,0.8-1.3,1.2-1.9 c0.6-1.3,1.3-2.5,1.8-3.7c0.5-1.2,1-2.4,1.4-3.5c0.3-1.1,0.6-2.2,0.9-3.2c0.2-1,0.4-1.9,0.5-2.8c0.1-0.4,0.1-0.8,0.2-1.2 c0-0.4,0.1-0.7,0.1-1.1c0.1-0.7,0.1-1.2,0.2-1.7C90,50.5,90,50,90,50s0,0.5,0,1.4c0,0.5,0,1,0,1.7c0,0.3,0,0.7,0,1.1 c0,0.4-0.1,0.8-0.1,1.2c-0.1,0.9-0.2,1.8-0.4,2.8c-0.2,1-0.5,2.1-0.7,3.3c-0.3,1.2-0.8,2.4-1.2,3.7c-0.2,0.7-0.5,1.3-0.8,1.9 c-0.3,0.7-0.6,1.3-0.9,2c-0.3,0.7-0.7,1.3-1.1,2c-0.4,0.7-0.7,1.4-1.2,2c-1,1.3-1.9,2.7-3.1,4c-2.2,2.7-5,5-8.1,7.1 c-0.8,0.5-1.6,1-2.4,1.5c-0.8,0.5-1.7,0.9-2.6,1.3L66,87.7l-1.4,0.5c-0.9,0.3-1.8,0.7-2.8,1c-3.8,1.1-7.9,1.7-11.8,1.8L47,90.8 c-1,0-2-0.2-3-0.3l-1.5-0.2l-0.7-0.1L41.1,90c-1-0.3-1.9-0.5-2.9-0.7c-0.9-0.3-1.9-0.7-2.8-1L34,87.7l-1.3-0.6 c-0.9-0.4-1.8-0.8-2.6-1.3c-0.8-0.5-1.6-1-2.4-1.5c-3.1-2.1-5.9-4.5-8.1-7.1c-1.2-1.2-2.1-2.7-3.1-4c-0.5-0.6-0.8-1.4-1.2-2 c-0.4-0.7-0.8-1.3-1.1-2c-0.3-0.7-0.6-1.3-0.9-2c-0.3-0.7-0.6-1.3-0.8-1.9c-0.4-1.3-0.9-2.5-1.2-3.7c-0.3-1.2-0.5-2.3-0.7-3.3 c-0.2-1-0.3-2-0.4-2.8c-0.1-0.4-0.1-0.8-0.1-1.2c0-0.4,0-0.7,0-1.1c0-0.7,0-1.2,0-1.7C10,50.5,10,50,10,50z" fill="#59ebff" filter="url(#uil-ring-shadow)"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" repeatCount="indefinite" dur="1s"></animateTransform></path></svg>
</template>
<script>
export default {
props: {
data: String
},
data: () => ({
loaded: false
}),
beforeMount () {
// Preload image
const img = new Image()
img.onload = () => {
this.loaded = true
};
img.src = this.data
}
}
</script>
<style scoped>
img {
width: 100%;
vertical-align: middle;
}
svg {
margin: 20px;
margin-left: 50%;
position: relative;
left: -30px;
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<p v-html="data"></p>
</template>
<script>
export default {
props: {
data: String
}
}
</script>
<style scoped>
p {
padding: 5px 20px;
}
</style>

View File

@ -0,0 +1,16 @@
const messages = [
{ component: 'vText', data: 'Welcome to the <b>Dynamic Component</b> demo!' },
{ component: 'vText', data: 'Look at this nice picture:' },
{ component: 'vImage', data: 'https://placeimg.com/350/200/animals' },
{ component: 'vText', data: 'If you prefer, look at this code component:' },
{ component: 'vCode', data: 'var a = 1;\nvar b = 2;\nb = a;' },
{ component: 'vText', data: 'End of demo 🎉' },
]
function streamMessages (fn, i = 0) {
if (i >= messages.length) return
fn(messages[i])
setTimeout(() => streamMessages(fn, i + 1), 2000)
}
export default streamMessages

View File

@ -0,0 +1,9 @@
module.exports = {
head: {
titleTemplate: 'Nuxt.js - Dynamic Components',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
}
}

View File

@ -0,0 +1,11 @@
{
"name": "dynamic-components-nuxt",
"dependencies": {
"nuxt": "latest"
},
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt"
}
}

View File

@ -0,0 +1,70 @@
<template>
<div>
<h1>Nuxt Chat</h1>
<transition-group name="list" tag="ul">
<li v-for="(message, index) in messages" :key="index">
<component :is="message.component" :data="message.data"></component>
</li>
</transition-group>
</div>
</template>
<script>
import streamMessages from '@/js/messages.js'
// Dynamic components
const components = {
vText: () => import('@/components/text.vue').then(m => m.default),
vImage: () => import('@/components/image.vue').then(m => m.default),
vCode: () => import('@/components/code.vue').then(m => m.default)
}
export default {
data: () => ({
messages: []
}),
mounted () {
// Listen to new messages
streamMessages(async (message) => {
// Make sure to wait for async chunk to be loaded before adding the message
await components[message.component]()
// Add the message to the list
this.messages.push(message)
})
},
components
}
</script>
<style scoped>
h1 {
text-align: center;
font-family: Helvetica, Arial, sans-serif;
}
ul {
list-style: none;
margin: 0;
padding: 0;
with: 100%;
max-width: 300px;
margin: auto;
}
ul li {
display: block;
width: 100%;
border-radius: 20px;
margin-bottom: 5px;
font-family: Helvetica, Arial, sans-serif;
background: white;
border: 1px #ddd solid;
overflow: hidden;
opacity: 1;
}
.list-enter-active, .list-leave-active {
transition: all 0.4s;
}
.list-enter, .list-leave-to {
opacity: 0;
transform: translateY(20px);
}
</style>

View File

@ -1,12 +1,13 @@
const { join } = require('path')
module.exports = { module.exports = {
head: {
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Meta description' }
]
},
css: [ css: [
'hover.css/css/hover-min.css', 'bulma/css/bulma.css',
'bulma/bulma.sass', '~/css/main.css'
join(__dirname, 'css/main.css') ]
],
build: {
extractCSS: true
}
} }

View File

@ -1,11 +1,8 @@
{ {
"name": "nuxt-global-css", "name": "nuxt-global-css",
"dependencies": { "dependencies": {
"bulma": "^0.4.0", "bulma": "^0.4.3",
"hover.css": "^2.2.0", "nuxt": "latest"
"node-sass": "^4.5.1",
"nuxt": "^0.10.0",
"sass-loader": "^6.0.3"
}, },
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt",

View File

@ -8,7 +8,7 @@
</template> </template>
<script> <script>
import TwitterHeadCard from '~components/twitter-head-card.vue' import TwitterHeadCard from '~/components/twitter-head-card.vue'
export default { export default {
head: { head: {

View File

@ -4,6 +4,8 @@
"nuxt": "latest" "nuxt": "latest"
}, },
"scripts": { "scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt" "start": "nuxt"
} }
} }

View File

@ -1,5 +1,5 @@
export default function ({ app, store, route, params, error, redirect, hotReload }) { export default function ({ app, store, route, params, error, redirect, hotReload }) {
// Check if middleware called from hot-reloading, ignore // If middleware is called from hot-reloading, ignore it
if (hotReload) return if (hotReload) return
// Get locale from params // Get locale from params
const locale = params.lang || 'en' const locale = params.lang || 'en'

View File

@ -6,7 +6,7 @@ module.exports = {
router: { router: {
middleware: 'i18n' middleware: 'i18n'
}, },
plugins: ['~plugins/i18n.js',], plugins: ['~/plugins/i18n.js',],
generate: { generate: {
routes: ['/', '/about', '/fr', '/fr/about'] routes: ['/', '/about', '/fr', '/fr/about']
} }

View File

@ -1,8 +1,8 @@
{ {
"name": "nuxt-i18n", "name": "nuxt-i18n",
"dependencies": { "dependencies": {
"nuxt": "1.0.0-alpha.3", "nuxt": "latest",
"vue-i18n": "^7.0.0" "vue-i18n": "^7.0.5"
}, },
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt",

View File

@ -1,4 +1,4 @@
<script> <script>
import About from '~pages/_lang/about' import About from '~/pages/_lang/about'
export default About export default About
</script> </script>

View File

@ -1,4 +1,4 @@
<script> <script>
import Index from '~pages/_lang/index' import Index from '~/pages/_lang/index'
export default Index export default Index
</script> </script>

View File

@ -6,7 +6,7 @@
</template> </template>
<script> <script>
import Visits from '~components/Visits' import Visits from '~/components/Visits'
export default { export default {
components: { Visits } components: { Visits }

View File

@ -1,6 +1,6 @@
export const state = { export const state = () => ({
visits: [] visits: []
} })
export const mutations = { export const mutations = {
ADD_VISIT (state, path) { ADD_VISIT (state, path) {

View File

@ -22,8 +22,8 @@
</template> </template>
<script> <script>
import Post from '~components/post' import Post from '~/components/post'
import vP from '~components/paragraph' import vP from '~/components/paragraph'
const vHr = { render: (h) => h('hr', { class: 'hr' }) } const vHr = { render: (h) => h('hr', { class: 'hr' }) }
export default { export default {

View File

@ -1,6 +0,0 @@
module.exports = {
offline: true, // true or https://github.com/NekR/offline-plugin/blob/master/docs/options.md
plugins: [
{ src: '~plugins/offline.js', ssr: false }
]
}

View File

@ -1,3 +0,0 @@
<template>
<div>This is offline test</div>
</template>

View File

@ -1,19 +0,0 @@
if (process.env.NODE_ENV === 'production') {
var OfflinePlugin = require('offline-plugin/runtime')
window.onNuxtReady(() => {
OfflinePlugin.install({
onInstalled: function () {
console.log('Offline plugin installed.') // eslint-disable-line no-console
},
onUpdating: function () {
},
onUpdateReady: function () {
OfflinePlugin.applyUpdate()
},
onUpdated: function () {
window.location.reload()
}
})
})
}

View File

@ -4,6 +4,6 @@ module.exports = {
}, },
plugins: [ plugins: [
// ssr: false to only include it on client-side // ssr: false to only include it on client-side
{ src: '~plugins/vue-notifications.js', ssr: false } { src: '~/plugins/vue-notifications.js', ssr: false }
] ]
} }

View File

@ -1,10 +1,10 @@
{ {
"name": "nuxt-plugins-vendor", "name": "nuxt-plugins-vendor",
"dependencies": { "dependencies": {
"axios": "^0.15.2", "axios": "^0.16.2",
"mini-toastr": "^0.3.10", "mini-toastr": "^0.6.5",
"nuxt": "latest", "nuxt": "latest",
"vue-notifications": "^0.7.0" "vue-notifications": "^0.8.0"
}, },
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt",

View File

@ -10,7 +10,8 @@ import axios from 'axios'
export default { export default {
asyncData () { asyncData () {
return axios.get('https://jsonplaceholder.typicode.com/photos/4').then(res => res.data) const nb = Math.max(1, Math.round(Math.random() * 10))
return axios.get(`https://jsonplaceholder.typicode.com/photos/${nb}`).then(res => res.data)
} }
} }
</script> </script>

View File

@ -2,6 +2,5 @@ module.exports = {
build: { build: {
vendor: ['axios'] vendor: ['axios']
}, },
css: ['assets/main.css'], css: ['~/assets/main.css']
loading: false
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="container" :key="page"> <div class="container">
<nuxt-link v-if="page > 1" :to="'?page=' + (page - 1)">&lt; Prev</nuxt-link> <nuxt-link v-if="page > 1" :to="'?page=' + (page - 1)">&lt; Prev</nuxt-link>
<a v-else class="disabled">&lt; Prev</a> <a v-else class="disabled">&lt; Prev</a>
<span>{{ page }}/{{ totalPages }}</span> <span>{{ page }}/{{ totalPages }}</span>
@ -23,16 +23,14 @@ export default {
if (!from) return 'slide-left' if (!from) return 'slide-left'
return +to.query.page < +from.query.page ? 'slide-right' : 'slide-left' return +to.query.page < +from.query.page ? 'slide-right' : 'slide-left'
}, },
asyncData ({ query }) { async asyncData ({ query }) {
const page = +query.page || 1 const page = +query.page || 1
return axios.get('https://reqres.in/api/users?page=' + page) const { data } = await axios.get(`https://reqres.in/api/users?page=${page}`)
.then((res) => { return {
return { page: +data.page,
page: +res.data.page, totalPages: data.total_pages,
totalPages: res.data.total_pages, users: data.data
users: res.data.data }
}
})
} }
} }
</script> </script>

View File

@ -1,4 +1,4 @@
module.exports = function (options, next) { module.exports = function (options) {
// Extend build // Extend build
this.extendBuild((config) => { this.extendBuild((config) => {
// Add TypeScript loader // Add TypeScript loader
@ -13,6 +13,4 @@ module.exports = function (options, next) {
} }
} }
}) })
next()
} }

View File

@ -23,9 +23,7 @@
"~middleware/*": ["./middleware/*"], "~middleware/*": ["./middleware/*"],
"~pages/*": ["./pages/*"], "~pages/*": ["./pages/*"],
"~plugins/*": ["./plugins/*"], "~plugins/*": ["./plugins/*"],
"~static/*": ["./static/*"], "~static/*": ["./static/*"]
"~store": ["./.nuxt/store"],
"~router": ["./.nuxt/router"]
} }
} }
} }

View File

@ -11,4 +11,4 @@ $theme := {
} }
// Import Vuetify styling // Import Vuetify styling
@require '~vuetify/src/stylus/main.styl' @require '~vuetify/src/stylus/main.styl'

View File

@ -1,2 +0,0 @@
@require './vendor/material-icons.styl'
@require './vendor/vuetify.styl'

View File

@ -1,21 +0,0 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'), url(https://fonts.gstatic.com/s/materialicons/v22/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2) format('woff2');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}

View File

@ -0,0 +1,45 @@
<template>
<v-app>
<v-toolbar>
<v-toolbar-side-icon @click.native.stop="sidebar = !sidebar" />
<v-toolbar-logo>Toolbar</v-toolbar-logo>
</v-toolbar>
<v-sidebar left fixed drawer v-model="sidebar">
<v-list>
<v-list-item>
<v-list-tile router nuxt href="/">
<v-list-tile-title>Home</v-list-tile-title>
</v-list-tile>
</v-list-item>
<v-list-item>
<v-list-tile router nuxt href="/about">
<v-list-tile-title>About</v-list-tile-title>
</v-list-tile>
</v-list-item>
</v-list>
</v-sidebar>
<main>
<v-content>
<v-container fluid>
<nuxt/>
</v-container>
</v-content>
</main>
</v-app>
</template>
<script>
export default {
data() {
return {
sidebar: false
}
}
}
</script>
<style scoped>
.title {
padding-left: 20px;
}
</style>

View File

@ -2,16 +2,28 @@
const { join } = require('path') const { join } = require('path')
module.exports = { module.exports = {
build: { /*
vendor: ['vuetify'] ** Head elements
}, ** Add Roboto font and Material Icons
plugins: ['~plugins/vuetify.js'], */
css: [
{ src: join(__dirname, 'css/app.styl'), lang: 'styl' }
],
head: { head: {
link: [ link: [
{ rel: 'preload', as: 'style', href: 'https://fonts.googleapis.com/css?family=Roboto' } { rel: 'stylesheet', type: 'text/css', href: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' }
] ]
} },
/*
** Add Vuetify into vendor.bundle.js
*/
build: {
vendor: ['vuetify'],
extractCSS: true
},
/*
** Load Vuetify into the app
*/
plugins: ['~plugins/vuetify'],
/*
** Load Vuetify CSS globally
*/
css: ['~assets/app.styl']
} }

View File

@ -1,16 +1,14 @@
{ {
"name": "with-vuetify", "name": "with-vuetify",
"dependencies": { "dependencies": {
"nuxt": "^0.10.7", "nuxt": "latest",
"vuetify": "^0.11.1" "vuetify": "latest"
}, },
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt",
"build": "nuxt build", "build": "nuxt build",
"start": "nuxt start", "start": "nuxt start",
"generate": "nuxt generate", "generate": "nuxt generate"
"predeploy": "yarn run generate",
"deploy": "surge --domain nuxt-with-vuetify-example.surge.sh dist"
}, },
"devDependencies": { "devDependencies": {
"stylus": "^0.54.5", "stylus": "^0.54.5",

View File

@ -0,0 +1,18 @@
<template>
<v-carousel>
<v-carousel-item v-for="(src, i) in images" v-bind:src="src" :key="i"></v-carousel-item>
</v-carousel>
</template>
<script>
export default {
data: () => ({
images: [
'https://vuetifyjs.com/static/doc-images/carousel/squirrel.jpg',
'https://vuetifyjs.com/static/doc-images/carousel/sky.jpg',
'https://vuetifyjs.com/static/doc-images/carousel/bird.jpg',
'https://vuetifyjs.com/static/doc-images/carousel/planet.jpg'
]
})
}
</script>

View File

@ -1,45 +1,8 @@
<template> <template>
<v-app top-toolbar left-fixed-sidebar> <div class="title">
<v-toolbar> <h2>Main content</h2>
<v-toolbar-side-icon @click.native.stop="sidebar = !sidebar" /> <v-btn primary>Primary button</v-btn>
<v-toolbar-logo>Toolbar</v-toolbar-logo> <v-btn secondary>Secondary button</v-btn>
</v-toolbar> <v-btn success>Success button</v-btn>
<main> </div>
<v-sidebar left fixed drawer v-model="sidebar"> </template>
<v-list>
<v-list-item v-for="i in 3" :key="i">
<v-list-tile>
<v-list-tile-title>Item {{ i }}</v-list-tile-title>
</v-list-tile>
</v-list-item>
</v-list>
</v-sidebar>
<v-content>
<v-container fluid>
<div class="title">
<h2>Main content</h2>
<v-btn primary>Primary button</v-btn>
<v-btn secondary>Secondary button</v-btn>
<v-btn success>Success button</v-btn>
</div>
</v-container>
</v-content>
</main>
</v-app>
</template>
<script>
export default {
asyncData() {
return {
sidebar: false
}
}
}
</script>
<style scoped>
.title {
padding-left: 20px;
}
</style>

View File

@ -1,11 +1,16 @@
/*! /*!
* Nuxt.js * Nuxt.js
* (c) 2016-2017 Chopin Brothers * (c) 2016-2017 Chopin Brothers
* Core maintainer: Pooya (@pi0)
* Released under the MIT License. * Released under the MIT License.
*/ */
// Node Source Map Support
// https://github.com/evanw/node-source-map-support
require('source-map-support').install()
// Fix babel flag
/* istanbul ignore else */
process.noDeprecation = true process.noDeprecation = true
var Nuxt = require('./dist/nuxt.js') module.exports = require('./dist/nuxt')
module.exports = Nuxt.default ? Nuxt.default : Nuxt

View File

@ -15,7 +15,7 @@ let layouts = {
<% <%
var layoutsKeys = Object.keys(layouts); var layoutsKeys = Object.keys(layouts);
layoutsKeys.forEach(function (key, i) { %> layoutsKeys.forEach(function (key, i) { %>
"_<%= key %>": () => import('<%= layouts[key] %>' /* webpackChunkName: "layouts/<%= key %>" */)<%= (i + 1) < layoutsKeys.length ? ',' : '' %> "_<%= key %>": () => import('<%= layouts[key] %>' /* webpackChunkName: "layouts/<%= key %>" */).then(m => m.default || m)<%= (i + 1) < layoutsKeys.length ? ',' : '' %>
<% }) %> <% }) %>
} }

View File

@ -1,185 +1,315 @@
'use strict'
import Vue from 'vue' import Vue from 'vue'
import middleware from './middleware' import middleware from './middleware'
import { createApp, NuxtError } from './index' import { createApp, NuxtError } from './index'
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getMatchedComponentsInstances, flatMapComponents, getContext, middlewareSeries, promisify, getLocation, compile } from './utils' import {
applyAsyncData,
sanitizeComponent,
getMatchedComponents,
getMatchedComponentsInstances,
flatMapComponents,
getContext,
middlewareSeries,
promisify,
getLocation,
compile
} from './utils'
const noopData = () => { return {} } const noopData = () => { return {} }
const noopFetch = () => {} const noopFetch = () => {}
// Global shared references
let _lastPaths = [] let _lastPaths = []
let _lastComponentsFiles = [] let _lastComponentsFiles = []
let app let app
let router let router
<%= (store ? 'let store' : '') %> <% if (store) { %>let store<% } %>
// Try to rehydrate SSR data from window
const NUXT = window.__NUXT__ || {}
NUXT.components = window.__COMPONENTS__ || null
// Create and mount App
createApp()
.then(mountApp)
.catch(err => {
console.error('[nuxt] Error while initializing app', err)
})
function componentOption(component, key, ...args) {
if (!component || !component.options || !component.options[key]) {
return {}
}
const option = component.options[key]
if (typeof option === 'function') {
return option(...args)
}
return option
}
function mapTransitions(Components, to, from) { function mapTransitions(Components, to, from) {
return Components.map((Component) => { const componentTransitions = component => {
let transition = Component.options.transition const transition = componentOption(component, 'transition', to, from)
if (typeof transition === 'function') { return (typeof transition === 'string' ? { name: transition } : transition)
return transition(to, from) }
return Components.map(Component => {
// Clone original object to prevent overrides
const transitions = Object.assign({}, componentTransitions(Component))
// Combine transitions & prefer `leave` transitions of 'from' route
if (from && from.matched.length && from.matched[0].components.default) {
const from_transitions = componentTransitions(from.matched[0].components.default)
Object.keys(from_transitions)
.filter(key => from_transitions[key] && key.toLowerCase().indexOf('leave') !== -1)
.forEach(key => { transitions[key] = from_transitions[key] })
} }
return transition
return transitions
}) })
} }
function loadAsyncComponents (to, from, next) { async function loadAsyncComponents (to, from, next) {
const resolveComponents = flatMapComponents(to, (Component, _, match, key) => { // Check if route hash changed
if (typeof Component === 'function' && !Component.options) {
return new Promise(function (resolve, reject) {
const _resolve = (Component) => {
Component = sanitizeComponent(Component)
match.components[key] = Component
resolve(Component)
}
Component().then(_resolve).catch(reject)
})
}
Component = sanitizeComponent(Component)
match.components[key] = Component
return match.components[key]
})
const fromPath = from.fullPath.split('#')[0] const fromPath = from.fullPath.split('#')[0]
const toPath = to.fullPath.split('#')[0] const toPath = to.fullPath.split('#')[0]
this._hashChanged = (fromPath === toPath) this._hashChanged = fromPath === toPath
if (!this._hashChanged) {
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %> <% if (loading) { %>
if (!this._hashChanged && this.$loading.start) {
this.$loading.start()
} }
Promise.all(resolveComponents) <% } %>
.then(() => next())
.catch((err) => { try {
let statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500 await Promise.all(flatMapComponents(to, (Component, _, match, key) => {
this.error({ statusCode, message: err.message }) // If component already resolved
next(false) if (typeof Component !== 'function' || Component.options) {
const _Component = sanitizeComponent(Component)
match.components[key] = _Component
return _Component
}
// Resolve component
return Component().then(Component => {
const _Component = sanitizeComponent(Component)
match.components[key] = _Component
return _Component
})
}))
next()
} catch (err) {
if (!err) err = {}
const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500
this.error({ statusCode, message: err.message })
next(false)
}
}
// Get matched components
function resolveComponents(router) {
const path = getLocation(router.options.base)
return flatMapComponents(router.match(path), (Component, _, match, key, index) => {
// If component already resolved
if (typeof Component !== 'function' || Component.options) {
const _Component = sanitizeComponent(Component)
match.components[key] = _Component
return _Component
}
// Resolve component
return Component().then(Component => {
const _Component = sanitizeComponent(Component)
if (NUXT.serverRendered) {
applyAsyncData(_Component, NUXT.data[index])
if (NUXT.components) {
Component.options.components = Object.assign(_Component.options.components, NUXT.components[index])
}
_Component._Ctor = _Component
}
match.components[key] = _Component
return _Component
})
}) })
} }
function callMiddleware (Components, context, layout) { function callMiddleware (Components, context, layout) {
// if layout is undefined, only call global middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %> let midd = <%= serialize(router.middleware, { isJSON: true }) %>
let unknownMiddleware = false let unknownMiddleware = false
// If layout is undefined, only call global middleware
if (typeof layout !== 'undefined') { if (typeof layout !== 'undefined') {
midd = [] // exclude global middleware if layout defined (already called before) midd = [] // Exclude global middleware if layout defined (already called before)
if (layout.middleware) { if (layout.middleware) {
midd = midd.concat(layout.middleware) midd = midd.concat(layout.middleware)
} }
Components.forEach((Component) => { Components.forEach(Component => {
if (Component.options.middleware) { if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware) midd = midd.concat(Component.options.middleware)
} }
}) })
} }
midd = midd.map((name) => {
midd = midd.map(name => {
if (typeof middleware[name] !== 'function') { if (typeof middleware[name] !== 'function') {
unknownMiddleware = true unknownMiddleware = true
this.error({ statusCode: 500, message: 'Unknown middleware ' + name }) this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
} }
return middleware[name] return middleware[name]
}) })
if (unknownMiddleware) return
if (unknownMiddleware) return
return middlewareSeries(midd, context) return middlewareSeries(midd, context)
} }
async function render (to, from, next) { async function render (to, from, next) {
if (this._hashChanged) return next() if (this._hashChanged) return next()
let layout
// nextCalled is true when redirected
let nextCalled = false let nextCalled = false
const _next = function (path) { const _next = path => {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %> <% if(loading) { %>if(this.$loading.finish) this.$loading.finish()<% } %>
if (nextCalled) return if (nextCalled) return
nextCalled = true nextCalled = true
next(path) next(path)
} }
let context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true, next: _next.bind(this), error: this.error.bind(this) }, app)
let Components = getMatchedComponents(to) // Update context
const context = getContext({
to,
<% if (store) { %>store,<% } %>
isClient: true,
next: _next.bind(this),
error: this.error.bind(this),
app
})
this._context = context this._context = context
this._dateLastError = this.$options._nuxt.dateErr this._dateLastError = this.$options._nuxt.dateErr
this._hadError = !!this.$options._nuxt.err this._hadError = !!this.$options._nuxt.err
// Get route's matched components
const Components = getMatchedComponents(to)
// If no Components matched, generate 404
if (!Components.length) { if (!Components.length) {
// Default layout // Default layout
await callMiddleware.call(this, Components, context) await callMiddleware.call(this, Components, context)
if (context._redirected) return if (context._redirected) return
// Load layout for error page
layout = await this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(context) : NuxtError.layout) layout = await this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(context) : NuxtError.layout)
await callMiddleware.call(this, Components, context, layout) await callMiddleware.call(this, Components, context, layout)
if (context._redirected) return if (context._redirected) return
this.error({ statusCode: 404, message: 'This page could not be found.' }) this.error({ statusCode: 404, message: 'This page could not be found.' })
return next() return next()
} }
// Update ._data and other properties if hot reloaded // Update ._data and other properties if hot reloaded
Components.forEach(function (Component) { Components.forEach(Component => {
if (Component._Ctor && Component._Ctor.options) { if (Component._Ctor && Component._Ctor.options) {
Component.options.asyncData = Component._Ctor.options.asyncData Component.options.asyncData = Component._Ctor.options.asyncData
Component.options.fetch = Component._Ctor.options.fetch Component.options.fetch = Component._Ctor.options.fetch
} }
}) })
// Apply transitions
this.setTransitions(mapTransitions(Components, to, from)) this.setTransitions(mapTransitions(Components, to, from))
try { try {
// Set layout // Call middleware
await callMiddleware.call(this, Components, context) await callMiddleware.call(this, Components, context)
if (context._redirected) return if (context._redirected) return
layout = Components[0].options.layout
// Set layout
let layout = Components[0].options.layout
if (typeof layout === 'function') { if (typeof layout === 'function') {
layout = layout(context) layout = layout(context)
} }
layout = await this.loadLayout(layout) layout = await this.loadLayout(layout)
// Call middleware for layout
await callMiddleware.call(this, Components, context, layout) await callMiddleware.call(this, Components, context, layout)
if (context._redirected) return if (context._redirected) return
// Pass validation?
// Call .validate()
let isValid = true let isValid = true
Components.forEach((Component) => { Components.forEach(Component => {
if (!isValid) return if (!isValid) return
if (typeof Component.options.validate !== 'function') return if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({ isValid = Component.options.validate({
params: to.params || {}, params: to.params || {},
query : to.query || {}<%= (store ? ', store: context.store' : '') %> query : to.query || {},
<% if(store) { %>store: context.store <% } %>
}) })
}) })
// ...If .validate() returned false
if (!isValid) { if (!isValid) {
this.error({ statusCode: 404, message: 'This page could not be found.' }) this.error({ statusCode: 404, message: 'This page could not be found.' })
return next() return next()
} }
// Call asyncData & fetch hooks on components matched by the route.
await Promise.all(Components.map((Component, i) => { await Promise.all(Components.map((Component, i) => {
// Check if only children route changed // Check if only children route changed
Component._path = compile(to.matched[i].path)(to.params) Component._path = compile(to.matched[i].path)(to.params)
if (!this._hadError && Component._path === _lastPaths[i] && (i + 1) !== Components.length) { if (!this._hadError && Component._path === _lastPaths[i] && (i + 1) !== Components.length) {
return Promise.resolve() return Promise.resolve()
} }
let promises = [] let promises = []
// asyncData method
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { const hasAsyncData = Component.options.asyncData && typeof Component.options.asyncData === 'function'
var promise = promisify(Component.options.asyncData, context) const hasFetch = !!Component.options.fetch
promise.then((asyncDataResult) => { <% if(loading) { %>const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45<% } %>
// Call asyncData(context)
if (hasAsyncData) {
const promise = promisify(Component.options.asyncData, context)
.then(asyncDataResult => {
applyAsyncData(Component, asyncDataResult) applyAsyncData(Component, asyncDataResult)
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %> <% if(loading) { %>if(this.$loading.increase) this.$loading.increase(loadingIncrease)<% } %>
}) })
promises.push(promise) promises.push(promise)
} }
if (Component.options.fetch) {
var p = Component.options.fetch(context) // Call fetch(context)
if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { p = Promise.resolve(p) } if (hasFetch) {
<%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %> let p = Component.options.fetch(context)
if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) {
p = Promise.resolve(p)
}
p.then(fetchResult => {
<% if(loading) { %>if(this.$loading.increase) this.$loading.increase(loadingIncrease)<% } %>
})
promises.push(p) promises.push(p)
} }
return Promise.all(promises) return Promise.all(promises)
})) }))
_lastPaths = Components.map((Component, i) => compile(to.matched[i].path)(to.params)) _lastPaths = Components.map((Component, i) => compile(to.matched[i].path)(to.params))
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
<% if(loading) { %>if(this.$loading.finish) this.$loading.finish()<% } %>
// If not redirected // If not redirected
if (!nextCalled) { if (!nextCalled) next()
next()
}
} catch (error) { } catch (error) {
if (!error) error = {}
_lastPaths = [] _lastPaths = []
error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500 error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
// Load error layout
let layout = NuxtError.layout let layout = NuxtError.layout
if (typeof layout === 'function') { if (typeof layout === 'function') {
layout = layout(context) layout = layout(context)
} }
this.loadLayout(layout) await this.loadLayout(layout)
.then(() => {
this.error(error) this.error(error)
next(false) next(false)
})
} }
} }
@ -197,46 +327,74 @@ function normalizeComponents (to, ___) {
} }
// When navigating on a different route but the same component is used, Vue.js // When navigating on a different route but the same component is used, Vue.js
// will not update the instance data, so we have to update $data ourselves // Will not update the instance data, so we have to update $data ourselves
function fixPrepatch (to, ___) { function fixPrepatch (to, ___) {
if (this._hashChanged) return if (this._hashChanged) return
Vue.nextTick(() => { Vue.nextTick(() => {
let instances = getMatchedComponentsInstances(to) const instances = getMatchedComponentsInstances(to)
_lastComponentsFiles = instances.map((instance, i) => { _lastComponentsFiles = instances.map((instance, i) => {
if (!instance) return ''; if (!instance) return '';
if (_lastPaths[i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') { if (_lastPaths[i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') {
let newData = instance.constructor.options.data.call(instance) const newData = instance.constructor.options.data.call(instance)
for (let key in newData) { for (let key in newData) {
Vue.set(instance.$data, key, newData[key]) Vue.set(instance.$data, key, newData[key])
} }
} }
return instance.constructor.options.__file return instance.constructor.options.__file
}) })
// hide error component if no error
// Hide error component if no error
if (this._hadError && this._dateLastError === this.$options._nuxt.dateErr) { if (this._hadError && this._dateLastError === this.$options._nuxt.dateErr) {
this.error() this.error()
} }
// Set layout // Set layout
let layout = this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout let layout = this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout
if (typeof layout === 'function') { if (typeof layout === 'function') {
layout = layout(this._context) layout = layout(this._context)
} }
this.setLayout(layout) this.setLayout(layout)
// hot reloading
<% if (isDev) { %>
// Hot reloading
setTimeout(() => hotReloadAPI(this), 100) setTimeout(() => hotReloadAPI(this), 100)
<% } %>
}) })
} }
function nuxtReady (app) {
window._nuxtReadyCbs.forEach((cb) => {
if (typeof cb === 'function') {
cb(app)
}
})
// Special JSDOM
if (typeof window._onNuxtLoaded === 'function') {
window._onNuxtLoaded(app)
}
// Add router hooks
router.afterEach(function (to, from) {
app.$nuxt.$emit('routeChanged', to, from)
})
}
<% if (isDev) { %>
// Special hot reload with asyncData(context) // Special hot reload with asyncData(context)
function hotReloadAPI (_app) { function hotReloadAPI (_app) {
if (!module.hot) return if (!module.hot) return
let $components = [] let $components = []
let $nuxt = _app.$nuxt let $nuxt = _app.$nuxt
while ($nuxt && $nuxt.$children && $nuxt.$children.length) { while ($nuxt && $nuxt.$children && $nuxt.$children.length) {
$nuxt.$children.forEach(function (child, i) { $nuxt.$children.forEach((child, i) => {
if (child.$vnode.data.nuxtChild) { if (child.$vnode.data.nuxtChild) {
let hasAlready = false let hasAlready = false
$components.forEach(function (component) { $components.forEach(component => {
if (component.$options.__file === child.$options.__file) { if (component.$options.__file === child.$options.__file) {
hasAlready = true hasAlready = true
} }
@ -248,13 +406,16 @@ function hotReloadAPI (_app) {
$nuxt = child $nuxt = child
}) })
} }
$components.forEach(addHotReload.bind(_app)) $components.forEach(addHotReload.bind(_app))
} }
function addHotReload ($component, depth) { function addHotReload ($component, depth) {
if ($component.$vnode.data._hasHotReload) return if ($component.$vnode.data._hasHotReload) return
$component.$vnode.data._hasHotReload = true $component.$vnode.data._hasHotReload = true
var _forceUpdate = $component.$forceUpdate.bind($component.$parent) var _forceUpdate = $component.$forceUpdate.bind($component.$parent)
$component.$vnode.context.$forceUpdate = () => { $component.$vnode.context.$forceUpdate = () => {
let Components = getMatchedComponents(router.currentRoute) let Components = getMatchedComponents(router.currentRoute)
let Component = Components[depth] let Component = Components[depth]
@ -314,104 +475,80 @@ function addHotReload ($component, depth) {
}) })
} }
} }
<% } %>
// Load vue app async function mountApp(__app) {
const NUXT = window.__NUXT__ || {} // Set global variables
if (!NUXT) {
throw new Error('[nuxt.js] cannot find the global variable __NUXT__, make sure the server is working.')
}
// Get matched components
const resolveComponents = function (router) {
const path = getLocation(router.options.base)
return flatMapComponents(router.match(path), (Component, _, match, key, index) => {
if (typeof Component === 'function' && !Component.options) {
return new Promise(function (resolve, reject) {
const _resolve = (Component) => {
Component = sanitizeComponent(Component)
if (NUXT.serverRendered) {
applyAsyncData(Component, NUXT.data[index])
}
match.components[key] = Component
resolve(Component)
}
Component().then(_resolve).catch(reject)
})
}
Component = sanitizeComponent(Component)
match.components[key] = Component
return Component
})
}
function nuxtReady (app) {
window._nuxtReadyCbs.forEach((cb) => {
if (typeof cb === 'function') {
cb(app)
}
})
// Special JSDOM
if (typeof window._onNuxtLoaded === 'function') {
window._onNuxtLoaded(app)
}
// Add router hooks
router.afterEach(function (to, from) {
app.$nuxt.$emit('routeChanged', to, from)
})
}
createApp()
.then(async (__app) => {
app = __app.app app = __app.app
router = __app.router router = __app.router
<%= (store ? 'store = __app.store' : '') %> <% if (store) { %>store = __app.store <% } %>
// Resolve route components
const Components = await Promise.all(resolveComponents(router)) const Components = await Promise.all(resolveComponents(router))
// Create Vue instance
const _app = new Vue(app) const _app = new Vue(app)
// Load layout
const layout = NUXT.layout || 'default' const layout = NUXT.layout || 'default'
await _app.loadLayout(layout) await _app.loadLayout(layout)
_app.setLayout(layout) _app.setLayout(layout)
// Mounts Vue app to DOM element
const mountApp = () => { const mountApp = () => {
_app.$mount('#__nuxt') _app.$mount('#__nuxt')
// Listen for first Vue update
Vue.nextTick(() => { Vue.nextTick(() => {
// Hot reloading
hotReloadAPI(_app)
// Call window.onNuxtReady callbacks // Call window.onNuxtReady callbacks
nuxtReady(_app) nuxtReady(_app)
<% if (isDev) { %>
// Enable hot reloading
hotReloadAPI(_app)
<% } %>
}) })
} }
// Enable transitions
_app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app) _app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app)
if (Components.length) { if (Components.length) {
_app.setTransitions(mapTransitions(Components, router.currentRoute)) _app.setTransitions(mapTransitions(Components, router.currentRoute))
_lastPaths = router.currentRoute.matched.map((route) => compile(route.path)(router.currentRoute.params)) _lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
_lastComponentsFiles = Components.map((Component) => Component.options.__file) _lastComponentsFiles = Components.map(Component => Component.options.__file)
} }
// Initialize error handler
_app.error = _app.$options._nuxt.error.bind(_app) _app.error = _app.$options._nuxt.error.bind(_app)
_app.$loading = {} // to avoid error while _app.$nuxt does not exist _app.$loading = {} // To avoid error while _app.$nuxt does not exist
if (NUXT.error) _app.error(NUXT.error) if (NUXT.error) _app.error(NUXT.error)
// Add router hooks // Add router hooks
router.beforeEach(loadAsyncComponents.bind(_app)) router.beforeEach(loadAsyncComponents.bind(_app))
router.beforeEach(render.bind(_app)) router.beforeEach(render.bind(_app))
router.afterEach(normalizeComponents) router.afterEach(normalizeComponents)
router.afterEach(fixPrepatch.bind(_app)) router.afterEach(fixPrepatch.bind(_app))
// If page already is server rendered
if (NUXT.serverRendered) { if (NUXT.serverRendered) {
mountApp() mountApp()
return return
} }
render.call(_app, router.currentRoute, router.currentRoute, function (path) {
if (path) { render.call(_app, router.currentRoute, router.currentRoute, path => {
let mounted = false if (!path) {
router.afterEach(function () { normalizeComponents(router.currentRoute, router.currentRoute)
if (mounted) return fixPrepatch.call(_app, router.currentRoute, router.currentRoute)
mounted = true mountApp()
mountApp()
})
router.push(path)
return return
} }
normalizeComponents(router.currentRoute, router.currentRoute)
fixPrepatch.call(_app, router.currentRoute, router.currentRoute) // Push the path and then mount app
mountApp() let mounted = false
router.afterEach(() => {
if (mounted) return
mounted = true
mountApp()
})
router.push(path)
}) })
}) }
.catch((err) => {
console.error('[nuxt.js] Cannot load components', err) // eslint-disable-line no-console
})

View File

@ -30,7 +30,7 @@ export default {
functional: true, functional: true,
render (h, { parent, data }) { render (h, { parent, data }) {
data.nuxtChild = true data.nuxtChild = true
const _parent = parent
const transitions = parent.$nuxt.nuxt.transitions const transitions = parent.$nuxt.nuxt.transitions
const defaultTransition = parent.$nuxt.nuxt.defaultTransition const defaultTransition = parent.$nuxt.nuxt.defaultTransition
let depth = 0 let depth = 0
@ -51,7 +51,7 @@ export default {
let listeners = {} let listeners = {}
listenersKeys.forEach((key) => { listenersKeys.forEach((key) => {
if (typeof transition[key] === 'function') { if (typeof transition[key] === 'function') {
listeners[key] = transition[key] listeners[key] = transition[key].bind(_parent)
} }
}) })
return h('transition', { return h('transition', {

View File

@ -1,15 +1,16 @@
<template> <template>
<nuxt-error v-if="nuxt.err" :error="nuxt.err"></nuxt-error> <nuxt-error v-if="nuxt.err" :error="nuxt.err"></nuxt-error>
<nuxt-child v-else></nuxt-child> <nuxt-child :key="routerViewKey" v-else></nuxt-child>
</template> </template>
<script> <script>
import Vue from 'vue' import Vue from 'vue'
import NuxtChild from './nuxt-child' import NuxtChild from './nuxt-child'
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./nuxt-error.vue" %>' import NuxtError from '<%= components.ErrorPage ? ((components.ErrorPage.includes('~') || components.ErrorPage.includes('@')) ? components.ErrorPage : "../" + components.ErrorPage) : "./nuxt-error.vue" %>'
export default { export default {
name: 'nuxt', name: 'nuxt',
props: ['nuxtChildKey'],
beforeCreate () { beforeCreate () {
Vue.util.defineReactive(this, 'nuxt', this.$root.$options._nuxt) Vue.util.defineReactive(this, 'nuxt', this.$root.$options._nuxt)
}, },
@ -45,6 +46,15 @@ export default {
} }
}, },
<% } %> <% } %>
computed: {
routerViewKey () {
// If nuxtChildKey prop is given or current route has children
if (typeof this.nuxtChildKey !== 'undefined' || this.$route.matched.length > 1) {
return this.nuxtChildKey || ''
}
return this.$route.fullPath.split('#')[0]
}
},
components: { components: {
NuxtChild, NuxtChild,
NuxtError NuxtError

1
lib/app/empty.js Normal file
View File

@ -0,0 +1 @@
// This file is intentially left empty for noop aliases

View File

@ -1,37 +1,24 @@
'use strict' import 'es6-promise/auto'
import Vue from 'vue' import Vue from 'vue'
import Meta from 'vue-meta' import Meta from 'vue-meta'
import { createRouter } from './router.js' import { createRouter } from './router.js'
<% if (store) { %>import { createStore } from './store.js'<% } %>
import NuxtChild from './components/nuxt-child.js' import NuxtChild from './components/nuxt-child.js'
import NuxtLink from './components/nuxt-link.js' import NuxtLink from './components/nuxt-link.js'
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>' import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
import Nuxt from './components/nuxt.vue' import Nuxt from './components/nuxt.vue'
import App from '<%= appPath %>' import App from '<%= appPath %>'
import { getContext } from './utils' import { getContext } from './utils'
<% if (store) { %>import { createStore } from './store.js'<% } %>
if (process.browser) { <% plugins.forEach(plugin => { %>import <%= plugin.name %> from '<%= plugin.name %>'
// window.onNuxtReady(() => console.log('Ready')) hook <% }) %>
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
window._nuxtReadyCbs = []
window.onNuxtReady = function (cb) {
window._nuxtReadyCbs.push(cb)
}
}
// Import SSR plugins
<% plugins.forEach(function (plugin) { if (plugin.ssr)
{ %>let <%= plugin.name %> = require('<%= r(plugin.src) %>')
<%= plugin.name %> = <%= plugin.name %>.default || <%= plugin.name %>
<% }}) %>
// Component: <nuxt-child> // Component: <nuxt-child>
Vue.component(NuxtChild.name, NuxtChild) Vue.component(NuxtChild.name, NuxtChild)
// Component: <nuxt-link> // Component: <nuxt-link>
Vue.component(NuxtLink.name, NuxtLink) Vue.component(NuxtLink.name, NuxtLink)
// Component: <nuxt>
// Component: <nuxt>`
Vue.component(Nuxt.name, Nuxt) Vue.component(Nuxt.name, Nuxt)
// vue-meta configuration // vue-meta configuration
@ -47,37 +34,36 @@ const defaultTransition = <%=
.replace('beforeEnter(', 'function(').replace('enter(', 'function(').replace('afterEnter(', 'function(') .replace('beforeEnter(', 'function(').replace('enter(', 'function(').replace('afterEnter(', 'function(')
.replace('enterCancelled(', 'function(').replace('beforeLeave(', 'function(').replace('leave(', 'function(') .replace('enterCancelled(', 'function(').replace('beforeLeave(', 'function(').replace('leave(', 'function(')
.replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(') .replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(')
%> %>
async function createApp (ssrContext) { async function createApp (ssrContext) {
<% if (store) { %>
const store = createStore()
<% } %>
const router = createRouter() const router = createRouter()
<% if (store) { %>const store = createStore()<% } %>
if (process.server && ssrContext && ssrContext.url) { if (process.server && ssrContext && ssrContext.url) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
router.push(ssrContext.url, resolve, reject) router.push(ssrContext.url, resolve, reject)
}) })
} }
<% if (store) { %>
if (process.browser) { if (process.browser) {
<% if (store) { %>
// Replace store state before calling plugins // Replace store state before calling plugins
if (window.__NUXT__ && window.__NUXT__.state) { if (window.__NUXT__ && window.__NUXT__.state) {
store.replaceState(window.__NUXT__.state) store.replaceState(window.__NUXT__.state)
} }
<% } %>
} }
<% } %>
// root instance // Create Root instance
// here we inject the router and store to all child components, // here we inject the router and store to all child components,
// making them available everywhere as `this.$router` and `this.$store`. // making them available everywhere as `this.$router` and `this.$store`.
let app = { const app = {
router, router,
<%= (store ? 'store,' : '') %> <% if(store) { %> store,<% } %>
_nuxt: { _nuxt: {
defaultTransition: defaultTransition, defaultTransition,
transitions: [ defaultTransition ], transitions: [ defaultTransition ],
setTransitions (transitions) { setTransitions (transitions) {
if (!Array.isArray(transitions)) { if (!Array.isArray(transitions)) {
@ -103,43 +89,44 @@ async function createApp (ssrContext) {
if (typeof err === 'string') { if (typeof err === 'string') {
err = { statusCode: 500, message: err } err = { statusCode: 500, message: err }
} }
this.$options._nuxt.dateErr = Date.now() const _nuxt = this._nuxt || this.$options._nuxt
this.$options._nuxt.err = err; _nuxt.dateErr = Date.now()
_nuxt.err = err
if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined') {
console.error(err)
console.error('[nuxt] ' + err)
}
return err return err
} }
}, },
...App ...App
} }
const next = ssrContext ? ssrContext.next : location => app.router.push(location)
const ctx = getContext({ const ctx = getContext({
isServer: !!ssrContext, isServer: !!ssrContext,
isClient: !ssrContext, isClient: !ssrContext,
route: router.currentRoute, route: router.currentRoute,
<%= (store ? 'store,' : '') %> next,
error: app._nuxt.error.bind(app),
<% if(store) { %>store,<% } %>
req: ssrContext ? ssrContext.req : undefined, req: ssrContext ? ssrContext.req : undefined,
res: ssrContext ? ssrContext.res : undefined, res: ssrContext ? ssrContext.res : undefined,
}, app) }, app)
delete ctx.redirect
delete ctx.error
// Inject external plugins <% plugins.filter(p => p.ssr).forEach(plugin => { %>
<% plugins.forEach(function (plugin) { if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx)<% }) %>
if (plugin.ssr) { %> <% if (plugins.filter(p => !p.ssr).length) { %>
if (typeof <%= plugin.name %> === 'function') { if (process.browser) { <% plugins.filter(p => !p.ssr).forEach(plugin => { %>
await <%= plugin.name %>(ctx) if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx)<% }) %>
} }<% } %>
<% } else { %>
if (process.browser) {
let <%= plugin.name %> = require('<%= plugin.src %>')
<%= plugin.name %> = <%= plugin.name %>.default || <%= plugin.name %>
if (typeof <%= plugin.name %> === 'function') {
await <%= plugin.name %>(ctx)
}
}
<% }
}) %>
return { app, router<%= (store ? ', store' : '') %> } return {
app,
router,
<% if(store) { %> store <% } %>
}
} }
export { createApp, NuxtError } export { createApp, NuxtError }

View File

@ -1,5 +1,5 @@
<% if (middleware) { %> <% if (middleware) { %>
let files = require.context('~/middleware', false, /^\.\/.*\.(js|ts)$/) let files = require.context('@/middleware', false, /^\.\/.*\.(js|ts)$/)
let filenames = files.keys() let filenames = files.keys()
function getModule (filename) { function getModule (filename) {

View File

@ -1,5 +1,3 @@
'use strict'
import Vue from 'vue' import Vue from 'vue'
import Router from 'vue-router' import Router from 'vue-router'
@ -7,7 +5,7 @@ Vue.use(Router)
<% <%
function recursiveRoutes(routes, tab, components) { function recursiveRoutes(routes, tab, components) {
var res = '' let res = ''
routes.forEach((route, i) => { routes.forEach((route, i) => {
route._name = '_' + hash(route.component) route._name = '_' + hash(route.component)
components.push({ _name: route._name, component: route.component, name: route.name }) components.push({ _name: route._name, component: route.component, name: route.name })
@ -20,31 +18,30 @@ function recursiveRoutes(routes, tab, components) {
}) })
return res return res
} }
var _components = [] const _components = []
var _routes = recursiveRoutes(router.routes, '\t\t', _components) const _routes = recursiveRoutes(router.routes, '\t\t', _components)
uniqBy(_components, '_name').forEach((route) => { %> uniqBy(_components, '_name').forEach((route) => { %>const <%= route._name %> = () => import('<%= relativeToBuild(route.component) %>' /* webpackChunkName: "pages/<%= route.name %>" */).then(m => m.default || m)
const <%= route._name %> = () => import('<%= route.component %>' /* webpackChunkName: "pages/<%= route.name %>" */)
<% }) %> <% }) %>
<% if (router.scrollBehavior) { %> <% if (router.scrollBehavior) { %>
const scrollBehavior = <%= serialize(router.scrollBehavior).replace('scrollBehavior(', 'function(') %> const scrollBehavior = <%= serialize(router.scrollBehavior).replace('scrollBehavior(', 'function(') %>
<% } else { %> <% } else { %>
const scrollBehavior = (to, from, savedPosition) => { const scrollBehavior = (to, from, savedPosition) => {
// savedPosition is only available for popstate navigations. // SavedPosition is only available for popstate navigations.
if (savedPosition) { if (savedPosition) {
return savedPosition return savedPosition
} else { } else {
let position = {} let position = {}
// if no children detected // If no children detected
if (to.matched.length < 2) { if (to.matched.length < 2) {
// scroll to the top of the page // Scroll to the top of the page
position = { x: 0, y: 0 } position = { x: 0, y: 0 }
} }
else if (to.matched.some((r) => r.components.default.options.scrollToTop)) { else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
// if one of the children has scrollToTop option set to true // If one of the children has scrollToTop option set to true
position = { x: 0, y: 0 } position = { x: 0, y: 0 }
} }
// if link has anchor, scroll to anchor by returning the selector // If link has anchor, scroll to anchor by returning the selector
if (to.hash) { if (to.hash) {
position = { selector: to.hash } position = { selector: to.hash }
} }
@ -61,7 +58,8 @@ export function createRouter () {
linkExactActiveClass: '<%= router.linkExactActiveClass %>', linkExactActiveClass: '<%= router.linkExactActiveClass %>',
scrollBehavior, scrollBehavior,
routes: [ routes: [
<%= _routes %> <%= _routes %>
] ],
fallback: <%= router.fallback %>
}) })
} }

View File

@ -1,79 +1,102 @@
'use strict'
import Vue from 'vue' import Vue from 'vue'
import clone from 'clone'
import { stringify } from 'querystring' import { stringify } from 'querystring'
import { omit } from 'lodash' import { omit } from 'lodash'
import middleware from './middleware' import middleware from './middleware'
import { createApp, NuxtError } from './index' import { createApp, NuxtError } from './index'
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils' import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils'
const debug = require('debug')('nuxt:render') const debug = require('debug')('nuxt:render')
debug.color = 4 // force blue color debug.color = 4 // force blue color
const isDev = <%= isDev %> const isDev = <%= isDev %>
const noopApp = () => new Vue({ render: (h) => h('div') })
const createNext = context => opts => {
context.redirected = opts
// If nuxt generate
if (!context.res) {
context.nuxt.serverRendered = false
return
}
opts.query = stringify(opts.query)
opts.path = opts.path + (opts.query ? '?' + opts.query : '')
if (opts.path.indexOf('http') !== 0 && ('<%= router.base %>' !== '/' && opts.path.indexOf('<%= router.base %>') !== 0)) {
opts.path = urlJoin('<%= router.base %>', opts.path)
}
// Avoid loop redirect
if (opts.path === context.url) {
context.redirected = false
return
}
context.res.writeHead(opts.status, {
'Location': opts.path
})
context.res.end()
}
// This exported function will be called by `bundleRenderer`. // This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the // This is where we perform data-prefetching to determine the
// state of our application before actually rendering it. // state of our application before actually rendering it.
// Since data fetching is async, this function is expected to // Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance. // return a Promise that resolves to the app instance.
export default async (context) => { export default async context => {
// Create context.next for simulate next() of beforeEach() when wanted to redirect
context.redirected = false
context.next = createNext(context)
const { app, router<%= (store ? ', store' : '') %> } = await createApp(context) const { app, router<%= (store ? ', store' : '') %> } = await createApp(context)
const _app = new Vue(app) const _app = new Vue(app)
const _noopApp = new Vue({ render: (h) => h('div') })
<% if (store) { %>
// Add store to the context // Add store to the context
<%= (store ? 'context.store = store' : '') %> context.store = store
<% } %>
// Add route to the context // Add route to the context
context.route = router.currentRoute context.route = router.currentRoute
// Nuxt object // Nuxt object
context.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true } context.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
// create context.next for simulate next() of beforeEach() when wanted to redirect
context.redirected = false
context.next = function (opts) {
context.redirected = opts
// if nuxt generate
if (!context.res) {
context.nuxt.serverRendered = false
return
}
opts.query = stringify(opts.query)
opts.path = opts.path + (opts.query ? '?' + opts.query : '')
if (opts.path.indexOf('http') !== 0 && ('<%= router.base %>' !== '/' && opts.path.indexOf('<%= router.base %>') !== 0)) {
opts.path = urlJoin('<%= router.base %>', opts.path)
}
context.res.writeHead(opts.status, {
'Location': opts.path
})
context.res.end()
}
// Add meta infos // Add meta infos
context.meta = _app.$meta() context.meta = _app.$meta()
// Error function // Error function
context.error = _app.$options._nuxt.error.bind(_app) context.error = _app.$options._nuxt.error.bind(_app)
// Keep asyncData for each matched component in context
context.asyncData = {}
<%= (isDev ? 'const s = isDev && Date.now()' : '') %> // Create shared ctx
let ctx = getContext(context, app) const ctx = getContext(context, app)
<% if (isDev) { %>const s = isDev && Date.now()<% } %>
// Resolve components
let Components = [] let Components = []
let promises = getMatchedComponents(router.match(context.url)).map((Component) => {
return new Promise((resolve, reject) => {
if (typeof Component !== 'function' || Component.super === Vue) return resolve(sanitizeComponent(Component))
const _resolve = (Component) => resolve(sanitizeComponent(Component))
Component().then(_resolve).catch(reject)
})
})
try { try {
Components = await Promise.all(promises) Components = await Promise.all(getMatchedComponents(router.match(context.url)).map(Component => {
if (typeof Component !== 'function' || Component.super === Vue) {
return sanitizeComponent(Component)
}
return Component().then(Component => sanitizeComponent(Component))
}))
} catch (err) { } catch (err) {
// Throw back error to renderRoute() // Throw back error to renderRoute()
throw err throw err
} }
// nuxtServerInit
<% if (store) { %> <% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context, app), 'redirect', 'error')) : null) // Dispatch store nuxtServerInit
if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) promise = Promise.resolve() if (store._actions && store._actions.nuxtServerInit) {
<% } else { %> await store.dispatch('nuxtServerInit', ctx)
let promise = Promise.resolve() }
// ...If there is a redirect
if (context.redirected) return noopApp()
<% } %> <% } %>
await promise
// Call global middleware (nuxt.config.js) // Call global middleware (nuxt.config.js)
let midd = <%= serialize(router.middleware, { isJSON: true }) %> let midd = <%= serialize(router.middleware, { isJSON: true }) %>
midd = midd.map((name) => { midd = midd.map((name) => {
@ -85,32 +108,39 @@ export default async (context) => {
if (!context.nuxt.error) { if (!context.nuxt.error) {
await middlewareSeries(midd, ctx) await middlewareSeries(midd, ctx)
} }
if (context.redirected) return _noopApp // ...If there is a redirect
if (context.redirected) return noopApp()
// Set layout // Set layout
let layout = Components.length ? Components[0].options.layout : NuxtError.layout let layout = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layout === 'function') layout = layout(ctx) if (typeof layout === 'function') layout = layout(ctx)
await _app.loadLayout(layout) await _app.loadLayout(layout)
layout = _app.setLayout(layout) layout = _app.setLayout(layout)
// Set layout to __NUXT__ // ...Set layout to __NUXT__
context.nuxt.layout = _app.layoutName context.nuxt.layout = _app.layoutName
// Call middleware (layout + pages) // Call middleware (layout + pages)
midd = []
if (layout.middleware) midd = midd.concat(layout.middleware)
Components.forEach((Component) => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
}
})
midd = midd.map((name) => {
if (typeof middleware[name] !== 'function') {
context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
if (!context.nuxt.error) { if (!context.nuxt.error) {
midd = []
if (layout.middleware) midd = midd.concat(layout.middleware)
Components.forEach((Component) => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
}
})
midd = midd.map((name) => {
if (typeof middleware[name] !== 'function') {
context.nuxt.error = context.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
await middlewareSeries(midd, ctx) await middlewareSeries(midd, ctx)
// If there is a redirect
if (context.redirected) return noopApp()
} }
if (context.redirected) return _noopApp
// Call .validate() // Call .validate()
let isValid = true let isValid = true
Components.forEach((Component) => { Components.forEach((Component) => {
@ -118,10 +148,11 @@ export default async (context) => {
if (typeof Component.options.validate !== 'function') return if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({ isValid = Component.options.validate({
params: context.route.params || {}, params: context.route.params || {},
query: context.route.query || {}<%= (store ? ', store: ctx.store' : '') %> query: context.route.query || {},
<%= (store ? 'store: ctx.store' : '') %>
}) })
}) })
// If .validate() returned false // ...If .validate() returned false
if (!isValid) { if (!isValid) {
// Don't server-render the page in generate mode // Don't server-render the page in generate mode
if (context._generate) { if (context._generate) {
@ -130,52 +161,65 @@ export default async (context) => {
// Call the 404 error by making the Components array empty // Call the 404 error by making the Components array empty
Components = [] Components = []
} }
// Call asyncData & fetch hooks on components matched by the route. // Call asyncData & fetch hooks on components matched by the route.
let asyncDatas = await Promise.all(Components.map((Component) => { let asyncDatas = await Promise.all(Components.map(Component => {
let promises = [] let promises = []
// Call asyncData(context)
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
let promise = promisify(Component.options.asyncData, ctx) let promise = promisify(Component.options.asyncData, ctx)
// Call asyncData(context) promise.then(asyncDataResult => {
promise.then((asyncDataResult) => { context.asyncData[Component.options.name] = asyncDataResult
applyAsyncData(Component, asyncDataResult) applyAsyncData(Component)
return asyncDataResult return asyncDataResult
}) })
promises.push(promise) promises.push(promise)
} else promises.push(null) } else {
// call fetch(context) promises.push(null)
if (Component.options.fetch) promises.push(Component.options.fetch(ctx)) }
else promises.push(null)
// Call fetch(context)
if (Component.options.fetch) {
promises.push(Component.options.fetch(ctx))
}
else {
promises.push(null)
}
return Promise.all(promises) return Promise.all(promises)
})) }))
// If no Components found, returns 404 // If no Components found, returns 404
if (!Components.length) { if (!Components.length) {
context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.' }) context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.' })
} }
<% if (isDev) { %>
if (asyncDatas.length) debug('Data fetching ' + context.url + ': ' + (Date.now() - s) + 'ms') <% if (isDev) { %>if (asyncDatas.length) debug('Data fetching ' + context.url + ': ' + (Date.now() - s) + 'ms')<% } %>
<% } %>
// datas are the first row of each // datas are the first row of each
context.nuxt.data = asyncDatas.map((r) => (r[0] || {})) context.nuxt.data = asyncDatas.map(r => r[0] || {})
// If an error occured in the execution // If an error occured in the execution
if (_app.$options._nuxt.err) { if (_app.$options._nuxt.err) {
context.nuxt.error = _app.$options._nuxt.err context.nuxt.error = _app.$options._nuxt.err
} }
<%= (store ? '// Add the state from the vuex store' : '') %>
<%= (store ? 'context.nuxt.state = store.state' : '') %> <% if (store) { %>
// Add the state from the vuex store
context.nuxt.state = store.state
<% } %>
// If no error, return main app // If no error, return main app
if (!context.nuxt.error) { if (!context.nuxt.error) {
return _app return _app
} }
// Load layout for error page // Load layout for error page
layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout) layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout)
context.nuxt.layout = layout || '' context.nuxt.layout = layout || ''
await _app.loadLayout(layout) await _app.loadLayout(layout)
_app.setLayout(layout) _app.setLayout(layout)
return _app return _app
// if (typeof error === 'string') {
// error = { statusCode: 500, message: error }
// }
// context.nuxt.error = context.error(error)
// <%= (store ? 'context.nuxt.state = store.state' : '') %>
// return _app
} }

View File

@ -3,15 +3,20 @@ import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex)
// Recursive find files in ~/store // Recursive find files in {srcDir}/store
const files = require.context('~/store', true, /^\.\/.*\.(js|ts)$/) const files = require.context('@/store', true, /^\.\/.*\.(js|ts)$/)
const filenames = files.keys() const filenames = files.keys()
// Store // Store
let storeData = {} let storeData = {}
// Check if store/index.js exists // Check if store/index.js exists
const indexFilename = filenames.find((filename) => filename.includes('./index.')) let indexFilename
filenames.forEach((filename) => {
if (filename.indexOf('./index.') !== -1) {
indexFilename = filename
}
})
if (indexFilename) { if (indexFilename) {
storeData = getModule(indexFilename) storeData = getModule(indexFilename)
} }

View File

@ -1,12 +1,28 @@
'use strict'
import Vue from 'vue' import Vue from 'vue'
const noopData = () => ({}) const noopData = () => ({})
// window.onNuxtReady(() => console.log('Ready')) hook
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
if (process.browser) {
window._nuxtReadyCbs = []
window.onNuxtReady = function (cb) {
window._nuxtReadyCbs.push(cb)
}
}
export function applyAsyncData (Component, asyncData = {}) { export function applyAsyncData (Component, asyncData = {}) {
const ComponentData = Component.options.data || noopData const ComponentData = Component.options.data || noopData
// Prevent calling this method for each request on SSR context
if(!asyncData && Component.options.hasAsyncData) {
return
}
Component.options.hasAsyncData = true
Component.options.data = function () { Component.options.data = function () {
const data = ComponentData.call(this) const data = ComponentData.call(this)
if(this.$ssrContext) {
asyncData = this.$ssrContext.asyncData[Component.options.name]
}
return { ...data, ...asyncData } return { ...data, ...asyncData }
} }
if (Component._Ctor && Component._Ctor.options) { if (Component._Ctor && Component._Ctor.options) {

View File

@ -1,565 +0,0 @@
'use strict'
import _ from 'lodash'
import chokidar from 'chokidar'
import fs from 'fs-extra'
import hash from 'hash-sum'
import pify from 'pify'
import webpack from 'webpack'
import PostCompilePlugin from 'post-compile-webpack-plugin'
import serialize from 'serialize-javascript'
import { createBundleRenderer } from 'vue-server-renderer'
import { join, resolve, basename, dirname } from 'path'
import { isUrl, r, wp } from './utils'
import clientWebpackConfig from './webpack/client.config.js'
import serverWebpackConfig from './webpack/server.config.js'
const debug = require('debug')('nuxt:build')
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(require('glob'))
let webpackStats = 'none'
debug.color = 2 // force green color
const defaults = {
analyze: false,
extractCSS: false,
publicPath: '/_nuxt/',
filenames: {
css: 'common.[chunkhash].css',
manifest: 'manifest.[hash].js',
vendor: 'vendor.bundle.[chunkhash].js',
app: 'nuxt.bundle.[chunkhash].js'
},
vendor: [],
loaders: [],
plugins: [],
babel: {},
postcss: [],
templates: [],
watch: []
}
const defaultsLoaders = [
{
test: /\.(png|jpe?g|gif|svg)$/,
loader: 'url-loader',
query: {
limit: 1000, // 1KO
name: 'img/[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 1000, // 1 KO
name: 'fonts/[name].[hash:7].[ext]'
}
}
]
const defaultsPostcss = [
require('autoprefixer')({
browsers: ['last 3 versions']
})
]
export function options () {
// Defaults build options
let extraDefaults = {}
if (this.options.build && !Array.isArray(this.options.build.loaders)) extraDefaults.loaders = defaultsLoaders
if (this.options.build && !Array.isArray(this.options.build.postcss)) extraDefaults.postcss = defaultsPostcss
this.options.build = _.defaultsDeep(this.options.build, defaults, extraDefaults)
/* istanbul ignore if */
if (this.dev && isUrl(this.options.build.publicPath)) {
this.options.build.publicPath = defaults.publicPath
}
}
export function production () {
// Production, create server-renderer
webpackStats = {
chunks: false,
children: false,
modules: false,
colors: true
}
const serverConfig = getWebpackServerConfig.call(this)
const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
if (fs.existsSync(bundlePath) && fs.existsSync(manifestPath)) {
const bundle = fs.readFileSync(bundlePath, 'utf8')
const manifest = fs.readFileSync(manifestPath, 'utf8')
createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
addAppTemplate.call(this)
}
}
export async function build () {
// Avoid calling this method multiple times
if (this._buildDone) {
return this
}
// If building
if (this._building) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(this.build())
}, 300)
})
}
this._building = true
// Wait for Nuxt.js to be ready
await this.ready()
// Check if pages dir exists and warn if not
this._nuxtPages = typeof this.createRoutes !== 'function'
if (this._nuxtPages) {
if (!fs.existsSync(join(this.srcDir, 'pages'))) {
if (fs.existsSync(join(this.srcDir, '..', 'pages'))) {
console.error('> No `pages` directory found. Did you mean to run `nuxt` in the parent (`../`) directory?') // eslint-disable-line no-console
} else {
console.error('> Couldn\'t find a `pages` directory. Please create one under the project root') // eslint-disable-line no-console
}
process.exit(1)
}
}
debug(`App root: ${this.srcDir}`)
debug(`Generating ${this.buildDir} files...`)
// Create .nuxt/, .nuxt/components and .nuxt/dist folders
await remove(r(this.buildDir))
await mkdirp(r(this.buildDir, 'components'))
if (!this.dev) {
await mkdirp(r(this.buildDir, 'dist'))
}
// Generate routes and interpret the template files
await generateRoutesAndFiles.call(this)
// Generate .nuxt/dist/ files
await buildFiles.call(this)
// Flag to set that building is done
this._buildDone = true
return this
}
async function buildFiles () {
if (this.dev) {
debug('Adding webpack middleware...')
createWebpackMiddleware.call(this)
webpackWatchAndUpdate.call(this)
watchFiles.call(this)
} else {
debug('Building files...')
await webpackRunClient.call(this)
await webpackRunServer.call(this)
addAppTemplate.call(this)
}
}
function addAppTemplate () {
let templatePath = resolve(this.buildDir, 'dist', 'index.html')
if (fs.existsSync(templatePath)) {
this.appTemplate = _.template(fs.readFileSync(templatePath, 'utf8'), {
interpolate: /{{([\s\S]+?)}}/g
})
}
}
async function generateRoutesAndFiles () {
debug('Generating files...')
// -- Templates --
let templatesFiles = [
'App.vue',
'client.js',
'index.js',
'middleware.js',
'router.js',
'server.js',
'utils.js',
'components/nuxt-error.vue',
'components/nuxt-loading.vue',
'components/nuxt-child.js',
'components/nuxt-link.js',
'components/nuxt.vue'
]
const templateVars = {
options: this.options,
uniqBy: _.uniqBy,
isDev: this.dev,
router: {
mode: this.options.router.mode,
base: this.options.router.base,
middleware: this.options.router.middleware,
linkActiveClass: this.options.router.linkActiveClass,
linkExactActiveClass: this.options.router.linkExactActiveClass,
scrollBehavior: this.options.router.scrollBehavior
},
env: this.options.env,
head: this.options.head,
middleware: fs.existsSync(join(this.srcDir, 'middleware')),
store: this.options.store || fs.existsSync(join(this.srcDir, 'store')),
css: this.options.css,
plugins: this.options.plugins.map((p, i) => {
if (typeof p === 'string') p = { src: p }
p.src = r(this.srcDir, p.src)
return { src: p.src, ssr: (p.ssr !== false), name: `plugin${i}` }
}),
appPath: './App.vue',
layouts: Object.assign({}, this.options.layouts),
loading: (typeof this.options.loading === 'string' ? r(this.srcDir, this.options.loading) : this.options.loading),
transition: this.options.transition,
components: {
ErrorPage: this.options.ErrorPage ? r(this.options.ErrorPage) : null
}
}
// -- Layouts --
if (fs.existsSync(resolve(this.srcDir, 'layouts'))) {
const layoutsFiles = await glob('layouts/*.vue', {cwd: this.srcDir})
layoutsFiles.forEach((file) => {
let name = file.split('/').slice(-1)[0].replace('.vue', '')
if (name === 'error') return
templateVars.layouts[name] = r(this.srcDir, file)
})
if (layoutsFiles.includes('layouts/error.vue')) {
templateVars.components.ErrorPage = r(this.srcDir, 'layouts/error.vue')
}
}
// If no default layout, create its folder and add the default folder
if (!templateVars.layouts.default) {
await mkdirp(r(this.buildDir, 'layouts'))
templatesFiles.push('layouts/default.vue')
templateVars.layouts.default = r(__dirname, 'app', 'layouts', 'default.vue')
}
// -- Routes --
debug('Generating routes...')
// If user defined a custom method to create routes
if (this._nuxtPages) {
// Use nuxt.js createRoutes bases on pages/
const files = await glob('pages/**/*.vue', {cwd: this.srcDir})
templateVars.router.routes = createRoutes(files, this.srcDir)
} else {
templateVars.router.routes = this.createRoutes(this.srcDir)
}
// router.extendRoutes method
if (typeof this.options.router.extendRoutes === 'function') {
// let the user extend the routes
this.options.router.extendRoutes.call(this, templateVars.router.routes || [], r)
}
// Routes for generate command
this.routes = flatRoutes(templateVars.router.routes || [])
// -- Store --
// Add store if needed
if (this.options.store) {
templatesFiles.push('store.js')
}
// Resolve template files
const customTemplateFiles = this.options.build.templates.map(t => t.dst || basename(t.src || t))
templatesFiles = templatesFiles.map(file => {
// Skip if custom file was already provided in build.templates[]
if (customTemplateFiles.indexOf(file) !== -1) {
return
}
// Allow override templates using a file with same name in ${srcDir}/app
const customPath = r(this.srcDir, 'app', file)
const customFileExists = fs.existsSync(customPath)
return {
src: customFileExists ? customPath : r(__dirname, 'app', file),
dst: file,
custom: customFileExists
}
}).filter(i => !!i)
// -- Custom templates --
// Add custom template files
templatesFiles = templatesFiles.concat(this.options.build.templates.map(t => {
return Object.assign({
src: r(this.dir, t.src || t),
dst: t.dst || basename(t.src || t),
custom: true
}, t)
}))
// Interpret and move template files to .nuxt/
return Promise.all(templatesFiles.map(async ({ src, dst, options, custom }) => {
// Add template to watchers
this.options.build.watch.push(src)
// Render template to dst
const fileContent = await readFile(src, 'utf8')
const template = _.template(fileContent, {
imports: {
serialize,
hash,
r,
wp
}
})
const content = template(Object.assign({}, templateVars, {
options: options || {},
custom,
src,
dst
}))
const path = r(this.buildDir, dst)
// Ensure parent dir exits
await mkdirp(dirname(path))
// Write file
await writeFile(path, content, 'utf8')
// Fix webpack loop (https://github.com/webpack/watchpack/issues/25#issuecomment-287789288)
const dateFS = Date.now() / 1000 - 30
return utimes(path, dateFS, dateFS)
}))
}
function createRoutes (files, srcDir) {
let routes = []
files.forEach((file) => {
let keys = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1)
let route = { name: '', path: '', component: r(srcDir, file) }
let parent = routes
keys.forEach((key, i) => {
route.name = route.name ? route.name + '-' + key.replace('_', '') : key.replace('_', '')
route.name += (key === '_') ? 'all' : ''
let child = _.find(parent, { name: route.name })
if (child) {
if (!child.children) {
child.children = []
}
parent = child.children
route.path = ''
} else {
if (key === 'index' && (i + 1) === keys.length) {
route.path += (i > 0 ? '' : '/')
} else {
route.path += '/' + (key === '_' ? '*' : key.replace('_', ':'))
if (key !== '_' && key.indexOf('_') !== -1) {
route.path += '?'
}
}
}
})
// Order Routes path
parent.push(route)
parent.sort((a, b) => {
if (!a.path.length || a.path === '/') { return -1 }
if (!b.path.length || b.path === '/') { return 1 }
var res = 0
var _a = a.path.split('/')
var _b = b.path.split('/')
for (var i = 0; i < _a.length; i++) {
if (res !== 0) { break }
var y = (_a[i].indexOf('*') > -1) ? 2 : (_a[i].indexOf(':') > -1 ? 1 : 0)
var z = (_b[i].indexOf('*') > -1) ? 2 : (_b[i].indexOf(':') > -1 ? 1 : 0)
res = y - z
if (i === _b.length - 1 && res === 0) {
res = 1
}
}
return res === 0 ? -1 : res
})
})
return cleanChildrenRoutes(routes)
}
function cleanChildrenRoutes (routes, isChild = false) {
let start = -1
let routesIndex = []
routes.forEach((route) => {
if (/-index$/.test(route.name) || route.name === 'index') {
// Save indexOf 'index' key in name
let res = route.name.split('-')
let s = res.indexOf('index')
start = (start === -1 || s < start) ? s : start
routesIndex.push(res)
}
})
routes.forEach((route) => {
route.path = (isChild) ? route.path.replace('/', '') : route.path
if (route.path.indexOf('?') > -1) {
let names = route.name.split('-')
let paths = route.path.split('/')
if (!isChild) { paths.shift() } // clean first / for parents
routesIndex.forEach((r) => {
let i = r.indexOf('index') - start // children names
if (i < paths.length) {
for (var a = 0; a <= i; a++) {
if (a === i) { paths[a] = paths[a].replace('?', '') }
if (a < i && names[a] !== r[a]) { break }
}
}
})
route.path = (isChild ? '' : '/') + paths.join('/')
}
route.name = route.name.replace(/-index$/, '')
if (route.children) {
if (route.children.find((child) => child.path === '')) {
delete route.name
}
route.children = cleanChildrenRoutes(route.children, true)
}
})
return routes
}
function flatRoutes (router, path = '', routes = []) {
router.forEach((r) => {
if (!r.path.includes(':') && !r.path.includes('*')) {
if (r.children) {
flatRoutes(r.children, path + r.path + '/', routes)
} else {
routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path)
}
}
})
return routes
}
function getWebpackClientConfig () {
return clientWebpackConfig.call(this)
}
function getWebpackServerConfig () {
return serverWebpackConfig.call(this)
}
function createWebpackMiddleware () {
const clientConfig = getWebpackClientConfig.call(this)
const host = process.env.HOST || process.env.npm_package_config_nuxt_host || '127.0.0.1'
const port = process.env.PORT || process.env.npm_package_config_nuxt_port || '3000'
// setup on the fly compilation + hot-reload
clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
clientConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new PostCompilePlugin(stats => {
if (!stats.hasErrors() && !stats.hasWarnings()) {
console.log(`> Open http://${host}:${port}\n`) // eslint-disable-line no-console
}
})
)
const clientCompiler = webpack(clientConfig)
this.clientCompiler = clientCompiler
// Add the middleware to the instance context
this.webpackDevMiddleware = pify(require('webpack-dev-middleware')(clientCompiler, {
publicPath: clientConfig.output.publicPath,
stats: webpackStats,
quiet: true,
noInfo: true,
watchOptions: this.options.watchers.webpack
}))
this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler, {
log: () => {}
}))
clientCompiler.plugin('done', () => {
const fs = this.webpackDevMiddleware.fileSystem
const filePath = join(clientConfig.output.path, 'index.html')
if (fs.existsSync(filePath)) {
const template = fs.readFileSync(filePath, 'utf-8')
this.appTemplate = _.template(template, {
interpolate: /{{([\s\S]+?)}}/g
})
}
this.watchHandler()
})
}
function webpackWatchAndUpdate () {
const MFS = require('memory-fs') // <- dependencies of webpack
const serverFS = new MFS()
const clientFS = this.clientCompiler.outputFileSystem
const serverConfig = getWebpackServerConfig.call(this)
const serverCompiler = webpack(serverConfig)
const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
serverCompiler.outputFileSystem = serverFS
const watchHandler = (err) => {
if (err) throw err
const bundleExists = serverFS.existsSync(bundlePath)
const manifestExists = clientFS.existsSync(manifestPath)
if (bundleExists && manifestExists) {
const bundle = serverFS.readFileSync(bundlePath, 'utf8')
const manifest = clientFS.readFileSync(manifestPath, 'utf8')
createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
}
}
this.watchHandler = watchHandler
this.webpackServerWatcher = serverCompiler.watch(this.options.watchers.webpack, watchHandler)
}
function webpackRunClient () {
return new Promise((resolve, reject) => {
const clientConfig = getWebpackClientConfig.call(this)
const clientCompiler = webpack(clientConfig)
clientCompiler.run((err, stats) => {
if (err) return reject(err)
console.log('[nuxt:build:client]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
resolve()
})
})
}
function webpackRunServer () {
return new Promise((resolve, reject) => {
const serverConfig = getWebpackServerConfig.call(this)
const serverCompiler = webpack(serverConfig)
serverCompiler.run((err, stats) => {
if (err) return reject(err)
console.log('[nuxt:build:server]\n', stats.toString(webpackStats)) // eslint-disable-line no-console
if (stats.hasErrors()) return reject(new Error('Webpack build exited with errors'))
const bundlePath = join(serverConfig.output.path, 'server-bundle.json')
const manifestPath = join(serverConfig.output.path, 'client-manifest.json')
readFile(bundlePath, 'utf8')
.then(bundle => {
readFile(manifestPath, 'utf8')
.then(manifest => {
createRenderer.call(this, JSON.parse(bundle), JSON.parse(manifest))
resolve()
})
})
})
})
}
function createRenderer (bundle, manifest) {
// Create bundle renderer to give a fresh context for every request
this.renderer = createBundleRenderer(bundle, Object.assign({
clientManifest: manifest,
runInNewContext: false,
basedir: this.dir
}, this.options.build.ssr))
this.renderToString = pify(this.renderer.renderToString)
this.renderToStream = this.renderer.renderToStream
}
function watchFiles () {
const patterns = [
r(this.srcDir, 'layouts'),
r(this.srcDir, 'store'),
r(this.srcDir, 'middleware'),
r(this.srcDir, 'layouts/*.vue'),
r(this.srcDir, 'layouts/**/*.vue')
]
if (this._nuxtPages) {
patterns.push(r(this.srcDir, 'pages'))
patterns.push(r(this.srcDir, 'pages/*.vue'))
patterns.push(r(this.srcDir, 'pages/**/*.vue'))
}
const options = Object.assign({}, this.options.watchers.chokidar, {
ignoreInitial: true
})
/* istanbul ignore next */
const refreshFiles = _.debounce(async () => {
await generateRoutesAndFiles.call(this)
}, 200)
// Watch for internals
this.filesWatcher = chokidar.watch(patterns, options)
.on('add', refreshFiles)
.on('unlink', refreshFiles)
// Watch for custom provided files
this.customFilesWatcher = chokidar.watch(_.uniq(this.options.build.watch), options)
.on('change', refreshFiles)
}

460
lib/builder/builder.js Normal file
View File

@ -0,0 +1,460 @@
import _ from 'lodash'
import chokidar from 'chokidar'
import fs, { remove, readFile, writeFile, mkdirp, utimes } from 'fs-extra'
import hash from 'hash-sum'
import pify from 'pify'
import webpack from 'webpack'
import serialize from 'serialize-javascript'
import { join, resolve, basename, dirname } from 'path'
import Tapable from 'tappable'
import MFS from 'memory-fs'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
import { r, wp, createRoutes, parallel, relativeTo } from 'utils'
import Debug from 'debug'
import Glob from 'glob'
import clientWebpackConfig from './webpack/client.config.js'
import serverWebpackConfig from './webpack/server.config.js'
const debug = Debug('nuxt:build')
debug.color = 2 // Force green color
const glob = pify(Glob)
export default class Builder extends Tapable {
constructor (nuxt) {
super()
this.nuxt = nuxt
this.options = nuxt.options
// Fields that set on build
this.compiler = null
this.webpackDevMiddleware = null
this.webpackHotMiddleware = null
// Mute stats on dev
this.webpackStats = this.options.dev ? false : {
chunks: false,
children: false,
modules: false,
colors: true
}
// Helper to resolve build paths
this.relativeToBuild = (...args) => relativeTo(this.options.buildDir, ...args)
this._buildStatus = STATUS.INITIAL
}
get plugins () {
return this.options.plugins.map((p, i) => {
if (typeof p === 'string') p = { src: p }
p.src = r(this.options.srcDir, p.src)
return { src: p.src, ssr: (p.ssr !== false), name: `plugin${i}` }
})
}
async build () {
// Avoid calling build() method multiple times when dev:true
/* istanbul ignore if */
if (this._buildStatus === STATUS.BUILD_DONE && this.options.dev) {
return this
}
// If building
/* istanbul ignore if */
if (this._buildStatus === STATUS.BUILDING) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(this.build())
}, 1000)
})
}
this._buildStatus = STATUS.BUILDING
// Wait for nuxt ready
await this.nuxt.ready()
await this.nuxt.applyPluginsAsync('build', this)
// Check if pages dir exists and warn if not
this._nuxtPages = typeof this.options.build.createRoutes !== 'function'
if (this._nuxtPages) {
if (!fs.existsSync(join(this.options.srcDir, 'pages'))) {
let dir = this.options.srcDir
if (fs.existsSync(join(this.options.srcDir, '..', 'pages'))) {
throw new Error(`No \`pages\` directory found in ${dir}. Did you mean to run \`nuxt\` in the parent (\`../\`) directory?`)
} else {
throw new Error(`Couldn't find a \`pages\` directory in ${dir}. Please create one under the project root`)
}
}
}
debug(`App root: ${this.options.srcDir}`)
debug(`Generating ${this.options.buildDir} files...`)
// Create .nuxt/, .nuxt/components and .nuxt/dist folders
await remove(r(this.options.buildDir))
await mkdirp(r(this.options.buildDir, 'components'))
if (!this.options.dev) {
await mkdirp(r(this.options.buildDir, 'dist'))
}
// Generate routes and interpret the template files
await this.generateRoutesAndFiles()
// Start webpack build
await this.webpackBuild()
await this.applyPluginsAsync('built', this)
// Flag to set that building is done
this._buildStatus = STATUS.BUILD_DONE
return this
}
async generateRoutesAndFiles () {
debug('Generating files...')
// -- Templates --
let templatesFiles = [
'App.vue',
'client.js',
'index.js',
'middleware.js',
'router.js',
'server.js',
'utils.js',
'empty.js',
'components/nuxt-error.vue',
'components/nuxt-loading.vue',
'components/nuxt-child.js',
'components/nuxt-link.js',
'components/nuxt.vue',
'views/app.template.html',
'views/error.html'
]
const templateVars = {
options: this.options,
uniqBy: _.uniqBy,
isDev: this.options.dev,
router: this.options.router,
env: this.options.env,
head: this.options.head,
middleware: fs.existsSync(join(this.options.srcDir, 'middleware')),
store: this.options.store,
css: this.options.css,
plugins: this.plugins,
appPath: './App.vue',
layouts: Object.assign({}, this.options.layouts),
loading: typeof this.options.loading === 'string' ? this.relativeToBuild(this.options.srcDir, this.options.loading) : this.options.loading,
transition: this.options.transition,
components: {
ErrorPage: this.options.ErrorPage ? this.relativeToBuild(this.options.ErrorPage) : null
}
}
// -- Layouts --
if (fs.existsSync(resolve(this.options.srcDir, 'layouts'))) {
const layoutsFiles = await glob('layouts/*.vue', { cwd: this.options.srcDir })
layoutsFiles.forEach((file) => {
let name = file.split('/').slice(-1)[0].replace('.vue', '')
if (name === 'error') return
templateVars.layouts[name] = this.relativeToBuild(this.options.srcDir, file)
})
if (layoutsFiles.includes('layouts/error.vue') && !templateVars.components.ErrorPage) {
templateVars.components.ErrorPage = this.relativeToBuild(this.options.srcDir, 'layouts/error.vue')
}
}
// If no default layout, create its folder and add the default folder
if (!templateVars.layouts.default) {
await mkdirp(r(this.options.buildDir, 'layouts'))
templatesFiles.push('layouts/default.vue')
templateVars.layouts.default = './layouts/default.vue'
}
// -- Routes --
debug('Generating routes...')
// If user defined a custom method to create routes
if (this._nuxtPages) {
// Use nuxt.js createRoutes bases on pages/
const files = await glob('pages/**/*.vue', { cwd: this.options.srcDir })
templateVars.router.routes = createRoutes(files, this.options.srcDir)
} else {
templateVars.router.routes = this.options.build.createRoutes(this.options.srcDir)
}
await this.applyPluginsAsync('extendRoutes', {routes: templateVars.router.routes, templateVars, r})
// router.extendRoutes method
if (typeof this.options.router.extendRoutes === 'function') {
// let the user extend the routes
this.options.router.extendRoutes(templateVars.router.routes, r)
}
// -- Store --
// Add store if needed
if (this.options.store) {
templatesFiles.push('store.js')
}
// Resolve template files
const customTemplateFiles = this.options.build.templates.map(t => t.dst || basename(t.src || t))
templatesFiles = templatesFiles.map(file => {
// Skip if custom file was already provided in build.templates[]
if (customTemplateFiles.indexOf(file) !== -1) {
return
}
// Allow override templates using a file with same name in ${srcDir}/app
const customPath = r(this.options.srcDir, 'app', file)
const customFileExists = fs.existsSync(customPath)
return {
src: customFileExists
? customPath
: r(this.options.nuxtAppDir, file),
dst: file,
custom: customFileExists
}
}).filter(i => !!i)
// -- Custom templates --
// Add custom template files
templatesFiles = templatesFiles.concat(this.options.build.templates.map(t => {
return Object.assign({
src: r(this.options.srcDir, t.src || t),
dst: t.dst || basename(t.src || t),
custom: true
}, t)
}))
await this.applyPluginsAsync('generate', { builder: this, templatesFiles, templateVars })
// Interpret and move template files to .nuxt/
await Promise.all(templatesFiles.map(async ({ src, dst, options, custom }) => {
// Add template to watchers
this.options.build.watch.push(src)
// Render template to dst
const fileContent = await readFile(src, 'utf8')
const template = _.template(fileContent, {
imports: {
serialize,
hash,
r,
wp,
relativeToBuild: this.relativeToBuild
}
})
const content = template(Object.assign({}, templateVars, {
options: options || {},
custom,
src,
dst
}))
const path = r(this.options.buildDir, dst)
// Ensure parent dir exits
await mkdirp(dirname(path))
// Write file
await writeFile(path, content, 'utf8')
// Fix webpack loop (https://github.com/webpack/watchpack/issues/25#issuecomment-287789288)
const dateFS = Date.now() / 1000 - 1000
return utimes(path, dateFS, dateFS)
}))
await this.applyPluginsAsync('generated', this)
}
async webpackBuild () {
debug('Building files...')
const compilersOptions = []
// Client
const clientConfig = clientWebpackConfig.call(this)
compilersOptions.push(clientConfig)
// Server
const serverConfig = serverWebpackConfig.call(this)
if (this.options.build.ssr) {
compilersOptions.push(serverConfig)
}
// Alias plugins to their real path
this.plugins.forEach(p => {
const src = this.relativeToBuild(p.src)
// Client config
if (!clientConfig.resolve.alias[p.name]) {
clientConfig.resolve.alias[p.name] = src
}
// Server config
if (!serverConfig.resolve.alias[p.name]) {
// Alias to noop for ssr:false plugins
serverConfig.resolve.alias[p.name] = p.ssr ? src : './empty.js'
}
})
// Simulate webpack multi compiler interface
// Separate compilers are simpler, safer and faster
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) {
this.compiler[compiler.name] = compiler
}
})
// Run after each compile
this.compiler.plugin('done', async stats => {
// Don't reload failed builds
/* istanbul ignore if */
if (stats.hasErrors() || stats.hasWarnings()) {
return
}
// Reload renderer if available
if (this.nuxt.renderer) {
this.nuxt.renderer.loadResources(sharedFS || fs)
}
await this.applyPluginsAsync('done', { builder: this, stats })
})
// Add dev Stuff
if (this.options.dev) {
this.webpackDev()
}
await this.applyPluginsAsync('compile', { builder: this, compiler: this.compiler })
// Start Builds
await parallel(this.compiler.compilers, compiler => new Promise((resolve, reject) => {
if (this.options.dev) {
// --- Dev Build ---
if (compiler.options.name === 'client') {
// Client watch is started by dev-middleware
resolve()
} else {
// Build and watch for changes
compiler.watch(this.options.watchers.webpack, (err) => {
/* istanbul ignore if */
if (err) {
return reject(err)
}
resolve()
})
}
} else {
// --- Production Build ---
compiler.run((err, stats) => {
/* istanbul ignore if */
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
/* istanbul ignore if */
if (stats.hasErrors()) {
return reject(new Error('Webpack build exited with errors'))
}
resolve()
})
}
}))
await this.applyPluginsAsync('compiled', this)
}
webpackDev () {
debug('Adding webpack middleware...')
// Create webpack dev middleware
this.webpackDevMiddleware = pify(webpackDevMiddleware(this.compiler.client, Object.assign({
publicPath: this.options.build.publicPath,
stats: this.webpackStats,
noInfo: true,
quiet: true,
watchOptions: this.options.watchers.webpack
}, this.options.build.devMiddleware)))
this.webpackHotMiddleware = pify(webpackHotMiddleware(this.compiler.client, Object.assign({
log: false,
heartbeat: 2500
}, this.options.build.hotMiddleware)))
// 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())
}))
// Start watching files
this.watchFiles()
}
watchFiles () {
const patterns = [
r(this.options.srcDir, 'layouts'),
r(this.options.srcDir, 'store'),
r(this.options.srcDir, 'middleware'),
r(this.options.srcDir, 'layouts/*.vue'),
r(this.options.srcDir, 'layouts/**/*.vue')
]
if (this._nuxtPages) {
patterns.push(r(this.options.srcDir, 'pages'))
patterns.push(r(this.options.srcDir, 'pages/*.vue'))
patterns.push(r(this.options.srcDir, 'pages/**/*.vue'))
}
const options = Object.assign({}, this.options.watchers.chokidar, {
ignoreInitial: true
})
/* istanbul ignore next */
const refreshFiles = _.debounce(() => this.generateRoutesAndFiles(), 200)
// Watch for src Files
let filesWatcher = chokidar.watch(patterns, options)
.on('add', refreshFiles)
.on('unlink', refreshFiles)
// Watch for custom provided files
let customFilesWatcher = chokidar.watch(_.uniq(this.options.build.watch), options)
.on('change', refreshFiles)
// Stop watching on nuxt.close()
this.nuxt.plugin('close', () => {
filesWatcher.close()
customFilesWatcher.close()
})
}
}
const STATUS = {
INITIAL: 1,
BUILD_DONE: 2,
BUILDING: 3
}

169
lib/builder/generator.js Normal file
View File

@ -0,0 +1,169 @@
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'
import Tapable from 'tappable'
import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils'
import Debug from 'debug'
const debug = Debug('nuxt:generate')
export default class Generator extends Tapable {
constructor (nuxt, builder) {
super()
this.nuxt = nuxt
this.options = nuxt.options
this.builder = builder
// Set variables
this.generateRoutes = resolve(this.options.srcDir, 'static')
this.srcBuiltPath = resolve(this.options.buildDir, 'dist')
this.distPath = resolve(this.options.rootDir, this.options.generate.dir)
this.distNuxtPath = join(this.distPath, (isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath))
}
async generate ({ build = true, init = true } = {}) {
const s = Date.now()
let errors = []
// Wait for nuxt be ready
await this.nuxt.ready()
// Start build process
if (this.builder && build) {
await this.builder.build()
}
await this.nuxt.applyPluginsAsync('generate', this)
// Initialize dist directory
if (init) {
await this.initDist()
}
// Resolve config.generate.routes promises before generating the routes
let generateRoutes = []
if (this.options.router.mode !== 'hash') {
try {
console.log('Generating routes') // eslint-disable-line no-console
generateRoutes = await promisifyRoute(this.options.generate.routes || [])
await this.applyPluginsAsync('generateRoutes', {generator: this, generateRoutes})
} catch (e) {
console.error('Could not resolve routes') // eslint-disable-line no-console
console.error(e) // eslint-disable-line no-console
throw e // eslint-disable-line no-unreachable
}
}
// Generate only index.html for router.mode = 'hash'
let routes = (this.options.router.mode === 'hash') ? ['/'] : flatRoutes(this.options.router.routes)
routes = this.decorateWithPayloads(routes, generateRoutes)
await this.applyPluginsAsync('generate', {generator: this, routes})
// Start generate process
while (routes.length) {
let n = 0
await Promise.all(routes.splice(0, this.options.generate.concurrency).map(async ({ route, payload }) => {
await waitFor(n++ * this.options.generate.interval)
await this.generateRoute({route, payload, errors})
}))
}
const duration = Math.round((Date.now() - s) / 100) / 10
debug(`HTML Files generated in ${duration}s`)
if (errors.length) {
const report = errors.map(({ type, route, error }) => {
/* istanbul ignore if */
if (type === 'unhandled') {
return `Route: '${route}'\n${error.stack}`
} else {
return `Route: '${route}' thrown an error: \n` + JSON.stringify(error)
}
})
console.error('==== Error report ==== \n' + report.join('\n\n')) // eslint-disable-line no-console
}
await this.applyPluginsAsync('generated', this)
return { duration, errors }
}
async initDist () {
// Clean destination folder
await remove(this.distPath)
debug('Destination folder cleaned')
// Copy static and built files
/* istanbul ignore if */
if (fs.existsSync(this.generateRoutes)) {
await copy(this.generateRoutes, this.distPath)
}
await copy(this.srcBuiltPath, this.distNuxtPath)
// Add .nojekyll file to let Github Pages add the _nuxt/ folder
// https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/
const nojekyllPath = resolve(this.distPath, '.nojekyll')
writeFile(nojekyllPath, '')
debug('Static & build files copied')
}
decorateWithPayloads (routes, generateRoutes) {
let routeMap = {}
// Fill routeMap for known routes
routes.forEach((route) => {
routeMap[route] = {
route,
payload: null
}
})
// Fill routeMap with given generate.routes
generateRoutes.forEach((route) => {
// route is either a string or like {route : "/my_route/1"}
const path = _.isString(route) ? route : route.route
routeMap[path] = {
route: path,
payload: route.payload || null
}
})
return _.values(routeMap)
}
async generateRoute ({route, payload = {}, errors = []}) {
let html
try {
const res = await this.nuxt.renderer.renderRoute(route, { _generate: true, payload })
html = res.html
if (res.error) {
errors.push({ type: 'handled', route, error: res.error })
}
} catch (err) {
/* istanbul ignore next */
return errors.push({ type: 'unhandled', route, error: err })
}
if (this.options.generate.minify) {
try {
html = minify(html, this.options.generate.minify)
} catch (err) /* istanbul ignore next */ {
const minifyErr = new Error(`HTML minification failed. Make sure the route generates valid HTML. Failed HTML:\n ${html}`)
errors.push({ type: 'unhandled', route, error: minifyErr })
}
}
let path = join(route, sep, 'index.html') // /about -> /about/index.html
path = (path === '/404/index.html') ? '/404.html' : path // /404 -> /404.html
debug('Generate file: ' + path)
path = join(this.distPath, path)
// Make sure the sub folders are created
await mkdirp(dirname(path))
await writeFile(path, html, 'utf8')
return true
}
}

7
lib/builder/index.js Executable file
View File

@ -0,0 +1,7 @@
import Builder from './builder'
import Generator from './generator'
export default {
Builder,
Generator
}

View File

@ -0,0 +1,145 @@
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import { defaults, cloneDeep } from 'lodash'
import { join, resolve } from 'path'
import webpack from 'webpack'
import { isUrl, urlJoin } from 'utils'
import autoprefixer from 'autoprefixer'
import vueLoaderConfig from './vue-loader.config'
import { styleLoader, extractStyles } from './helpers'
/*
|--------------------------------------------------------------------------
| Webpack Shared Config
|
| This is the config which is extended by the server and client
| webpack config files
|--------------------------------------------------------------------------
*/
export default function webpackBaseConfig ({ isClient, isServer }) {
const nodeModulesDir = join(__dirname, '..', 'node_modules')
/* istanbul ignore if */
if (!Array.isArray(this.options.build.postcss)) {
this.options.build.postcss = [
autoprefixer({
browsers: ['last 3 versions']
})
]
}
const config = {
devtool: this.options.dev ? 'cheap-module-source-map' : 'nosources-source-map',
entry: {
vendor: ['vue', 'vue-router', 'vue-meta']
},
output: {
path: resolve(this.options.buildDir, 'dist'),
filename: this.options.build.filenames.app,
publicPath: (isUrl(this.options.build.publicPath)
? this.options.build.publicPath
: urlJoin(this.options.router.base, this.options.build.publicPath))
},
performance: {
maxEntrypointSize: 1000000,
maxAssetSize: 300000,
hints: this.options.dev ? false : 'warning'
},
resolve: {
extensions: ['.js', '.json', '.vue', '.ts'],
alias: {
'~': join(this.options.srcDir),
'~~': join(this.options.rootDir),
'@': join(this.options.srcDir),
'@@': join(this.options.rootDir),
'static': join(this.options.srcDir, 'static'), // use in template with <img src="~static/nuxt.png" />
'assets': join(this.options.srcDir, 'assets') // use in template with <img src="~assets/nuxt.png" />
},
modules: [
join(this.options.rootDir, 'node_modules'),
nodeModulesDir
]
},
resolveLoader: {
modules: [
join(this.options.rootDir, 'node_modules'),
nodeModulesDir
]
},
module: {
noParse: /es6-promise\.js$/, // avoid webpack shimming process
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
query: vueLoaderConfig.call(this, { isClient, isServer })
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: defaults(this.options.build.babel, {
presets: ['vue-app'],
babelrc: false,
cacheDirectory: !!this.options.dev
})
},
{ test: /\.css$/, use: styleLoader.call(this, 'css') },
{ test: /\.less$/, use: styleLoader.call(this, 'less', 'less-loader') },
{ test: /\.sass$/, use: styleLoader.call(this, 'sass', 'sass-loader?indentedSyntax&sourceMap') },
{ test: /\.scss$/, use: styleLoader.call(this, 'sass', 'sass-loader?sourceMap') },
{ test: /\.styl(us)?$/, use: styleLoader.call(this, 'stylus', 'stylus-loader') },
{
test: /\.(png|jpe?g|gif|svg)$/,
loader: 'url-loader',
query: {
limit: 1000, // 1KO
name: 'img/[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 1000, // 1 KO
name: 'fonts/[name].[hash:7].[ext]'
}
}
]
},
plugins: this.options.build.plugins
}
// CSS extraction
if (extractStyles.call(this)) {
config.plugins.push(
new ExtractTextPlugin({ filename: this.options.build.filenames.css })
)
}
// --------------------------------------
// Dev specific config
// --------------------------------------
if (this.options.dev) {
//
}
// --------------------------------------
// Production specific config
// --------------------------------------
if (!this.options.dev) {
// This is needed in webpack 2 for minify CSS
config.plugins.push(
new webpack.LoaderOptionsPlugin({
minimize: true
})
)
// Scope Hoisting
// config.plugins.push(
// new webpack.optimize.ModuleConcatenationPlugin()
// )
}
// Clone deep avoid leaking config between Client and Server
return cloneDeep(config)
}

Some files were not shown because too many files have changed in this diff Show More