mirror of
https://github.com/nuxt/nuxt.git
synced 2025-03-09 03:03:18 +00:00
Merge remote-tracking branch 'nuxt/master'
This commit is contained in:
commit
bd5ec528c5
11
README.md
11
README.md
@ -49,7 +49,7 @@ So far, we get:
|
||||
- Automatic transpilation and bundling (with webpack and babel)
|
||||
- Hot code reloading
|
||||
- Server rendering and indexing of `./pages`
|
||||
- Static file serving. `./static/` is mapped to `/static/`
|
||||
- Static file serving. `./static/` is mapped to `/`
|
||||
- Config file `nuxt.config.js`
|
||||
- Code splitting via webpack
|
||||
|
||||
@ -97,8 +97,13 @@ This is mostly used for tests purpose but who knows!
|
||||
|
||||
```js
|
||||
nuxt.renderRoute('/about', context)
|
||||
.then(function (html) {
|
||||
// HTML
|
||||
.then(function ({ html, error }) {
|
||||
// You can check error to know if your app displayed the error page for this route
|
||||
// Useful to set the correct status status code if an error appended:
|
||||
if (error) {
|
||||
return res.status(error.statusCode || 500).send(html)
|
||||
}
|
||||
res.send(html)
|
||||
})
|
||||
.catch(function (error) {
|
||||
// And error appended while rendering the route
|
||||
|
@ -23,5 +23,5 @@ new Nuxt(options)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
process.exit()
|
||||
process.exit(1)
|
||||
})
|
||||
|
@ -15,6 +15,8 @@ if (typeof options.rootDir !== 'string') {
|
||||
options.rootDir = rootDir
|
||||
}
|
||||
|
||||
options.dev = true // Add hot reloading and watching changes
|
||||
|
||||
new Nuxt(options)
|
||||
.then((nuxt) => {
|
||||
new Server(nuxt)
|
||||
@ -22,5 +24,5 @@ new Nuxt(options)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
process.exit()
|
||||
process.exit(1)
|
||||
})
|
||||
|
@ -25,5 +25,5 @@ new Nuxt(options)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
process.exit()
|
||||
process.exit(1)
|
||||
})
|
||||
|
@ -1,15 +1,25 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<img src="/static/nuxt.png" />
|
||||
<h2>About</h2>
|
||||
<p><router-link to="/">Home</router-link></p>
|
||||
<img src="/nuxt-square.png" />
|
||||
<h2>Thank you for testing nuxt.js</h2>
|
||||
<p><router-link to="/">Back home</router-link></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
font-family: serif;
|
||||
margin-top: 200px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: black;
|
||||
color: white;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
padding-top: 130px;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
color: silver;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<img src="/static/nuxt.png" />
|
||||
<img src="/nuxt.png" />
|
||||
<h2>Hello World.</h2>
|
||||
<p><router-link to="/about">About</router-link></p>
|
||||
</div>
|
||||
|
BIN
examples/hello-world/static/nuxt-square.png
Normal file
BIN
examples/hello-world/static/nuxt-square.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
@ -40,6 +40,13 @@ async function renderAndGetWindow (route) {
|
||||
},
|
||||
done (err, window) {
|
||||
if (err) return reject(err)
|
||||
// If Nuxt could not be loaded (error from the server-side)
|
||||
if (!window.__NUXT__) {
|
||||
return reject({
|
||||
message: 'Could not load the nuxt app',
|
||||
body: window.document.getElementsByTagName('body')[0].innerHTML
|
||||
})
|
||||
}
|
||||
// Used by nuxt.js to say when the components are loaded and the app ready
|
||||
window.onNuxtReady = function () {
|
||||
resolve(window)
|
||||
@ -54,7 +61,7 @@ async function renderAndGetWindow (route) {
|
||||
*/
|
||||
test('Route / exits and render HTML', async t => {
|
||||
let context = {}
|
||||
const html = await nuxt.renderRoute('/', context)
|
||||
const { html } = await nuxt.renderRoute('/', context)
|
||||
t.true(html.includes('<p class="red-color">Hello world!</p>'))
|
||||
t.is(context.nuxt.error, null)
|
||||
t.is(context.nuxt.data[0].name, 'world')
|
||||
@ -75,5 +82,5 @@ test('Route / exits and render HTML', async t => {
|
||||
// Close server and ask nuxt to stop listening to file changes
|
||||
test.after('Closing server and nuxt.js', t => {
|
||||
server.close()
|
||||
nuxt.stop()
|
||||
nuxt.close()
|
||||
})
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
require('es6-promise').polyfill()
|
||||
require('es6-object-assign').polyfill()
|
||||
import Vue from 'vue'
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
// The Vue build version to load with the `import` command
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import Vue from 'vue'
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Meta from 'vue-meta'
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('nuxt:render')
|
||||
import Vue from 'vue'
|
||||
import { pick } from 'lodash'
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
const debug = require('debug')('nuxt:build')
|
||||
const _ = require('lodash')
|
||||
const co = require('co')
|
||||
const del = require('del')
|
||||
const chokidar = require('chokidar')
|
||||
const fs = require('fs')
|
||||
const glob = require('glob-promise')
|
||||
const hash = require('hash-sum')
|
||||
@ -52,6 +54,10 @@ module.exports = function * () {
|
||||
if (noBuild) {
|
||||
const serverConfig = getWebpackServerConfig.call(this)
|
||||
const bundlePath = join(serverConfig.output.path, serverConfig.output.filename)
|
||||
if (!fs.existsSync(bundlePath)) {
|
||||
console.error('> No build files found, please run `nuxt build` before launching `nuxt start`')
|
||||
process.exit(1)
|
||||
}
|
||||
createRenderer.call(this, fs.readFileSync(bundlePath, 'utf8'))
|
||||
return Promise.resolve()
|
||||
}
|
||||
@ -64,15 +70,15 @@ module.exports = function * () {
|
||||
} else {
|
||||
console.error('> Couldn\'t find a `pages` directory. Please create one under the project root')
|
||||
}
|
||||
process.exit()
|
||||
process.exit(1)
|
||||
}
|
||||
if (this.options.store && !fs.existsSync(join(this.dir, 'store'))) {
|
||||
console.error('> No `store` directory found (store option activated). Please create on under the project root')
|
||||
process.exit()
|
||||
process.exit(1)
|
||||
}
|
||||
if (this.options.store && !fs.existsSync(join(this.dir, 'store', 'index.js'))) {
|
||||
console.error('> No `store/index.js` file found (store option activated). Please create the file.')
|
||||
process.exit()
|
||||
process.exit(1)
|
||||
}
|
||||
debug(`App root: ${this.dir}`)
|
||||
debug('Generating .nuxt/ files...')
|
||||
@ -86,6 +92,34 @@ module.exports = function * () {
|
||||
if (!this.dev) {
|
||||
yield mkdirp(r(this.dir, '.nuxt/dist'))
|
||||
}
|
||||
// Resolve custom routes component path
|
||||
this.options.routes.forEach((route) => {
|
||||
if (route.component.slice(-4) !== '.vue') {
|
||||
route.component = route.component + '.vue'
|
||||
}
|
||||
route.component = r(this.dir, route.component)
|
||||
})
|
||||
// Generate routes and interpret the template files
|
||||
yield generateRoutesAndFiles.call(this)
|
||||
/*
|
||||
** Generate .nuxt/dist/ files
|
||||
*/
|
||||
if (this.dev) {
|
||||
debug('Adding webpack middlewares...')
|
||||
createWebpackMiddlewares.call(this)
|
||||
webpackWatchAndUpdate.call(this)
|
||||
watchPages.call(this)
|
||||
} else {
|
||||
debug('Building files...')
|
||||
yield [
|
||||
webpackRunClient.call(this),
|
||||
webpackRunServer.call(this)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function * generateRoutesAndFiles () {
|
||||
debug('Generating routes...')
|
||||
/*
|
||||
** Generate routes based on files
|
||||
*/
|
||||
@ -94,24 +128,14 @@ module.exports = function * () {
|
||||
files.forEach((file) => {
|
||||
let path = file.replace(/^pages/, '').replace(/index\.vue$/, '/').replace(/\.vue$/, '').replace(/\/{2,}/g, '/')
|
||||
if (path[1] === '_') return
|
||||
routes.push({ path: path, component: file })
|
||||
})
|
||||
this.options.routes.forEach((route) => {
|
||||
if (route.component.slice(-4) !== '.vue') {
|
||||
route.component = route.component + '.vue'
|
||||
}
|
||||
route.component = r(this.dir, route.component)
|
||||
})
|
||||
this.options.routes = routes.concat(this.options.routes)
|
||||
// TODO: check .children
|
||||
this.options.routes.forEach((route) => {
|
||||
route._component = r(this.dir, route.component)
|
||||
route._name = '_' + hash(route._component)
|
||||
route.component = route._name
|
||||
routes.push({ path: path, component: r(this.dir, file) })
|
||||
})
|
||||
// Concat pages routes and custom routes in this.routes
|
||||
this.routes = routes.concat(this.options.routes)
|
||||
/*
|
||||
** Interpret and move template files to .nuxt/
|
||||
*/
|
||||
debug('Generating files...')
|
||||
let templatesFiles = [
|
||||
'App.vue',
|
||||
'client.js',
|
||||
@ -131,8 +155,15 @@ module.exports = function * () {
|
||||
Loading: r(__dirname, '..', 'app', 'components', 'Loading.vue'),
|
||||
ErrorPage: r(__dirname, '..', '..', 'pages', (this.dev ? '_error-debug.vue' : '_error.vue'))
|
||||
},
|
||||
routes: this.options.routes
|
||||
routes: this.routes
|
||||
}
|
||||
// Format routes for the lib/app/router.js template
|
||||
// TODO: check .children
|
||||
templateVars.routes.forEach((route) => {
|
||||
route._component = route.component
|
||||
route._name = '_' + hash(route._component)
|
||||
route.component = route._name
|
||||
})
|
||||
if (this.options.store) {
|
||||
templateVars.storePath = r(this.dir, 'store')
|
||||
}
|
||||
@ -153,21 +184,6 @@ module.exports = function * () {
|
||||
})
|
||||
})
|
||||
yield moveTemplates
|
||||
debug('Files moved!')
|
||||
/*
|
||||
** Generate .nuxt/dist/ files
|
||||
*/
|
||||
if (this.dev) {
|
||||
debug('Adding webpack middlewares...')
|
||||
createWebpackMiddlewares.call(this)
|
||||
webpackWatchAndUpdate.call(this)
|
||||
} else {
|
||||
debug('Building files...')
|
||||
yield [
|
||||
webpackRunClient.call(this),
|
||||
webpackRunServer.call(this)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function getWebpackClientConfig () {
|
||||
@ -183,7 +199,7 @@ function getWebpackServerConfig () {
|
||||
function createWebpackMiddlewares () {
|
||||
const clientConfig = getWebpackClientConfig.call(this)
|
||||
// setup on the fly compilation + hot-reload
|
||||
clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app]
|
||||
clientConfig.entry.app = ['webpack-hot-middleware/client?reload=true', clientConfig.entry.app]
|
||||
clientConfig.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin()
|
||||
@ -261,3 +277,22 @@ function createRenderer (bundle) {
|
||||
this.renderToString = pify(this.renderer.renderToString)
|
||||
this.renderToStream = this.renderer.renderToStream
|
||||
}
|
||||
|
||||
function watchPages () {
|
||||
const patterns = [ r(this.dir, 'pages/*.vue'), r(this.dir, 'pages/**/*.vue') ]
|
||||
const options = {
|
||||
ignored: '**/_*.vue',
|
||||
ignoreInitial: true
|
||||
}
|
||||
const refreshFiles = _.debounce(() => {
|
||||
console.log('Reload files', this.routes.length)
|
||||
var d = Date.now()
|
||||
co(generateRoutesAndFiles.bind(this))
|
||||
.then(() => {
|
||||
console.log('Time to gen:' + (Date.now() - d) + 'ms')
|
||||
})
|
||||
}, 200)
|
||||
this.pagesFilesWatcher = chokidar.watch(patterns, options)
|
||||
.on('add', refreshFiles)
|
||||
.on('unlink', refreshFiles)
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const vueLoaderConfig = require('./vue-loader.config')
|
||||
const { join } = require('path')
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const webpack = require('webpack')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const base = require('./base.config')
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
const webpack = require('webpack')
|
||||
const base = require('./base.config')
|
||||
const { uniq } = require('lodash')
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function () {
|
||||
let config = {
|
||||
postcss: [
|
||||
|
36
lib/nuxt.js
36
lib/nuxt.js
@ -45,8 +45,11 @@ class Nuxt {
|
||||
})
|
||||
// renderer used by Vue.js (via createBundleRenderer)
|
||||
this.renderer = null
|
||||
// For serving static/ files to /
|
||||
this.serveStatic = pify(serveStatic(resolve(this.dir, 'static')))
|
||||
// For serving .nuxt/dist/ files
|
||||
this.serveStatic = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist')))
|
||||
this._nuxtRegexp = /^\/_nuxt\//
|
||||
this.serveStaticNuxt = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist')))
|
||||
// Add this.build
|
||||
this.build = build.bind(this)
|
||||
// Add this.generate
|
||||
@ -77,25 +80,27 @@ class Nuxt {
|
||||
// Call webpack middlewares only in development
|
||||
yield self.webpackDevMiddleware(req, res)
|
||||
yield self.webpackHotMiddleware(req, res)
|
||||
return
|
||||
}
|
||||
if (req.url.includes('/_nuxt/')) {
|
||||
// Serve static/ files
|
||||
yield self.serveStatic(req, res)
|
||||
// Serve .nuxt/dist/ files (only for production)
|
||||
if (!self.dev && self._nuxtRegexp.test(req.url)) {
|
||||
const url = req.url
|
||||
req.url = req.url.replace('/_nuxt/', '/')
|
||||
yield self.serveStatic(req, res)
|
||||
req.url = req.url.replace(self._nuxtRegexp, '/')
|
||||
yield self.serveStaticNuxt(req, res)
|
||||
req.url = url
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (this.dev && req.url.includes('/_nuxt/') && req.url.includes('.hot-update.json')) {
|
||||
if (this.dev && this._nuxtRegexp.test(req.url) && req.url.includes('.hot-update.json')) {
|
||||
res.statusCode = 404
|
||||
return res.end()
|
||||
}
|
||||
return this.renderRoute(req.url, context)
|
||||
})
|
||||
.then((html) => {
|
||||
if (context.nuxt.error && context.nuxt.error.statusCode) {
|
||||
res.statusCode = context.nuxt.error.statusCode
|
||||
.then(({ html, error }) => {
|
||||
if (error) {
|
||||
res.statusCode = context.nuxt.error.statusCode || 500
|
||||
}
|
||||
res.end(html, 'utf8')
|
||||
})
|
||||
@ -113,13 +118,13 @@ class Nuxt {
|
||||
// Call rendertoSting from the bundleRenderer and generate the HTML (will update the context as well)
|
||||
const self = this
|
||||
return co(function * () {
|
||||
const html = yield self.renderToString(context)
|
||||
const app = yield self.renderToString(context)
|
||||
if (context.nuxt && context.nuxt.error instanceof Error) {
|
||||
context.nuxt.error = { statusCode: 500, message: context.nuxt.error.message }
|
||||
}
|
||||
const app = self.appTemplate({
|
||||
const html = self.appTemplate({
|
||||
dev: self.dev, // Use to add the extracted CSS <link> in production
|
||||
APP: html,
|
||||
APP: app,
|
||||
context: context,
|
||||
files: {
|
||||
css: join('/_nuxt/', self.options.build.filenames.css),
|
||||
@ -127,11 +132,11 @@ class Nuxt {
|
||||
app: join('/_nuxt/', self.options.build.filenames.app)
|
||||
}
|
||||
})
|
||||
return app
|
||||
return { html, error: context.nuxt.error }
|
||||
})
|
||||
}
|
||||
|
||||
stop (callback) {
|
||||
close (callback) {
|
||||
let promises = []
|
||||
if (this.webpackDevMiddleware) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
@ -145,6 +150,9 @@ class Nuxt {
|
||||
})
|
||||
promises.push(p)
|
||||
}
|
||||
if (this.pagesFilesWatcher) {
|
||||
this.pagesFilesWatcher.close()
|
||||
}
|
||||
return co(function * () {
|
||||
yield promises
|
||||
})
|
||||
|
@ -1,41 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
const http = require('http')
|
||||
const co = require('co')
|
||||
const pify = require('pify')
|
||||
const serveStatic = require('serve-static')
|
||||
const { resolve } = require('path')
|
||||
|
||||
class Server {
|
||||
|
||||
constructor (nuxt) {
|
||||
this.server = http.createServer(this.handle.bind(this))
|
||||
this.serveStatic = pify(serveStatic(resolve(nuxt.dir, 'static')))
|
||||
this.nuxt = nuxt
|
||||
this.server = http.createServer(nuxt.render.bind(nuxt))
|
||||
return this
|
||||
}
|
||||
|
||||
handle (req, res) {
|
||||
const method = req.method.toUpperCase()
|
||||
const self = this
|
||||
|
||||
if (method !== 'GET' && method !== 'HEAD') {
|
||||
return this.nuxt.render(req, res)
|
||||
}
|
||||
co(function * () {
|
||||
if (req.url.includes('/static/')) {
|
||||
const url = req.url
|
||||
req.url = req.url.replace('/static/', '/')
|
||||
yield self.serveStatic(req, res)
|
||||
req.url = url
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// File not found
|
||||
this.nuxt.render(req, res)
|
||||
})
|
||||
}
|
||||
|
||||
listen (port, host) {
|
||||
host = host || 'localhost'
|
||||
port = port || 3000
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nuxt",
|
||||
"version": "0.2.6",
|
||||
"version": "0.3.1",
|
||||
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
@ -22,6 +22,7 @@
|
||||
"babel-loader": "^6.2.7",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-stage-2": "^6.18.0",
|
||||
"chokidar": "^1.6.1",
|
||||
"co": "^4.6.0",
|
||||
"cross-spawn": "^5.0.1",
|
||||
"css-loader": "^0.25.0",
|
||||
|
Loading…
Reference in New Issue
Block a user