Merge branch 'dev' into feat-cleanup-test-console-output

This commit is contained in:
Sébastien Chopin 2017-12-12 16:34:21 +01:00 committed by GitHub
commit a00f482d24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
101 changed files with 1528 additions and 912 deletions

View File

@ -23,12 +23,6 @@ jobs:
paths: paths:
- "node_modules" - "node_modules"
# Build
- run:
name: Build
command: |
yarn build
# Test # Test
- run: - run:
name: Tests name: Tests
@ -36,10 +30,10 @@ jobs:
# Release next # Release next
- run: - run:
name: Release Next version name: Publish nuxt-next
command: | command: |
if [ "${CIRCLE_BRANCH}" == "dev" ]; then if [ "${CIRCLE_BRANCH}" == "dev" ]; then
echo "TOKEN: $NPM_TOKEN"
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm run release-next echo "//registry.yarnpkg.com/:_authToken=$NPM_TOKEN" >> ~/.npmrc
./scripts/release-next && npm publish --tag next
fi fi

View File

@ -8,7 +8,7 @@ module.exports = {
browser: true, browser: true,
node: true node: true
}, },
extends: 'standard', extends: ['standard', 'standard-jsx'],
// required to lint *.vue files // required to lint *.vue files
plugins: [ plugins: [
'html' 'html'

View File

@ -1,14 +1,13 @@
language: node_js language: node_js
node_js: node_js:
- "8" - "8"
- "6" - "9"
cache: cache:
yarn: true yarn: true
directories: directories:
- node_modules - node_modules
install: install:
- yarn install - yarn install
- yarn run build
script: script:
- yarn run test - yarn run test
after_success: after_success:

View File

@ -1,8 +1,8 @@
# Test against the latest version of this Node.js version # Test against the latest version of this Node.js version
environment: environment:
matrix: matrix:
- nodejs_version: "6"
- nodejs_version: "8" - nodejs_version: "8"
- nodejs_version: "9"
cache: cache:
- "%LOCALAPPDATA%\\Yarn" - "%LOCALAPPDATA%\\Yarn"

View File

@ -1,116 +0,0 @@
// Some parts brought from https://github.com/vuejs/vue/blob/dev/build/config.js
const { resolve } = require('path')
const rollupBabel = require('rollup-plugin-babel')
const rollupAlias = require('rollup-plugin-alias')
const rollupCommonJS = require('rollup-plugin-commonjs')
const rollupReplace = require('rollup-plugin-replace')
const rollupNodeResolve = require('rollup-plugin-node-resolve')
const packageJson = require('../package.json')
const dependencies = Object.keys(packageJson.dependencies)
const version = packageJson.version || process.env.VERSION
// -----------------------------
// Banner
// -----------------------------
const banner =
'/*!\n' +
' * Nuxt.js v' + version + '\n' +
' * Released under the MIT License.\n' +
' */'
// -----------------------------
// Aliases
// -----------------------------
const rootDir = resolve(__dirname, '..')
const libDir = resolve(rootDir, 'lib')
const distDir = resolve(rootDir, 'dist')
const aliases = {
core: resolve(libDir, 'core/index.js'),
builder: resolve(libDir, 'builder/index.js'),
common: resolve(libDir, 'common/index.js'),
utils: resolve(libDir, 'common/utils.js'),
app: resolve(libDir, 'app')
}
// -----------------------------
// Builds
// -----------------------------
const builds = {
nuxt: {
entry: resolve(libDir, 'index.js'),
file: resolve(distDir, 'nuxt.js')
},
core: {
entry: resolve(libDir, 'core/index.js'),
file: resolve(distDir, 'core.js')
}
}
// -----------------------------
// Default config
// -----------------------------
function genConfig (opts) {
const config = {
input: opts.entry,
output: {
file: opts.file,
format: 'cjs',
sourcemap: true
},
external: ['fs', 'path', 'http', 'module', 'vue-server-renderer/server-plugin', 'vue-server-renderer/client-plugin']
.concat(dependencies, opts.external),
banner: opts.banner || banner,
name: opts.modulename || 'Nuxt',
plugins: [
rollupAlias(Object.assign({
resolve: ['.js', '.json', '.jsx', '.ts']
}, aliases, opts.alias)),
rollupNodeResolve({ preferBuiltins: true }),
rollupCommonJS(),
rollupBabel(Object.assign({
exclude: 'node_modules/**',
plugins: [
['transform-runtime', { 'helpers': false, 'polyfill': false }],
'transform-async-to-generator',
'array-includes',
'external-helpers'
],
presets: [
['env', {
targets: {
node: '6.11.0'
},
modules: false
}]
],
'env': {
'test': {
'plugins': [ 'istanbul' ]
}
}
}, opts.babel)),
rollupReplace({ __VERSION__: version })
].concat(opts.plugins || [])
}
if (opts.env) {
config.plugins.push(rollupReplace({
'process.env.NODE_ENV': JSON.stringify(opts.env)
}))
}
return config
}
if (process.env.TARGET) {
module.exports = genConfig(builds[process.env.TARGET])
} else {
exports.getBuild = name => genConfig(builds[name])
exports.getAllBuilds = () => Object.keys(builds).map(name => genConfig(builds[name]))
}

View File

@ -3,7 +3,7 @@
<div class="container"> <div class="container">
<h1>Blog</h1> <h1>Blog</h1>
<ul> <ul>
<li v-for="post in posts"> <li v-for="(post, index) in posts" :key="index">
<nuxt-link :to="{ name: 'posts-id', params: { id: post.id } }">{{ post.title }}</nuxt-link> <nuxt-link :to="{ name: 'posts-id', params: { id: post.id } }">{{ post.title }}</nuxt-link>
</li> </li>
</ul> </ul>

View File

@ -2,7 +2,7 @@
<div class="container"> <div class="container">
<h2>Users</h2> <h2>Users</h2>
<ul class="users"> <ul class="users">
<li v-for="user in users"> <li v-for="user in users" :key="user.id">
<nuxt-link :to="'/users/'+user.id">{{ user.name }}</nuxt-link> <nuxt-link :to="'/users/'+user.id">{{ user.name }}</nuxt-link>
</li> </li>
</ul> </ul>

View File

@ -1,15 +0,0 @@
<script>
export default {
asyncData({ req }) {
return {
name: req ? 'server' : 'client'
}
},
render(h) {
return <div>
<p>Hi from {this.name}</p>
<nuxt-link to="/">Home page</nuxt-link>
</div>
}
}
</script>

View File

@ -1,8 +0,0 @@
export default {
render(h) {
return <div>
<h1>Welcome !</h1>
<nuxt-link to="/about">About page</nuxt-link>
</div>
}
}

View File

@ -4,10 +4,10 @@
<div class="container"> <div class="container">
<h1 class="Header__Title">Nuxt i18n</h1> <h1 class="Header__Title">Nuxt i18n</h1>
<nav class="Header__Menu"> <nav class="Header__Menu">
<nuxt-link class="Header__Link" :to="path('/')" exact> <nuxt-link class="Header__Link" :to="$i18n.path('')" exact>
{{ $t('links.home') }} {{ $t('links.home') }}
</nuxt-link> </nuxt-link>
<nuxt-link class="Header__Link" :to="path('/about')" exact> <nuxt-link class="Header__Link" :to="$i18n.path('about')" exact>
{{ $t('links.about') }} {{ $t('links.about') }}
</nuxt-link> </nuxt-link>
<nuxt-link class="Header__Link" v-if="$i18n.locale === 'en'" :to="`/fr` + $route.fullPath" active-class="none" exact> <nuxt-link class="Header__Link" v-if="$i18n.locale === 'en'" :to="`/fr` + $route.fullPath" active-class="none" exact>
@ -24,13 +24,7 @@
</template> </template>
<script> <script>
export default { export default {}
methods: {
path(url) {
return (this.$i18n.locale === 'en' ? url : '/' + this.$i18n.locale + url)
}
}
}
</script> </script>
<style> <style>

View File

@ -14,4 +14,12 @@ export default ({ app, store }) => {
'fr': require('~/locales/fr.json') 'fr': require('~/locales/fr.json')
} }
}) })
app.i18n.path = (link) => {
if (app.i18n.locale === app.i18n.fallbackLocale) {
return `/${link}`
}
return `/${app.i18n.locale}/${link}`
}
} }

5
examples/jsx/README.md Normal file
View File

@ -0,0 +1,5 @@
# Render Functions & JSX Example
## Documentation
Vue: https://vuejs.org/v2/guide/render-function.html

View File

@ -0,0 +1,17 @@
<template>
<p v-html="data"></p>
</template>
<script>
export default {
props: {
data: String
}
}
</script>
<style scoped>
p {
padding: 5px 20px;
}
</style>

View File

@ -1,5 +1,5 @@
{ {
"name": "hello-nuxt-jsx", "name": "nuxt-jsx",
"dependencies": { "dependencies": {
"nuxt": "latest" "nuxt": "latest"
}, },

View File

@ -0,0 +1,21 @@
import Test from '~/components/test.vue'
export default {
head: {
title: 'About Page',
meta: [
{ hid: 'description', name: 'description', content: 'About page description' }
]
},
components: {
Test
},
render() {
return <div class='container'>
<h1>About page</h1>
<test data='I am test component' />
<p><nuxt-link to='/'>Home page</nuxt-link></p>
</div>
}
}

20
examples/jsx/pages/index.js Executable file
View File

@ -0,0 +1,20 @@
export default {
head: {
title: 'Home page 🚀',
meta: [
{ hid: 'description', name: 'description', content: 'Home page description' }
],
script: [
{ src: '/head.js' },
// Supported since 1.0
{ src: '/body.js', body: true },
{ src: '/defer.js', defer: '' }
]
},
render() {
return <div class='container'>
<h1>Home page 🚀</h1>
<nuxt-link to='/about'>About page</nuxt-link>
</div>
}
}

View File

@ -1,16 +1,8 @@
module.exports = { module.exports = {
modules: [ modules: [
'@nuxtjs/markdownit' '@nuxtjs/markdownit'
],
plugins: [
'~/plugins/md-it'
] ]
// [optional] markdownit options
// See https://github.com/markdown-it/markdown-it
// markdownit: {
// preset: 'default',
// linkify: true,
// breaks: true,
// use: [
// 'markdown-it-container',
// 'markdown-it-attrs'
// ]
// }
} }

View File

@ -10,5 +10,8 @@
"dev": "nuxt", "dev": "nuxt",
"build": "nuxt build", "build": "nuxt build",
"start": "nuxt start" "start": "nuxt start"
},
"devDependencies": {
"jstransformer-markdown-it": "^2.0.0"
} }
} }

View File

@ -1,8 +1,10 @@
<template lang="pug"> <template lang="pug">
div div
h1 Pug Page h1 Pug Page
div(language="md") Current route is: {{ $route.name }} :markdown-it()
div(language="md") Data model is: {{ model }} ## Current route is: {{ $route.name }}
div(v-html="$md.render(model)")
br br
nuxt-link(to='/') Back Home nuxt-link(to='/') Back Home
</template> </template>
@ -11,7 +13,7 @@
export default { export default {
data() { data() {
return { return {
model: 'I am pug' model: '## Title h2\n### title h3\n\nLong text Long text Long text Long text Long text Long text Long text Long text Long text \n\n* gimme a list item\n* and one more yeehaw'
} }
} }
} }

View File

@ -0,0 +1,5 @@
import MarkdownIt from 'markdown-it'
export default ({ app }, inject) => {
inject('md', new MarkdownIt())
}

View File

@ -1,6 +1,6 @@
<template> <template>
<ul> <ul>
<li v-for="visit in visits"><i>{{ visit.date | hours }}</i> - {{ visit.path }}</li> <li v-for="(visit, index) in visits" :key="index"><i>{{ visit.date | hours }}</i> - {{ visit.path }}</li>
</ul> </ul>
</template> </template>

View File

@ -4,7 +4,9 @@
<pre>{{ userAgent }}</pre> <pre>{{ userAgent }}</pre>
<ul> <ul>
<li><nuxt-link to="/">Home</nuxt-link></li> <li><nuxt-link to="/">Home</nuxt-link></li>
<li v-for="slug in slugs"><nuxt-link :to="{ name: 'slug', params: { slug } }">{{ slug }}</nuxt-link></li> <li v-for="(slug, index) in slugs" :key="index">
<nuxt-link :to="{ name: 'slug', params: { slug } }">{{ slug }}</nuxt-link>
</li>
</ul> </ul>
</div> </div>
</template> </template>

View File

@ -3,7 +3,7 @@
<div class="left"> <div class="left">
<h2><nuxt-link to="/">Players</nuxt-link></h2> <h2><nuxt-link to="/">Players</nuxt-link></h2>
<ul class="players"> <ul class="players">
<li v-for="user in users"> <li v-for="user in users" :key="user.id">
<nuxt-link :to="'/'+user.id">{{ user.name }}</nuxt-link> <nuxt-link :to="'/'+user.id">{{ user.name }}</nuxt-link>
</li> </li>
</ul> </ul>

View File

@ -6,7 +6,7 @@
<nuxt-link v-if="page < totalPages" :to="'?page=' + (page + 1)">Next &gt;</nuxt-link> <nuxt-link v-if="page < totalPages" :to="'?page=' + (page + 1)">Next &gt;</nuxt-link>
<a v-else class="disabled">Next &gt;</a> <a v-else class="disabled">Next &gt;</a>
<ul> <ul>
<li v-for="user in users"> <li v-for="user in users" :key="user.id">
<img :src="user.avatar" class="avatar" /> <img :src="user.avatar" class="avatar" />
<span>{{ user.first_name }} {{ user.last_name }}</span> <span>{{ user.first_name }} {{ user.last_name }}</span>
</li> </li>

View File

@ -6,7 +6,7 @@
{{selected}} {{selected}}
</div> </div>
<div class="flex flex-wrap ph2 justify-between bg-white-80"> <div class="flex flex-wrap ph2 justify-between bg-white-80">
<div v-for="person in people"> <div v-for="person in people" :key="person.id">
<Card :person="person"></Card> <Card :person="person"></Card>
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@
<div> <div>
<h3>Cars</h3> <h3>Cars</h3>
<ul> <ul>
<li v-for="car in allCars"> <li v-for="car in allCars" :key="car.id">
<nuxt-link :to="`car/${car.id}`"> <nuxt-link :to="`car/${car.id}`">
{{ car.year }} {{ car.make }} {{ car.model }} {{ car.year }} {{ car.make }} {{ car.model }}
</nuxt-link> </nuxt-link>

