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
*.iml
.idea
# Macos
.DS_Store

View File

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

View File

@ -15,7 +15,7 @@
</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:
@ -98,6 +98,7 @@ Support us with a monthly donation and help us continue our activities. [[Become
- 📘 Documentation: [https://nuxtjs.org](https://nuxtjs.org)
- 🎬 Video: [1 minute demo](https://www.youtube.com/watch?v=kmf-p-pTi40)
- 🐦 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)
## Getting started
@ -170,13 +171,8 @@ const Nuxt = require('nuxt')
// Launch nuxt build with given options
let config = require('./nuxt.config.js')
let nuxt = new Nuxt(config)
nuxt.build()
.then(() => {
// You can use nuxt.render(req, res) or nuxt.renderRoute(route, context)
})
.catch((e) => {
// An error happened during the build
})
// You can use nuxt.render(req, res) or nuxt.renderRoute(route, context)
```
Learn more: https://nuxtjs.org/api/nuxt

View File

@ -1,9 +1,9 @@
#!/usr/bin/env node
var join = require('path').join
const join = require('path').join
var defaultCommand = 'dev'
var commands = new Set([
const defaultCommand = 'dev'
const commands = new Set([
defaultCommand,
'init',
'build',
@ -19,6 +19,13 @@ if (commands.has(cmd)) {
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)

View File

@ -1,61 +1,71 @@
#!/usr/bin/env node
// Show logs
process.env.DEBUG = 'nuxt:*'
process.env.DEBUG = process.env.DEBUG || 'nuxt:*'
var fs = require('fs')
var without = require('lodash').without
var Nuxt = require('../')
var resolve = require('path').resolve
const fs = require('fs')
const parseArgs = require('minimist')
const { Nuxt, Builder } = require('../')
const resolve = require('path').resolve
const debug = require('debug')('nuxt:build')
debug.color = 2 // Force green color
// --analyze option
var analyzeBuild = false
if (process.argv.indexOf('--analyze') !== -1 || process.argv.indexOf('-a') !== -1) {
analyzeBuild = true
process.argv = without(process.argv, '--analyze', '-a')
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
c: 'config-file',
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'
// --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)
const rootDir = resolve(argv._[0] || '.')
const nuxtConfigFile = resolve(rootDir, argv['config-file'])
var options = {}
if (fs.existsSync(nuxtConfigFilePath)) {
options = require(nuxtConfigFilePath)
} else {
console.log(`Could not locate ${nuxtConfigFilePath}`) // eslint-disable-line no-console
if (fs.existsSync(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
}
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 || {}
if (analyzeBuild) {
options.build.analyze = analyzeBuild
if (argv.analyze) {
options.build.analyze = true
}
console.log('[nuxt] Building...') // eslint-disable-line no-console
var nuxt = module.exports = new Nuxt(options)
nuxt.build()
debug('Building...')
const nuxt = new Nuxt(options)
const builder = new Builder(nuxt)
builder.build()
.then(() => {
console.log('[nuxt] Building done') // eslint-disable-line no-console
debug('Building done')
})
.catch((err) => {
console.error(err) // eslint-disable-line no-console

View File

@ -1,80 +1,113 @@
#!/usr/bin/env node
// Show logs
process.env.DEBUG = 'nuxt:*'
process.env.DEBUG = process.env.DEBUG || 'nuxt:*'
var _ = require('lodash')
var debug = require('debug')('nuxt:build')
const _ = require('lodash')
const debug = require('debug')('nuxt:build')
debug.color = 2 // force green color
var fs = require('fs')
var Nuxt = require('../')
var chokidar = require('chokidar')
var resolve = require('path').resolve
var without = require('lodash').without
const fs = require('fs')
const parseArgs = require('minimist')
const { Nuxt, Builder } = require('../')
const chokidar = require('chokidar')
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
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 (argv.hostname === '') {
console.error(`> Provided hostname argument has no value`)
process.exit(1)
}
if (indexOfConfig !== false) {
nuxtConfigFileName = process.argv.slice(indexOfConfig)[1]
process.argv = without(process.argv, '--config-file', '-c', nuxtConfigFileName)
if (argv.help) {
console.log(`
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] || '.')
var nuxtConfigFile = resolve(rootDir, nuxtConfigFileName)
const rootDir = resolve(argv._[0] || '.')
const nuxtConfigFile = resolve(rootDir, argv['config-file'])
var options = {}
if (fs.existsSync(nuxtConfigFile)) {
options = require(nuxtConfigFile)
}
if (typeof options.rootDir !== 'string') {
options.rootDir = rootDir
}
options.dev = true // Add hot reloading and watching changes
// Load config once for chokidar
const nuxtConfig = loadNuxtConfig()
_.defaultsDeep(nuxtConfig, { watchers: { chokidar: { ignoreInitial: true } } })
var nuxt = module.exports = new Nuxt(options)
var port = process.env.PORT || process.env.npm_package_config_nuxt_port
var host = process.env.HOST || process.env.npm_package_config_nuxt_host
var server = nuxt.server = new nuxt.Server(nuxt).listen(port, host)
// Start dev
let dev = startDev()
listenOnConfigChanges(nuxt, server)
function listenOnConfigChanges(nuxt, server) {
// Listen on nuxt.config.js changes
var build = _.debounce(() => {
// Start watching for nuxt.config.js changes
chokidar
.watch(nuxtConfigFile, nuxtConfig.watchers.chokidar)
.on('all', _.debounce(() => {
debug('[nuxt.config.js] changed')
delete require.cache[nuxtConfigFile]
var options = {}
if (fs.existsSync(nuxtConfigFile)) {
try {
options = require(nuxtConfigFile)
} catch (e) {
return console.error(e) // eslint-disable-line no-console
}
}
options.rootDir = rootDir
nuxt.close()
.then(() => {
nuxt.renderer = null
debug('Rebuilding the app...')
return new Nuxt(options).build()
})
.then((nuxt) => {
server.nuxt = nuxt
})
.catch((error) => {
console.error('Error while rebuild the app:', error) // eslint-disable-line no-console
process.exit(1)
})
}, 200)
var nuxtConfigFile = resolve(rootDir, nuxtConfigFileName)
chokidar.watch(nuxtConfigFile, Object.assign({}, nuxt.options.watchers.chokidar, {ignoreInitial: true}))
.on('all', build)
debug('Rebuilding the app...')
dev = dev.then(startDev)
}), 2500)
function startDev (oldNuxt) {
// Get latest environment variables
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
// Load options
let options = {}
try {
options = loadNuxtConfig()
} catch (err) {
console.error(err)
return // Wait for next reload
}
// Create nuxt and builder instance
const nuxt = new Nuxt(options)
const builder = new Builder(nuxt)
return Promise.resolve()
.then(() => builder.build()) // 1- Start build
.then(() => oldNuxt ? oldNuxt.close() : Promise.resolve()) // 2- Close old nuxt after successful build
.then(() => nuxt.listen(port, host)) // 3- Start listening
.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
// Show logs
process.env.DEBUG = 'nuxt:*'
process.env.DEBUG = process.env.DEBUG || 'nuxt:*'
var fs = require('fs')
var Nuxt = require('../')
var resolve = require('path').resolve
const fs = require('fs')
const parseArgs = require('minimist')
const debug = require('debug')('nuxt:generate')
var rootDir = resolve(process.argv.slice(2)[0] || '.')
var nuxtConfigFile = resolve(rootDir, 'nuxt.config.js')
const { Nuxt, Builder, Generator } = require('../')
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 = {}
if (fs.existsSync(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
}
options.dev = false // Force production mode (no webpack middleware called)
console.log('[nuxt] Generating...') // eslint-disable-line no-console
var nuxt = module.exports = new Nuxt(options)
nuxt.generate()
debug('Generating...')
const nuxt = new Nuxt(options)
const builder = new Builder(nuxt)
const generator = new Generator(nuxt, builder)
generator.generate()
.then(() => {
console.log('[nuxt] Generate done') // eslint-disable-line no-console
debug('Generate done')
process.exit(0)
})
.catch((err) => {
console.error(err) // eslint-disable-line no-console

View File

@ -1,22 +1,72 @@
#!/usr/bin/env node
var fs = require('fs')
var Nuxt = require('../')
var resolve = require('path').resolve
const fs = require('fs')
const parseArgs = require('minimist')
const { Nuxt } = require('../')
const { join, resolve } = require('path')
var rootDir = resolve(process.argv.slice(2)[0] || '.')
var nuxtConfigFile = resolve(rootDir, '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'
}
})
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)) {
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
}
options.dev = false // Force production mode (no webpack middleware called)
var nuxt = module.exports = new Nuxt(options)
var port = process.env.PORT || process.env.npm_package_config_nuxt_port
var host = process.env.HOST || process.env.npm_package_config_nuxt_host
var server = nuxt.server = new nuxt.Server(nuxt).listen(port, host)
// Force production mode (no webpack middleware called)
options.dev = false
// 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": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start"
},
"dependencies": {
"nuxt": "latest"
"start": "nuxt"
}
}

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",
"description": "",
"dependencies": {
"axios": "^0.15.2",
"axios": "latest",
"nuxt": "latest"
},
"scripts": {

View File

@ -9,14 +9,9 @@
<script>
export default {
asyncData ({ req }, callback) {
setTimeout(function () {
// callback(err, data)
callback(null, {
userAgent: (req ? req.headers['user-agent'] : navigator.userAgent)
})
}, 100)
}
asyncData: ({ req }) => ({
userAgent: (req ? req.headers['user-agent'] : navigator.userAgent)
})
}
</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 }) {
// If user not connected, redirect to /
export default function ({ store, error }) {
if (!store.state.authUser) {
// return redirect('/')
error({
message: 'You are not connected',
statusCode: 403

View File

@ -1,3 +1,6 @@
const bodyParser = require('body-parser')
const session = require('express-session')
module.exports = {
head: {
title: 'Auth Routes',
@ -9,5 +12,24 @@ module.exports = {
},
build: {
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",
"description": "",
"dependencies": {
"axios": "^0.16.1",
"body-parser": "^1.17.2",
"cross-env": "^5.0.0",
"express": "^4.15.3",
"express-session": "^1.15.3",
"nuxt": "^1.0.0-alpha1"
"nuxt": "latest"
},
"scripts": {
"dev": "node server.js",
"dev": "nuxt",
"build": "nuxt build",
"start": "cross-env NODE_ENV=production node server.js"
"start": "nuxt start"
}
}

View File

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

View File

@ -1,7 +1,7 @@
<template>
<div>
<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>
</div>
</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 = {
// nuxtServerInit is called by Nuxt.js before server-rendering every page
nuxtServerInit ({ commit }, { req }) {
if (req.session && req.session.authUser) {
commit('SET_USER', req.session.authUser)
}
},
login ({ commit }, { username, password }) {
return axios.post('/api/login', {
username,
password
})
.then((res) => {
commit('SET_USER', res.data)
})
.catch((error) => {
if (error.response.status === 401) {
async login ({ commit }, { username, password }) {
try {
const { data } = await axios.post('/api/login', { username, password })
commit('SET_USER', data)
} catch (error) {
if (error.response && error.response.status === 401) {
throw new Error('Bad credentials')
}
})
throw error
}
},
logout ({ commit }) {
return axios.post('/api/logout')
.then(() => {
commit('SET_USER', null)
})
async logout ({ commit }) {
await axios.post('/api/logout')
commit('SET_USER', null)
}
}

View File

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

View File

@ -7,21 +7,13 @@ module.exports = {
app: 'app.[chunkhash].js' // default: nuxt.bundle.[chunkhash].js
},
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 }) {
if (dev) {
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>
<style>
body {
overflow: hidden;
}
.dark {
position: absolute;
top: 0;

View File

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

View File

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

View File

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

View File

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

View File

@ -14,12 +14,13 @@ export default {
validate ({ params }) {
return !isNaN(+params.id)
},
asyncData ({ params, error }) {
return axios.get(`https://jsonplaceholder.typicode.com/users/${+params.id}`)
.then((res) => res.data)
.catch(() => {
async asyncData ({ params, error }) {
try {
const { data } = await axios.get(`https://jsonplaceholder.typicode.com/users/${+params.id}`)
return data
} catch (e) {
error({ message: 'User not found', statusCode: 404 })
})
}
}
}
</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 = {
head: {
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Meta description' }
]
},
css: [
'hover.css/css/hover-min.css',
'bulma/bulma.sass',
join(__dirname, 'css/main.css')
],
build: {
extractCSS: true
}
'bulma/css/bulma.css',
'~/css/main.css'
]
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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
// Get locale from params
const locale = params.lang || 'en'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,8 +22,8 @@
</template>
<script>
import Post from '~components/post'
import vP from '~components/paragraph'
import Post from '~/components/post'
import vP from '~/components/paragraph'
const vHr = { render: (h) => h('hr', { class: 'hr' }) }
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: [
// 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",
"dependencies": {
"axios": "^0.15.2",
"mini-toastr": "^0.3.10",
"axios": "^0.16.2",
"mini-toastr": "^0.6.5",
"nuxt": "latest",
"vue-notifications": "^0.7.0"
"vue-notifications": "^0.8.0"
},
"scripts": {
"dev": "nuxt",

View File

@ -10,7 +10,8 @@ import axios from 'axios'
export default {
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>

View File

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

View File

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

View File

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

View File

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

View File

@ -11,4 +11,4 @@ $theme := {
}
// 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')
module.exports = {
build: {
vendor: ['vuetify']
},
plugins: ['~plugins/vuetify.js'],
css: [
{ src: join(__dirname, 'css/app.styl'), lang: 'styl' }
],
/*
** Head elements
** Add Roboto font and Material Icons
*/
head: {
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",
"dependencies": {
"nuxt": "^0.10.7",
"vuetify": "^0.11.1"
"nuxt": "latest",
"vuetify": "latest"
},
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"predeploy": "yarn run generate",
"deploy": "surge --domain nuxt-with-vuetify-example.surge.sh dist"
"generate": "nuxt generate"
},
"devDependencies": {
"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>
<v-app top-toolbar left-fixed-sidebar>
<v-toolbar>
<v-toolbar-side-icon @click.native.stop="sidebar = !sidebar" />
<v-toolbar-logo>Toolbar</v-toolbar-logo>
</v-toolbar>
<main>
<v-sidebar left fixed drawer v-model="sidebar">
<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>
<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>
</template>

View File

@ -1,11 +1,16 @@
/*!
* Nuxt.js
* (c) 2016-2017 Chopin Brothers
* Core maintainer: Pooya (@pi0)
* 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
var Nuxt = require('./dist/nuxt.js')
module.exports = Nuxt.default ? Nuxt.default : Nuxt
module.exports = require('./dist/nuxt')

View File

@ -15,7 +15,7 @@ let layouts = {
<%
var layoutsKeys = Object.keys(layouts);
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 middleware from './middleware'
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 noopFetch = () => {}
// Global shared references
let _lastPaths = []
let _lastComponentsFiles = []
let app
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) {
return Components.map((Component) => {
let transition = Component.options.transition
if (typeof transition === 'function') {
return transition(to, from)
const componentTransitions = component => {
const transition = componentOption(component, 'transition', to, from)
return (typeof transition === 'string' ? { name: transition } : transition)
}
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) {
const resolveComponents = flatMapComponents(to, (Component, _, match, key) => {
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]
})
async function loadAsyncComponents (to, from, next) {
// Check if route hash changed
const fromPath = from.fullPath.split('#')[0]
const toPath = to.fullPath.split('#')[0]
this._hashChanged = (fromPath === toPath)
if (!this._hashChanged) {
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
this._hashChanged = fromPath === toPath
<% if (loading) { %>
if (!this._hashChanged && this.$loading.start) {
this.$loading.start()
}
Promise.all(resolveComponents)
.then(() => next())
.catch((err) => {
let statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500
this.error({ statusCode, message: err.message })
next(false)
<% } %>
try {
await Promise.all(flatMapComponents(to, (Component, _, match, key) => {
// 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)
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) {
// if layout is undefined, only call global middleware
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
let unknownMiddleware = false
// If layout is undefined, only call global middleware
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) {
midd = midd.concat(layout.middleware)
}
Components.forEach((Component) => {
Components.forEach(Component => {
if (Component.options.middleware) {
midd = midd.concat(Component.options.middleware)
}
})
}
midd = midd.map((name) => {
midd = midd.map(name => {
if (typeof middleware[name] !== 'function') {
unknownMiddleware = true
this.error({ statusCode: 500, message: 'Unknown middleware ' + name })
}
return middleware[name]
})
if (unknownMiddleware) return
if (unknownMiddleware) return
return middlewareSeries(midd, context)
}
async function render (to, from, next) {
if (this._hashChanged) return next()
let layout
// nextCalled is true when redirected
let nextCalled = false
const _next = function (path) {
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
const _next = path => {
<% if(loading) { %>if(this.$loading.finish) this.$loading.finish()<% } %>
if (nextCalled) return
nextCalled = true
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._dateLastError = this.$options._nuxt.dateErr
this._hadError = !!this.$options._nuxt.err
// Get route's matched components
const Components = getMatchedComponents(to)
// If no Components matched, generate 404
if (!Components.length) {
// Default layout
await callMiddleware.call(this, Components, context)
if (context._redirected) return
// Load layout for error page
layout = await this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(context) : NuxtError.layout)
await callMiddleware.call(this, Components, context, layout)
if (context._redirected) return
this.error({ statusCode: 404, message: 'This page could not be found.' })
return next()
}
// Update ._data and other properties if hot reloaded
Components.forEach(function (Component) {
Components.forEach(Component => {
if (Component._Ctor && Component._Ctor.options) {
Component.options.asyncData = Component._Ctor.options.asyncData
Component.options.fetch = Component._Ctor.options.fetch
}
})
// Apply transitions
this.setTransitions(mapTransitions(Components, to, from))
try {
// Set layout
// Call middleware
await callMiddleware.call(this, Components, context)
if (context._redirected) return
layout = Components[0].options.layout
// Set layout
let layout = Components[0].options.layout
if (typeof layout === 'function') {
layout = layout(context)
}
layout = await this.loadLayout(layout)
// Call middleware for layout
await callMiddleware.call(this, Components, context, layout)
if (context._redirected) return
// Pass validation?
// Call .validate()
let isValid = true
Components.forEach((Component) => {
Components.forEach(Component => {
if (!isValid) return
if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({
params: to.params || {},
query : to.query || {}<%= (store ? ', store: context.store' : '') %>
query : to.query || {},
<% if(store) { %>store: context.store <% } %>
})
})
// ...If .validate() returned false
if (!isValid) {
this.error({ statusCode: 404, message: 'This page could not be found.' })
return next()
}
// Call asyncData & fetch hooks on components matched by the route.
await Promise.all(Components.map((Component, i) => {
// Check if only children route changed
Component._path = compile(to.matched[i].path)(to.params)
if (!this._hadError && Component._path === _lastPaths[i] && (i + 1) !== Components.length) {
return Promise.resolve()
}
let promises = []
// asyncData method
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
var promise = promisify(Component.options.asyncData, context)
promise.then((asyncDataResult) => {
const hasAsyncData = Component.options.asyncData && typeof Component.options.asyncData === 'function'
const hasFetch = !!Component.options.fetch
<% 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)
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
<% if(loading) { %>if(this.$loading.increase) this.$loading.increase(loadingIncrease)<% } %>
})
promises.push(promise)
}
if (Component.options.fetch) {
var p = Component.options.fetch(context)
if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { p = Promise.resolve(p) }
<%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
// Call fetch(context)
if (hasFetch) {
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)
}
return Promise.all(promises)
}))
_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 (!nextCalled) {
next()
}
if (!nextCalled) next()
} catch (error) {
if (!error) error = {}
_lastPaths = []
error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500
// Load error layout
let layout = NuxtError.layout
if (typeof layout === 'function') {
layout = layout(context)
}
this.loadLayout(layout)
.then(() => {
this.error(error)
next(false)
})
await this.loadLayout(layout)
this.error(error)
next(false)
}
}
@ -197,46 +327,74 @@ function normalizeComponents (to, ___) {
}
// 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, ___) {
if (this._hashChanged) return
Vue.nextTick(() => {
let instances = getMatchedComponentsInstances(to)
const instances = getMatchedComponentsInstances(to)
_lastComponentsFiles = instances.map((instance, i) => {
if (!instance) return '';
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) {
Vue.set(instance.$data, key, newData[key])
}
}
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) {
this.error()
}
// Set layout
let layout = this.$options._nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout
if (typeof layout === 'function') {
layout = layout(this._context)
}
this.setLayout(layout)
// hot reloading
<% if (isDev) { %>
// Hot reloading
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)
function hotReloadAPI (_app) {
if (!module.hot) return
let $components = []
let $nuxt = _app.$nuxt
while ($nuxt && $nuxt.$children && $nuxt.$children.length) {
$nuxt.$children.forEach(function (child, i) {
$nuxt.$children.forEach((child, i) => {
if (child.$vnode.data.nuxtChild) {
let hasAlready = false
$components.forEach(function (component) {
$components.forEach(component => {
if (component.$options.__file === child.$options.__file) {
hasAlready = true
}
@ -248,13 +406,16 @@ function hotReloadAPI (_app) {
$nuxt = child
})
}
$components.forEach(addHotReload.bind(_app))
}
function addHotReload ($component, depth) {
if ($component.$vnode.data._hasHotReload) return
$component.$vnode.data._hasHotReload = true
var _forceUpdate = $component.$forceUpdate.bind($component.$parent)
$component.$vnode.context.$forceUpdate = () => {
let Components = getMatchedComponents(router.currentRoute)
let Component = Components[depth]
@ -314,104 +475,80 @@ function addHotReload ($component, depth) {
})
}
}
<% } %>
// Load vue app
const NUXT = window.__NUXT__ || {}
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) => {
async function mountApp(__app) {
// Set global variables
app = __app.app
router = __app.router
<%= (store ? 'store = __app.store' : '') %>
<% if (store) { %>store = __app.store <% } %>
// Resolve route components
const Components = await Promise.all(resolveComponents(router))
// Create Vue instance
const _app = new Vue(app)
// Load layout
const layout = NUXT.layout || 'default'
await _app.loadLayout(layout)
_app.setLayout(layout)
// Mounts Vue app to DOM element
const mountApp = () => {
_app.$mount('#__nuxt')
// Listen for first Vue update
Vue.nextTick(() => {
// Hot reloading
hotReloadAPI(_app)
// Call window.onNuxtReady callbacks
nuxtReady(_app)
<% if (isDev) { %>
// Enable hot reloading
hotReloadAPI(_app)
<% } %>
})
}
// Enable transitions
_app.setTransitions = _app.$options._nuxt.setTransitions.bind(_app)
if (Components.length) {
_app.setTransitions(mapTransitions(Components, router.currentRoute))
_lastPaths = router.currentRoute.matched.map((route) => compile(route.path)(router.currentRoute.params))
_lastComponentsFiles = Components.map((Component) => Component.options.__file)
_lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
_lastComponentsFiles = Components.map(Component => Component.options.__file)
}
// Initialize error handler
_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)
// Add router hooks
router.beforeEach(loadAsyncComponents.bind(_app))
router.beforeEach(render.bind(_app))
router.afterEach(normalizeComponents)
router.afterEach(fixPrepatch.bind(_app))
// If page already is server rendered
if (NUXT.serverRendered) {
mountApp()
return
}
render.call(_app, router.currentRoute, router.currentRoute, function (path) {
if (path) {
let mounted = false
router.afterEach(function () {
if (mounted) return
mounted = true
mountApp()
})
router.push(path)
render.call(_app, router.currentRoute, router.currentRoute, path => {
if (!path) {
normalizeComponents(router.currentRoute, router.currentRoute)
fixPrepatch.call(_app, router.currentRoute, router.currentRoute)
mountApp()
return
}
normalizeComponents(router.currentRoute, router.currentRoute)
fixPrepatch.call(_app, router.currentRoute, router.currentRoute)
mountApp()
// Push the path and then mount app
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,
render (h, { parent, data }) {
data.nuxtChild = true
const _parent = parent
const transitions = parent.$nuxt.nuxt.transitions
const defaultTransition = parent.$nuxt.nuxt.defaultTransition
let depth = 0
@ -51,7 +51,7 @@ export default {
let listeners = {}
listenersKeys.forEach((key) => {
if (typeof transition[key] === 'function') {
listeners[key] = transition[key]
listeners[key] = transition[key].bind(_parent)
}
})
return h('transition', {

View File

@ -1,15 +1,16 @@
<template>
<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>
<script>
import Vue from 'vue'
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 {
name: 'nuxt',
props: ['nuxtChildKey'],
beforeCreate () {
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: {
NuxtChild,
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 Meta from 'vue-meta'
import { createRouter } from './router.js'
<% if (store) { %>import { createStore } from './store.js'<% } %>
import NuxtChild from './components/nuxt-child.js'
import NuxtLink from './components/nuxt-link.js'
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
import Nuxt from './components/nuxt.vue'
import App from '<%= appPath %>'
import { getContext } from './utils'
if (process.browser) {
// 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 %>
<% }}) %>
<% if (store) { %>import { createStore } from './store.js'<% } %>
<% plugins.forEach(plugin => { %>import <%= plugin.name %> from '<%= plugin.name %>'
<% }) %>
// Component: <nuxt-child>
Vue.component(NuxtChild.name, NuxtChild)
// Component: <nuxt-link>
Vue.component(NuxtLink.name, NuxtLink)
// Component: <nuxt>
// Component: <nuxt>`
Vue.component(Nuxt.name, Nuxt)
// vue-meta configuration
@ -47,37 +34,36 @@ const defaultTransition = <%=
.replace('beforeEnter(', 'function(').replace('enter(', 'function(').replace('afterEnter(', 'function(')
.replace('enterCancelled(', 'function(').replace('beforeLeave(', 'function(').replace('leave(', 'function(')
.replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(')
%>
%>
async function createApp (ssrContext) {
<% if (store) { %>
const store = createStore()
<% } %>
const router = createRouter()
<% if (store) { %>const store = createStore()<% } %>
if (process.server && ssrContext && ssrContext.url) {
await new Promise((resolve, reject) => {
router.push(ssrContext.url, resolve, reject)
})
}
<% if (store) { %>
if (process.browser) {
<% if (store) { %>
// Replace store state before calling plugins
if (window.__NUXT__ && window.__NUXT__.state) {
store.replaceState(window.__NUXT__.state)
}
<% } %>
}
<% } %>
// root instance
// Create Root instance
// here we inject the router and store to all child components,
// making them available everywhere as `this.$router` and `this.$store`.
let app = {
const app = {
router,
<%= (store ? 'store,' : '') %>
<% if(store) { %> store,<% } %>
_nuxt: {
defaultTransition: defaultTransition,
defaultTransition,
transitions: [ defaultTransition ],
setTransitions (transitions) {
if (!Array.isArray(transitions)) {
@ -103,43 +89,44 @@ async function createApp (ssrContext) {
if (typeof err === 'string') {
err = { statusCode: 500, message: err }
}
this.$options._nuxt.dateErr = Date.now()
this.$options._nuxt.err = err;
const _nuxt = this._nuxt || this.$options._nuxt
_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
}
},
...App
}
const next = ssrContext ? ssrContext.next : location => app.router.push(location)
const ctx = getContext({
isServer: !!ssrContext,
isClient: !ssrContext,
route: router.currentRoute,
<%= (store ? 'store,' : '') %>
next,
error: app._nuxt.error.bind(app),
<% if(store) { %>store,<% } %>
req: ssrContext ? ssrContext.req : undefined,
res: ssrContext ? ssrContext.res : undefined,
}, app)
delete ctx.redirect
delete ctx.error
// Inject external plugins
<% plugins.forEach(function (plugin) {
if (plugin.ssr) { %>
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)
}
}
<% }
}) %>
<% plugins.filter(p => p.ssr).forEach(plugin => { %>
if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(ctx)<% }) %>
<% if (plugins.filter(p => !p.ssr).length) { %>
if (process.browser) { <% plugins.filter(p => !p.ssr).forEach(plugin => { %>
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) { %>
let files = require.context('~/middleware', false, /^\.\/.*\.(js|ts)$/)
let files = require.context('@/middleware', false, /^\.\/.*\.(js|ts)$/)
let filenames = files.keys()
function getModule (filename) {

View File

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

View File

@ -1,79 +1,102 @@
'use strict'
import Vue from 'vue'
import clone from 'clone'
import { stringify } from 'querystring'
import { omit } from 'lodash'
import middleware from './middleware'
import { createApp, NuxtError } from './index'
import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils'
const debug = require('debug')('nuxt:render')
debug.color = 4 // force blue color
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 is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// 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 = new Vue(app)
const _noopApp = new Vue({ render: (h) => h('div') })
<% if (store) { %>
// Add store to the context
<%= (store ? 'context.store = store' : '') %>
context.store = store
<% } %>
// Add route to the context
context.route = router.currentRoute
// Nuxt object
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
context.meta = _app.$meta()
// Error function
context.error = _app.$options._nuxt.error.bind(_app)
// Keep asyncData for each matched component in context
context.asyncData = {}
<%= (isDev ? 'const s = isDev && Date.now()' : '') %>
let ctx = getContext(context, app)
// Create shared ctx
const ctx = getContext(context, app)
<% if (isDev) { %>const s = isDev && Date.now()<% } %>
// Resolve 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 {
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) {
// Throw back error to renderRoute()
throw err
}
// nuxtServerInit
<% if (store) { %>
let promise = (store._actions && store._actions.nuxtServerInit ? store.dispatch('nuxtServerInit', omit(getContext(context, app), 'redirect', 'error')) : null)
if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) promise = Promise.resolve()
<% } else { %>
let promise = Promise.resolve()
// Dispatch store nuxtServerInit
if (store._actions && store._actions.nuxtServerInit) {
await store.dispatch('nuxtServerInit', ctx)
}
// ...If there is a redirect
if (context.redirected) return noopApp()
<% } %>
await promise
// Call global middleware (nuxt.config.js)
let midd = <%= serialize(router.middleware, { isJSON: true }) %>
midd = midd.map((name) => {
@ -85,32 +108,39 @@ export default async (context) => {
if (!context.nuxt.error) {
await middlewareSeries(midd, ctx)
}
if (context.redirected) return _noopApp
// ...If there is a redirect
if (context.redirected) return noopApp()
// Set layout
let layout = Components.length ? Components[0].options.layout : NuxtError.layout
if (typeof layout === 'function') layout = layout(ctx)
await _app.loadLayout(layout)
layout = _app.setLayout(layout)
// Set layout to __NUXT__
// ...Set layout to __NUXT__
context.nuxt.layout = _app.layoutName
// 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) {
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)
// If there is a redirect
if (context.redirected) return noopApp()
}
if (context.redirected) return _noopApp
// Call .validate()
let isValid = true
Components.forEach((Component) => {
@ -118,10 +148,11 @@ export default async (context) => {
if (typeof Component.options.validate !== 'function') return
isValid = Component.options.validate({
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) {
// Don't server-render the page in generate mode
if (context._generate) {
@ -130,52 +161,65 @@ export default async (context) => {
// Call the 404 error by making the Components array empty
Components = []
}
// 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 = []
// Call asyncData(context)
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
let promise = promisify(Component.options.asyncData, ctx)
// Call asyncData(context)
promise.then((asyncDataResult) => {
applyAsyncData(Component, asyncDataResult)
promise.then(asyncDataResult => {
context.asyncData[Component.options.name] = asyncDataResult
applyAsyncData(Component)
return asyncDataResult
})
promises.push(promise)
} else promises.push(null)
// call fetch(context)
if (Component.options.fetch) promises.push(Component.options.fetch(ctx))
else promises.push(null)
} 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)
}))
// If no Components found, returns 404
if (!Components.length) {
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
context.nuxt.data = asyncDatas.map((r) => (r[0] || {}))
context.nuxt.data = asyncDatas.map(r => r[0] || {})
// If an error occured in the execution
if (_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 (!context.nuxt.error) {
return _app
}
// Load layout for error page
layout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(ctx) : NuxtError.layout)
context.nuxt.layout = layout || ''
await _app.loadLayout(layout)
_app.setLayout(layout)
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)
// Recursive find files in ~/store
const files = require.context('~/store', true, /^\.\/.*\.(js|ts)$/)
// Recursive find files in {srcDir}/store
const files = require.context('@/store', true, /^\.\/.*\.(js|ts)$/)
const filenames = files.keys()
// Store
let storeData = {}
// 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) {
storeData = getModule(indexFilename)
}

View File

@ -1,12 +1,28 @@
'use strict'
import Vue from 'vue'
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 = {}) {
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 () {
const data = ComponentData.call(this)
if(this.$ssrContext) {
asyncData = this.$ssrContext.asyncData[Component.options.name]
}
return { ...data, ...asyncData }
}
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