Merged branch master into master

This commit is contained in:
Brendan 2016-12-30 14:28:21 +01:00
commit f0fba0b0c5
29 changed files with 273 additions and 119 deletions

View File

@ -9,13 +9,15 @@
</p> </p>
> Nuxt.js is a minimalistic framework for server-rendered Vue applications (inspired by [Next.js](https://github.com/zeit/next.js)) > Nuxt.js is a minimalistic framework for server-rendered Vue applications (inspired by [Next.js](https://github.com/zeit/next.js))
## 🚧 Under development, 1.0 will be released soon :fire: ## 🚧 Under active development, 1.0 will be released soon :fire:
## 🎬 Video: [1 minute demo](https://www.youtube.com/watch?v=kmf-p-pTi40) ## Links
## 🐦 Twitter: [@nuxt_js](https://twitter.com/nuxt_js) - 📘 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)
## 📓 How to use ## Getting started
``` ```
$ npm install nuxt --save $ npm install nuxt --save
@ -60,32 +62,20 @@ So far, we get:
- Automatic transpilation and bundling (with webpack and babel) - Automatic transpilation and bundling (with webpack and babel)
- Hot code reloading - Hot code reloading
- Server rendering and indexing of `./pages` - Server rendering and indexing of `pages/`
- Static file serving. `./static/` is mapped to `/` - Static file serving. `./static/` is mapped to `/`
- Config file `nuxt.config.js` - Configurable with a `nuxt.config.js` file
- Custom layouts with the `layouts/` directory
- Code splitting via webpack - Code splitting via webpack
## Using nuxt.js programmatically ## Using nuxt.js programmatically
Nuxt is built on the top of ES2015, which makes the code more enjoyable and cleaner to read. It doesn't make use of any transpilers and depends upon Core V8 implemented features.
For these reasons, nuxt.js targets Node.js `4.0` or higher (you might want to launch node with the `--harmony-proxies` flag if you running `node <= 6.5.0` )
```js ```js
const Nuxt = require('nuxt') const Nuxt = require('nuxt')
const options = {
routes: [], // see examples/custom-routes
css: ['/dist/bootstrap.css'] // see examples/global-css
store: true // see examples/vuex-store
plugins: ['public/plugin.js'], // see examples/plugins-vendor
loading: false or { color: 'blue', failedColor: 'red' } or 'components/my-spinner' // see examples/custom-loading
build: {
vendor: ['axios'] // see examples/plugins-vendor
}
}
// Launch nuxt build with given options // Launch nuxt build with given options
let nuxt = new Nuxt(options) let config = require('./nuxt.config.js')
let nuxt = new Nuxt(config)
nuxt.build() nuxt.build()
.then(() => { .then(() => {
// You can use nuxt.render(req, res) or nuxt.renderRoute(route, context) // You can use nuxt.render(req, res) or nuxt.renderRoute(route, context)
@ -106,7 +96,7 @@ app.use(nuxt.render)
## Render a specific route ## Render a specific route
This is mostly used for tests purpose but who knows! This is mostly used for `nuxt generate` and tests purposes but you might found another utility!
```js ```js
nuxt.renderRoute('/about', context) nuxt.renderRoute('/about', context)
@ -125,14 +115,7 @@ nuxt.renderRoute('/about', context)
## Examples ## Examples
Please take a look at the examples/ folder. Please take a look at the [examples/](https://github.com/nuxt/nuxt.js/tree/master/examples) directory.
If you want to launch one example to see it live:
```bash
cd node_modules/nuxt/
bin/nuxt examples/hello-world
# Go to http://localhost:3000
```
## Production deployment ## Production deployment

View File

@ -6,10 +6,11 @@
`data` is called every time before loading the component (*only if attached to a route*). It can be called from the server-side or before navigating to the corresponding route. `data` is called every time before loading the component (*only if attached to a route*). It can be called from the server-side or before navigating to the corresponding route.
The `data` method receives the context as the first argument, you can use it to fetch some data and return the component data. To make the data method asynchronous, Nuxt.js offers you 2 ways, choose the one you're the most familiar with: The `data` method receives the context as the first argument, you can use it to fetch some data and return the component data. To make the data method asynchronous, Nuxt.js offers you different ways, choose the one you're the most familiar with:
1. returning a `Promise`, Nuxt.js will wait for the promise to be resolved before rendering the Component 1. returning a `Promise`, Nuxt.js will wait for the promise to be resolved before rendering the Component
2. Define a second argument which is a callback method to be called like this: `callback(err, data)` 2. Using the [async/await proposal](https://github.com/lukehoban/ecmascript-asyncawait) ([learn more about it]([learn more about it](https://zeit.co/blog/async-and-await))
3. Define a second argument which is a callback method to be called like this: `callback(err, data)`
Example with returning a `Promise`: Example with returning a `Promise`:
```js ```js
@ -23,6 +24,16 @@ export default {
} }
``` ```
Example with using `async/await`:
```js
export default {
async data ({ params }) {
let { data } = await axios.get(`https://my-api/posts/${params.id}`)
return { title: data.title }
}
}
```
Example with using the `callback` argument: Example with using the `callback` argument:
```js ```js
export default { export default {

View File

@ -1,8 +1,9 @@
<template> <template>
<div> <div class="container">
<h1>User Agent</h1>
<p>{{ userAgent }}</p> <p>{{ userAgent }}</p>
<p><nuxt-link to="/post">See a post (http request / Ajax)</nuxt-link></p> <p><nuxt-link to="/posts">Blog</nuxt-link></p>
</div> </div>
</template> </template>
@ -20,10 +21,16 @@ export default {
</script> </script>
<style scoped> <style scoped>
.container {
width: 70%;
margin: auto;
text-align: center;
padding-top: 100px;
}
p { p {
font-size: 20px; font-size: 20px;
text-align: center; }
padding: 100px; a {
padding-bottom: 0; color: #41B883;
} }
</style> </style>

View File

@ -1,35 +0,0 @@
<template>
<div>
<p>{{ post.title }}!</p>
<p><nuxt-link to="/">Back home</nuxt-link></p>
</div>
</template>
<script>
import axios from 'axios'
export default {
data ({ req }) {
// We can return a Promise instead of calling the callback
return axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then((res) => {
return { post: res.data }
})
},
head () {
return {
title: this.post.title
}
}
}
</script>
<style scoped>
p {
font-size: 20px;
text-align: center;
padding: 100px;
padding-bottom: 0;
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<div class="container">
<h1>{{ post.title }}</h1>
<pre>{{ post.body }}</pre>
<p><nuxt-link to="/posts">Back to the list</nuxt-link></p>
</div>
</template>
<script>
import axios from 'axios'
export default {
async data ({ params }) {
// We can use async/await ES6 feature
let { data } = await axios.get(`https://jsonplaceholder.typicode.com/posts/${params.id}`)
return { post: data }
},
head () {
return {
title: this.post.title
}
}
}
</script>
<style scoped>
.container {
width: 70%;
margin: auto;
text-align: center;
padding-top: 100px;
}
ul {
list-style-type: none;
padding: 0;
}
ul li {
border: 1px #ddd solid;
padding: 20px;
text-align: left;
}
ul li a {
color: gray;
}
p {
font-size: 20px;
}
a {
color: #41B883;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<div class="container">
<h1>Blog</h1>
<ul>
<li v-for="post in posts">
<nuxt-link :to="{ name: 'posts-id', params: { id: post.id } }">{{ post.title }}</nuxt-link>
</li>
</ul>
<p><nuxt-link to="/">Back to home page</nuxt-link></p>
</div>
</template>
<script>
import axios from 'axios'
export default {
data ({ req, params }) {
// We can return a Promise instead of calling the callback
return axios.get('https://jsonplaceholder.typicode.com/posts')
.then((res) => {
return { posts: res.data.slice(0, 5) }
})
}
}
</script>
<style scoped>
.container {
width: 70%;
margin: auto;
text-align: center;
padding-top: 100px;
}
ul {
list-style-type: none;
padding: 0;
}
ul li {
border: 1px #ddd solid;
padding: 20px;
text-align: left;
}
ul li a {
color: gray;
}
p {
font-size: 20px;
}
a {
color: #41B883;
}
</style>

View File

@ -16,6 +16,11 @@ module.exports = {
name: 'img/[name].[ext]?[hash]' name: 'img/[name].[ext]?[hash]'
} }
} }
] ],
extend (config, { dev }) {
if (dev) {
config.devtool = (dev ? 'eval-source-map' : false)
}
}
} }
} }

