mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-31 07:40:33 +00:00
Prototype 0.1.0 working
Alpha 0.1.0
This commit is contained in:
parent
6cce8b161a
commit
8ab135af55
27
.eslintrc.js
Normal file
27
.eslintrc.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
mocha: true
|
||||
},
|
||||
extends: 'standard',
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'html'
|
||||
],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
// allow paren-less arrow functions
|
||||
'arrow-parens': 0,
|
||||
// allow async-await
|
||||
'generator-star-spacing': 0,
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||
},
|
||||
globals: {}
|
||||
}
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,3 @@
|
||||
# build output
|
||||
dist
|
||||
|
||||
# dependencies
|
||||
yarn.lock
|
||||
node_modules
|
||||
|
@ -47,5 +47,6 @@ So far, we get:
|
||||
- Hot code reloading
|
||||
- Server rendering and indexing of `./pages`
|
||||
- Static file serving. `./static/` is mapped to `/static/`
|
||||
- Config file nuxt.config.js
|
||||
|
||||
To see how simple this is, check out the [sample app - nuxtgram](https://github.com/atinux/nuxtgram)
|
||||
|
29
bin/nuxt
Executable file
29
bin/nuxt
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env node --harmony_proxies
|
||||
|
||||
const { join } = require('path')
|
||||
const { spawn } = require('cross-spawn')
|
||||
|
||||
const defaultCommand = 'start'
|
||||
const commands = new Set([
|
||||
defaultCommand,
|
||||
'init'
|
||||
])
|
||||
|
||||
let cmd = process.argv[2]
|
||||
let args
|
||||
|
||||
if (commands.has(cmd)) {
|
||||
args = process.argv.slice(3)
|
||||
} else {
|
||||
cmd = defaultCommand
|
||||
args = process.argv.slice(2)
|
||||
}
|
||||
|
||||
const bin = join(__dirname, 'nuxt-' + cmd)
|
||||
|
||||
const proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] })
|
||||
proc.on('close', (code) => process.exit(code))
|
||||
proc.on('error', (err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
81
bin/nuxt-init
Executable file
81
bin/nuxt-init
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env node --harmony_proxies
|
||||
|
||||
const co = require('co')
|
||||
const mkdirp = require('mkdirp-then')
|
||||
const pify = require('pify')
|
||||
const { resolve, join, basename } = require('path')
|
||||
const { existsSync, writeFile } = require('fs')
|
||||
|
||||
const rootDir = resolve(process.argv.slice(2)[0] || '.')
|
||||
|
||||
if (basename(rootDir) === 'pages') {
|
||||
console.warn('Your root directory is named "pages". This looks suspicious. You probably want to go one directory up.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
co(function * () {
|
||||
yield new Promise((resolve) => setTimeout(resolve, 0)) // avoid undefined variables basePackage, etc.
|
||||
if (!existsSync(rootDir)) {
|
||||
yield mkdirp(rootDir)
|
||||
}
|
||||
if (!existsSync(join(rootDir, 'package.json'))) {
|
||||
yield pify(writeFile)(join(rootDir, 'package.json'), basePackage.replace(/my-app/g, basename(rootDir)))
|
||||
}
|
||||
if (!existsSync(join(rootDir, 'nuxt.config.js'))) {
|
||||
yield pify(writeFile)(join(rootDir, 'nuxt.config.js'), baseConfig)
|
||||
}
|
||||
if (!existsSync(join(rootDir, 'static'))) {
|
||||
yield mkdirp(join(rootDir, 'static'))
|
||||
}
|
||||
if (!existsSync(join(rootDir, 'pages'))) {
|
||||
yield mkdirp(join(rootDir, 'pages'))
|
||||
yield pify(writeFile)(join(rootDir, 'pages', 'index.vue'), basePage)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Nuxt project [' + basename(rootDir) + '] created')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
const basePackage = `{
|
||||
"name": "my-app",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"nuxt": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "nuxt"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const baseConfig = `module.exports = {
|
||||
// Nuxt.js configuration file
|
||||
// Please look at https://nuxtjs.org/docs/config-file
|
||||
}
|
||||
`
|
||||
|
||||
const basePage = `
|
||||
<template>
|
||||
<p>Hello {{ name }}!</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return { name: 'world' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
p {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
padding: 100px;
|
||||
}
|
||||
</style>
|
||||
`
|
70
bin/nuxt-start
Executable file
70
bin/nuxt-start
Executable file
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env node --harmony_proxies
|
||||
|
||||
const http = require('http')
|
||||
const fs = require('fs')
|
||||
const serveStatic = require('serve-static')
|
||||
const Nuxt = require('../')
|
||||
const { resolve } = require('path')
|
||||
|
||||
const rootDir = resolve(process.argv.slice(2)[0] || '.')
|
||||
const nuxtConfigFile = resolve(rootDir, 'nuxt.config.js')
|
||||
let options = {}
|
||||
if (fs.existsSync(nuxtConfigFile)) {
|
||||
options = require(nuxtConfigFile)
|
||||
}
|
||||
if (typeof options.rootDir !== 'string') {
|
||||
options.rootDir = rootDir
|
||||
}
|
||||
|
||||
new Nuxt(options)
|
||||
.then((nuxt) => {
|
||||
new Server(nuxt)
|
||||
.listen(process.env.PORT, process.env.HOST)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
process.exit()
|
||||
})
|
||||
|
||||
class Server {
|
||||
|
||||
constructor (nuxt) {
|
||||
this.server = http.createServer(this.handle.bind(this))
|
||||
this.staticServer = serveStatic('static', { fallthrough: false })
|
||||
this.nuxt = nuxt
|
||||
return this
|
||||
}
|
||||
|
||||
handle (req, res) {
|
||||
const method = req.method.toUpperCase()
|
||||
|
||||
if (method !== 'GET' && method !== 'HEAD') {
|
||||
return this.nuxt.render(req, res)
|
||||
}
|
||||
this._staticHandler(req, res)
|
||||
.catch((e) => {
|
||||
// File not found
|
||||
this.nuxt.render(req, res)
|
||||
})
|
||||
}
|
||||
|
||||
listen (port, host) {
|
||||
host = host || 'localhost'
|
||||
port = port || 3000
|
||||
this.server.listen(port, host, () => {
|
||||
console.log('Ready on http://%s:%s', host, port)
|
||||
})
|
||||
}
|
||||
|
||||
_staticHandler (req, res) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.staticServer(req, res, (error) => {
|
||||
if (!error) {
|
||||
return resolve()
|
||||
}
|
||||
error.message = `Route ${error.message} while resolving ${req.url}`
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
18
examples/basic-css/pages/index.vue
Executable file
18
examples/basic-css/pages/index.vue
Executable file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="basic-css">
|
||||
<p>Hello World</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.basic-css {
|
||||
font: 15px Helvetica, Arial, sans-serif;
|
||||
background: #eee;
|
||||
padding: 100px;
|
||||
text-align: center;
|
||||
transition: 100ms ease-in background;
|
||||
}
|
||||
.basic-css:hover {
|
||||
background: #ccc
|
||||
}
|
||||
</style>
|
15
examples/head-elements/pages/index.vue
Executable file
15
examples/head-elements/pages/index.vue
Executable file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<h1>This page has a title 🤔</h1>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
metaInfo: {
|
||||
title: 'This page has a title 🤔',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
]
|
||||
}
|
||||
}
|
||||
</script>
|
3
examples/hello-world/pages/about.vue
Executable file
3
examples/hello-world/pages/about.vue
Executable file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>About us</div>
|
||||
</template>
|
3
examples/hello-world/pages/index.vue
Executable file
3
examples/hello-world/pages/index.vue
Executable file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>Hello World. <router-link to='/about'>About</router-link></div>
|
||||
</template>
|
12
examples/nested-components/components/paragraph.vue
Executable file
12
examples/nested-components/components/paragraph.vue
Executable file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<p class="p">
|
||||
<slot></slot>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.p {
|
||||
font: 13px Helvetica, Arial;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
25
examples/nested-components/components/post.vue
Executable file
25
examples/nested-components/components/post.vue
Executable file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<h1 class="title">{{ title }}</h1>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['title']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main {
|
||||
font: 15px Helvetica, Arial;
|
||||
border: 1px solid #eee;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin: 10px 0
|
||||
}
|
||||
</style>
|
54
examples/nested-components/pages/index.vue
Executable file
54
examples/nested-components/pages/index.vue
Executable file
@ -0,0 +1,54 @@
|
||||
<template lang="html">
|
||||
<div class="main">
|
||||
<post title="My first blog post">
|
||||
<v-p>Hello there</v-p>
|
||||
<v-p>This is an example of a componentized blog post</v-p>
|
||||
</post>
|
||||
|
||||
<v-hr/>
|
||||
|
||||
<post title="My second blog post">
|
||||
<v-p>Hello there</v-p>
|
||||
<v-p>This is another example.</v-p>
|
||||
<v-p>Wa-hoo!</v-p>
|
||||
</post>
|
||||
|
||||
<v-hr/>
|
||||
|
||||
<post title="The final blog post">
|
||||
<v-p>C'est la fin !</v-p>
|
||||
</post>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Post from '../components/post.vue'
|
||||
import vP from '../components/paragraph.vue'
|
||||
const vHr = { render: (h) => h('hr', { class: 'hr' }) }
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Post,
|
||||
vP,
|
||||
vHr
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main {
|
||||
margin: auto;
|
||||
max-width: 420px;
|
||||
padding: 10px;
|
||||
}
|
||||
.hr {
|
||||
width: 100px;
|
||||
border-width: 0;
|
||||
margin: 20px auto;
|
||||
text-align: center;
|
||||
}
|
||||
hr::before {
|
||||
content: '***';
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
25
examples/with-ava/Readme.md
Executable file
25
examples/with-ava/Readme.md
Executable file
@ -0,0 +1,25 @@
|
||||
## Add testing to your `nuxt` app using `ava` and `jsdom`
|
||||
|
||||
[`ava`](https://github.com/avajs/ava) is a powerful JavaScript testing framework, mixed with [`jsdom`](https://github.com/tmpvar/jsdom), we can use them to do end-to-end testing easily for `nuxt` applications.
|
||||
|
||||
```bash
|
||||
npm install --save-dev ava jsdom
|
||||
```
|
||||
|
||||
Add test script to the `package.json`
|
||||
|
||||
__package.json__
|
||||
|
||||
```javascript
|
||||
// ...
|
||||
"scripts": {
|
||||
"test": "ava",
|
||||
}
|
||||
// ...
|
||||
|
||||
```
|
||||
|
||||
Launch the tests:
|
||||
```bash
|
||||
npm test
|
||||
```
|
11
examples/with-ava/package.json
Executable file
11
examples/with-ava/package.json
Executable file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "ava-tests",
|
||||
"scripts": {
|
||||
"start": "../../bin/nuxt .",
|
||||
"test": "ava"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^0.16.0",
|
||||
"jsdom": "^9.8.3"
|
||||
}
|
||||
}
|
19
examples/with-ava/pages/index.vue
Executable file
19
examples/with-ava/pages/index.vue
Executable file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="red-color">Hello {{ name }}!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return { name: 'world' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.red-color {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
77
examples/with-ava/test/index.test.js
Executable file
77
examples/with-ava/test/index.test.js
Executable file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
** Test with Ava can be written in ES6 \o/
|
||||
*/
|
||||
import test from 'ava'
|
||||
import jsdom from 'jsdom'
|
||||
import { createServer } from 'http'
|
||||
import { resolve } from 'path'
|
||||
|
||||
let nuxt = null
|
||||
let server = null
|
||||
|
||||
// Init nuxt.js and create server listening on localhost:4000
|
||||
test.before('Init nuxt.js', (t) => {
|
||||
process.env.NODE_ENV = 'test'
|
||||
const Nuxt = require('../../../')
|
||||
const options = {
|
||||
rootDir: resolve(__dirname, '..')
|
||||
}
|
||||
return new Nuxt(options)
|
||||
.then(function (_nuxt) {
|
||||
nuxt = _nuxt
|
||||
server = createServer((req, res) => nuxt.render(req, res))
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(4000, 'localhost', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Function used to do dom checking via jsdom
|
||||
async function renderAndGetWindow (route) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = 'http://localhost:4000' + route
|
||||
jsdom.env({
|
||||
url: url,
|
||||
features: {
|
||||
FetchExternalResources: ['script', 'link'],
|
||||
ProcessExternalResources: ['script']
|
||||
},
|
||||
done (err, window) {
|
||||
if (err) return reject(err)
|
||||
// Used by nuxt.js to say when the components are loaded and the app ready
|
||||
window.onNuxtReady = function () {
|
||||
resolve(window)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
** Example of testing only the 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')
|
||||
})
|
||||
|
||||
/*
|
||||
** Example of testing via dom checking
|
||||
*/
|
||||
test('Route / exits and render HTML', async t => {
|
||||
const window = await renderAndGetWindow('/')
|
||||
t.is(window.document.querySelector('p').textContent, 'Hello world!')
|
||||
t.is(window.document.querySelector('p').className, 'red-color')
|
||||
t.true(window.document.querySelectorAll('style')[2].textContent.includes('.red-color {\n color: red;\n}'))
|
||||
})
|
||||
|
||||
// Close server and ask nuxt to stop listening to file changes
|
||||
test.after('Closing server and nuxt.js', t => {
|
||||
server.close()
|
||||
nuxt.stop()
|
||||
})
|
8
index.js
Normal file
8
index.js
Normal file
@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* nuxt.js
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
module.exports = require('./lib/nuxt')
|
40
lib/app/App.vue
Normal file
40
lib/app/App.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<% if (loading) { %><loading ref="loading"></loading><% } %>
|
||||
<router-view v-if="!err"></router-view>
|
||||
<error-page v-if="err" :error="err"></error-page>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ErrorPage from '<%= components.ErrorPage %>'
|
||||
<% if (loading) { %>import Loading from '<%= (typeof loading === "string" ? loading : "./components/Loading.vue") %>'<% } %>
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
err: null
|
||||
}
|
||||
},
|
||||
<% if (loading) { %>
|
||||
mounted () {
|
||||
this.$loading = this.$refs.loading
|
||||
},
|
||||
<% } %>
|
||||
methods: {
|
||||
error (err) {
|
||||
err = err || null
|
||||
this.err = err || null
|
||||
<% if (loading) { %>
|
||||
if (this.err && this.$loading) {
|
||||
this.$loading.fail && this.$loading.fail()
|
||||
}
|
||||
<% } %>
|
||||
return this.err
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ErrorPage
|
||||
}
|
||||
}
|
||||
</script>
|
179
lib/app/client.js
Normal file
179
lib/app/client.js
Normal file
@ -0,0 +1,179 @@
|
||||
require('es6-promise').polyfill()
|
||||
require('es6-object-assign').polyfill()
|
||||
import Vue from 'vue'
|
||||
import { app, router<%= (store ? ', store' : '') %> } from './index'
|
||||
import { getMatchedComponents, flatMapComponents, getContext, getLocation } from './utils'
|
||||
const noopData = () => { return {} }
|
||||
const noopFetch = () => {}
|
||||
|
||||
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) => {
|
||||
// console.log('Component loaded', Component, match.path, key)
|
||||
match.components[key] = Component
|
||||
resolve(Component)
|
||||
}
|
||||
Component().then(_resolve).catch(reject)
|
||||
})
|
||||
}
|
||||
// console.log('Return Component', match)
|
||||
return Component
|
||||
})
|
||||
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
|
||||
Promise.all(resolveComponents)
|
||||
.then(() => next())
|
||||
.catch((err) => {
|
||||
this.error({ statusCode: 500, message: err.message })
|
||||
next(false)
|
||||
})
|
||||
}
|
||||
|
||||
function render (to, from, next) {
|
||||
let Components = getMatchedComponents(to)
|
||||
if (!Components.length) {
|
||||
this.error({ statusCode: 404, message: 'This page could not be found.', url: to.path })
|
||||
return next()
|
||||
}
|
||||
// console.log('Load components', Components, to.path)
|
||||
// Update ._data and other properties if hot reloaded
|
||||
Components.forEach(function (Component) {
|
||||
if (!Component._data) {
|
||||
Component._data = Component.data || noopData
|
||||
}
|
||||
if (Component._Ctor && Component._Ctor.options) {
|
||||
Component.fetch = Component._Ctor.options.fetch
|
||||
const originalDataFn = Component._data.toString().replace(/\s/g, '')
|
||||
const dataFn = (Component.data || noopData).toString().replace(/\s/g, '')
|
||||
const newDataFn = (Component._Ctor.options.data || noopData).toString().replace(/\s/g, '')
|
||||
// If component data method changed
|
||||
if (newDataFn !== originalDataFn && newDataFn !== dataFn) {
|
||||
Component._data = Component._Ctor.options.data || noopData
|
||||
}
|
||||
}
|
||||
})
|
||||
this.error()
|
||||
Promise.all(Components.map((Component) => {
|
||||
let promises = []
|
||||
const context = getContext({ to<%= (store ? ', store' : '') %>, isClient: true })
|
||||
if (Component._data && typeof Component._data === 'function') {
|
||||
var promise = Component._data(context)
|
||||
if (!(promise instanceof Promise)) promise = Promise.resolve(promise)
|
||||
promise.then((data) => {
|
||||
Component.data = () => data
|
||||
if (Component._Ctor && Component._Ctor.options) {
|
||||
Component._Ctor.options.data = Component.data
|
||||
}
|
||||
<%= (loading ? 'this.$loading.start && this.$loading.increase(30)' : '') %>
|
||||
})
|
||||
promises.push(promise)
|
||||
}
|
||||
if (Component.fetch) {
|
||||
var p = Component.fetch(context)
|
||||
<%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
|
||||
promises.push(p)
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}))
|
||||
.then(() => {
|
||||
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
|
||||
next()
|
||||
})
|
||||
.catch(function (error) {
|
||||
this.error(error)
|
||||
next(false)
|
||||
})
|
||||
}
|
||||
|
||||
// Special hot reload with data(context)
|
||||
function hotReloadAPI (_app) {
|
||||
var _forceUpdate = _app.$forceUpdate.bind(_app)
|
||||
_app.$forceUpdate = function () {
|
||||
let Component = getMatchedComponents(router.currentRoute)[0]
|
||||
if (!Component) return _forceUpdate()
|
||||
<%= (loading ? 'this.$loading.start && this.$loading.start()' : '') %>
|
||||
let promises = []
|
||||
const context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true })
|
||||
// Check if data has been updated
|
||||
const originalDataFn = (Component._data || noopData).toString().replace(/\s/g, '')
|
||||
const newDataFn = (Component._Ctor.options.data || noopData).toString().replace(/\s/g, '')
|
||||
if (originalDataFn !== newDataFn) {
|
||||
Component._data = Component._Ctor.options.data
|
||||
let p = Component._data(context)
|
||||
if (!(p instanceof Promise)) { p = Promise.resolve(p) }
|
||||
p.then((data) => {
|
||||
Component.data = () => data
|
||||
Component._Ctor.options.data = Component.data
|
||||
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
|
||||
})
|
||||
promises.push(p)
|
||||
}
|
||||
// Check if fetch has been updated
|
||||
const originalFetchFn = (Component.fetch || noopFetch).toString().replace(/\s/g, '')
|
||||
const newFetchFn = (Component._Ctor.options.fetch || noopFetch).toString().replace(/\s/g, '')
|
||||
// Fetch has been updated, we call it to update the store
|
||||
if (originalFetchFn !== newFetchFn) {
|
||||
Component.fetch = Component._Ctor.options.fetch
|
||||
let p = Component.fetch(context)
|
||||
if (!(p instanceof Promise)) { p = Promise.resolve(p) }
|
||||
<%= (loading ? 'p.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
|
||||
promises.push(p)
|
||||
}
|
||||
return Promise.all(promises).then(() => {
|
||||
<%= (loading ? 'this.$loading.finish && this.$loading.finish(30)' : '') %>
|
||||
_forceUpdate()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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.')
|
||||
}
|
||||
<% if (store) { %>
|
||||
// Replace store state
|
||||
if (NUXT.state) {
|
||||
store.replaceState(NUXT.state)
|
||||
}
|
||||
<% } %>
|
||||
// Get matched components
|
||||
const path = getLocation(router.options.base)
|
||||
const resolveComponents = flatMapComponents(router.match(path), (Component, _, match, key, index) => {
|
||||
if (typeof Component === 'function' && !Component.options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const _resolve = (Component) => {
|
||||
if (Component.data && typeof Component.data === 'function') {
|
||||
Component._data = Component.data
|
||||
Component.data = () => NUXT.data[index]
|
||||
if (Component._Ctor && Component._Ctor.options) {
|
||||
Component._Ctor.options.data = Component.data
|
||||
}
|
||||
}
|
||||
match.components[key] = Component
|
||||
resolve(Component)
|
||||
}
|
||||
Component().then(_resolve).catch(reject)
|
||||
})
|
||||
}
|
||||
return Component
|
||||
})
|
||||
|
||||
Promise.all(resolveComponents)
|
||||
.then((Components) => {
|
||||
const _app = new Vue(app)
|
||||
if (NUXT.error) _app.error(NUXT.error)
|
||||
if (module.hot) hotReloadAPI(_app)
|
||||
_app.$mount('#app')
|
||||
// Add router hooks
|
||||
router.beforeEach(loadAsyncComponents.bind(_app))
|
||||
router.beforeEach(render.bind(_app))
|
||||
// Call window.onModulesLoaded for jsdom testing (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
|
||||
if (typeof window.onNuxtReady === 'function') {
|
||||
window.onNuxtReady()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('[Nuxt.js] Cannot load components', err)
|
||||
})
|
31
lib/app/components/Loading.vue
Normal file
31
lib/app/components/Loading.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<p>LOADING...</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
duration: <%= loading.duration %>,
|
||||
loadingColor: '<%= loading.loadingColor %>',
|
||||
errorColor: '<%= loading.errorColor %>',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
start () {},
|
||||
increase () {},
|
||||
decrease () {},
|
||||
set () {},
|
||||
finish () {},
|
||||
pause () {},
|
||||
hide () {},
|
||||
fail () {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
color: grey;
|
||||
}
|
||||
</style>
|
24
lib/app/index.js
Normal file
24
lib/app/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
// 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'
|
||||
import router from './router'
|
||||
<% if (store && storePath) { %>import store from '<%= storePath %>'<% } %>
|
||||
|
||||
// import VueProgressBar from './plugins/vue-progressbar'
|
||||
// Vue.use(VueProgressBar, {
|
||||
// color: '#efc14e',
|
||||
// failedColor: 'red',
|
||||
// height: '2px'
|
||||
// })
|
||||
|
||||
import App from './App.vue'
|
||||
// create the app instance.
|
||||
// here we inject the router and store to all child components,
|
||||
// making them available everywhere as `this.$router` and `this.$store`.
|
||||
const app = {
|
||||
router,
|
||||
<%= (store ? 'store,' : '') %>
|
||||
...App
|
||||
}
|
||||
|
||||
export { app, router<%= (store ? ', store' : '') %> }
|
38
lib/app/router.js
Normal file
38
lib/app/router.js
Normal file
@ -0,0 +1,38 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Meta from 'vue-meta'
|
||||
|
||||
Vue.use(Router)
|
||||
Vue.use(Meta)
|
||||
|
||||
<% routes.forEach(function (route) { %>
|
||||
const <%= route._name %> = process.BROWSER ? () => System.import('<%= route._component %>') : require('<%= route._component %>')
|
||||
<% }) %>
|
||||
|
||||
const scrollBehavior = (to, from, savedPosition) => {
|
||||
if (savedPosition) {
|
||||
// savedPosition is only available for popstate navigations.
|
||||
return savedPosition
|
||||
} else {
|
||||
// Scroll to the top by default
|
||||
let position = { x: 0, y: 0 }
|
||||
// if link has anchor, scroll to anchor by returning the selector
|
||||
if (to.hash) {
|
||||
position = { selector: to.hash }
|
||||
}
|
||||
return position
|
||||
}
|
||||
}
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
scrollBehavior,
|
||||
routes: [
|
||||
<% routes.forEach((route, i) => { %>
|
||||
{
|
||||
path: '<%= route.path %>',
|
||||
component: <%= route._name %>
|
||||
}<%= (i + 1 === routes.length ? '' : ',') %>
|
||||
<% }) %>
|
||||
]
|
||||
})
|
70
lib/app/server.js
Normal file
70
lib/app/server.js
Normal file
@ -0,0 +1,70 @@
|
||||
const debug = require('debug')('nuxt:render')
|
||||
import Vue from 'vue'
|
||||
import { pick } from 'lodash'
|
||||
import { app, router<%= (store ? ', store' : '') %> } from './index'
|
||||
import { getMatchedComponents, getContext } from './utils'
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production'
|
||||
const _app = new Vue(app)
|
||||
|
||||
// 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 context => {
|
||||
// set router's location
|
||||
router.push(context.url)
|
||||
|
||||
// Add route to the context
|
||||
context.route = router.currentRoute
|
||||
// Add meta infos
|
||||
context.meta = _app.$meta()
|
||||
// Add store to the context
|
||||
<%= (store ? 'context.store = store' : '') %>
|
||||
|
||||
// Nuxt object
|
||||
context.nuxt = { data: [], error: null<%= (store ? ', state: null' : '') %> }
|
||||
|
||||
<%= (isDev ? 'const s = isDev && Date.now()' : '') %>
|
||||
// Call data & fecth hooks on components matched by the route.
|
||||
let Components = getMatchedComponents(context.route)
|
||||
if (!Components.length) {
|
||||
context.nuxt.error = _app.error({ statusCode: 404, message: 'This page could not be found.', url: context.route.path })
|
||||
<%= (store ? 'context.nuxt.state = store.state' : '') %>
|
||||
return Promise.resolve(_app)
|
||||
}
|
||||
return Promise.all(Components.map((Component) => {
|
||||
let promises = []
|
||||
if (Component.data && typeof Component.data === 'function') {
|
||||
Component._data = Component.data
|
||||
var promise = Component.data(getContext(context))
|
||||
if (!(promise instanceof Promise)) promise = Promise.resolve(promise)
|
||||
promise.then((data) => {
|
||||
Component.data = () => data
|
||||
})
|
||||
promises.push(promise)
|
||||
} else {
|
||||
promises.push(null)
|
||||
}
|
||||
if (Component.fetch) {
|
||||
promises.push(Component.fetch(getContext(context)))
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}))
|
||||
.then((res) => {
|
||||
<% if (isDev) { %>
|
||||
debug('Data fetch ' + context.req.url + ': ' + (Date.now() - s) + 'ms')
|
||||
<% } %>
|
||||
// datas are the first row of each
|
||||
context.nuxt.data = res.map((tab) => tab[0])
|
||||
<%= (store ? '// Add the state from the vuex store' : '') %>
|
||||
<%= (store ? 'context.nuxt.state = store.state' : '') %>
|
||||
return _app
|
||||
})
|
||||
.catch(function (error) {
|
||||
context.nuxt.error = _app.error(error)
|
||||
<%= (store ? 'context.nuxt.state = store.state' : '') %>
|
||||
return _app
|
||||
})
|
||||
}
|
38
lib/app/utils.js
Normal file
38
lib/app/utils.js
Normal file
@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
export function getMatchedComponents (route) {
|
||||
return [].concat.apply([], route.matched.map(function (m) {
|
||||
return Object.keys(m.components).map(function (key) {
|
||||
return m.components[key]
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export function flatMapComponents (route, fn) {
|
||||
return Array.prototype.concat.apply([], route.matched.map(function (m, index) {
|
||||
return Object.keys(m.components).map(function (key) {
|
||||
return fn(m.components[key], m.instances[key], m, key, index)
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export function getContext (context) {
|
||||
let ctx = {
|
||||
isServer: !!context.isServer,
|
||||
isClient: !!context.isClient,
|
||||
<%= (store ? 'store: context.store,' : '') %>
|
||||
route: (context.to ? context.to : context.route)
|
||||
}
|
||||
if (context.req) ctx.req = context.req
|
||||
if (context.res) ctx.req = context.res
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Imported from vue-router
|
||||
export function getLocation (base) {
|
||||
var path = window.location.pathname
|
||||
if (base && path.indexOf(base) === 0) {
|
||||
path = path.slice(base.length)
|
||||
}
|
||||
return (path || '/') + window.location.search + window.location.hash
|
||||
}
|
248
lib/build/index.js
Normal file
248
lib/build/index.js
Normal file
@ -0,0 +1,248 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('nuxt:build')
|
||||
const _ = require('lodash')
|
||||
const del = require('del')
|
||||
const fs = require('fs')
|
||||
const glob = require('glob-promise')
|
||||
const hash = require('hash-sum')
|
||||
const mkdirp = require('mkdirp-then')
|
||||
const pify = require('pify')
|
||||
const webpack = require('webpack')
|
||||
const { createBundleRenderer } = require('vue-server-renderer')
|
||||
const { join, resolve } = require('path')
|
||||
const r = resolve
|
||||
|
||||
module.exports = function * () {
|
||||
/*
|
||||
** Check if pages dir exists and warn if not
|
||||
*/
|
||||
if (!fs.existsSync(join(this.dir, 'pages'))) {
|
||||
if (fs.existsSync(join(this.dir, '..', 'pages'))) {
|
||||
console.error('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?')
|
||||
} else {
|
||||
console.error('> Couldn\'t find a `pages` directory. Please create one under the project root')
|
||||
}
|
||||
process.exit()
|
||||
}
|
||||
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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
debug(`App root: ${this.dir}`)
|
||||
debug('Generating .nuxt/ files...')
|
||||
/*
|
||||
** Create .nuxt/, .nuxt/components and .nuxt/dist folders
|
||||
*/
|
||||
yield del(r(this.dir, '.nuxt'), { force: process.env.NODE_ENV === 'test' })
|
||||
yield mkdirp(r(this.dir, '.nuxt/components'))
|
||||
if (this.isProd) {
|
||||
yield mkdirp(r(this.dir, '.nuxt/dist'))
|
||||
}
|
||||
/*
|
||||
** Generate routes based on files
|
||||
*/
|
||||
const files = yield glob('pages/**/*.vue', { cwd: this.dir })
|
||||
let routes = []
|
||||
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) => {
|
||||
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
|
||||
})
|
||||
/*
|
||||
** Interpret and move template files to .nuxt/
|
||||
*/
|
||||
let templatesFiles = [
|
||||
'App.vue',
|
||||
'client.js',
|
||||
'index.js',
|
||||
'router.js',
|
||||
'server.js',
|
||||
'utils.js',
|
||||
'components/Loading.vue'
|
||||
]
|
||||
let templateVars = {
|
||||
isDev: this.isDev,
|
||||
store: this.options.store,
|
||||
loading: (this.options.loading === 'string' ? r(this.dir, this.options.loading) : this.options.loading),
|
||||
components: {
|
||||
Loading: r(__dirname, '..', 'app', 'components', 'Loading.vue'),
|
||||
ErrorPage: r(__dirname, '..', '..', 'pages', (this.isDev ? '_error-debug.vue' : '_error.vue'))
|
||||
},
|
||||
routes: this.options.routes
|
||||
}
|
||||
if (this.options.store) {
|
||||
templateVars.storePath = r(this.dir, 'store')
|
||||
}
|
||||
if (this.isDev && files.includes('pages/_error-debug.vue')) {
|
||||
templateVars.components.ErrorPage = r(this.dir, 'pages/_error-debug.vue')
|
||||
}
|
||||
if (!this.isDev && files.includes('pages/_error.vue')) {
|
||||
templateVars.components.ErrorPage = r(this.dir, 'pages/_error.vue')
|
||||
}
|
||||
const readFile = pify(fs.readFile)
|
||||
const writeFile = pify(fs.writeFile)
|
||||
let moveTemplates = templatesFiles.map((file) => {
|
||||
return readFile(r(__dirname, '..', 'app', file), 'utf8')
|
||||
.then((fileContent) => {
|
||||
const template = _.template(fileContent)
|
||||
const content = template(templateVars)
|
||||
return writeFile(r(this.dir, '.nuxt', file), content, 'utf8')
|
||||
})
|
||||
})
|
||||
yield moveTemplates
|
||||
debug('Files moved!')
|
||||
/*
|
||||
** Generate .nuxt/dist/ files
|
||||
*/
|
||||
if (this.isDev) {
|
||||
debug('Adding webpack middlewares...')
|
||||
createWebpackMiddlewares.call(this)
|
||||
webpackWatchAndUpdate.call(this)
|
||||
} else {
|
||||
debug('Building files...')
|
||||
yield [
|
||||
webpackRunClient.call(this),
|
||||
webpackRunServer.call(this)
|
||||
]
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
function getWebpackClientConfig () {
|
||||
var config = require(r(__dirname, 'webpack', 'client.config.js'))
|
||||
// Entry
|
||||
config.entry.app = r(this.dir, '.nuxt', 'client.js')
|
||||
// Add vendors
|
||||
if (this.options.store) config.entry.vendor.push('vuex')
|
||||
config.entry.vendor = config.entry.vendor.concat(this.options.vendor)
|
||||
// extract vendor chunks for better caching
|
||||
config.plugins.push(
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
filename: this.options.filenames.vendor
|
||||
})
|
||||
)
|
||||
// Output
|
||||
config.output.path = r(this.dir, '.nuxt', 'dist')
|
||||
config.output.filename = this.options.filenames.app
|
||||
// Extract text plugin
|
||||
if (this.isProd) {
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
let plugin = config.plugins.find((plugin) => plugin instanceof ExtractTextPlugin)
|
||||
if (plugin) plugin.filename = this.options.filenames.css
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
function getWebpackServerConfig () {
|
||||
var config = require(r(__dirname, 'webpack', 'server.config.js'))
|
||||
// Entry
|
||||
config.entry = r(this.dir, '.nuxt', 'server.js')
|
||||
// Output
|
||||
config.output.path = r(this.dir, '.nuxt', 'dist')
|
||||
// Externals
|
||||
config.externals = Object.keys(require(r(__dirname, '..', '..', 'package.json')).dependencies || {})
|
||||
const projectPackageJson = r(this.dir, 'package.json')
|
||||
if (fs.existsSync(projectPackageJson)) {
|
||||
config.externals = [].concat(Object.keys(require(r(this.dir, 'package.json')).dependencies || {}))
|
||||
}
|
||||
config.externals = _.uniq(config.externals)
|
||||
return config
|
||||
}
|
||||
|
||||
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.plugins.push(
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin()
|
||||
)
|
||||
const clientCompiler = webpack(clientConfig)
|
||||
// Add the middlewares to the instance context
|
||||
this.webpackDevMiddleware = pify(require('webpack-dev-middleware')(clientCompiler, {
|
||||
publicPath: clientConfig.output.publicPath,
|
||||
stats: {
|
||||
colors: true,
|
||||
chunks: false
|
||||
},
|
||||
quiet: true,
|
||||
noInfo: true
|
||||
}))
|
||||
this.webpackHotMiddleware = pify(require('webpack-hot-middleware')(clientCompiler))
|
||||
}
|
||||
|
||||
function webpackWatchAndUpdate () {
|
||||
const MFS = require('memory-fs') // <- dependencies of webpack
|
||||
const mfs = new MFS()
|
||||
const serverConfig = getWebpackServerConfig.call(this)
|
||||
const serverCompiler = webpack(serverConfig)
|
||||
const outputPath = join(serverConfig.output.path, serverConfig.output.filename)
|
||||
serverCompiler.outputFileSystem = mfs
|
||||
this.webpackServerWatcher = serverCompiler.watch({}, (err, stats) => {
|
||||
if (err) throw err
|
||||
stats = stats.toJson()
|
||||
stats.errors.forEach(err => console.error(err))
|
||||
stats.warnings.forEach(err => console.warn(err))
|
||||
createRenderer.call(this, mfs.readFileSync(outputPath, 'utf-8'))
|
||||
})
|
||||
}
|
||||
|
||||
function webpackRunClient () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const clientConfig = getWebpackClientConfig.call(this)
|
||||
const serverCompiler = webpack(clientConfig)
|
||||
serverCompiler.run((err, stats) => {
|
||||
if (err) return reject(err)
|
||||
debug('[webpack:build:client]\n', stats.toString({ chunks: false, colors: true }))
|
||||
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)
|
||||
debug('[webpack:build:server]\n', stats.toString({ chunks: false, colors: true }))
|
||||
const bundlePath = join(serverConfig.output.path, serverConfig.output.filename)
|
||||
createRenderer.call(this, fs.readFileSync(bundlePath, 'utf8'))
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function createRenderer (bundle) {
|
||||
process.env.VUE_ENV = (process.env.VUE_ENV ? process.env.VUE_ENV : 'server')
|
||||
// Create bundle renderer to give a fresh context for every request
|
||||
let cacheConfig = false
|
||||
if (this.options.cache) {
|
||||
this.options.cache = (typeof this.options.cache !== 'object' ? {} : this.options.cache)
|
||||
cacheConfig = require('lru-cache')(_.defaults(this.options.cache, {
|
||||
max: 1000,
|
||||
maxAge: 1000 * 60 * 15
|
||||
}))
|
||||
}
|
||||
this.renderer = createBundleRenderer(bundle, {
|
||||
cache: cacheConfig
|
||||
})
|
||||
this.renderToString = pify(this.renderer.renderToString)
|
||||
this.renderToStream = this.renderer.renderToStream
|
||||
}
|
52
lib/build/webpack/base.config.js
Normal file
52
lib/build/webpack/base.config.js
Normal file
@ -0,0 +1,52 @@
|
||||
const vueLoaderConfig = require('./vue-loader.config')
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webpack Shared Config
|
||||
|
|
||||
| This is the config which is extented by the server and client
|
||||
| webpack config files
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
entry: {
|
||||
vendor: ['vue', 'vue-router', 'vue-meta', 'es6-promise', 'es6-object-assign']
|
||||
},
|
||||
output: {
|
||||
publicPath: '/_nuxt/'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
presets: ['es2015', 'stage-2']
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'url',
|
||||
options: {
|
||||
limit: 1000, // 1KO
|
||||
name: 'img/[name].[ext]?[hash]'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url',
|
||||
query: {
|
||||
limit: 1000, // 1 KO
|
||||
name: 'fonts/[name].[hash:7].[ext]'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
55
lib/build/webpack/client.config.js
Normal file
55
lib/build/webpack/client.config.js
Normal file
@ -0,0 +1,55 @@
|
||||
const webpack = require('webpack')
|
||||
const base = require('./base.config')
|
||||
const vueConfig = require('./vue-loader.config')
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webpack Client Config
|
||||
|
|
||||
| Generate public/dist/client-vendor-bundle.js
|
||||
| Generate public/dist/client-bundle.js
|
||||
|
|
||||
| In production, will generate public/dist/style.css
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
const config = Object.assign({}, base, {
|
||||
plugins: (base.plugins || []).concat([
|
||||
// strip comments in Vue code
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
'process.BROWSER': true
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// Use ExtractTextPlugin to extract CSS into a single file
|
||||
// so it's applied on initial render
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
|
||||
// vueConfig is already included in the config via LoaderOptionsPlugin
|
||||
// here we overwrite the loader config for <style lang='stylus'>
|
||||
// so they are extracted.
|
||||
vueConfig.loaders.css = ExtractTextPlugin.extract({ loader: 'css-loader' })
|
||||
vueConfig.loaders.scss = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader', fallbackLoader: 'vue-style-loader' })
|
||||
vueConfig.loaders.sass = ExtractTextPlugin.extract({ loader: 'css-loader!sass-loader?indentedSyntax', fallbackLoader: 'vue-style-loader' })
|
||||
vueConfig.loaders.stylus = ExtractTextPlugin.extract({ loader: 'css-loader!stylus-loader', fallbackLoader: 'vue-style-loader' })
|
||||
vueConfig.loaders.less = ExtractTextPlugin.extract({ loader: 'css-loader!less-loader', fallbackLoader: 'vue-style-loader' })
|
||||
|
||||
config.plugins.push(
|
||||
new ExtractTextPlugin('style.css'),
|
||||
// this is needed in webpack 2 for minifying CSS
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
}),
|
||||
// minify JS
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = config
|
23
lib/build/webpack/server.config.js
Normal file
23
lib/build/webpack/server.config.js
Normal file
@ -0,0 +1,23 @@
|
||||
const webpack = require('webpack')
|
||||
const base = require('./base.config')
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Webpack Server Config
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
module.exports = Object.assign({}, base, {
|
||||
target: 'node',
|
||||
devtool: false,
|
||||
output: Object.assign({}, base.output, {
|
||||
filename: 'server-bundle.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
}),
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
'process.env.VUE_ENV': '"server"',
|
||||
'process.BROWSER': false
|
||||
})
|
||||
]
|
||||
})
|
16
lib/build/webpack/vue-loader.config.js
Normal file
16
lib/build/webpack/vue-loader.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
postcss: [
|
||||
require('autoprefixer')({
|
||||
browsers: ['last 3 versions']
|
||||
})
|
||||
],
|
||||
loaders: {
|
||||
'js': 'babel-loader?presets[]=es2015&presets[]=stage-2',
|
||||
'postcss': 'vue-style-loader!css-loader',
|
||||
'less': 'vue-style-loader!css-loader!less-loader',
|
||||
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
|
||||
'scss': 'vue-style-loader!css-loader!sass-loader',
|
||||
'stylus': 'vue-style-loader!css-loader!stylus-loader',
|
||||
'styl': 'vue-style-loader!css-loader!stylus-loader'
|
||||
}
|
||||
}
|
155
lib/nuxt.js
Normal file
155
lib/nuxt.js
Normal file
@ -0,0 +1,155 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('nuxt:render')
|
||||
const _ = require('lodash')
|
||||
const co = require('co')
|
||||
const fs = require('fs')
|
||||
const pify = require('pify')
|
||||
const ansiHTML = require('ansi-html')
|
||||
const serialize = require('serialize-javascript')
|
||||
const build = require('./build')
|
||||
const serveStatic = require('serve-static')
|
||||
const { resolve, join } = require('path')
|
||||
const { encodeHtml, getContext, setAnsiColors } = require('./utils')
|
||||
setAnsiColors(ansiHTML)
|
||||
|
||||
class Nuxt {
|
||||
|
||||
constructor (options = {}, cb) {
|
||||
var defaults = {
|
||||
filenames: {
|
||||
css: 'style.css',
|
||||
vendor: 'vendor.bundle.js',
|
||||
app: 'nuxt.bundle.js'
|
||||
},
|
||||
routes: [],
|
||||
vendor: [],
|
||||
css: [],
|
||||
store: false,
|
||||
cache: false,
|
||||
loading: {
|
||||
loadingColor: 'black',
|
||||
errorColor: 'red',
|
||||
duration: 5000
|
||||
}
|
||||
}
|
||||
if (options.loading === true) delete options.loading
|
||||
this.options = _.defaultsDeep(options, defaults)
|
||||
// Env variables
|
||||
this.isProd = process.env.NODE_ENV === 'production'
|
||||
this.isDev = !process.env.NODE_ENV || process.env.NODE_ENV === 'development'
|
||||
this.dir = (typeof options.rootDir === 'string' && options.rootDir ? options.rootDir : process.cwd())
|
||||
// Template
|
||||
this.appTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'app.html'), 'utf8'), {
|
||||
imports: { serialize }
|
||||
})
|
||||
this.errorTemplate = _.template(fs.readFileSync(resolve(__dirname, 'views', 'error.html'), 'utf8'), {
|
||||
imports: { ansiHTML, encodeHtml }
|
||||
})
|
||||
// renderer used by Vue.js (via createBundleRenderer)
|
||||
this.renderer = null
|
||||
this.getContext = (this.options.getContext === 'function' ? this.options.getContext : getContext)
|
||||
// For serving .nuxt/dist/ files
|
||||
this.serveStatic = pify(serveStatic(resolve(this.dir, '.nuxt', 'dist')))
|
||||
// Add this.build
|
||||
this.build = build.bind(this)
|
||||
// Launch build and set this.renderer
|
||||
return co(this.build)
|
||||
// .then((nuxt) => {
|
||||
// if (typeof cb === 'function') cb(null, nuxt)
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// if (typeof cb === 'function') cb(err)
|
||||
// })
|
||||
}
|
||||
|
||||
render (req, res) {
|
||||
if (!this.renderer) {
|
||||
setTimeout(() => {
|
||||
this.render(req, res)
|
||||
}, 1000)
|
||||
return
|
||||
}
|
||||
const self = this
|
||||
const context = this.getContext(req, res)
|
||||
co(function * () {
|
||||
if (self.isDev) {
|
||||
// Call webpack middlewares only in development
|
||||
yield self.webpackDevMiddleware(req, res)
|
||||
yield self.webpackHotMiddleware(req, res)
|
||||
return
|
||||
}
|
||||
if (req.url.includes('/_nuxt/')) {
|
||||
const url = req.url
|
||||
req.url = req.url.replace('/_nuxt/', '/')
|
||||
yield self.serveStatic(req, res)
|
||||
req.url = url
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (this.isDev && req.url.includes('/_nuxt/') && 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
|
||||
}
|
||||
res.end(html, 'utf8')
|
||||
})
|
||||
.catch((err) => {
|
||||
res.statusCode = 500
|
||||
res.end(this.errorTemplate({ err }), 'utf8')
|
||||
})
|
||||
}
|
||||
|
||||
renderRoute (url, context = {}) {
|
||||
debug(`Rendering url ${url}`)
|
||||
// Add url and isSever to the context
|
||||
context.url = url
|
||||
context.isServer = true
|
||||
// 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 = self.appTemplate({
|
||||
isProd: self.isProd, // Use to add the extracted CSS <link> in production
|
||||
APP: html,
|
||||
context: context,
|
||||
files: {
|
||||
css: join('/_nuxt/', self.options.filenames.css),
|
||||
vendor: join('/_nuxt/', self.options.filenames.vendor),
|
||||
app: join('/_nuxt/', self.options.filenames.app)
|
||||
}
|
||||
})
|
||||
return app
|
||||
})
|
||||
}
|
||||
|
||||
stop (callback) {
|
||||
let promises = []
|
||||
if (this.webpackDevMiddleware) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
this.webpackDevMiddleware.close(() => resolve())
|
||||
})
|
||||
promises.push(p)
|
||||
}
|
||||
if (this.webpackServerWatcher) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
this.webpackServerWatcher.close(() => resolve())
|
||||
})
|
||||
promises.push(p)
|
||||
}
|
||||
return co(function * () {
|
||||
yield promises
|
||||
})
|
||||
.then(function () {
|
||||
if (typeof callback === 'function') callback()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Nuxt
|
19
lib/render.js
Normal file
19
lib/render.js
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('nuxt:render')
|
||||
const { join } = require('path')
|
||||
const { getRoute, waitFor } = require('./utils')
|
||||
|
||||
function * render (req, res, next) {
|
||||
if (!this.renderer) {
|
||||
yield waitFor(1000)
|
||||
yield this.render(req, res, next)
|
||||
return
|
||||
}
|
||||
debug(`Start rendering ${req.url}...`)
|
||||
const route = getRoute(req.url)
|
||||
const path = join('pages', (route === '/' ? 'index' : route)).replace('.vue', '')
|
||||
debug(`Find ${path}.vue`)
|
||||
}
|
||||
|
||||
module.exports = render
|
7
lib/renderRoute.js
Normal file
7
lib/renderRoute.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')('nuxt:render-route')
|
||||
|
||||
module.exports = function (url, context) {
|
||||
debug(`Rendering route ${url}`)
|
||||
}
|
29
lib/utils.js
Normal file
29
lib/utils.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict'
|
||||
|
||||
exports.encodeHtml = (str) => str.replace(/</g, '<').replace(/>/g, '>')
|
||||
|
||||
exports.getContext = function (req, res) {
|
||||
return {
|
||||
req: req,
|
||||
res: res
|
||||
}
|
||||
}
|
||||
|
||||
exports.setAnsiColors = function (ansiHTML) {
|
||||
ansiHTML.setColors({
|
||||
reset: ['efefef', 'a6004c'],
|
||||
darkgrey: '5a012b',
|
||||
yellow: 'ffab07',
|
||||
green: 'aeefba',
|
||||
magenta: 'ff84bf',
|
||||
blue: '3505a0',
|
||||
cyan: '56eaec',
|
||||
red: '4e053a'
|
||||
})
|
||||
}
|
||||
|
||||
exports.waitFor = function * (ms) {
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(resolve, (ms || 0))
|
||||
})
|
||||
}
|
19
lib/views/app.html
Normal file
19
lib/views/app.html
Normal file
@ -0,0 +1,19 @@
|
||||
<% const {
|
||||
title,
|
||||
htmlAttrs,
|
||||
bodyAttrs,
|
||||
meta
|
||||
} = context.meta.inject()
|
||||
%><!DOCTYPE html data-vue-meta-server-rendered>
|
||||
<html>
|
||||
<head>
|
||||
${title.toString()}
|
||||
<% if (isProd) { %><link rel="stylesheet" href="<%= files.css %>"><% } %>
|
||||
</head>
|
||||
<body>
|
||||
<%= APP %>
|
||||
<script type="text/javascript" defer>window.__NUXT__=<%= serialize(context.nuxt) %></script>
|
||||
<script src="<%= files.vendor %>" defer></script>
|
||||
<script src="<%= files.app %>" defer></script>
|
||||
</body>
|
||||
</html>
|
11
lib/views/error.html
Normal file
11
lib/views/error.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Vue.js error</title>
|
||||
</head>
|
||||
<body style="background-color: #a6004c;color: #efe;font-family: monospace;">
|
||||
<h2>Vue.js error</h2>
|
||||
<pre><%= ansiHTML(encodeHtml(err.stack)) %></pre>
|
||||
</body>
|
||||
</html>
|
112
package.json
112
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nuxt",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.0",
|
||||
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
|
||||
"main": "./dist/lib/index.js",
|
||||
"license": "MIT",
|
||||
@ -9,76 +9,58 @@
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"next": "./dist/bin/nuxt"
|
||||
"nuxt": "./dist/bin/nuxt"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
"test": "standard && gulp test",
|
||||
"lint": "standard",
|
||||
"prepublish": "gulp release",
|
||||
"start": "DEBUG=nuxt:* bin/nuxt",
|
||||
"dev": "DEBUG=nuxt:* nodemon bin/nuxt",
|
||||
"test": "npm run lint",
|
||||
"lint": "eslint --ext .js,.vue bin lib pages test index.js --ignore-pattern lib/app",
|
||||
"precommit": "npm run lint"
|
||||
},
|
||||
"ava": {
|
||||
"babel": {
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-async-to-generator",
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties",
|
||||
"transform-runtime"
|
||||
]
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-core": "6.17.0",
|
||||
"babel-generator": "6.17.0",
|
||||
"babel-loader": "6.2.5",
|
||||
"babel-plugin-module-alias": "1.6.0",
|
||||
"babel-plugin-transform-async-to-generator": "6.16.0",
|
||||
"babel-plugin-transform-class-properties": "6.16.0",
|
||||
"babel-plugin-transform-object-rest-spread": "6.16.0",
|
||||
"babel-plugin-transform-runtime": "6.15.0",
|
||||
"babel-preset-es2015": "6.16.0",
|
||||
"babel-preset-react": "6.16.0",
|
||||
"babel-runtime": "6.11.6",
|
||||
"cross-spawn": "4.0.2",
|
||||
"del": "2.2.2",
|
||||
"glamor": "2.17.10",
|
||||
"glob-promise": "1.0.6",
|
||||
"htmlescape": "1.1.1",
|
||||
"loader-utils": "0.2.16",
|
||||
"minimist": "1.2.0",
|
||||
"mz": "2.4.0",
|
||||
"path-match": "1.2.4",
|
||||
"react": "15.3.2",
|
||||
"react-dom": "15.3.2",
|
||||
"react-hot-loader": "3.0.0-beta.6",
|
||||
"send": "0.14.1",
|
||||
"strip-ansi": "3.0.1",
|
||||
"url": "0.11.0",
|
||||
"webpack": "1.13.2",
|
||||
"webpack-dev-server": "1.16.2",
|
||||
"write-file-webpack-plugin": "3.3.0"
|
||||
"ansi-html": "^0.0.6",
|
||||
"autoprefixer": "^6.5.1",
|
||||
"babel-core": "^6.18.2",
|
||||
"babel-loader": "^6.2.7",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-stage-2": "^6.18.0",
|
||||
"co": "^4.6.0",
|
||||
"cross-spawn": "^5.0.1",
|
||||
"css-loader": "^0.25.0",
|
||||
"debug": "^2.2.0",
|
||||
"del": "^2.2.2",
|
||||
"es6-object-assign": "^1.0.3",
|
||||
"es6-promise": "^4.0.5",
|
||||
"extract-text-webpack-plugin": "2.0.0-beta.4",
|
||||
"file-loader": "^0.9.0",
|
||||
"glob-promise": "^1.0.6",
|
||||
"hash-sum": "^1.0.2",
|
||||
"lodash": "^4.16.6",
|
||||
"lru-cache": "^4.0.1",
|
||||
"mkdirp-then": "^1.2.0",
|
||||
"pify": "^2.3.0",
|
||||
"serialize-javascript": "^1.3.0",
|
||||
"serve-static": "^1.11.1",
|
||||
"url-loader": "^0.5.7",
|
||||
"vue": "^2.0.5",
|
||||
"vue-loader": "^9.8.0",
|
||||
"vue-meta": "^0.0.0",
|
||||
"vue-router": "^2.0.1",
|
||||
"vue-server-renderer": "^2.0.5",
|
||||
"vuex": "^2.0.0",
|
||||
"webpack": "2.1.0-beta.25",
|
||||
"webpack-dev-middleware": "^1.8.4",
|
||||
"webpack-hot-middleware": "^2.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "7.0.0",
|
||||
"babel-plugin-transform-remove-strict-mode": "0.0.2",
|
||||
"benchmark": "2.1.1",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-ava": "0.14.1",
|
||||
"gulp-babel": "6.1.2",
|
||||
"gulp-benchmark": "1.1.1",
|
||||
"gulp-cached": "1.1.0",
|
||||
"gulp-notify": "2.2.0",
|
||||
"husky": "0.11.9",
|
||||
"run-sequence": "1.2.2",
|
||||
"standard": "^8.4.0",
|
||||
"webpack-stream": "3.2.0"
|
||||
"babel-eslint": "^7.1.0",
|
||||
"benchmark": "^2.1.2",
|
||||
"eslint": "^3.8.1",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-plugin-html": "^1.5.5",
|
||||
"eslint-plugin-promise": "^3.3.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"nodemon": "^1.11.0"
|
||||
}
|
||||
}
|
||||
|
61
pages/_error-debug.vue
Executable file
61
pages/_error-debug.vue
Executable file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<div>
|
||||
<h1 class="error-code">{{ error.statusCode }}</h1>
|
||||
<div class="error-wrapper-message">
|
||||
<h2 class="error-message">{{ error.message }}</h2>
|
||||
</div>
|
||||
<pre v-if="error.stack">{{ error.stack }}</pre>
|
||||
<p v-if="error.statusCode === 404"><router-link class="error-link" to="/">Back to the home page</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['error']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
color: #000;
|
||||
background: #fff;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
font-family: "SF UI Text", "Helvetica Neue", "Lucida Grande";
|
||||
text-align: center;
|
||||
padding-top: 20%;
|
||||
}
|
||||
.error-code {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
vertical-align: top;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.298039);
|
||||
margin: 0px 20px 0px 0px;
|
||||
padding: 10px 23px;
|
||||
}
|
||||
.error-wrapper-message {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
line-height: 49px;
|
||||
height: 49px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.error-link {
|
||||
color: #42b983;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
60
pages/_error.vue
Normal file
60
pages/_error.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="error-page">
|
||||
<div>
|
||||
<h1 class="error-code">{{ error.statusCode }}</h1>
|
||||
<div class="error-wrapper-message">
|
||||
<h2 class="error-message">{{ error.message }}</h2>
|
||||
</div>
|
||||
<p v-if="error.statusCode === 404"><router-link class="error-link" to="/">Back to the home page</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['error']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-page {
|
||||
color: #000;
|
||||
background: #fff;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
font-family: "SF UI Text", "Helvetica Neue", "Lucida Grande";
|
||||
text-align: center;
|
||||
padding-top: 20%;
|
||||
}
|
||||
.error-code {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
vertical-align: top;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.298039);
|
||||
margin: 0px 20px 0px 0px;
|
||||
padding: 10px 23px;
|
||||
}
|
||||
.error-wrapper-message {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
line-height: 49px;
|
||||
height: 49px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.error-message {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.error-link {
|
||||
color: #42b983;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
13
test/fixtures/basic/pages/async-props.vue
vendored
Executable file
13
test/fixtures/basic/pages/async-props.vue
vendored
Executable file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<p>{{ name }}</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve({ name: 'Kobe Bryant' }), 10)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
9
test/fixtures/basic/pages/css.vue
vendored
Executable file
9
test/fixtures/basic/pages/css.vue
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="red">This is red</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
8
test/fixtures/basic/pages/head.vue
vendored
Executable file
8
test/fixtures/basic/pages/head.vue
vendored
Executable file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<Head>
|
||||
<meta content='my meta' />
|
||||
</Head>
|
||||
<h1>I can haz meta tags</h1>
|
||||
</div>
|
||||
</template>
|
16
test/fixtures/basic/pages/stateful.vue
vendored
Executable file
16
test/fixtures/basic/pages/stateful.vue
vendored
Executable file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>The answer is {{ answer }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return { answer: null }
|
||||
},
|
||||
beforeMount () {
|
||||
this.answer = 42
|
||||
}
|
||||
}
|
||||
</script>
|
3
test/fixtures/basic/pages/stateless.vue
vendored
Executable file
3
test/fixtures/basic/pages/stateless.vue
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<h1>My component!</h1>
|
||||
</template>
|
39
test/index.js
Executable file
39
test/index.js
Executable file
@ -0,0 +1,39 @@
|
||||
import test from 'ava'
|
||||
import { join } from 'path'
|
||||
import build from '../server/build'
|
||||
import { render as _render } from '../server/render'
|
||||
|
||||
const dir = join(__dirname, 'fixtures', 'basic')
|
||||
|
||||
test.before(() => build(dir))
|
||||
|
||||
test(async t => {
|
||||
const html = await render('/stateless')
|
||||
t.true(html.includes('<h1>My component!</h1>'))
|
||||
})
|
||||
|
||||
test(async t => {
|
||||
const html = await render('/css')
|
||||
t.true(html.includes('.red{color:red;}'))
|
||||
t.true(html.includes('<div class="red">This is red</div>'))
|
||||
})
|
||||
|
||||
test(async t => {
|
||||
const html = await render('/stateful')
|
||||
t.true(html.includes('<div><p>The answer is 42</p></div>'))
|
||||
})
|
||||
|
||||
test(async t => {
|
||||
const html = await (render('/head'))
|
||||
t.true(html.includes('<meta content="my meta" class="next-head"/>'))
|
||||
t.true(html.includes('<div><h1>I can haz meta tags</h1></div>'))
|
||||
})
|
||||
|
||||
test(async t => {
|
||||
const html = await render('/async-props')
|
||||
t.true(html.includes('<p>Kobe Bryant</p>'))
|
||||
})
|
||||
|
||||
function render (url, ctx) {
|
||||
return _render(url, ctx, { dir, staticMarkup: true })
|
||||
}
|
Loading…
Reference in New Issue
Block a user