View File

@ -2,7 +2,7 @@
<div> <div>
<h2>Todos</h2> <h2>Todos</h2>
<ul> <ul>
<li v-for="todo in todos"> <li v-for="(todo, index) in todos" :key="index">
<input type="checkbox" :checked="todo.done" @change="toggle(todo)"> <input type="checkbox" :checked="todo.done" @change="toggle(todo)">
<span :class="{ done: todo.done }">{{ todo.text }}</span> <span :class="{ done: todo.done }">{{ todo.text }}</span>
</li> </li>

View File

@ -2,13 +2,13 @@
<div> <div>
<h2>Articles</h2> <h2>Articles</h2>
<ul> <ul>
<li v-for="article in articles"> <li v-for="(article, index) in articles" :key="index">
<span>{{article}}</span> <span>{{article}}</span>
</li> </li>
</ul> </ul>
<h2>Comments <small>(nested under articles)</small></h2> <h2>Comments <small>(nested under articles)</small></h2>
<ul> <ul>
<li v-for="comment in comments"> <li v-for="(comment, index) in comments" :key="index">
<span>{{comment}}</span> <span>{{comment}}</span>
</li> </li>
</ul> </ul>

View File

@ -8,7 +8,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(value, key) in cookies"> <tr v-for="(value, key) in cookies" :key="key">
<td>{{ key }}</td> <td>{{ key }}</td>
<td>{{ value }}</td> <td>{{ value }}</td>
<td><button @click="removeCookie(key)">Remove</button></td> <td><button @click="removeCookie(key)">Remove</button></td>

View File

@ -10,7 +10,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(user, key) in users"> <tr v-for="(user, key) in users" :key="key">
<td> <td>
<nuxt-link :to="{ path: `/users/${key}`}"> <nuxt-link :to="{ path: `/users/${key}`}">
<img :src="user.avatar" class="rounded" alt="avatar"> <img :src="user.avatar" class="rounded" alt="avatar">

View File

@ -4,7 +4,9 @@
<li class="chat page"> <li class="chat page">
<div class="chatArea"> <div class="chatArea">
<ul class="messages" ref="messages"> <ul class="messages" ref="messages">
<li class="message" v-for="message in messages"><i :title="message.date">{{ message.date.split('T')[1].slice(0, -2) }}</i>: {{ message.text }}</li> <li class="message" v-for="(message, index) in messages" :key="index">
<i :title="message.date">{{ message.date.split('T')[1].slice(0, -2) }}</i>: {{ message.text }}
</li>
</ul> </ul>
</div> </div>
<input class="inputMessage" type="text" v-model="message" @keyup.enter="sendMessage" placeholder="Type here..." /> <input class="inputMessage" type="text" v-model="message" @keyup.enter="sendMessage" placeholder="Type here..." />

View File

@ -0,0 +1,7 @@
# Nuxt With Vue-Material
## Compatibility
Vue-Material >= 1.0.0beta
[View Demo](https://nuxt-vue-material.now.sh)

View File

@ -0,0 +1,25 @@
module.exports = {
head: {
meta: [
{
name: 'viewport',
content: 'width=device-width, initial-scale=1, minimal-ui'
}
],
link: [
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons'
},
{
rel: 'stylesheet',
href: 'https://unpkg.com/vue-material@beta/dist/vue-material.min.css'
},
{
rel: 'stylesheet',
href: 'https://unpkg.com/vue-material@beta/dist/theme/default.css'
}
]
},
plugins: ['~/plugins/vue-material']
}

View File

@ -0,0 +1,14 @@
{
"name": "with-vue-material",
"version": "1.0.0",
"dependencies": {
"nuxt": "latest",
"vue": "~2.4.4",
"vue-material": "beta"
},
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start"
}
}

View File