View File

@ -1,6 +1,7 @@
<template> <template>
<div class="container"> <div class="container">
<h1>Sorry, page not found</h1> <h1 v-if="error.statusCode === 404">Page not found</h1>
<h1 v-else>An error occured</h1>
<nuxt-link to="/">Home page</nuxt-link> <nuxt-link to="/">Home page</nuxt-link>
</div> </div>
</template> </template>

View File

@ -1,11 +1,16 @@
{ {
"name": "nuxt-with-ava", "name": "nuxt-with-ava",
"scripts": { "scripts": {
"start": "../../bin/nuxt .", "dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"test": "ava" "test": "ava"
}, },
"devDependencies": { "devDependencies": {
"ava": "^0.16.0", "ava": "^0.16.0",
"jsdom": "^9.8.3" "jsdom": "^9.8.3"
},
"dependencies": {
"nuxt": "^0.9.5"
} }
} }

View File

@ -1,7 +1,5 @@
<template> <template>
<div> <h1 class="red">Hello {{ name }}!</h1>
<p class="red-color">Hello {{ name }}!</p>
</div>
</template> </template>
<script> <script>
@ -13,7 +11,7 @@ export default {
</script> </script>
<style> <style>
.red-color { .red {
color: red; color: red;
} }
</style> </style>

View File

@ -1,48 +1,39 @@
/*
** Test with Ava can be written in ES6 \o/
*/
import test from 'ava' import test from 'ava'
import { createServer } from 'http' import Nuxt from 'nuxt'
import { resolve } from 'path' import { resolve } from 'path'
// We keep the nuxt and server instance
// So we can close them at the end of the test
let nuxt = null let nuxt = null
let server = null let server = null
// Init nuxt.js and create server listening on localhost:4000 // Init Nuxt.js and create a server listening on localhost:4000
test.before('Init Nuxt.js', (t) => { test.before('Init Nuxt.js', async t => {
const Nuxt = require('../../../') const rootDir = resolve(__dirname, '..')
const options = { let config = {}
rootDir: resolve(__dirname, '..'), try { config = require(resolve(rootDir, 'nuxt.config.js')) } catch (e) {}
dev: false config.rootDir = rootDir // project folder
} config.dev = false // production build
nuxt = new Nuxt(options) nuxt = new Nuxt(config)
return nuxt.build() await nuxt.build()
.then(function () { server = new nuxt.Server(nuxt)
server = createServer((req, res) => nuxt.render(req, res))
server.listen(4000, 'localhost') server.listen(4000, 'localhost')
}) })
})
/* // Example of testing only generated html
** Example of testing only the html
*/
test('Route / exits and render HTML', async t => { test('Route / exits and render HTML', async t => {
let context = {} 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.true(html.includes('<h1 class="red">Hello world!</h1>'))
t.is(context.nuxt.error, null)
t.is(context.nuxt.data[0].name, 'world')
}) })
/* // Example of testing via dom checking
** Example of testing via dom checking test('Route / exits and render HTML with CSS applied', async t => {
*/
test('Route / exits and render HTML', async t => {
const window = await nuxt.renderAndGetWindow('http://localhost:4000/') const window = await nuxt.renderAndGetWindow('http://localhost:4000/')
const element = window.document.querySelector('.red-color') const element = window.document.querySelector('.red')
t.not(element, null) t.not(element, null)
t.is(element.textContent, 'Hello world!') t.is(element.textContent, 'Hello world!')
t.is(element.className, 'red-color') t.is(element.className, 'red')
t.is(window.getComputedStyle(element).color, 'red') t.is(window.getComputedStyle(element).color, 'red')
}) })

