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>
> 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
@ -60,32 +62,20 @@ So far, we get:
- Automatic transpilation and bundling (with webpack and babel)
- Hot code reloading
- Server rendering and indexing of `./pages`
- Server rendering and indexing of `pages/`
- 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
## 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
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
let nuxt = new Nuxt(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)
@ -106,7 +96,7 @@ app.use(nuxt.render)
## 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
nuxt.renderRoute('/about', context)
@ -125,14 +115,7 @@ nuxt.renderRoute('/about', context)
## Examples
Please take a look at the examples/ folder.
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
```
Please take a look at the [examples/](https://github.com/nuxt/nuxt.js/tree/master/examples) directory.
## 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.
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
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`:
```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:
```js
export default {

View File

@ -1,8 +1,9 @@
<template>
<div>
<div class="container">
<h1>User Agent</h1>
<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>
</template>
@ -20,10 +21,16 @@ export default {
</script>
<style scoped>
.container {
width: 70%;
margin: auto;
text-align: center;
padding-top: 100px;
}
p {
font-size: 20px;
text-align: center;
padding: 100px;
padding-bottom: 0;
}
a {
color: #41B883;
}
</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]'
}
}
]
],
extend (config, { dev }) {
if (dev) {
config.devtool = (dev ? 'eval-source-map' : false)
}
}
}
}

View File

@ -1,6 +1,7 @@
<template>
<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>
</div>
</template>

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ let layouts = {
<%
var layoutsKeys = Object.keys(layouts);
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)
} else if (Component._cData) {
Component._data = Component.options.data
Component.options.data = Component._cData
Component._Ctor.options.data = Component.options.data
}

View File

@ -35,6 +35,9 @@ const scrollBehavior = (to, from, savedPosition) => {
if (to.matched.length < 2) {
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 (to.hash) {
position = { selector: to.hash }

View File

@ -280,7 +280,7 @@ function getWebpackServerConfig () {
function createWebpackMiddlewares () {
const clientConfig = getWebpackClientConfig.call(this)
// 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(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()

View File

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

View File

@ -51,6 +51,12 @@ module.exports = function () {
}
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
}

View File

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

View File

@ -12,7 +12,7 @@
</head>
<body <%= m.bodyAttrs.text() %>>
<%= 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.app %>" defer></script>
</body>

View File

@ -1,6 +1,6 @@
{
"name": "nuxt",
"version": "0.9.3",
"version": "0.9.5",
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
"contributors": [
{
@ -48,14 +48,16 @@
"autoprefixer": "^6.6.0",
"babel-core": "^6.21.0",
"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-stage-2": "^6.18.0",
"chokidar": "^1.6.1",
"co": "^4.6.0",
"cross-spawn": "^5.0.1",
"css-loader": "^0.26.1",
"debug": "^2.5.1",
"debug": "^2.5.2",
"es6-object-assign": "^1.0.3",
"es6-promise": "^4.0.5",
"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>'))
})
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 => {
const { html } = await nuxt.renderRoute('/users/1')
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,
num: 23,
string: 'Nuxt.js'
},
build: {
extend (config, options) {
config.devtool = 'nosources-source-map'
}
}
}

View File

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

View File

@ -41,6 +41,7 @@ test('/test/about (custom layout)', async t => {
test('/test/env', async t => {
const window = await nuxt.renderAndGetWindow(url('/test/env'))
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('"num": 23'))
t.true(html.includes('"string": "Nuxt.js"'))

View File

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