@ -0,0 +1,106 @@
<template>
<div class="page-container md-layout-column">
<md-toolbar class="md-primary">
<md-button class="md-icon-button" @click="showNavigation = true">
<md-icon>menu</md-icon>
</md-button>
<span class="md-title">Nuxt + Vue Material</span>
<div class="md-toolbar-section-end">
<md-button @click="showSidepanel = true">Favorites</md-button>
</div>
</md-toolbar>
<md-drawer :md-active.sync="showNavigation">
<md-toolbar class="md-transparent" md-elevation="0">
<span class="md-title">My App name</span>
</md-toolbar>
<md-list>
<md-list-item>
<md-icon>move_to_inbox</md-icon>
<span class="md-list-item-text">Inbox</span>
</md-list-item>
<md-list-item>
<md-icon>send</md-icon>
<span class="md-list-item-text">Sent Mail</span>
</md-list-item>
<md-list-item>
<md-icon>delete</md-icon>
<span class="md-list-item-text">Trash</span>
</md-list-item>
<md-list-item>
<md-icon>error</md-icon>
<span class="md-list-item-text">Spam</span>
</md-list-item>
</md-list>
</md-drawer>
<md-drawer class="md-right" :md-active.sync="showSidepanel">
<md-toolbar class="md-transparent" md-elevation="0">
<span class="md-title">Favorites</span>
</md-toolbar>
<md-list>
<md-list-item>
<span class="md-list-item-text">Abbey Christansen</span>
<md-button class="md-icon-button md-list-action">
<md-icon class="md-primary">chat_bubble</md-icon>
</md-button>
</md-list-item>
<md-list-item>
<span class="md-list-item-text">Alex Nelson</span>
<md-button class="md-icon-button md-list-action">
<md-icon class="md-primary">chat_bubble</md-icon>
</md-button>
</md-list-item>
<md-list-item>
<span class="md-list-item-text">Mary Johnson</span>
<md-button class="md-icon-button md-list-action">
<md-icon>chat_bubble</md-icon>
</md-button>
</md-list-item>
</md-list>
</md-drawer>
<md-content>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error quibusdam, non molestias et! Earum magnam, similique, quo recusandae placeat dicta asperiores modi sint ea repudiandae maxime? Quae non explicabo, neque.
</md-content>
</div>
</template>
<script>
export default {
name: 'Sidenav',
data: () => ({
showNavigation: false,
showSidepanel: false
})
}
</script>
<style scoped>
.page-container {
min-height: 100vh;
overflow: hidden;
position: relative;
border: 1px solid rgba(#000, 0.12);
}
.md-drawer {
width: 230px;
max-width: calc(100vw - 125px);
}
.md-content {
padding: 16px;
}
</style>

View File

@ -0,0 +1,4 @@
import Vue from 'vue'
import VueMaterial from 'vue-material'
Vue.use(VueMaterial)

View File

@ -1,14 +1,27 @@
// Specify overrides (theme and/or base variables etc.) // Specify overrides (theme and/or base variables etc.)
// See https://vuetifyjs.com/quick-start // See https://vuetifyjs.com/quick-start
// And for themes customizing: https://vuetifyjs.com/style/theme
// Color Palette: https://vuetifyjs.com/style/colors
// Although, since version https://github.com/vuetifyjs/vuetify/releases/tag/v0.17.0, colors are no
// longer defined **explicitly** in the stylus files.
// you can use `import colors from 'vuetify/es5/util/colors'` in the vuetify instance, under `plugins`
// if you only want to define your color palette.
// you have to define the same palette colors just if you want to use them inside your own stylus files.
@require '~vuetify/src/stylus/settings/_colors'
$theme := { $theme := {
primary: #9c27b0 primary: $red.darken-2
accent: #ce93d8 accent: $red.accent-2
secondary: #424242 secondary: $grey.lighten-1
info: #0D47A1 info: $blue.lighten-1
warning: #ffb300 warning: $amber.darken-2
error: #B71C1C error: $red.accent-4
success: #2E7D32 success: $green.lighten-2
} }
// Import Vuetify styling // Import Vuetify styling
@require '~vuetify/src/stylus/main.styl' @require '~vuetify/src/stylus/main'

View File

@ -1,3 +1,5 @@
const nodeExternals = require('webpack-node-externals')
module.exports = { module.exports = {
/* /*
** Head elements ** Head elements
@ -13,7 +15,16 @@ module.exports = {
*/ */
build: { build: {
vendor: ['vuetify'], vendor: ['vuetify'],
extractCSS: true extractCSS: true,
extend(config, ctx) {
if (ctx.isServer) {
config.externals = [
nodeExternals({
whitelist: [/^vuetify/]
})
]
}
}
}, },
/* /*
** Load Vuetify into the app ** Load Vuetify into the app

View File

@ -1,4 +1,19 @@
import Vue from 'vue' import Vue from 'vue'
import Vuetify from 'vuetify' import Vuetify from 'vuetify'
import colors from 'vuetify/es5/util/colors'
Vue.use(Vuetify) // You can also specify those components you are going to use for "a la carte" build:
// https://github.com/vuetifyjs/nuxt/blob/master/template/plugins/vuetify.js
// https://github.com/vuetifyjs/a-la-carte/blob/master/template/src/main.js
Vue.use(Vuetify, {
theme: {
primary: '#121212', // a color that is not in the material colors palette
accent: colors.grey.darken3,
secondary: colors.amber.darken3,
info: colors.teal.lighten1,
warning: colors.amber,
error: colors.deepOrange.accent4,
success: colors.green.accent3
}
})

View File

@ -1,16 +0,0 @@
/*!
* Nuxt.js
* (c) 2016-2017 Chopin Brothers
* Core maintainer: Pooya (@pi0)
* Released under the MIT License.
*/
// Node Source Map Support
// https://github.com/evanw/node-source-map-support
require('source-map-support').install()
// Fix babel flag
/* istanbul ignore else */
process.noDeprecation = true
module.exports = require('./dist/nuxt')

View File

@ -1,12 +1,15 @@
import Vue from 'vue' import Vue from 'vue'
import NuxtChild from './nuxt-child' import NuxtChild from './nuxt-child'
import NuxtError from '<%= components.ErrorPage ? ((components.ErrorPage.includes('~') || components.ErrorPage.includes('@')) ? components.ErrorPage : "../" + components.ErrorPage) : "./nuxt-error.vue" %>' import NuxtError from '<%= components.ErrorPage ? ((components.ErrorPage.indexOf('~') === 0 || components.ErrorPage.indexOf('@') === 0) ? components.ErrorPage : "../" + components.ErrorPage) : "./nuxt-error.vue" %>'
import { compile } from '../utils' import { compile } from '../utils'
export default { export default {
name: 'nuxt', name: 'nuxt',
props: ['nuxtChildKey'], props: ['nuxtChildKey'],
render(h) { render(h) {
if (this.nuxt._redirected) {
return h('div', [ '<%= messages.redirect %>' ])
}
// If there is some error // If there is some error
if (this.nuxt.err) { if (this.nuxt.err) {
return h('nuxt-error', { return h('nuxt-error', {

View File

@ -8,13 +8,12 @@ import NuxtLink from './components/nuxt-link.js'
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>' import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
import Nuxt from './components/nuxt.js' import Nuxt from './components/nuxt.js'
import App from '<%= appPath %>' import App from '<%= appPath %>'
import { setContext, getLocation } from './utils' import { setContext, getLocation, getRouteData } from './utils'
<% if (store) { %>import { createStore } from './store.js'<% } %> <% if (store) { %>import { createStore } from './store.js'<% } %>
/* Plugins */ /* Plugins */
<% plugins.forEach(plugin => { %>import <%= plugin.name %> from '<%= plugin.name %>' // Source: <%= relativeToBuild(plugin.src) %><%= (plugin.ssr===false) ? ' (ssr: false)' : '' %>
<% plugins.forEach(plugin => { %>// <%= plugin.src %><%= (plugin.ssr===false) ? ' (Only included in client bundle)' : '' %> <% }) %>
import <%= plugin.name %> from '<%= plugin.name %>'<% }) %>
// Component: <no-ssr> // Component: <no-ssr>
Vue.component(NoSSR.name, NoSSR) Vue.component(NoSSR.name, NoSSR)
@ -167,7 +166,17 @@ async function createApp (ssrContext) {
// If server-side, wait for async component to be resolved first // If server-side, wait for async component to be resolved first
if (process.server && ssrContext && ssrContext.url) { if (process.server && ssrContext && ssrContext.url) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
router.push(ssrContext.url, resolve, reject) router.push(ssrContext.url, resolve, () => {
// navigated to a different route in router guard
const unregister = router.afterEach(async (to, from, next) => {
ssrContext.url = to.fullPath
app.context.route = await getRouteData(to)
app.context.params = to.params || {}
app.context.query = to.query || {}
unregister()
resolve()
})
})
}) })
} }

View File

@ -85,7 +85,7 @@ export function resolveRouteComponents(route) {
) )
} }
async function getRouteData(route) { export async function getRouteData(route) {
// Make sure the components are resolved (code-splitting) // Make sure the components are resolved (code-splitting)
await resolveRouteComponents(route) await resolveRouteComponents(route)
// Send back a copy of route with meta based on Component definition // Send back a copy of route with meta based on Component definition
@ -132,11 +132,24 @@ export async function setContext(app, context) {
path = status path = status
status = 302 status = 302
} }
app.context.next({ // "/absolute/route", "./relative/route" or "../relative/route"
path: path, if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) {
query: query, app.context.next({
status: status path: path,
}) query: query,
status: status
})
} else {
path = formatUrl(path, query)
if (process.server) {
app.context.res.setHeader('Location', path)
app.context.res.statusCode = status
app.nuxt._redirected = true
}
if (process.client) {
window.location = path
}
}
} }
if (process.server) app.context.beforeNuxtRender = (fn) => context.beforeRenderFns.push(fn) if (process.server) app.context.beforeNuxtRender = (fn) => context.beforeRenderFns.push(fn)
if (process.client) app.context.nuxtState = window.__NUXT__ if (process.client) app.context.nuxtState = window.__NUXT__
@ -442,3 +455,60 @@ function escapeString(str) {
function escapeGroup(group) { function escapeGroup(group) {
return group.replace(/([=!:$\/()])/g, '\\$1') return group.replace(/([=!:$\/()])/g, '\\$1')
} }
/**
* Format given url, append query to url query string
*
* @param {string} url
* @param {string} query
* @return {string}
*/
function formatUrl (url, query) {
let protocol
let index = url.indexOf('://')
if (index !== -1) {
protocol = url.substring(0, index)
url = url.substring(index + 3)
} else if (url.indexOf('//') === 0) {
url = url.substring(2)
}
let parts = url.split('/')
let result = (protocol ? protocol + '://' : '//') + parts.shift()
let path = parts.filter(Boolean).join('/')
let hash
parts = path.split('#')
if (parts.length === 2) {
path = parts[0]
hash = parts[1]
}
result += path ? '/' + path : ''
if (query && JSON.stringify(query) !== '{}') {
result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query)
}
result += hash ? '#' + hash : ''
return result
}
/**
* Transform data object to query string
*
* @param {object} query
* @return {string}
*/
function formatQuery (query) {
return Object.keys(query).sort().map(key => {
var val = query[key]
if (val == null) {
return ''
}
if (Array.isArray(val)) {
return val.slice().map(val2 => [key, '=', val2].join('')).join('&')
}
return key + '=' + val
}).filter(Boolean).join('&')
}

View File

@ -1,29 +1,30 @@
import _ from 'lodash' const { promisify } = require('util')
import chokidar from 'chokidar' const _ = require('lodash')
import fs, { remove, readFile, writeFile, mkdirp, existsSync } from 'fs-extra' const chokidar = require('chokidar')
import hash from 'hash-sum' const { remove, readFile, writeFile, mkdirp, existsSync } = require('fs-extra')
import pify from 'pify' const hash = require('hash-sum')
import webpack from 'webpack' const webpack = require('webpack')
import serialize from 'serialize-javascript' const serialize = require('serialize-javascript')
import { join, resolve, basename, extname, dirname } from 'path' const { join, resolve, basename, extname, dirname } = require('path')
import MFS from 'memory-fs' const MFS = require('memory-fs')
import webpackDevMiddleware from 'webpack-dev-middleware' const webpackDevMiddleware = require('webpack-dev-middleware')
import webpackHotMiddleware from 'webpack-hot-middleware' const webpackHotMiddleware = require('webpack-hot-middleware')
import { r, wp, wChunk, createRoutes, sequence, relativeTo, isPureObject, waitFor } from 'utils' const { r, wp, wChunk, createRoutes, sequence, relativeTo, isPureObject, waitFor, rmCache } = require('../common/utils')
import Debug from 'debug' const Debug = require('debug')
import Glob from 'glob' const Glob = require('glob')
import clientWebpackConfig from './webpack/client.config.js' const clientWebpackConfig = require('./webpack/client.config.js')
import serverWebpackConfig from './webpack/server.config.js' const serverWebpackConfig = require('./webpack/server.config.js')
import dllWebpackConfig from './webpack/dll.config.js' const dllWebpackConfig = require('./webpack/dll.config.js')
import vueLoaderConfig from './webpack/vue-loader.config' const vueLoaderConfig = require('./webpack/vue-loader.config')
import styleLoader from './webpack/style-loader' const styleLoader = require('./webpack/style-loader')
const { Options } = require('../common')
const debug = Debug('nuxt:build') const debug = Debug('nuxt:build')
debug.color = 2 // Force green color debug.color = 2 // Force green color
const glob = pify(Glob) const glob = promisify(Glob)
export default class Builder { module.exports = class Builder {
constructor(nuxt) { constructor(nuxt) {
this.nuxt = nuxt this.nuxt = nuxt
this.isStatic = false // Flag to know if the build is for a generated app this.isStatic = false // Flag to know if the build is for a generated app
@ -62,18 +63,21 @@ export default class Builder {
// Stop watching on nuxt.close() // Stop watching on nuxt.close()
if (this.options.dev) { if (this.options.dev) {
this.nuxt.hook('close', () => this.unwatch()) this.nuxt.hook('close', () => this.unwatch())
} else {
this.nuxt.hook('build:done', () => this.generateConfig())
} }
} }
get plugins() { get plugins() {
return this.options.plugins.map((p, i) => { return _.uniqBy(this.options.plugins.map((p, i) => {
if (typeof p === 'string') p = { src: p } if (typeof p === 'string') p = { src: p }
const pluginBaseName = basename(p.src, extname(p.src)).replace(/[^a-zA-Z?\d\s:]/g, '')
return { return {
src: this.nuxt.resolvePath(p.src), src: this.nuxt.resolvePath(p.src),
ssr: (p.ssr !== false), ssr: (p.ssr !== false),
name: basename(p.src, extname(p.src)).replace(/[^a-zA-Z?\d\s:]/g, '') + '_plugin_' + hash(p.src) name: 'nuxt_plugin_' + pluginBaseName + '_' + hash(p.src)
} }
}) }), p => p.name)
} }
vendor() { vendor() {
@ -124,19 +128,6 @@ export default class Builder {
// Call before hook // Call before hook
await this.nuxt.callHook('build:before', this, this.options.build) await this.nuxt.callHook('build:before', this, this.options.build)
// Babel options
this.babelOptions = _.defaults(this.options.build.babel, {
babelrc: false,
cacheDirectory: !!this.options.dev
})
if (!this.babelOptions.babelrc && !this.babelOptions.presets) {
this.babelOptions.presets = [
[require.resolve('babel-preset-vue-app'), {
targets: { ie: 9, uglify: true }
}]
]
}
// Map postcss plugins into instances on object mode once // Map postcss plugins into instances on object mode once
if (isPureObject(this.options.build.postcss)) { if (isPureObject(this.options.build.postcss)) {
if (isPureObject(this.options.build.postcss.plugins)) { if (isPureObject(this.options.build.postcss.plugins)) {
@ -154,9 +145,9 @@ export default class Builder {
// Check if pages dir exists and warn if not // Check if pages dir exists and warn if not
this._nuxtPages = typeof this.options.build.createRoutes !== 'function' this._nuxtPages = typeof this.options.build.createRoutes !== 'function'
if (this._nuxtPages) { if (this._nuxtPages) {
if (!fs.existsSync(join(this.options.srcDir, 'pages'))) { if (!existsSync(join(this.options.srcDir, 'pages'))) {
let dir = this.options.srcDir let dir = this.options.srcDir
if (fs.existsSync(join(this.options.srcDir, '..', 'pages'))) { if (existsSync(join(this.options.srcDir, '..', 'pages'))) {
throw new Error(`No \`pages\` directory found in ${dir}. Did you mean to run \`nuxt\` in the parent (\`../\`) directory?`) throw new Error(`No \`pages\` directory found in ${dir}. Did you mean to run \`nuxt\` in the parent (\`../\`) directory?`)
} else { } else {
throw new Error(`Couldn't find a \`pages\` directory in ${dir}. Please create one under the project root`) throw new Error(`Couldn't find a \`pages\` directory in ${dir}. Please create one under the project root`)
@ -189,6 +180,42 @@ export default class Builder {
return this return this
} }
getBabelOptions({ isServer }) {
const options = _.defaults({}, {
babelrc: false,
cacheDirectory: !!this.options.dev
}, this.options.build.babel)
if (typeof options.presets === 'function') {
options.presets = options.presets({ isServer })
}
if (!options.babelrc && !options.presets) {
options.presets = [
[
require.resolve('babel-preset-vue-app'),
{
targets: isServer ? { node: '8.0.0' } : { ie: 9, uglify: true }
}
]
]
}
return options
}
getFileName(name) {
let fileName = this.options.build.filenames[name]
// Don't use hashes when watching
// https://github.com/webpack/webpack/issues/1914#issuecomment-174171709
if (this.options.dev) {
fileName = fileName.replace(/\[(chunkhash|contenthash|hash)\]\./g, '')
}
return fileName
}
async generateRoutesAndFiles() { async generateRoutesAndFiles() {
debug('Generating files...') debug('Generating files...')
// -- Templates -- // -- Templates --
@ -220,7 +247,7 @@ export default class Builder {
router: this.options.router, router: this.options.router,
env: this.options.env, env: this.options.env,
head: this.options.head, head: this.options.head,
middleware: fs.existsSync(join(this.options.srcDir, 'middleware')), middleware: existsSync(join(this.options.srcDir, 'middleware')),
store: this.options.store, store: this.options.store,
css: this.options.css, css: this.options.css,
plugins: this.plugins, plugins: this.plugins,
@ -235,16 +262,18 @@ export default class Builder {
} }
// -- Layouts -- // -- Layouts --
if (fs.existsSync(resolve(this.options.srcDir, 'layouts'))) { if (existsSync(resolve(this.options.srcDir, 'layouts'))) {
const layoutsFiles = await glob('layouts/**/*.vue', { cwd: this.options.srcDir }) const layoutsFiles = await glob('layouts/**/*.{vue,js}', { cwd: this.options.srcDir })
let hasErrorLayout = false let hasErrorLayout = false
layoutsFiles.forEach((file) => { layoutsFiles.forEach((file) => {
let name = file.split('/').slice(1).join('/').replace(/\.vue$/, '') let name = file.split('/').slice(1).join('/').replace(/\.(vue|js)$/, '')
if (name === 'error') { if (name === 'error') {
hasErrorLayout = true hasErrorLayout = true
return return
} }
templateVars.layouts[name] = this.relativeToBuild(this.options.srcDir, file) if (!templateVars.layouts[name] || /\.vue$/.test(file)) {
templateVars.layouts[name] = this.relativeToBuild(this.options.srcDir, file)
}
}) })
if (!templateVars.components.ErrorPage && hasErrorLayout) { if (!templateVars.components.ErrorPage && hasErrorLayout) {
templateVars.components.ErrorPage = this.relativeToBuild(this.options.srcDir, 'layouts/error.vue') templateVars.components.ErrorPage = this.relativeToBuild(this.options.srcDir, 'layouts/error.vue')
@ -262,8 +291,14 @@ export default class Builder {
// If user defined a custom method to create routes // If user defined a custom method to create routes
if (this._nuxtPages) { if (this._nuxtPages) {
// Use nuxt.js createRoutes bases on pages/ // Use nuxt.js createRoutes bases on pages/
const files = await glob('pages/**/*.vue', { cwd: this.options.srcDir }) const files = {};
templateVars.router.routes = createRoutes(files, this.options.srcDir) (await glob('pages/**/*.{vue,js}', { cwd: this.options.srcDir })).forEach(f => {
const key = f.replace(/\.(js|vue)$/, '')
if (/\.vue$/.test(f) || !files[key]) {
files[key] = f
}
})
templateVars.router.routes = createRoutes(Object.values(files), this.options.srcDir)
} else { } else {
templateVars.router.routes = this.options.build.createRoutes(this.options.srcDir) templateVars.router.routes = this.options.build.createRoutes(this.options.srcDir)
} }
@ -299,7 +334,7 @@ export default class Builder {
} }
// Allow override templates using a file with same name in ${srcDir}/app // Allow override templates using a file with same name in ${srcDir}/app
const customPath = r(this.options.srcDir, 'app', file) const customPath = r(this.options.srcDir, 'app', file)
const customFileExists = fs.existsSync(customPath) const customFileExists = existsSync(customPath)
return { return {
src: customFileExists src: customFileExists
@ -436,7 +471,7 @@ export default class Builder {
compiler.plugin('done', async (stats) => { compiler.plugin('done', async (stats) => {
await this.nuxt.callHook('build:compiled', { name, compiler, stats }) await this.nuxt.callHook('build:compiled', { name, compiler, stats })
// Reload renderer if available // Reload renderer if available
this.nuxt.renderer.loadResources(sharedFS || fs) this.nuxt.renderer.loadResources(sharedFS || require('fs'))
// Resolve on next tick // Resolve on next tick
process.nextTick(resolve) process.nextTick(resolve)
}) })
@ -459,6 +494,12 @@ export default class Builder {
compiler.watch(this.options.watchers.webpack, (err) => { compiler.watch(this.options.watchers.webpack, (err) => {
/* istanbul ignore if */ /* istanbul ignore if */
if (err) return reject(err) if (err) return reject(err)
// not keep modified or deleted items in Vue.prototype
Object.keys(require.cache).forEach(key => {
if (!/(node_modules\/postcss)|(\.node$)/.test(key)) {
rmCache(key)
}
})
}) })
) )
return return
@ -486,7 +527,7 @@ export default class Builder {
debug('Adding webpack middleware...') debug('Adding webpack middleware...')
// Create webpack dev middleware // Create webpack dev middleware
this.webpackDevMiddleware = pify(webpackDevMiddleware(compiler, Object.assign({ this.webpackDevMiddleware = promisify(webpackDevMiddleware(compiler, Object.assign({
publicPath: this.options.build.publicPath, publicPath: this.options.build.publicPath,
stats: this.webpackStats, stats: this.webpackStats,
noInfo: true, noInfo: true,
@ -494,9 +535,9 @@ export default class Builder {
watchOptions: this.options.watchers.webpack watchOptions: this.options.watchers.webpack
}, this.options.build.devMiddleware))) }, this.options.build.devMiddleware)))
this.webpackDevMiddleware.close = pify(this.webpackDevMiddleware.close) this.webpackDevMiddleware.close = promisify(this.webpackDevMiddleware.close)
this.webpackHotMiddleware = pify(webpackHotMiddleware(compiler, Object.assign({ this.webpackHotMiddleware = promisify(webpackHotMiddleware(compiler, Object.assign({
log: false, log: false,
heartbeat: 10000 heartbeat: 10000
}, this.options.build.hotMiddleware))) }, this.options.build.hotMiddleware)))
@ -517,14 +558,14 @@ export default class Builder {
r(src, 'layouts'), r(src, 'layouts'),
r(src, 'store'), r(src, 'store'),
r(src, 'middleware'), r(src, 'middleware'),
r(src, 'layouts/*.vue'), r(src, 'layouts/*.{vue,js}'),
r(src, 'layouts/**/*.vue') r(src, 'layouts/**/*.{vue,js}')
] ]
if (this._nuxtPages) { if (this._nuxtPages) {
patterns.push( patterns.push(
r(src, 'pages'), r(src, 'pages'),
r(src, 'pages/*.vue'), r(src, 'pages/*.{vue,js}'),
r(src, 'pages/**/*.vue') r(src, 'pages/**/*.{vue,js}')
) )
} }
const options = Object.assign({}, this.options.watchers.chokidar, { const options = Object.assign({}, this.options.watchers.chokidar, {
@ -557,6 +598,12 @@ export default class Builder {
// Stop webpack middleware // Stop webpack middleware
await this.webpackDevMiddleware.close() await this.webpackDevMiddleware.close()
} }
async generateConfig() {
const config = resolve(this.options.buildDir, 'build.config.js')
const options = _.omit(this.options, Options.unsafeKeys)
await writeFile(config, `module.exports = ${JSON.stringify(options, null, ' ')}`, 'utf8')
}
} }
const STATUS = { const STATUS = {

View File

@ -1,10 +1,10 @@
import { copy, remove, writeFile, mkdirp, removeSync, existsSync } from 'fs-extra' const { copy, remove, writeFile, mkdirp, removeSync, existsSync } = require('fs-extra')
import _ from 'lodash' const _ = require('lodash')
import { resolve, join, dirname, sep } from 'path' const { resolve, join, dirname, sep } = require('path')
import { minify } from 'html-minifier' const { minify } = require('html-minifier')
import { isUrl, promisifyRoute, waitFor, flatRoutes } from 'utils' const { isUrl, promisifyRoute, waitFor, flatRoutes } = require('../common/utils')
export default class Generator { module.exports = class Generator {
constructor(nuxt, builder) { constructor(nuxt, builder) {
this.nuxt = nuxt this.nuxt = nuxt
this.options = nuxt.options this.options = nuxt.options
@ -38,11 +38,11 @@ export default class Generator {
// Call before hook // Call before hook
await this.nuxt.callHook('generate:before', this, this.options.generate) await this.nuxt.callHook('generate:before', this, this.options.generate)
// Add flag to set process.static
this.builder.forGenerate()
// Start build process
if (build) { if (build) {
// Add flag to set process.static
this.builder.forGenerate()
// Start build process
await this.builder.build() await this.builder.build()
} }
@ -52,18 +52,17 @@ export default class Generator {
} }
} }
async initRoutes() { async initRoutes(...args) {
// Resolve config.generate.routes promises before generating the routes // Resolve config.generate.routes promises before generating the routes
let generateRoutes = [] let generateRoutes = []
if (this.options.router.mode !== 'hash') { if (this.options.router.mode !== 'hash') {
try { try {
generateRoutes = await promisifyRoute(this.options.generate.routes || []) generateRoutes = await promisifyRoute(this.options.generate.routes || [], ...args)
} catch (e) { } catch (e) {
console.error('Could not resolve routes') // eslint-disable-line no-console console.error('Could not resolve routes') // eslint-disable-line no-console
throw e // eslint-disable-line no-unreachable throw e // eslint-disable-line no-unreachable
} }
} }
// Generate only index.html for router.mode = 'hash' // Generate only index.html for router.mode = 'hash'
let routes = (this.options.router.mode === 'hash') ? ['/'] : flatRoutes(this.options.router.routes) let routes = (this.options.router.mode === 'hash') ? ['/'] : flatRoutes(this.options.router.routes)
routes = this.decorateWithPayloads(routes, generateRoutes) routes = this.decorateWithPayloads(routes, generateRoutes)

View File

@ -1,7 +1,7 @@
import Builder from './builder' const Builder = require('./builder')
import Generator from './generator' const Generator = require('./generator')
export { module.exports = {
Builder, Builder,
Generator Generator
} }

View File

@ -1,9 +1,10 @@
import ExtractTextPlugin from 'extract-text-webpack-plugin' const ExtractTextPlugin = require('extract-text-webpack-plugin')
import { cloneDeep } from 'lodash' const { cloneDeep } = require('lodash')
import { join, resolve } from 'path' const { join, resolve } = require('path')
import webpack from 'webpack' const webpack = require('webpack')
import { isUrl, urlJoin } from 'utils' const { isUrl, urlJoin } = require('../../common/utils')
import TimeFixPlugin from './timefix-plugin' const TimeFixPlugin = require('./plugins/timefix')
const WarnFixPlugin = require('./plugins/warnfix')
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -13,19 +14,17 @@ import TimeFixPlugin from './timefix-plugin'
| webpack config files | webpack config files
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
export default function webpackBaseConfig(name) { module.exports = function webpackBaseConfig({ name, isServer }) {
const nodeModulesDir = join(__dirname, '..', 'node_modules')
const config = { const config = {
name, name,
devtool: this.options.dev ? 'cheap-module-source-map' : 'nosources-source-map', devtool: this.options.dev ? 'cheap-module-eval-source-map' : false,
entry: { entry: {
app: null app: null
}, },
output: { output: {
path: resolve(this.options.buildDir, 'dist'), path: resolve(this.options.buildDir, 'dist'),
filename: this.options.build.filenames.app, filename: this.getFileName('app'),
chunkFilename: this.options.build.filenames.chunk, chunkFilename: this.getFileName('chunk'),
publicPath: (isUrl(this.options.build.publicPath) publicPath: (isUrl(this.options.build.publicPath)
? this.options.build.publicPath ? this.options.build.publicPath
: urlJoin(this.options.router.base, this.options.build.publicPath)) : urlJoin(this.options.router.base, this.options.build.publicPath))
@ -47,16 +46,10 @@ export default function webpackBaseConfig(name) {
'assets': join(this.options.srcDir, 'assets'), 'assets': join(this.options.srcDir, 'assets'),
'static': join(this.options.srcDir, 'static') 'static': join(this.options.srcDir, 'static')
}, },
modules: [ modules: this.options.modulesDir
...this.options.modulesDir,
nodeModulesDir
]
}, },
resolveLoader: { resolveLoader: {
modules: [ modules: this.options.modulesDir
...this.options.modulesDir,
nodeModulesDir
]
}, },
module: { module: {
noParse: /es6-promise\.js$/, // Avoid webpack shimming process noParse: /es6-promise\.js$/, // Avoid webpack shimming process
@ -64,13 +57,13 @@ export default function webpackBaseConfig(name) {
{ {
test: /\.vue$/, test: /\.vue$/,
loader: 'vue-loader', loader: 'vue-loader',
options: this.vueLoader() options: this.vueLoader({ isServer })
}, },
{ {
test: /\.js$/, test: /\.js$/,
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/, exclude: /node_modules/,
options: Object.assign({}, this.babelOptions) options: this.getBabelOptions({ isServer })
}, },
{ test: /\.css$/, use: this.styleLoader('css') }, { test: /\.css$/, use: this.styleLoader('css') },
{ test: /\.less$/, use: this.styleLoader('less', 'less-loader') }, { test: /\.less$/, use: this.styleLoader('less', 'less-loader') },
@ -106,32 +99,23 @@ export default function webpackBaseConfig(name) {
} }
// Add timefix-plugin before others plugins // Add timefix-plugin before others plugins
config.plugins.unshift(new TimeFixPlugin()) if (this.options.dev) {
config.plugins.unshift(new TimeFixPlugin())
}
// Hide warnings about plugins without a default export (#1179)
config.plugins.push(new WarnFixPlugin())
// CSS extraction // CSS extraction
const extractCSS = this.options.build.extractCSS const extractCSS = this.options.build.extractCSS
if (extractCSS) { if (extractCSS) {
const extractOptions = Object.assign( const extractOptions = Object.assign(
{ filename: this.options.build.filenames.css }, { filename: this.getFileName('css') },
typeof extractCSS === 'object' ? extractCSS : {} typeof extractCSS === 'object' ? extractCSS : {}
) )
config.plugins.push(new ExtractTextPlugin(extractOptions)) config.plugins.push(new ExtractTextPlugin(extractOptions))
} }
// Workaround for hiding Warnings about plugins without a default export (#1179)
config.plugins.push({
apply(compiler) {
compiler.plugin('done', stats => {
stats.compilation.warnings = stats.compilation.warnings.filter(warn => {
if (warn.name === 'ModuleDependencyWarning' && warn.message.includes(`export 'default'`) && warn.message.includes('plugin')) {
return false
}
return true
})
})
}
})
// -------------------------------------- // --------------------------------------
// Dev specific config // Dev specific config
// -------------------------------------- // --------------------------------------

View File

@ -1,15 +1,17 @@
import { each } from 'lodash' const { each } = require('lodash')
import webpack from 'webpack' const webpack = require('webpack')
import VueSSRClientPlugin from 'vue-server-renderer/client-plugin' const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
import HTMLPlugin from 'html-webpack-plugin' const HTMLPlugin = require('html-webpack-plugin')
import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin' const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
import UglifyJSPlugin from 'uglifyjs-webpack-plugin' const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
import ProgressBarPlugin from 'progress-bar-webpack-plugin' const ProgressBarPlugin = require('progress-bar-webpack-plugin')
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' const ProgressPlugin = require('webpack/lib/ProgressPlugin')
import { resolve } from 'path' const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
import { existsSync } from 'fs' const { resolve } = require('path')
import Debug from 'debug' const { existsSync } = require('fs')
import base from './base.config.js' const Debug = require('debug')
const Chalk = require('chalk')
const base = require('./base.config.js')
const debug = Debug('nuxt:build') const debug = Debug('nuxt:build')
debug.color = 2 // Force green color debug.color = 2 // Force green color
@ -24,8 +26,8 @@ debug.color = 2 // Force green color
| In production, will generate public/dist/style.css | In production, will generate public/dist/style.css
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
export default function webpackClientConfig() { module.exports = function webpackClientConfig() {
let config = base.call(this, 'client') let config = base.call(this, { name: 'client', isServer: false })
// Entry points // Entry points
config.entry.app = resolve(this.options.buildDir, 'client.js') config.entry.app = resolve(this.options.buildDir, 'client.js')
@ -73,7 +75,7 @@ export default function webpackClientConfig() {
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'manifest', name: 'manifest',
minChunks: Infinity, minChunks: Infinity,
filename: this.options.build.filenames.manifest filename: this.getFileName('manifest')
}) })
) )
@ -90,18 +92,26 @@ export default function webpackClientConfig() {
})) }))
) )
// Add friendly error plugin
config.plugins.push(new FriendlyErrorsWebpackPlugin())
// Build progress bar // Build progress bar
config.plugins.push( if (this.options.build.profile) {
new ProgressBarPlugin() config.plugins.push(new ProgressPlugin({
) profile: true
}))
} else {
config.plugins.push(new ProgressBarPlugin({
complete: Chalk.green('█'),
incomplete: Chalk.white('█'),
format: ' :bar ' + Chalk.green.bold(':percent') + ' :msg'
}))
}
// -------------------------------------- // --------------------------------------
// Dev specific config // Dev specific config
// -------------------------------------- // --------------------------------------
if (this.options.dev) { if (this.options.dev) {
// Add friendly error plugin
config.plugins.push(new FriendlyErrorsWebpackPlugin())
// https://webpack.js.org/plugins/named-modules-plugin // https://webpack.js.org/plugins/named-modules-plugin
config.plugins.push(new webpack.NamedModulesPlugin()) config.plugins.push(new webpack.NamedModulesPlugin())
@ -139,7 +149,7 @@ export default function webpackClientConfig() {
if (this.options.build.uglify !== false) { if (this.options.build.uglify !== false) {
config.plugins.push( config.plugins.push(
new UglifyJSPlugin(Object.assign({ new UglifyJSPlugin(Object.assign({
cache: true, // cache: true,
sourceMap: true, sourceMap: true,
parallel: true, parallel: true,
extractComments: { extractComments: {
@ -184,36 +194,18 @@ export default function webpackClientConfig() {
// Adds Common Chunks Plugin // Adds Common Chunks Plugin
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
function commonChunksPlugin(config) { function commonChunksPlugin(config) {
const _this = this
const totalPages = _this.routes ? _this.routes.length : 0
// This well-known vendor may exist as a dependency of other requests.
const maybeVendor = [
'/core-js/',
'/regenerator-runtime/',
'/es6-promise/',
'/babel-runtime/',
'/lodash/'
]
// Create explicit vendor chunk // Create explicit vendor chunk
config.plugins.unshift( config.plugins.unshift(
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', name: 'vendor',
filename: this.options.build.filenames.vendor, filename: this.getFileName('vendor'),
minChunks(module, count) { minChunks(module, count) {
// Detect and externalize well-known vendor if detected
if (module.context && maybeVendor.some(v => module.context.includes(v))) {
return true
}
// A module is extracted into the vendor chunk when... // A module is extracted into the vendor chunk when...
return ( return (
// If it's inside node_modules // If it's inside node_modules
/node_modules/.test(module.context) && /node_modules/.test(module.context) &&
// Do not externalize if the request is a CSS file // Do not externalize if the request is a CSS file
!/\.(css|less|scss|sass|styl|stylus)$/.test(module.request) && !/\.(css|less|scss|sass|styl|stylus)$/.test(module.request)
// Used in at-least 1/2 of the total pages
(totalPages <= 2 ? count >= totalPages : count >= totalPages * 0.5)
) )
} }
}) })

View File

@ -1,6 +1,6 @@
import webpack from 'webpack' const webpack = require('webpack')
import { resolve } from 'path' const { resolve } = require('path')
import ClientConfig from './client.config' const ClientConfig = require('./client.config')
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -8,7 +8,7 @@ import ClientConfig from './client.config'
| https://github.com/webpack/webpack/tree/master/examples/dll | https://github.com/webpack/webpack/tree/master/examples/dll
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
export default function webpackDllConfig(_refConfig) { module.exports = function webpackDllConfig(_refConfig) {
const refConfig = _refConfig || new ClientConfig() const refConfig = _refConfig || new ClientConfig()
const name = refConfig.name + '-dll' const name = refConfig.name + '-dll'

View File

@ -1,6 +1,6 @@
// Taken from https://github.com/egoist/poi/blob/3e93c88c520db2d20c25647415e6ae0d3de61145/packages/poi/lib/webpack/timefix-plugin.js#L1-L16 // Taken from https://github.com/egoist/poi/blob/3e93c88c520db2d20c25647415e6ae0d3de61145/packages/poi/lib/webpack/timefix-plugin.js#L1-L16
// Thanks to @egoist // Thanks to @egoist
export default class TimeFixPlugin { module.exports = class TimeFixPlugin {
constructor(timefix = 11000) { constructor(timefix = 11000) {
this.timefix = timefix this.timefix = timefix
} }

View File

@ -0,0 +1,14 @@
module.exports = class WarnFixPlugin {
apply(compiler) /* istanbul ignore next */ {
compiler.plugin('done', stats => {
stats.compilation.warnings = stats.compilation.warnings.filter(warn => {
if (warn.name === 'ModuleDependencyWarning' &&
warn.message.includes(`export 'default'`) &&
warn.message.includes('nuxt_plugin_')) {
return false
}
return true
})
})
}
}

View File

@ -1,18 +1,18 @@
import webpack from 'webpack' const webpack = require('webpack')
import VueSSRServerPlugin from 'vue-server-renderer/server-plugin' const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
import nodeExternals from 'webpack-node-externals' const nodeExternals = require('webpack-node-externals')
import { each } from 'lodash' const { each } = require('lodash')
import { resolve } from 'path' const { resolve } = require('path')
import { existsSync } from 'fs' const { existsSync } = require('fs')
import base from './base.config.js' const base = require('./base.config.js')
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Webpack Server Config | Webpack Server Config
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
export default function webpackServerConfig() { module.exports = function webpackServerConfig() {
let config = base.call(this, 'server') let config = base.call(this, { name: 'server', isServer: true })
// env object defined in nuxt.config.js // env object defined in nuxt.config.js
let env = {} let env = {}

View File

@ -1,7 +1,7 @@
import ExtractTextPlugin from 'extract-text-webpack-plugin' const ExtractTextPlugin = require('extract-text-webpack-plugin')
import { join } from 'path' const { join } = require('path')
export default function styleLoader(ext, loaders = [], isVueLoader = false) { module.exports = function styleLoader(ext, loaders = [], isVueLoader = false) {
// Normalize loaders // Normalize loaders
loaders = (Array.isArray(loaders) ? loaders : [loaders]).map(loader => { loaders = (Array.isArray(loaders) ? loaders : [loaders]).map(loader => {
if (typeof loader === 'string') { if (typeof loader === 'string') {

View File

@ -1,4 +1,4 @@
export default function vueLoader() { module.exports = function vueLoader({ isServer }) {
// https://vue-loader.vuejs.org/en // https://vue-loader.vuejs.org/en
const config = { const config = {
postcss: this.options.build.postcss, postcss: this.options.build.postcss,
@ -8,7 +8,7 @@ export default function vueLoader() {
loaders: { loaders: {
'js': { 'js': {
loader: 'babel-loader', loader: 'babel-loader',
options: Object.assign({}, this.babelOptions) options: this.getBabelOptions({ isServer })
}, },
// Note: do not nest the `postcss` option under `loaders` // Note: do not nest the `postcss` option under `loaders`
'css': this.styleLoader('css', [], true), 'css': this.styleLoader('css', [], true),

View File

@ -8,10 +8,9 @@ pe.skipPackage('regenerator-runtime')
pe.skipPackage('babel-runtime') pe.skipPackage('babel-runtime')
pe.skipPackage('core-js') pe.skipPackage('core-js')
// Skip dist artifacts and Node internals // Skip node internals
const skipFiles = [ 'nuxt.js', 'core.js' ]
pe.skip((traceLine, lineNumber) => { pe.skip((traceLine, lineNumber) => {
if (!traceLine.file || skipFiles.indexOf(traceLine.file) !== -1) { if (!traceLine.file) {
return true return true
} }
}) })

View File

@ -1,12 +1,7 @@
import * as Utils from './utils' const Utils = require('./utils')
import Options from './options' const Options = require('./options')
export default { module.exports = {
Utils,
Options
}
export {
Utils, Utils,
Options Options
} }

View File

@ -1,15 +1,15 @@
import _ from 'lodash' const _ = require('lodash')
import Debug from 'debug' const Debug = require('debug')
import { join, resolve } from 'path' const { join, resolve } = require('path')
import { existsSync } from 'fs' const { existsSync } = require('fs')
import { isUrl, isPureObject } from 'utils' const { isUrl, isPureObject } = require('../common/utils')
const debug = Debug('nuxt:build') const debug = Debug('nuxt:build')
debug.color = 2 // Force green color debug.color = 2 // Force green color
const Options = {} const Options = {}
export default Options module.exports = Options
Options.from = function (_options) { Options.from = function (_options) {
// Clone options to prevent unwanted side-effects // Clone options to prevent unwanted side-effects
@ -32,26 +32,37 @@ Options.from = function (_options) {
options.layoutTransition = { name: options.layoutTransition } options.layoutTransition = { name: options.layoutTransition }
} }
const hasValue = v => typeof v === 'string' && v
options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd()
// Apply defaults by ${buildDir}/dist/build.config.js
const buildDir = options.buildDir || Options.defaults.buildDir
const buildConfig = resolve(options.rootDir, buildDir, 'build.config.js')
if (existsSync(buildConfig)) {
_.defaultsDeep(options, require(buildConfig))
}
// Apply defaults // Apply defaults
_.defaultsDeep(options, Options.defaults) _.defaultsDeep(options, Options.defaults)
// Resolve dirs // Resolve dirs
const hasValue = v => typeof v === 'string' && v
options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd()
options.srcDir = hasValue(options.srcDir) ? resolve(options.rootDir, options.srcDir) : options.rootDir options.srcDir = hasValue(options.srcDir) ? resolve(options.rootDir, options.srcDir) : options.rootDir
options.buildDir = resolve(options.rootDir, options.buildDir) options.buildDir = resolve(options.rootDir, options.buildDir)
options.cacheDir = resolve(options.rootDir, options.cacheDir) options.cacheDir = resolve(options.rootDir, options.cacheDir)
// Normalize modulesDir // Normalize modulesDir
/* istanbul ignore if */
if (!options.modulesDir) {
options.modulesDir = ['node_modules']
}
if (!Array.isArray(options.modulesDir)) { if (!Array.isArray(options.modulesDir)) {
options.modulesDir = [options.modulesDir] options.modulesDir = [options.modulesDir]
} }
/* istanbul ignore if */
if (!options.modulesDir.length) {
options.modulesDir = ['node_modules']
}
options.modulesDir = options.modulesDir.filter(dir => hasValue(dir)).map(dir => resolve(options.rootDir, dir)) options.modulesDir = options.modulesDir.filter(dir => hasValue(dir)).map(dir => resolve(options.rootDir, dir))
// Always add nuxt to modulesDir array
const nuxtModulesDir = join(options.nuxtDir, 'node_modules')
options.modulesDir.push(nuxtModulesDir)
// If app.html is defined, set the template path to the user template // If app.html is defined, set the template path to the user template
options.appTemplatePath = resolve(options.buildDir, 'views/app.template.html') options.appTemplatePath = resolve(options.buildDir, 'views/app.template.html')
if (existsSync(join(options.srcDir, 'app.html'))) { if (existsSync(join(options.srcDir, 'app.html'))) {
@ -185,16 +196,22 @@ Options.modes = {
} }
} }
Options.unsafeKeys = [
'rootDir', 'srcDir', 'buildDir', 'modulesDir', 'cacheDir', 'nuxtDir',
'nuxtAppDir', 'build', 'generate', 'router.routes', 'appTemplatePath'
]
Options.defaults = { Options.defaults = {
mode: 'universal', mode: 'universal',
dev: process.env.NODE_ENV !== 'production', dev: process.env.NODE_ENV !== 'production',
debug: undefined, // Will be equal to dev if not provided debug: undefined, // Will be equal to dev if not provided
buildDir: '.nuxt', buildDir: '.nuxt',
cacheDir: '.cache', cacheDir: '.cache',
nuxtDir: resolve(__dirname, '..'), // Relative to dist nuxtDir: resolve(__dirname, '../..'),
nuxtAppDir: resolve(__dirname, '../lib/app/'), // Relative to dist nuxtAppDir: resolve(__dirname, '../app'),
build: { build: {
analyze: false, analyze: false,
profile: process.argv.includes('--profile'),
dll: false, dll: false,
scopeHoisting: false, scopeHoisting: false,
extractCSS: false, extractCSS: false,
@ -323,6 +340,7 @@ Options.defaults = {
back_to_home: 'Back to the home page', back_to_home: 'Back to the home page',
server_error_details: 'An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details.', server_error_details: 'An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details.',
client_error: 'Error', client_error: 'Error',
client_error_details: 'An error occurred while rendering the page. Check developer tools console for details.' client_error_details: 'An error occurred while rendering the page. Check developer tools console for details.',
redirect: 'Redirecting to external page.'
} }
} }

View File

@ -1,15 +1,15 @@
import { resolve, relative, sep } from 'path' const { resolve, relative, sep } = require('path')
import _ from 'lodash' const _ = require('lodash')
export function encodeHtml(str) { exports.encodeHtml = function encodeHtml(str) {
return str.replace(/</g, '&lt;').replace(/>/g, '&gt;') return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
} }
export function getContext(req, res) { exports.getContext = function getContext(req, res) {
return { req, res } return { req, res }
} }
export function setAnsiColors(ansiHTML) { exports.setAnsiColors = function setAnsiColors(ansiHTML) {
ansiHTML.setColors({ ansiHTML.setColors({
reset: ['efefef', 'a6004c'], reset: ['efefef', 'a6004c'],
darkgrey: '5a012b', darkgrey: '5a012b',
@ -22,50 +22,50 @@ export function setAnsiColors(ansiHTML) {
}) })
} }
export async function waitFor(ms) { exports.waitFor = async function waitFor(ms) {
return new Promise(resolve => setTimeout(resolve, (ms || 0))) return new Promise(resolve => setTimeout(resolve, (ms || 0)))
} }
export function urlJoin() { exports.urlJoin = function urlJoin() {
return [].slice.call(arguments).join('/').replace(/\/+/g, '/').replace(':/', '://') return [].slice.call(arguments).join('/').replace(/\/+/g, '/').replace(':/', '://')
} }
export function isUrl(url) { exports.isUrl = function isUrl(url) {
return (url.indexOf('http') === 0 || url.indexOf('//') === 0) return (url.indexOf('http') === 0 || url.indexOf('//') === 0)
} }
export function promisifyRoute(fn) { exports.promisifyRoute = function promisifyRoute(fn, ...args) {
// If routes is an array // If routes is an array
if (Array.isArray(fn)) { if (Array.isArray(fn)) {
return Promise.resolve(fn) return Promise.resolve(fn)
} }
// If routes is a function expecting a callback // If routes is a function expecting a callback
if (fn.length === 1) { if (fn.length === arguments.length) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fn(function (err, routeParams) { fn((err, routeParams) => {
if (err) { if (err) {
reject(err) reject(err)
} }
resolve(routeParams) resolve(routeParams)
}) }, ...args)
}) })
} }
let promise = fn() let promise = fn(...args)
if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) { if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) {
promise = Promise.resolve(promise) promise = Promise.resolve(promise)
} }
return promise return promise
} }
export function sequence(tasks, fn) { exports.sequence = function sequence(tasks, fn) {
return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve()) return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve())
} }
export function parallel(tasks, fn) { exports.parallel = function parallel(tasks, fn) {
return Promise.all(tasks.map(task => fn(task))) return Promise.all(tasks.map(task => fn(task)))
} }
export function chainFn(base, fn) { exports.chainFn = function chainFn(base, fn) {
/* istanbul ignore if */ /* istanbul ignore if */
if (!(fn instanceof Function)) { if (!(fn instanceof Function)) {
return return
@ -88,13 +88,13 @@ export function chainFn(base, fn) {
} }
} }
export function isPureObject(o) { exports.isPureObject = function isPureObject(o) {
return !Array.isArray(o) && typeof o === 'object' return !Array.isArray(o) && typeof o === 'object'
} }
export const isWindows = /^win/.test(process.platform) const isWindows = exports.isWindows = /^win/.test(process.platform)
export function wp(p = '') { const wp = exports.wp = function wp(p = '') {
/* istanbul ignore if */ /* istanbul ignore if */
if (isWindows) { if (isWindows) {
return p.replace(/\\/g, '\\\\') return p.replace(/\\/g, '\\\\')
@ -102,7 +102,7 @@ export function wp(p = '') {
return p return p
} }
export function wChunk(p = '') { exports.wChunk = function wChunk(p = '') {
/* istanbul ignore if */ /* istanbul ignore if */
if (isWindows) { if (isWindows) {
return p.replace(/\//g, '_') return p.replace(/\//g, '_')
@ -114,18 +114,18 @@ const reqSep = /\//g
const sysSep = _.escapeRegExp(sep) const sysSep = _.escapeRegExp(sep)
const normalize = string => string.replace(reqSep, sysSep) const normalize = string => string.replace(reqSep, sysSep)
export function r() { const r = exports.r = function r() {
let args = Array.prototype.slice.apply(arguments) let args = Array.prototype.slice.apply(arguments)
let lastArg = _.last(args) let lastArg = _.last(args)
if (lastArg.includes('@') || lastArg.includes('~')) { if (lastArg.indexOf('@') === 0 || lastArg.indexOf('~') === 0) {
return wp(lastArg) return wp(lastArg)
} }
return wp(resolve(...args.map(normalize))) return wp(resolve(...args.map(normalize)))
} }
export function relativeTo() { exports.relativeTo = function relativeTo() {
let args = Array.prototype.slice.apply(arguments) let args = Array.prototype.slice.apply(arguments)
let dir = args.shift() let dir = args.shift()
@ -133,7 +133,7 @@ export function relativeTo() {
let path = r(...args) let path = r(...args)
// Check if path is an alias // Check if path is an alias
if (path.includes('@') || path.includes('~')) { if (path.indexOf('@') === 0 || path.indexOf('~') === 0) {
return path return path
} }
@ -145,13 +145,17 @@ export function relativeTo() {
return wp(rp) return wp(rp)
} }
export function flatRoutes(router, path = '', routes = []) { exports.flatRoutes = function flatRoutes(router, path = '', routes = []) {
router.forEach((r) => { router.forEach((r) => {
if (!r.path.includes(':') && !r.path.includes('*')) { if (!r.path.includes(':') && !r.path.includes('*')) {
/* istanbul ignore if */ /* istanbul ignore if */
if (r.children) { if (r.children) {
if (path === '' && r.path === '/') {
routes.push('/')
}
flatRoutes(r.children, path + r.path + '/', routes) flatRoutes(r.children, path + r.path + '/', routes)
} else { } else {
path = path.replace(/^\/+$/, '/')
routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path) routes.push((r.path === '' && path[path.length - 1] === '/' ? path.slice(0, -1) : path) + r.path)
} }
} }
@ -159,7 +163,7 @@ export function flatRoutes(router, path = '', routes = []) {
return routes return routes
} }
export function cleanChildrenRoutes(routes, isChild = false) { function cleanChildrenRoutes(routes, isChild = false) {
let start = -1 let start = -1
let routesIndex = [] let routesIndex = []
routes.forEach((route) => { routes.forEach((route) => {
@ -205,16 +209,16 @@ export function cleanChildrenRoutes(routes, isChild = false) {
return routes return routes
} }
export function createRoutes(files, srcDir) { exports.createRoutes = function createRoutes(files, srcDir) {
let routes = [] let routes = []
files.forEach((file) => { files.forEach((file) => {
let keys = file.replace(/^pages/, '').replace(/\.vue$/, '').replace(/\/{2,}/g, '/').split('/').slice(1) let keys = file.replace(/^pages/, '').replace(/\.(vue|js)$/, '').replace(/\/{2,}/g, '/').split('/').slice(1)
let route = { name: '', path: '', component: r(srcDir, file) } let route = { name: '', path: '', component: r(srcDir, file) }
let parent = routes let parent = routes
keys.forEach((key, i) => { keys.forEach((key, i) => {
route.name = route.name ? route.name + '-' + key.replace('_', '') : key.replace('_', '') route.name = route.name ? route.name + '-' + key.replace('_', '') : key.replace('_', '')
route.name += (key === '_') ? 'all' : '' route.name += (key === '_') ? 'all' : ''
route.chunkName = file.replace(/\.vue$/, '') route.chunkName = file.replace(/\.(vue|js)$/, '')
let child = _.find(parent, { name: route.name }) let child = _.find(parent, { name: route.name })
if (child) { if (child) {
child.children = child.children || [] child.children = child.children || []
@ -264,3 +268,16 @@ export function createRoutes(files, srcDir) {
}) })
return cleanChildrenRoutes(routes) return cleanChildrenRoutes(routes)
} }
exports.rmCache = function rmCache(path) {
const mod = require.cache[path]
delete require.cache[path]
if (mod.parent && mod.parent.children) {
for (let i = 0; i < mod.parent.children.length; i++) {
if (mod.parent.children[i] === mod) {
mod.parent.children.splice(i, 1)
break
}
}
}
}

View File

@ -1,9 +1,9 @@
import { Options, Utils } from 'common' const { Options, Utils } = require('../common')
import Module from './module' const Module = require('./module')
import Nuxt from './nuxt' const Nuxt = require('./nuxt')
import Renderer from './renderer' const Renderer = require('./renderer')
export { module.exports = {
Nuxt, Nuxt,
Module, Module,
Renderer, Renderer,

View File

@ -1,10 +1,10 @@
import Vue from 'vue' const Vue = require('vue')
import VueMeta from 'vue-meta' const VueMeta = require('vue-meta')
import VueServerRenderer from 'vue-server-renderer' const VueServerRenderer = require('vue-server-renderer')
import LRU from 'lru-cache' const LRU = require('lru-cache')
export default class MetaRenderer { module.exports = class MetaRenderer {
constructor(nuxt, renderer) { constructor(nuxt, renderer) {
this.nuxt = nuxt this.nuxt = nuxt
this.renderer = renderer this.renderer = renderer

View File

@ -1,13 +1,13 @@
import path from 'path' const path = require('path')
import fs from 'fs' const fs = require('fs')
import { uniq } from 'lodash' const { uniq } = require('lodash')
import hash from 'hash-sum' const hash = require('hash-sum')
import { chainFn, sequence } from 'utils' const { chainFn, sequence } = require('../common/utils')
import Debug from 'debug' const Debug = require('debug')
const debug = Debug('nuxt:module') const debug = Debug('nuxt:module')
export default class ModuleContainer { module.exports = class ModuleContainer {
constructor(nuxt) { constructor(nuxt) {
this.nuxt = nuxt this.nuxt = nuxt
this.options = nuxt.options this.options = nuxt.options

View File

@ -1,18 +1,19 @@
import chalk from 'chalk' const Debug = require('debug')
import { Options } from 'common' const enableDestroy = require('server-destroy')
import { sequence } from 'utils' const Module = require('module')
import ModuleContainer from './module' const { isPlainObject } = require('lodash')
import Renderer from './renderer' const chalk = require('chalk')
import Debug from 'debug' const { Options } = require('../common')
import enableDestroy from 'server-destroy' const { sequence } = require('../common/utils')
import Module from 'module' const { join, resolve } = require('path')
import { isPlainObject } from 'lodash' const { version } = require('../../package.json')
import { join, resolve } from 'path' const ModuleContainer = require('./module')
const Renderer = require('./renderer')
const debug = Debug('nuxt:') const debug = Debug('nuxt:')
debug.color = 5 debug.color = 5
export default class Nuxt { module.exports = class Nuxt {
constructor(options = {}) { constructor(options = {}) {
this.options = Options.from(options) this.options = Options.from(options)
@ -44,6 +45,10 @@ export default class Nuxt {
this._ready = this.ready().catch(this.errorHandler) this._ready = this.ready().catch(this.errorHandler)
} }
static get version() {
return version
}
async ready() { async ready() {
if (this._ready) { if (this._ready) {
return this._ready return this._ready

View File

@ -1,20 +1,20 @@
import ansiHTML from 'ansi-html' const ansiHTML = require('ansi-html')
import serialize from 'serialize-javascript' const serialize = require('serialize-javascript')
import generateETag from 'etag' const generateETag = require('etag')
import fresh from 'fresh' const fresh = require('fresh')
import serveStatic from 'serve-static' const serveStatic = require('serve-static')
import compression from 'compression' const compression = require('compression')
import _ from 'lodash' const _ = require('lodash')
import { join, resolve } from 'path' const { join, resolve } = require('path')
import fs from 'fs-extra' const fs = require('fs-extra')
import { createBundleRenderer } from 'vue-server-renderer' const { createBundleRenderer } = require('vue-server-renderer')
import { getContext, setAnsiColors, isUrl, waitFor } from 'utils' const { getContext, setAnsiColors, isUrl, waitFor } = require('../common/utils')
import Debug from 'debug' const Debug = require('debug')
import Youch from '@nuxtjs/youch' const Youch = require('@nuxtjs/youch')
import { SourceMapConsumer } from 'source-map' const { SourceMapConsumer } = require('source-map')
import connect from 'connect' const connect = require('connect')
import { Options } from 'common' const { Options } = require('../common')
import MetaRenderer from './meta' const MetaRenderer = require('./meta')
const debug = Debug('nuxt:render') const debug = Debug('nuxt:render')
debug.color = 4 // Force blue color debug.color = 4 // Force blue color
@ -23,7 +23,7 @@ setAnsiColors(ansiHTML)
let jsdom = null let jsdom = null
export default class Renderer { module.exports = class Renderer {
constructor(nuxt) { constructor(nuxt) {
this.nuxt = nuxt this.nuxt = nuxt
this.options = nuxt.options this.options = nuxt.options
@ -126,6 +126,7 @@ export default class Renderer {
get isResourcesAvailable() { get isResourcesAvailable() {
// Required for both // Required for both
/* istanbul ignore if */
if (!this.resources.clientManifest) { if (!this.resources.clientManifest) {
return false return false
} }
@ -223,7 +224,7 @@ export default class Renderer {
// open in editor for debug mode only // open in editor for debug mode only
const _this = this const _this = this
if (this.options.debug) { if (this.options.debug && this.options.dev) {
this.useMiddleware({ this.useMiddleware({
path: '_open', path: '_open',
handler(req, res) { handler(req, res) {
@ -417,9 +418,11 @@ export default class Renderer {
if (line) { if (line) {
frame.lineNumber = line frame.lineNumber = line
} }
/* istanbul ignore if */
if (column) { if (column) {
frame.columnNumber = column frame.columnNumber = column
} }
/* istanbul ignore if */
if (name) { if (name) {
frame.functionName = name frame.functionName = name
} }

View File

@ -1,4 +1,11 @@
import * as core from './core' /*!
import * as builder from './builder' * Nuxt.js
* (c) 2016-2017 Chopin Brothers
* Core maintainer: Pooya Parsa (@pi0)
* Released under the MIT License.
*/
export default Object.assign({}, core, builder) const core = require('./core')
const builder = require('./builder')
module.exports = Object.assign({}, core, builder)

View File

@ -13,7 +13,7 @@
"name": "Pooya Parsa (@pi0)" "name": "Pooya Parsa (@pi0)"
} }
], ],
"main": "./index.js", "main": "./lib/index.js",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
@ -21,9 +21,7 @@
}, },
"files": [ "files": [
"bin", "bin",
"dist", "lib"
"lib",
"index.js"
], ],
"keywords": [ "keywords": [
"nuxt", "nuxt",
@ -47,30 +45,24 @@
] ]
}, },
"scripts": { "scripts": {
"test": "npm run lint && cross-env NODE_ENV=test npm run build:nuxt && nyc ava --verbose --serial test/ -- && nyc report --reporter=html", "test": "npm run lint && nyc ava --verbose --serial test/ -- && nyc report --reporter=html",
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"lint": "eslint --ext .js,.vue bin/ lib/ test/ examples/", "lint": "eslint --ext .js,.vue bin/* build/ lib/ test/ examples/",
"build": "rimraf dist/ && npm run build:nuxt && npm run build:core",
"build:nuxt": "rollup -c build/rollup.config.js --environment TARGET:nuxt",
"build:core": "rollup -c build/rollup.config.js --environment TARGET:core",
"watch": "npm run build:nuxt -- -w",
"make-start": "node ./build/start.js",
"precommit": "npm run lint", "precommit": "npm run lint",
"postinstall": "opencollective postinstall || exit 0", "postinstall": "opencollective postinstall || exit 0"
"release-next": "npm run build && node ./build/release-next && npm publish --tag next"
}, },
"engines": { "engines": {
"node": ">=6.11", "node": ">=8.0.0",
"npm": ">=3.10.0" "npm": ">=5.0.0"
}, },
"dependencies": { "dependencies": {
"@nuxtjs/youch": "^3.1.0", "@nuxtjs/youch": "^3.1.0",
"ansi-html": "^0.0.7", "ansi-html": "^0.0.7",
"autoprefixer": "^7.1.6", "autoprefixer": "^7.2.2",
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.2",
"babel-preset-vue-app": "^1.3.1", "babel-preset-vue-app": "^2.0.0",
"caniuse-lite": "^1.0.30000758", "caniuse-lite": "^1.0.30000782",
"chalk": "^2.3.0", "chalk": "^2.3.0",
"chokidar": "^1.7.0", "chokidar": "^1.7.0",
"clone": "^2.1.1", "clone": "^2.1.1",
@ -84,10 +76,10 @@
"file-loader": "^1.1.5", "file-loader": "^1.1.5",
"fresh": "^0.5.2", "fresh": "^0.5.2",
"friendly-errors-webpack-plugin": "^1.6.1", "friendly-errors-webpack-plugin": "^1.6.1",
"fs-extra": "^4.0.2", "fs-extra": "^5.0.0",
"glob": "^7.1.2", "glob": "^7.1.2",
"hash-sum": "^1.0.2", "hash-sum": "^1.0.2",
"html-minifier": "^3.5.6", "html-minifier": "3.5.7",
"html-webpack-plugin": "^2.30.1", "html-webpack-plugin": "^2.30.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"lru-cache": "^4.1.1", "lru-cache": "^4.1.1",
@ -95,70 +87,62 @@
"minimist": "^1.2.0", "minimist": "^1.2.0",
"open-in-editor": "^2.2.0", "open-in-editor": "^2.2.0",
"opencollective": "^1.0.3", "opencollective": "^1.0.3",
"pify": "^3.0.0",
"postcss": "^6.0.14", "postcss": "^6.0.14",
"postcss-cssnext": "^3.0.2", "postcss-cssnext": "^3.0.2",
"postcss-import": "^11.0.0", "postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8", "postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1", "postcss-url": "^7.3.0",
"pretty-error": "^2.1.1", "pretty-error": "^2.1.1",
"progress-bar-webpack-plugin": "^1.10.0", "progress-bar-webpack-plugin": "^1.10.0",
"serialize-javascript": "^1.4.0", "serialize-javascript": "^1.4.0",
"serve-static": "^1.13.1", "serve-static": "^1.13.1",
"server-destroy": "^1.0.1", "server-destroy": "^1.0.1",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"source-map-support": "^0.5.0", "style-resources-loader": "^1.0.0",
"style-resources-loader": "^0.3.0", "uglifyjs-webpack-plugin": "^1.1.2",
"uglifyjs-webpack-plugin": "^1.0.1",
"url-loader": "^0.6.2", "url-loader": "^0.6.2",
"vue": "^2.5.6", "vue": "^2.5.9",
"vue-loader": "^13.5.0", "vue-loader": "^13.5.0",
"vue-meta": "^1.3.1", "vue-meta": "^1.4.0",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vue-server-renderer": "^2.5.6", "vue-server-renderer": "^2.5.9",
"vue-template-compiler": "^2.5.6", "vue-template-compiler": "^2.5.9",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"webpack": "^3.8.1", "webpack": "^3.10.0",
"webpack-bundle-analyzer": "^2.9.0", "webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-middleware": "^1.12.0", "webpack-dev-middleware": "^1.12.2",
"webpack-hot-middleware": "^2.20.0", "webpack-hot-middleware": "^2.21.0",
"webpack-node-externals": "^1.6.0" "webpack-node-externals": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"ava": "^0.23.0", "ava": "^0.24.0",
"babel-eslint": "^8.0.1", "babel-eslint": "^8.0.3",
"babel-plugin-array-includes": "^2.0.3", "babel-plugin-array-includes": "^2.0.3",
"babel-plugin-external-helpers": "^6.22.0", "babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-istanbul": "^4.1.5", "babel-plugin-istanbul": "^4.1.5",
"codecov": "^3.0.0", "codecov": "^3.0.0",
"copy-webpack-plugin": "^4.2.0", "copy-webpack-plugin": "^4.2.0",
"cross-env": "^5.1.1", "cross-env": "^5.1.1",
"eslint": "^4.10.0", "eslint": "^4.13.1",
"eslint-config-standard": "^10.2.1", "eslint-config-standard": "^11.0.0-beta.0",
"eslint-config-standard-jsx": "^4.0.2",
"eslint-plugin-html": "^4.0.1", "eslint-plugin-html": "^4.0.1",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^5.2.1", "eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.6.0", "eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.5.1",
"eslint-plugin-standard": "^3.0.1", "eslint-plugin-standard": "^3.0.1",
"express": "^4.16.2", "express": "^4.16.2",
"finalhandler": "^1.1.0", "finalhandler": "^1.1.0",
"jsdom": "^11.3.0", "jsdom": "^11.5.1",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"nyc": "^11.3.0", "nyc": "^11.3.0",
"puppeteer": "^0.13.0", "puppeteer": "^0.13.0",
"request": "^2.83.0", "request": "^2.83.0",
"request-promise-native": "^1.0.5", "request-promise-native": "^1.0.5",
"rimraf": "^2.6.2",
"rollup": "^0.51.7",
"rollup-plugin-alias": "^1.4.0",
"rollup-plugin-babel": "^3.0.2",
"rollup-plugin-commonjs": "^8.2.6",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-watch": "^4.3.1",
"sinon": "^4.1.2", "sinon": "^4.1.2",
"std-mocks": "^1.0.1", "std-mocks": "^1.0.1",
"uglify-js": "^3.1.7" "uglify-js": "^3.2.2"
}, },
"collective": { "collective": {
"type": "opencollective", "type": "opencollective",

5
build/release-next.js → scripts/release-next Normal file → Executable file
View File

@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
const { readFileSync, writeFileSync } = require('fs-extra') const { readFileSync, writeFileSync } = require('fs-extra')
const { resolve } = require('path') const { resolve } = require('path')
const { spawnSync } = require('child_process') const { spawnSync } = require('child_process')
@ -16,12 +17,12 @@ const originalPackage = readFileSync(packagePath, 'utf-8')
const p = JSON.parse(originalPackage) const p = JSON.parse(originalPackage)
// Change package name // Change package name
p.name = 'nuxt-next' // p.name = 'nuxt-next'
// Get latest git commit id // Get latest git commit id
const gitCommit = String(spawnSync('git', 'rev-parse --short HEAD'.split(' ')).stdout).trim() const gitCommit = String(spawnSync('git', 'rev-parse --short HEAD'.split(' ')).stdout).trim()
// Version with git tag // Version with latest git commit id
p.version = p.version.split('-')[0] + '-gh-' + gitCommit p.version = p.version.split('-')[0] + '-gh-' + gitCommit
// Write package.json // Write package.json

View File

@ -3,7 +3,7 @@
const now = Date.now() const now = Date.now()
const { readFileSync, readJSONSync, writeFileSync, copySync, removeSync } = require('fs-extra') const { readFileSync, readJSONSync, writeFileSync, copySync, removeSync } = require('fs-extra')
const { resolve, relative } = require('path') const { resolve } = require('path')
// Dirs // Dirs
const rootDir = resolve(__dirname, '..') const rootDir = resolve(__dirname, '..')
@ -42,6 +42,7 @@ requires = requires.filter(r => excludes.indexOf(r) === -1)
let dependencies = {} let dependencies = {}
requires.forEach(r => { requires.forEach(r => {
if (!packageJSON.dependencies[r]) { if (!packageJSON.dependencies[r]) {
// eslint-disable-next-line no-console
console.warn('Cannot resolve dependency version for ' + r) console.warn('Cannot resolve dependency version for ' + r)
return return
} }
@ -104,5 +105,5 @@ writeFileSync(startIndexjs, String(readFileSync(startIndexjs)).replace('./dist/n
const binStart = resolve(startDir, 'bin/nuxt-start') const binStart = resolve(startDir, 'bin/nuxt-start')
writeFileSync(binStart, String(readFileSync(binStart)).replace(/nuxt start/g, 'nuxt-start')) writeFileSync(binStart, String(readFileSync(binStart)).replace(/nuxt start/g, 'nuxt-start'))
const ms = Date.now() - now // eslint-disable-next-line no-console
console.log(`Generated ${packageJSON.name}@${packageJSON.version} in ${ms}ms`) console.log(`Generated ${packageJSON.name}@${packageJSON.version} in ${Date.now() - now}ms`)

View File

@ -0,0 +1,14 @@
import test from 'ava'
import { resolve } from 'path'
import { Nuxt, Options } from '..'
import { version } from '../package.json'
test('Nuxt.version is same as package', t => {
t.is(Nuxt.version, version)
})
test('modulesDir uses /node_modules as default if not set', async t => {
const options = Options.from({})
const currentNodeModulesDir = resolve(__dirname, '..', 'node_modules')
t.true(options.modulesDir.includes(currentNodeModulesDir))
})

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import { Nuxt, Builder } from '../index' import { Nuxt, Builder } from '..'
import * as browser from './helpers/browser' import * as browser from './helpers/browser'
const port = 4003 const port = 4003
@ -143,6 +143,15 @@ test('/redirect2', async t => {
t.is(await page.$text('h1'), 'Index page') t.is(await page.$text('h1'), 'Index page')
}) })
test('/redirect3', async t => {
// New page for redirecting to external link.
const page = await browser.page(url('/'))
await page.nuxt.navigate('/redirect3', false)
await page.waitForFunction(() => window.location.href === 'https://nuxtjs.org/')
page.close()
t.pass()
})
test('/no-ssr', async t => { test('/no-ssr', async t => {
await page.nuxt.navigate('/no-ssr') await page.nuxt.navigate('/no-ssr')
@ -170,6 +179,12 @@ test('/fn-midd?please=true', async t => {
t.true(h1.includes('Date:')) t.true(h1.includes('Date:'))
}) })
test('/router-guard', async t => {
await page.nuxt.navigate('/router-guard')
t.is(await page.$text('p'), 'Nuxt.js')
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', t => { test.after('Closing server and nuxt.js', t => {
nuxt.close() nuxt.close()

View File

@ -1,18 +1,26 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import stdMocks from 'std-mocks'
// import rp from 'request-promise-native' // import rp from 'request-promise-native'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder, Utils } from '..'
import { truncateSync, readFileSync, writeFileSync } from 'fs'
const port = 4001 const port = 4001
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route
const rootDir = resolve(__dirname, 'fixtures/basic')
const pluginPath = resolve(rootDir, 'plugins', 'watch.js')
const pluginContent = readFileSync(pluginPath)
let nuxt = null let nuxt = null
// Init nuxt.js and create server listening on localhost:4000 // Init nuxt.js and create server listening on localhost:4000
test.before('Init Nuxt.js', async t => { test.before('Init Nuxt.js', async t => {
const options = { const options = {
rootDir: resolve(__dirname, 'fixtures/basic'), rootDir,
dev: true dev: true,
plugins: [
'~/plugins/watch.js'
]
} }
nuxt = new Nuxt(options) nuxt = new Nuxt(options)
await new Builder(nuxt).build() await new Builder(nuxt).build()
@ -20,6 +28,29 @@ test.before('Init Nuxt.js', async t => {
await nuxt.listen(port, 'localhost') await nuxt.listen(port, 'localhost')
}) })
test('remove mixins in live reloading', async t => {
stdMocks.use()
await nuxt.renderRoute(url('/'))
t.true(stdMocks.flush().stdout.some(v => v === 'I am mixin\n'))
truncateSync(pluginPath)
await new Promise(async (resolve, reject) => {
let waitTimes = 0
while (!stdMocks.flush().stdout.some(v => ~v.indexOf('Compiled successfully'))) {
await Utils.waitFor(100) && waitTimes++
if (waitTimes === 20) {
reject(Error('Dev server doesn\'t reload after 2000ms'))
}
}
resolve()
})
await nuxt.renderRoute(url('/'))
t.false(stdMocks.flush().stdout.some(v => v === 'I am mixin\n'))
stdMocks.restore()
})
test('/stateless', async t => { test('/stateless', async t => {
const window = await nuxt.renderAndGetWindow(url('/stateless')) const window = await nuxt.renderAndGetWindow(url('/stateless'))
const html = window.document.body.innerHTML const html = window.document.body.innerHTML
@ -37,5 +68,6 @@ test('/stateless', async t => {
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', async t => { test.after('Closing server and nuxt.js', async t => {
writeFileSync(pluginPath, pluginContent)
await nuxt.close() await nuxt.close()
}) })

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import { Nuxt, Builder, Generator } from '../index.js' import { Nuxt, Builder, Generator } from '..'
test('Fail with routes() which throw an error', async t => { test('Fail with routes() which throw an error', async t => {
const options = { const options = {

View File

@ -5,7 +5,7 @@ import http from 'http'
import serveStatic from 'serve-static' import serveStatic from 'serve-static'
import finalhandler from 'finalhandler' import finalhandler from 'finalhandler'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Nuxt, Builder, Generator } from '../index.js' import { Nuxt, Builder, Generator } from '..'
const port = 4002 const port = 4002
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route

View File

@ -5,7 +5,7 @@ import http from 'http'
import serveStatic from 'serve-static' import serveStatic from 'serve-static'
import finalhandler from 'finalhandler' import finalhandler from 'finalhandler'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Nuxt, Builder, Generator } from '../index.js' import { Nuxt, Builder, Generator } from '..'
const port = 4002 const port = 4002
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route

View File

@ -1,7 +1,7 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
import { interceptLog, interceptError } from './helpers/console' import { interceptLog, interceptError } from './helpers/console'
const port = 4003 const port = 4003
@ -121,6 +121,19 @@ test('/redirect -> check redirected source', async t => {
t.true(html.includes('<h1>Index page</h1>')) t.true(html.includes('<h1>Index page</h1>'))
}) })
test('/redirect -> external link', async t => {
const headers = {}
const { html } = await nuxt.renderRoute('/redirect3', {
res: {
setHeader(k, v) {
headers[k] = v
}
}
})
t.is(headers.Location, 'https://nuxtjs.org')
t.true(html.includes('<div>Redirecting to external page.</div>'))
})
test('/special-state -> check window.__NUXT__.test = true', async t => { test('/special-state -> check window.__NUXT__.test = true', async t => {
const window = await nuxt.renderAndGetWindow(url('/special-state')) const window = await nuxt.renderAndGetWindow(url('/special-state'))
t.is(window.document.title, 'Nuxt.js') t.is(window.document.title, 'Nuxt.js')
@ -228,6 +241,22 @@ test('/fn-midd?please=true', async t => {
t.true(html.includes('<h1>Date:')) t.true(html.includes('<h1>Date:'))
}) })
test('/router-guard', async t => {
const { html } = await nuxt.renderRoute('/router-guard')
t.true(html.includes('<p>Nuxt.js</p>'))
t.false(html.includes('Router Guard'))
})
test('/jsx', async t => {
const { html } = await nuxt.renderRoute('/jsx')
t.true(html.includes('<h1>JSX Page</h1>'))
})
test('/js-link', async t => {
const { html } = await nuxt.renderRoute('/js-link')
t.true(html.includes('<h1>vue file is first-class</h1>'))
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', async t => { test.after('Closing server and nuxt.js', async t => {
await nuxt.close() await nuxt.close()

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import { Nuxt, Builder, Utils } from '../index.js' import { Nuxt, Builder, Utils } from '..'
import * as browser from './helpers/browser' import * as browser from './helpers/browser'
const port = 4005 const port = 4005

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
const port = 4004 const port = 4004
// const url = (route) => 'http://localhost:' + port + route // const url = (route) => 'http://localhost:' + port + route

View File

@ -1,11 +1,11 @@
import { promisify } from 'util'
import test from 'ava' import test from 'ava'
import { resolve, sep } from 'path' import { resolve, sep } from 'path'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Utils } from '../index.js'
import pify from 'pify'
import { exec, spawn } from 'child_process' import { exec, spawn } from 'child_process'
import { Utils } from '..'
const execify = pify(exec, { multiArgs: true }) const execify = promisify(exec)
const rootDir = resolve(__dirname, 'fixtures/basic') const rootDir = resolve(__dirname, 'fixtures/basic')
const port = 4011 const port = 4011
@ -14,7 +14,7 @@ const url = (route) => 'http://localhost:' + port + route
test('bin/nuxt-build', async t => { test('bin/nuxt-build', async t => {
const binBuild = resolve(__dirname, '..', 'bin', 'nuxt-build') const binBuild = resolve(__dirname, '..', 'bin', 'nuxt-build')
const [ stdout, stderr ] = await execify(`node ${binBuild} ${rootDir}`) const { stdout, stderr } = await execify(`node ${binBuild} ${rootDir}`)
t.true(stdout.includes('server-bundle.json')) t.true(stdout.includes('server-bundle.json'))
t.true(stderr.includes('Building done')) t.true(stderr.includes('Building done'))
@ -82,7 +82,7 @@ test('bin/nuxt-start', async t => {
test('bin/nuxt-generate', async t => { test('bin/nuxt-generate', async t => {
const binGenerate = resolve(__dirname, '..', 'bin', 'nuxt-generate') const binGenerate = resolve(__dirname, '..', 'bin', 'nuxt-generate')
const [ stdout, stderr ] = await execify(`node ${binGenerate} ${rootDir}`) const { stdout, stderr } = await execify(`node ${binGenerate} ${rootDir}`)
t.true(stdout.includes('server-bundle.json')) t.true(stdout.includes('server-bundle.json'))
t.true(stderr.includes('Destination folder cleaned')) t.true(stderr.includes('Destination folder cleaned'))

View File

@ -1,7 +1,7 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
const port = 4009 const port = 4009
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route
@ -13,7 +13,7 @@ test.before('Init Nuxt.js', async t => {
const rootDir = resolve(__dirname, 'fixtures/debug') const rootDir = resolve(__dirname, 'fixtures/debug')
let config = require(resolve(rootDir, 'nuxt.config.js')) let config = require(resolve(rootDir, 'nuxt.config.js'))
config.rootDir = rootDir config.rootDir = rootDir
config.dev = false config.dev = true // Needed for _open middleware
nuxt = new Nuxt(config) nuxt = new Nuxt(config)
await new Builder(nuxt).build() await new Builder(nuxt).build()
@ -38,6 +38,30 @@ test('/test/error should return error stack trace (Youch)', async t => {
t.true(error.includes('<div class="error-frames">')) t.true(error.includes('<div class="error-frames">'))
}) })
test('/test/error no source-map (Youch)', async t => {
const sourceMaps = nuxt.renderer.resources.serverBundle.maps
nuxt.renderer.resources.serverBundle.maps = {}
const { response, error } = await t.throws(nuxt.renderAndGetWindow(url('/test/error')))
t.is(response.statusCode, 500)
t.is(response.statusMessage, 'NuxtServerError')
t.true(error.includes('test youch !'))
t.true(error.includes('<div class="error-frames">'))
nuxt.renderer.resources.serverBundle.maps = sourceMaps
})
test('/test/error should return json format error (Youch)', async t => {
const opts = {
headers: {
'accept': 'application/json'
},
resolveWithFullResponse: true
}
const { response: { headers } } = await t.throws(rp(url('/test/error'), opts))
t.is(headers['content-type'], 'text/json; charset=utf-8')
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', t => { test.after('Closing server and nuxt.js', t => {
nuxt.close() nuxt.close()

View File

@ -2,7 +2,7 @@ import test from 'ava'
import stdMocks from 'std-mocks' import stdMocks from 'std-mocks'
import { resolve } from 'path' import { resolve } from 'path'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
const port = 4010 const port = 4010
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route

View File

@ -1,11 +1,11 @@
import { promisify } from 'util'
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import fs from 'fs' import fs from 'fs'
import pify from 'pify'
import stdMocks from 'std-mocks' import stdMocks from 'std-mocks'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
const readFile = pify(fs.readFile) const readFile = promisify(fs.readFile)
const rootDir = resolve(__dirname, './fixtures/dll') const rootDir = resolve(__dirname, './fixtures/dll')
const dllDir = resolve(rootDir, '.cache/client-dll') const dllDir = resolve(rootDir, '.cache/client-dll')

View File

@ -1,10 +1,10 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import fs from 'fs' import fs from 'fs'
import pify from 'pify' import { Nuxt, Builder } from '..'
import { Nuxt, Builder } from '../index.js' import { promisify } from 'util'
const readFile = pify(fs.readFile) const readFile = promisify(fs.readFile)
test.before('Init Nuxt.js', async t => { test.before('Init Nuxt.js', async t => {
const nuxt = new Nuxt({ const nuxt = new Nuxt({

View File

@ -1,6 +1,7 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import { Nuxt, Builder } from '../index.js' import rp from 'request-promise-native'
import { Nuxt, Builder } from '..'
const port = 4005 const port = 4005
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route
@ -38,6 +39,17 @@ test('/ with renderAndGetWindow()', async t => {
t.is(err.response.statusMessage, 'NuxtServerError') t.is(err.response.statusMessage, 'NuxtServerError')
}) })
test('/ with text/json content', async t => {
const opts = {
headers: {
'accept': 'application/json'
},
resolveWithFullResponse: true
}
const { response: { headers } } = await t.throws(rp(url('/'), opts))
t.is(headers['content-type'], 'text/json; charset=utf-8')
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', t => { test.after('Closing server and nuxt.js', t => {
nuxt.close() nuxt.close()

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
import express from 'express' import express from 'express'
import rp from 'request-promise-native' import rp from 'request-promise-native'

View File

@ -20,6 +20,7 @@ module.exports = {
}, },
transition: false, transition: false,
build: { build: {
scopeHoisting: true,
postcss: [ postcss: [
require('postcss-cssnext')() require('postcss-cssnext')()
] ]

2
test/fixtures/basic/pages/js-link.js vendored Normal file
View File

@ -0,0 +1,2 @@
export default {
}

5
test/fixtures/basic/pages/js-link.vue vendored Normal file
View File

@ -0,0 +1,5 @@
<template>
<h1>vue file is first-class</h1>
</template>
<script src="./js-link.js"></script>

7
test/fixtures/basic/pages/jsx.js vendored Normal file
View File

@ -0,0 +1,7 @@
export default {
render() {
return <div class='container'>
<h1>JSX Page</h1>
</div>
}
}

11
test/fixtures/basic/pages/redirect3.vue vendored Normal file
View File

@ -0,0 +1,11 @@
<template>
<div>Redirecting...</div>
</template>
<script>
export default {
fetch({ redirect }) {
return redirect('https://nuxtjs.org/')
}
}
</script>

View File

@ -0,0 +1,11 @@
<template>
<div>Router Guard</div>
</template>
<script>
export default {
beforeRouteEnter(to, from, next) {
next({path: '/async-data'})
}
}
</script>

12
test/fixtures/basic/plugins/watch.js vendored Normal file
View File

@ -0,0 +1,12 @@
import Vue from 'vue'
const Plugin = {
install(Vue) {
Vue.mixin({
created() {
console.log('I am mixin') // eslint-disable-line no-console
}
})
}
}
Vue.use(Plugin)

View File

@ -3,9 +3,6 @@ module.exports = {
base: '/test/' base: '/test/'
}, },
debug: true, debug: true,
build: {
scopeHoisting: true
},
editor: { editor: {
cmd: 'echo', cmd: 'echo',
pattern: '' pattern: ''

View File

@ -0,0 +1,3 @@
module.exports = function (req, res, next) {
res.end('Use external middleware')
}

View File

@ -21,5 +21,12 @@ module.exports = {
hook('build:done', builder => { hook('build:done', builder => {
builder.__build_done__ = true builder.__build_done__ = true
}) })
// Add hook for renderer
hook('render:before', (renderer) => {
renderer.useMiddleware({
path: '/use-middleware',
handler: '~/modules/middleware/use-middleware'
})
})
} }
} }

View File

@ -42,12 +42,21 @@ module.exports = {
} }
}, },
build: { build: {
// extractCSS: true,
publicPath: '/orion/', publicPath: '/orion/',
analyze: { analyze: {
analyzerMode: 'disabled', analyzerMode: 'disabled',
generateStatsFile: true generateStatsFile: true
}, },
styleResources: {
patterns: [
'~/assets/pre-process.scss'
]
},
babel: {
presets({ isServer }) {
return null // Coverage: Return null, so defaults will be used.
}
},
extend(config, options) { extend(config, options) {
return Object.assign({}, config, { return Object.assign({}, config, {
devtool: 'nosources-source-map' devtool: 'nosources-source-map'

76
test/generator.test.js Normal file
View File

@ -0,0 +1,76 @@
import test from 'ava'
import { Nuxt, Generator } from '..'
test('initRoutes with routes (fn => array)', async t => {
const array = ['/1', '/2', '/3', '/4']
const config = {
generate: {
routes: array
}
}
const nuxt = new Nuxt(config)
const generator = new Generator(nuxt)
const routes = await generator.initRoutes()
t.is(routes.length, array.length)
routes.map((route, index) => {
t.is(route.route, array[index])
})
})
test('initRoutes with routes (fn())', async t => {
const array = ['/1', '/2', '/3', '/4']
const config = {
generate: {
routes() {
return array
}
}
}
const nuxt = new Nuxt(config)
const generator = new Generator(nuxt)
const routes = await generator.initRoutes()
t.is(routes.length, array.length)
routes.map((route, index) => {
t.is(route.route, array[index])
})
})
test('initRoutes with routes (fn(args))', async t => {
const config = {
generate: {
routes(array) {
return array
}
}
}
const nuxt = new Nuxt(config)
const generator = new Generator(nuxt)
const array = ['/1', '/2', '/3', '/4']
const routes = await generator.initRoutes(array)
t.is(routes.length, array.length)
routes.map((route, index) => {
t.is(route.route, array[index])
})
})
test('initRoutes with routes (fn(cb, args))', async t => {
const config = {
generate: {
routes(cb, arg1, arg2, arg3, arg4) {
cb(null, [ arg1, arg2, arg3, arg4 ])
}
}
}
const nuxt = new Nuxt(config)
const generator = new Generator(nuxt)
const array = ['/1', '/2', '/3', '/4']
const routes = await generator.initRoutes(...array)
t.is(routes.length, array.length)
routes.map((route, index) => {
t.is(route.route, array[index])
})
})

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
test('Nuxt.js Class', t => { test('Nuxt.js Class', t => {
t.is(typeof Nuxt, 'function') t.is(typeof Nuxt, 'function')

View File

@ -2,7 +2,7 @@ import test from 'ava'
import stdMocks from 'std-mocks' import stdMocks from 'std-mocks'
import { resolve, normalize } from 'path' import { resolve, normalize } from 'path'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
const port = 4006 const port = 4006
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route
@ -63,6 +63,11 @@ test('Hooks - Error', async t => {
t.true(errors.length === 1) t.true(errors.length === 1)
}) })
test('Hooks - Use external middleware before render', async t => {
let response = await rp(url('/use-middleware'))
t.is(response, 'Use external middleware')
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', t => { test.after('Closing server and nuxt.js', t => {
nuxt.close() nuxt.close()

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import stdMocks from 'std-mocks' import stdMocks from 'std-mocks'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
let nuxt = null let nuxt = null
@ -47,6 +47,12 @@ test('/custom (call mounted and created once)', async t => {
t.true(mounts.length === 1) t.true(mounts.length === 1)
}) })
test('/_nuxt/ (access publicPath in spa mode)', async t => {
const { response: { statusCode, statusMessage } } = await t.throws(renderRoute('/_nuxt/'))
t.is(statusCode, 404)
t.is(statusMessage, 'ResourceNotFound')
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', t => { test.after('Closing server and nuxt.js', t => {
nuxt.close() nuxt.close()

View File

@ -1,6 +1,6 @@
import test from 'ava' import test from 'ava'
import ansiHTML from 'ansi-html' import ansiHTML from 'ansi-html'
import { Utils } from '../index.js' import { Utils } from '..'
test('encodeHtml', t => { test('encodeHtml', t => {
const html = '<h1>Hello</h1>' const html = '<h1>Hello</h1>'
@ -70,6 +70,21 @@ test('promisifyRoute (fn => promise)', t => {
}) })
}) })
test('promisifyRoute ((fn(args) => promise))', t => {
const fn = function (array) {
return new Promise((resolve) => {
resolve(array)
})
}
const array = [1, 2, 3]
const promise = Utils.promisifyRoute(fn, array)
t.is(typeof promise, 'object')
return promise
.then((res) => {
t.is(res, array)
})
})
test('promisifyRoute (fn(cb) with error)', t => { test('promisifyRoute (fn(cb) with error)', t => {
const fn = function (cb) { const fn = function (cb) {
cb(new Error('Error here')) cb(new Error('Error here'))
@ -82,6 +97,19 @@ test('promisifyRoute (fn(cb) with error)', t => {
}) })
}) })
test('promisifyRoute (fn(cb, args) with error)', t => {
const fn = function (cb, array) {
cb(new Error('Error here: ' + array.join()))
}
const array = [1, 2, 3, 4]
const promise = Utils.promisifyRoute(fn, array)
t.is(typeof promise, 'object')
return promise
.catch((e) => {
t.is(e.message, 'Error here: ' + array.join())
})
})
test('promisifyRoute (fn(cb) with result)', t => { test('promisifyRoute (fn(cb) with result)', t => {
const array = [1, 2, 3, 4] const array = [1, 2, 3, 4]
const fn = function (cb) { const fn = function (cb) {
@ -95,6 +123,21 @@ test('promisifyRoute (fn(cb) with result)', t => {
}) })
}) })
test('promisifyRoute (fn(cb, args) with result)', t => {
const fn = function (cb, array, object) {
cb(null, { array, object })
}
const array = [1, 2, 3, 4]
const object = { a: 1 }
const promise = Utils.promisifyRoute(fn, array, object)
t.is(typeof promise, 'object')
return promise
.then((res) => {
t.is(res.array, array)
t.is(res.object, object)
})
})
test('chainFn (mutate, mutate)', t => { test('chainFn (mutate, mutate)', t => {
// Pass more than one argument to test that they're actually taken into account // Pass more than one argument to test that they're actually taken into account
const firstFn = function (obj, count) { const firstFn = function (obj, count) {

View File

@ -1,13 +1,14 @@
import test from 'ava' import test from 'ava'
import { resolve } from 'path' import { resolve } from 'path'
import rp from 'request-promise-native' import rp from 'request-promise-native'
import { Nuxt, Builder } from '../index.js' import { Nuxt, Builder } from '..'
import { interceptLog, release } from './helpers/console' import { interceptLog, release } from './helpers/console'
const port = 4007 const port = 4007
const url = (route) => 'http://localhost:' + port + route const url = (route) => 'http://localhost:' + port + route
let nuxt = null let nuxt = null
let builder = null
// Init nuxt.js and create server listening on localhost:4000 // Init nuxt.js and create server listening on localhost:4000
test.before('Init Nuxt.js', async t => { test.before('Init Nuxt.js', async t => {
@ -16,11 +17,11 @@ test.before('Init Nuxt.js', async t => {
config.rootDir = rootDir config.rootDir = rootDir
config.dev = false config.dev = false
nuxt = new Nuxt(config) nuxt = new Nuxt(config)
builder = new Builder(nuxt)
await interceptLog('building nuxt', async () => { await interceptLog('building nuxt', async () => {
await new Builder(nuxt).build() await builder.build()
await nuxt.listen(port, 'localhost')
}) })
await nuxt.listen(port, 'localhost')
}) })
test('/', async t => { test('/', async t => {
@ -177,6 +178,17 @@ test('Check /test.txt should return 404', async t => {
t.is(err.response.statusCode, 404) t.is(err.response.statusCode, 404)
}) })
test('Check build.styleResources for style-resources-loader', async t => {
const loaders = builder.styleLoader('scss')
const loader = loaders.find(l => l.loader === 'style-resources-loader')
t.is(typeof loader, 'object')
t.deepEqual(loader.options, {
patterns: [
'~/assets/pre-process.scss'
]
})
})
// Close server and ask nuxt to stop listening to file changes // Close server and ask nuxt to stop listening to file changes
test.after('Closing server and nuxt.js', async t => { test.after('Closing server and nuxt.js', async t => {
await nuxt.close() await nuxt.close()

Some files were not shown because too many files have changed in this diff Show More