View File

@ -9,7 +9,7 @@ let layouts = {
<% <%
var layoutsKeys = Object.keys(layouts); var layoutsKeys = Object.keys(layouts);
layoutsKeys.forEach(function (key, i) { %> layoutsKeys.forEach(function (key, i) { %>
_<%= key %>: process.BROWSER_BUILD ? () => System.import('<%= layouts[key] %>') : require('<%= layouts[key] %>')<%= (i + 1) < layoutsKeys.length ? ',' : '' %> "_<%= key %>": process.BROWSER_BUILD ? () => System.import('<%= layouts[key] %>') : require('<%= layouts[key] %>')<%= (i + 1) < layoutsKeys.length ? ',' : '' %>
<% }) %> <% }) %>
} }

View File

@ -216,6 +216,7 @@ function hotReloadAPI (_app) {
}) })
promises.push(p) promises.push(p)
} else if (Component._cData) { } else if (Component._cData) {
Component._data = Component.options.data
Component.options.data = Component._cData Component.options.data = Component._cData
Component._Ctor.options.data = Component.options.data Component._Ctor.options.data = Component.options.data
} }

View File

@ -35,6 +35,9 @@ const scrollBehavior = (to, from, savedPosition) => {
if (to.matched.length < 2) { if (to.matched.length < 2) {
position = { x: 0, y: 0 } position = { x: 0, y: 0 }
} }
else if (to.matched.some((r) => r.components.default.scrollToTop || (r.components.default.options && r.components.default.options.scrollToTop))) {
position = { x: 0, y: 0 }
}
// if link has anchor, scroll to anchor by returning the selector // if link has anchor, scroll to anchor by returning the selector
if (to.hash) { if (to.hash) {
position = { selector: to.hash } position = { selector: to.hash }

View File

@ -280,7 +280,7 @@ function getWebpackServerConfig () {
function createWebpackMiddlewares () { function createWebpackMiddlewares () {
const clientConfig = getWebpackClientConfig.call(this) const clientConfig = getWebpackClientConfig.call(this)
// setup on the fly compilation + hot-reload // setup on the fly compilation + hot-reload
clientConfig.entry.app = ['webpack-hot-middleware/client?reload=true', clientConfig.entry.app] clientConfig.entry.app = _.flatten(['webpack-hot-middleware/client?reload=true', clientConfig.entry.app])
clientConfig.plugins.push( clientConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin() new webpack.NoErrorsPlugin()

View File

@ -64,6 +64,10 @@ module.exports = function () {
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/, exclude: /node_modules/,
query: defaults(this.options.build.babel, { query: defaults(this.options.build.babel, {
plugins: [
'transform-async-to-generator',
'transform-runtime'
],
presets: [ presets: [
['es2015', { modules: false }], ['es2015', { modules: false }],
'stage-2' 'stage-2'

View File

@ -72,5 +72,12 @@ module.exports = function () {
}) })
) )
} }
// Extend config
if (typeof this.options.build.extend === 'function') {
this.options.build.extend(config, {
dev: this.dev,
isClient: true
})
}
return config return config
} }

View File

@ -51,6 +51,12 @@ module.exports = function () {
} }
config.externals = uniq(config.externals) config.externals = uniq(config.externals)
// Return config // Extend config
if (typeof this.options.build.extend === 'function') {
this.options.build.extend(config, {
dev: this.dev,
isServer: true
})
}
return config return config
} }

View File

@ -4,6 +4,10 @@ const { defaults } = require('lodash')
module.exports = function () { module.exports = function () {
let babelOptions = JSON.stringify(defaults(this.options.build.babel, { let babelOptions = JSON.stringify(defaults(this.options.build.babel, {
plugins: [
'transform-async-to-generator',
'transform-runtime'
],
presets: [ presets: [
['es2015', { modules: false }], ['es2015', { modules: false }],
'stage-2' 'stage-2'

View File

@ -12,7 +12,7 @@
</head> </head>
<body <%= m.bodyAttrs.text() %>> <body <%= m.bodyAttrs.text() %>>
<%= APP %> <%= APP %>
<script type="text/javascript" defer>window.__NUXT__=<%= serialize(context.nuxt) %></script> <script type="text/javascript" defer>window.__NUXT__=<%= serialize(context.nuxt, { isJSON: true }) %></script>
<script src="<%= files.vendor %>" defer></script> <script src="<%= files.vendor %>" defer></script>
<script src="<%= files.app %>" defer></script> <script src="<%= files.app %>" defer></script>
</body> </body>

View File

@ -1,6 +1,6 @@
{ {
"name": "nuxt", "name": "nuxt",
"version": "0.9.3", "version": "0.9.5",
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)", "description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
"contributors": [ "contributors": [
{ {
@ -48,14 +48,16 @@
"autoprefixer": "^6.6.0", "autoprefixer": "^6.6.0",
"babel-core": "^6.21.0", "babel-core": "^6.21.0",
"babel-loader": "^6.2.10", "babel-loader": "^6.2.10",
"babel-polyfill": "^6.20.0", "babel-plugin-array-includes": "^2.0.3",
"babel-plugin-transform-async-to-generator": "^6.16.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.18.0", "babel-preset-es2015": "^6.18.0",
"babel-preset-stage-2": "^6.18.0", "babel-preset-stage-2": "^6.18.0",
"chokidar": "^1.6.1", "chokidar": "^1.6.1",
"co": "^4.6.0", "co": "^4.6.0",
"cross-spawn": "^5.0.1", "cross-spawn": "^5.0.1",
"css-loader": "^0.26.1", "css-loader": "^0.26.1",
"debug": "^2.5.1", "debug": "^2.5.2",
"es6-object-assign": "^1.0.3", "es6-object-assign": "^1.0.3",
"es6-promise": "^4.0.5", "es6-promise": "^4.0.5",
"extract-text-webpack-plugin": "2.0.0-beta.4", "extract-text-webpack-plugin": "2.0.0-beta.4",

View File

@ -56,6 +56,16 @@ test('/async-data', async t => {
t.true(html.includes('<p>Nuxt.js</p>')) t.true(html.includes('<p>Nuxt.js</p>'))
}) })
test('/await-async-data', async t => {
const { html } = await nuxt.renderRoute('/await-async-data')
t.true(html.includes('<p>Await Nuxt.js</p>'))
})
test('/callback-async-data', async t => {
const { html } = await nuxt.renderRoute('/callback-async-data')
t.true(html.includes('<p>Callback Nuxt.js</p>'))
})
test('/users/1', async t => { test('/users/1', async t => {
const { html } = await nuxt.renderRoute('/users/1') const { html } = await nuxt.renderRoute('/users/1')
t.true(html.includes('<h1>User: 1</h1>')) t.true(html.includes('<h1>User: 1</h1>'))

View File

@ -0,0 +1,17 @@
<template>
<p>{{ name }}</p>
</template>
<script>
const fetchData = () => {
return new Promise(resolve => {
setTimeout(() => resolve({ name: 'Await Nuxt.js' }), 10)
})
}
export default {
async data () {
return await fetchData()
}
}
</script>

View File

@ -0,0 +1,13 @@
<template>
<p>{{ name }}</p>
</template>
<script>
export default {
async data (context, callback) {
setTimeout(function () {
callback(null, { name: 'Callback Nuxt.js' })
}, 10)
}
}
</script>

View File

@ -0,0 +1,6 @@
<template>
<div>
<h1>Custom env layout</h1>
<nuxt/>
</div>
</template>

View File

@ -9,5 +9,10 @@ module.exports = {
bool: true, bool: true,
num: 23, num: 23,
string: 'Nuxt.js' string: 'Nuxt.js'
},
build: {
extend (config, options) {
config.devtool = 'nosources-source-map'
}
} }
} }

View File

@ -4,6 +4,7 @@
<script> <script>
export default { export default {
layout: 'custom-env',
data ({ env }) { data ({ env }) {
return { env } return { env }
} }

View File

@ -41,6 +41,7 @@ test('/test/about (custom layout)', async t => {
test('/test/env', async t => { test('/test/env', async t => {
const window = await nuxt.renderAndGetWindow(url('/test/env')) const window = await nuxt.renderAndGetWindow(url('/test/env'))
const html = window.document.body.innerHTML const html = window.document.body.innerHTML
t.true(html.includes('<h1>Custom env layout</h1>'))
t.true(html.includes('"bool": true')) t.true(html.includes('"bool": true'))
t.true(html.includes('"num": 23')) t.true(html.includes('"num": 23'))
t.true(html.includes('"string": "Nuxt.js"')) t.true(html.includes('"string": "Nuxt.js"'))

View File

@ -10,16 +10,14 @@ module.exports = {
__filename: false __filename: false
}, },
devtool: 'source-map', devtool: 'source-map',
entry: ['babel-polyfill', r('./lib/nuxt.js')], entry: r('./lib/nuxt.js'),
output: { output: {
path: r('./dist'), path: r('./dist'),
filename: 'nuxt.js', filename: 'nuxt.js',
libraryTarget: 'commonjs2' libraryTarget: 'commonjs2'
}, },
externals: [ externals: [
nodeExternals({ nodeExternals()
whitelist: ['babel-polyfill']
})
], ],
module: { module: {
rules: [ rules: [
@ -32,6 +30,11 @@ module.exports = {
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/, exclude: /node_modules/,
query: { query: {
plugins: [
'transform-async-to-generator',
'array-includes',
'transform-runtime'
],
presets: [ presets: [
['es2015', { modules: false }], ['es2015', { modules: false }],
'stage-2' 'stage-2'