mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 15:42:09 +00:00
Add pages/_app.vue possibility + example
This commit is contained in:
parent
cb20e417fa
commit
edd0227e74
42
examples/extend-app/README.md
Normal file
42
examples/extend-app/README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Extending the main app
|
||||
|
||||
> Nuxt.js allows you to extend the main application by adding a `pages/_app.vue` file
|
||||
|
||||
## The default app
|
||||
|
||||
The default source code of the main app is:
|
||||
```html
|
||||
<template>
|
||||
<nuxt-container>
|
||||
<nuxt/>
|
||||
</nuxt-container>
|
||||
</template>
|
||||
```
|
||||
|
||||
## The `pages/_app.vue` file
|
||||
|
||||
### 🎬 [Example video](https://www.youtube.com/watch?v=wBhia7uBxDA)
|
||||
|
||||
You have to make sure to add the `<nuxt-container>` and `<nuxt>` components when extending the app.
|
||||
|
||||
It is important that the code you add stays inside `<nuxt-container>`.
|
||||
|
||||
Example:
|
||||
```html
|
||||
<template>
|
||||
<nuxt-container>
|
||||
<div>My navigation bar here</div>
|
||||
<nuxt/>
|
||||
</nuxt-container>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
Go to [http://localhost:3000](http://localhost:3000) and navigate trough the app. Notice how the logo at the top right stays between the pages, even on the error page: [http://localhost:3000/404](http://localhost:3000/404)
|
11
examples/extend-app/package.json
Normal file
11
examples/extend-app/package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "nuxt-extend-app",
|
||||
"dependencies": {
|
||||
"nuxt": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start"
|
||||
}
|
||||
}
|
15
examples/extend-app/pages/_app.vue
Normal file
15
examples/extend-app/pages/_app.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<nuxt-container>
|
||||
<img src="logo.png"/>
|
||||
<nuxt/>
|
||||
</nuxt-container>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
img {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
16
examples/extend-app/pages/about.vue
Normal file
16
examples/extend-app/pages/about.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>Hi from {{ name }}</p>
|
||||
<router-link to="/">Home page</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data ({ req }) {
|
||||
return {
|
||||
name: req ? 'server' : 'client'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
6
examples/extend-app/pages/index.vue
Normal file
6
examples/extend-app/pages/index.vue
Normal file
@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>Welcome!</h1>
|
||||
<router-link to="/about">About page</router-link>
|
||||
</div>
|
||||
</template>
|
BIN
examples/extend-app/static/logo.png
Normal file
BIN
examples/extend-app/static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// https://cards-dev.twitter.com/validator
|
||||
// Test on: https://cards-dev.twitter.com/validator
|
||||
export default {
|
||||
data: () => ({
|
||||
href: 'https://twitter.com/intent/tweet?url=' + encodeURIComponent('https://head-elements.now.sh/about')
|
||||
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<nuxt-container>
|
||||
<nuxt></nuxt>
|
||||
</nuxt-container>
|
||||
</template>
|
@ -1,49 +1,5 @@
|
||||
<template>
|
||||
<div id="__nuxt">
|
||||
<% if (loading) { %><nuxt-loading ref="loading"></nuxt-loading><% } %>
|
||||
<router-view v-if="!err"></router-view>
|
||||
<nuxt-error v-if="err" :error="err"></nuxt-error>
|
||||
</div>
|
||||
<nuxt-container>
|
||||
<nuxt/>
|
||||
</nuxt-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NuxtError from '<%= components.ErrorPage %>'
|
||||
<% if (loading) { %>import NuxtLoading from '<%= (typeof loading === "string" ? loading : "./components/nuxt-loading.vue") %>'<% } %>
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
err: null
|
||||
}
|
||||
},
|
||||
<% if (loading) { %>
|
||||
created () {
|
||||
this.$loading = {} // for NUXT.serverRendered = false
|
||||
},
|
||||
mounted () {
|
||||
this.$loading = this.$refs.loading
|
||||
},
|
||||
<% } %>
|
||||
methods: {
|
||||
error (err) {
|
||||
err = err || null
|
||||
this.err = err || null
|
||||
<% if (loading) { %>
|
||||
if (this.err && this.$loading) {
|
||||
if (this.$loading.fail) this.$loading.fail()
|
||||
if (this.$loading.finish) this.$loading.finish()
|
||||
}
|
||||
<% } %>
|
||||
return this.err
|
||||
}
|
||||
},
|
||||
components: {
|
||||
NuxtError<%= (loading ? ',\n\t\tNuxtLoading' : '') %>
|
||||
},
|
||||
head: <%= JSON.stringify(head) %>
|
||||
}
|
||||
</script>
|
||||
|
||||
<% css.forEach(function (c) { %>
|
||||
<style src="<%= (typeof c === 'string' ? c : c.src) %>" lang="<%= (c.lang ? c.lang : 'css') %>"></style>
|
||||
<% }) %>
|
||||
|
@ -112,8 +112,9 @@ function render (to, ___, next) {
|
||||
|
||||
// Special hot reload with data(context)
|
||||
function hotReloadAPI (_app) {
|
||||
var _forceUpdate = _app.$forceUpdate.bind(_app)
|
||||
_app.$forceUpdate = function () {
|
||||
const $nuxt = _app.$nuxt
|
||||
var _forceUpdate = $nuxt.$forceUpdate.bind($nuxt)
|
||||
$nuxt.$forceUpdate = function () {
|
||||
let Component = getMatchedComponents(router.currentRoute)[0]
|
||||
if (!Component) return _forceUpdate()
|
||||
if (typeof Component === 'object' && !Component.options) {
|
||||
@ -127,7 +128,7 @@ function hotReloadAPI (_app) {
|
||||
<%= (loading ? 'this.$loading.finish && this.$loading.finish()' : '') %>
|
||||
router.push(path)
|
||||
}
|
||||
const context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: _app.error.bind(_app) })
|
||||
const context = getContext({ route: router.currentRoute<%= (store ? ', store' : '') %>, isClient: true, next: next.bind(this), error: _app.error })
|
||||
// Check if data has been updated
|
||||
const originalDataFn = (Component._data || noopData).toString().replace(/\s/g, '')
|
||||
const newDataFn = (Component._Ctor.options.data || noopData).toString().replace(/\s/g, '')
|
||||
@ -206,17 +207,27 @@ const resolveComponents = flatMapComponents(router.match(path), (Component, _, m
|
||||
Promise.all(resolveComponents)
|
||||
.then((Components) => {
|
||||
const _app = new Vue(app)
|
||||
const mountApp = () => _app.$mount('#__nuxt')
|
||||
const onNuxtReady = window.onNuxtReady || function () {}
|
||||
const mountApp = () => {
|
||||
_app.$mount('#__nuxt')
|
||||
<% if (loading) { %>
|
||||
// Special loading bar
|
||||
_app.$loading = _app.$nuxt.$loading
|
||||
<% } %>
|
||||
// Hot reloading
|
||||
if (module.hot) hotReloadAPI(_app)
|
||||
// Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
|
||||
if (typeof window.onNuxtReady === 'function') {
|
||||
window.onNuxtReady(_app)
|
||||
}
|
||||
}
|
||||
_app.error = _app.$options._nuxt.error.bind(_app)
|
||||
_app.$loading = {} // to avoid error while _app.$nuxt does not exist
|
||||
if (NUXT.error) _app.error(NUXT.error)
|
||||
if (module.hot) hotReloadAPI(_app)
|
||||
// Add router hooks
|
||||
router.beforeEach(loadAsyncComponents.bind(_app))
|
||||
router.beforeEach(render.bind(_app))
|
||||
if (NUXT.serverRendered) {
|
||||
mountApp()
|
||||
// Call window.onModulesLoaded for jsdom testing (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading)
|
||||
onNuxtReady(_app)
|
||||
return
|
||||
}
|
||||
render.call(_app, router.currentRoute, router.currentRoute, function (path) {
|
||||
@ -226,13 +237,11 @@ Promise.all(resolveComponents)
|
||||
if (mounted) return
|
||||
mounted = true
|
||||
mountApp()
|
||||
onNuxtReady(_app)
|
||||
})
|
||||
router.push(path)
|
||||
return
|
||||
}
|
||||
mountApp()
|
||||
onNuxtReady(_app)
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'nuxt-error',
|
||||
props: ['error'],
|
||||
head () {
|
||||
return {
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'nuxt-error',
|
||||
props: ['error'],
|
||||
head () {
|
||||
return {
|
||||
|
@ -8,9 +8,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const Vue = require('vue')
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'nuxt-loading',
|
||||
data () {
|
||||
return {
|
||||
percent: 0,
|
||||
|
@ -1,57 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<% if (loading) { %><nuxt-loading ref="loading"></nuxt-loading><% } %>
|
||||
<transition>
|
||||
<router-view v-if="!err"></router-view>
|
||||
<nuxt-error v-if="err" :error="err"></nuxt-error>
|
||||
<transition mode="out-in">
|
||||
<router-view v-if="!nuxt.err"></router-view>
|
||||
<nuxt-error v-if="nuxt.err" :error="nuxt.err"></nuxt-error>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import NuxtError from '<%= components.ErrorPage %>'
|
||||
<% if (loading) { %>import NuxtLoading from '<%= (typeof loading === "string" ? loading : "./nuxt-loading.vue") %>'<% } %>
|
||||
|
||||
export default {
|
||||
name: 'nuxt',
|
||||
data () {
|
||||
return {
|
||||
err: null
|
||||
}
|
||||
beforeCreate () {
|
||||
Vue.util.defineReactive(this, 'nuxt', this.$root.$options._nuxt)
|
||||
},
|
||||
<% if (loading) { %>
|
||||
created () {
|
||||
if (this.$root.$nuxt) {
|
||||
return console.error('Only one instance of Nuxt.js is possible, make sure to use <nuxt></nuxt> only once.')
|
||||
}
|
||||
// Add $nuxt in the root instance
|
||||
this.$root.$nuxt = this
|
||||
// add vm.$nuxt to child instances
|
||||
// Add this.$nuxt in child instances
|
||||
Vue.prototype.$nuxt = this
|
||||
// Add this.$root.$nuxt
|
||||
this.$root.$nuxt = this
|
||||
// add to window so we can listen when ready
|
||||
if (typeof window !== 'undefined') {
|
||||
window.$nuxt = this
|
||||
}
|
||||
// for NUXT.serverRendered = false
|
||||
this.$loading = {}
|
||||
},
|
||||
<% if (loading) { %>
|
||||
mounted () {
|
||||
this.$loading = this.$refs.loading
|
||||
},
|
||||
<% } %>
|
||||
watch: {
|
||||
'nuxt.err': 'errorChanged'
|
||||
},
|
||||
methods: {
|
||||
error (err) {
|
||||
err = err || null
|
||||
this.err = err || null
|
||||
<% if (loading) { %>
|
||||
if (this.err && this.$loading) {
|
||||
errorChanged () {
|
||||
if (this.nuxt.err && this.$loading) {
|
||||
if (this.$loading.fail) this.$loading.fail()
|
||||
if (this.$loading.finish) this.$loading.finish()
|
||||
}
|
||||
<% } %>
|
||||
return this.err
|
||||
}
|
||||
},
|
||||
<% } %>
|
||||
components: {
|
||||
NuxtError<%= (loading ? ',\n\t\tNuxtLoading' : '') %>
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
// The Vue build version to load with the `import` command
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import Vue from 'vue'
|
||||
import Meta from 'vue-meta/lib/vue-meta.js' // require the ES2015 lib
|
||||
import router from './router.js'
|
||||
<% if (store) { %>import store from '~store/index.js'<% } %>
|
||||
|
||||
import NuxtContainer from './components/nuxt-container.vue'
|
||||
import Nuxt from './components/nuxt.vue'
|
||||
import App from '<%= appPath %>'
|
||||
|
||||
// Component: <nuxt-container>
|
||||
Vue.component(NuxtContainer.name, NuxtContainer)
|
||||
// Component: <nuxt>
|
||||
Vue.component(Nuxt.name, Nuxt)
|
||||
|
||||
// vue-meta configuration
|
||||
Vue.use(Meta, {
|
||||
keyName: 'head', // the component option name that vue-meta looks for meta info on.
|
||||
attribute: 'n-head', // the attribute name vue-meta adds to the tags it observes
|
||||
@ -20,17 +21,25 @@ Vue.use(Meta, {
|
||||
tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag
|
||||
})
|
||||
|
||||
// Includes external plugins
|
||||
<% plugins.forEach(function (pluginPath) { %>
|
||||
require('<%= pluginPath %>')
|
||||
<% }) %>
|
||||
|
||||
import App from '<%= appPath %>'
|
||||
// create the app instance.
|
||||
// root instance
|
||||
// here we inject the router and store to all child components,
|
||||
// making them available everywhere as `this.$router` and `this.$store`.
|
||||
const app = {
|
||||
router,
|
||||
<%= (store ? 'store,' : '') %>
|
||||
_nuxt: {
|
||||
err: null,
|
||||
error (err) {
|
||||
err = err || null
|
||||
this.$options._nuxt.err = err;
|
||||
return err
|
||||
}
|
||||
},
|
||||
...App
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { getMatchedComponents, getContext, promisify, urlJoin } from './utils'
|
||||
const isDev = <%= isDev %>
|
||||
const _app = new Vue(app)
|
||||
|
||||
// Fix issue from vue-router Abstract mode with base (use for server-side rendering)
|
||||
// Fix issue from vue-router Abstract mode with base (used for server-side rendering)
|
||||
router.history.base = '<%= router.base %>'
|
||||
|
||||
// This exported function will be called by `bundleRenderer`.
|
||||
@ -46,7 +46,7 @@ export default context => {
|
||||
// Add meta infos
|
||||
context.meta = _app.$meta()
|
||||
// Error function
|
||||
context.error = _app.error.bind(_app)
|
||||
context.error = _app.$options._nuxt.error.bind(_app)
|
||||
|
||||
<%= (isDev ? 'const s = isDev && Date.now()' : '') %>
|
||||
let Components = getMatchedComponents(context.route)
|
||||
@ -88,7 +88,7 @@ export default context => {
|
||||
})
|
||||
.then((res) => {
|
||||
if (!Components.length) {
|
||||
context.nuxt.error = _app.error({ statusCode: 404, message: 'This page could not be found.', url: context.route.path })
|
||||
context.nuxt.error = context.error({ statusCode: 404, message: 'This page could not be found.', url: context.route.path })
|
||||
<%= (store ? 'context.nuxt.state = store.state' : '') %>
|
||||
return _app
|
||||
}
|
||||
@ -97,13 +97,13 @@ export default context => {
|
||||
<% } %>
|
||||
// datas are the first row of each
|
||||
context.nuxt.data = res.map((tab) => tab[0])
|
||||
context.nuxt.error = _app.err
|
||||
context.nuxt.error = _app.$options._nuxt.err
|
||||
<%= (store ? '// Add the state from the vuex store' : '') %>
|
||||
<%= (store ? 'context.nuxt.state = store.state' : '') %>
|
||||
return _app
|
||||
})
|
||||
.catch(function (error) {
|
||||
context.nuxt.error = _app.error(error)
|
||||
context.nuxt.error = context.error(error)
|
||||
<%= (store ? 'context.nuxt.state = store.state' : '') %>
|
||||
return _app
|
||||
})
|
||||
|
@ -25,6 +25,7 @@ export function getContext (context) {
|
||||
route: (context.to ? context.to : context.route),
|
||||
error: context.error
|
||||
}
|
||||
const next = context.next
|
||||
ctx.params = ctx.route.params || {}
|
||||
ctx.query = ctx.route.query || {}
|
||||
ctx.redirect = function (status, path, query) {
|
||||
@ -35,7 +36,7 @@ export function getContext (context) {
|
||||
path = status
|
||||
status = 302
|
||||
}
|
||||
context.next({
|
||||
next({
|
||||
path: path,
|
||||
query: query,
|
||||
status: status
|
||||
@ -51,7 +52,7 @@ export function promisify (fn, context) {
|
||||
if (fn.length === 2) {
|
||||
// fn(context, callback)
|
||||
promise = new Promise((resolve) => {
|
||||
fn(getContext(context), function (err, data) {
|
||||
fn(context, function (err, data) {
|
||||
if (err) {
|
||||
context.error(err)
|
||||
}
|
||||
@ -60,7 +61,7 @@ export function promisify (fn, context) {
|
||||
})
|
||||
})
|
||||
} else {
|
||||
promise = fn(getContext(context))
|
||||
promise = fn(context)
|
||||
}
|
||||
if (!(promise instanceof Promise)) {
|
||||
promise = Promise.resolve(promise)
|
||||
|
@ -27,10 +27,10 @@ const wp = function (p) {
|
||||
}
|
||||
const r = function () {
|
||||
let args = Array.from(arguments)
|
||||
args = args.map(normalize)
|
||||
if (_.last(args).includes('~')) {
|
||||
return wp(_.last(args))
|
||||
}
|
||||
args = args.map(normalize)
|
||||
return wp(resolve.apply(null, args))
|
||||
}
|
||||
|
||||
@ -184,7 +184,6 @@ function * generateRoutesAndFiles () {
|
||||
templateVars.loading = templateVars.loading + '.vue'
|
||||
}
|
||||
// Format routes for the lib/app/router.js template
|
||||
// TODO: check .children
|
||||
templateVars.router.routes.forEach((route) => {
|
||||
route._component = route.component
|
||||
route._name = '_' + hash(route._component)
|
||||
@ -308,7 +307,7 @@ function createRenderer (bundle) {
|
||||
function watchPages () {
|
||||
const patterns = [ r(this.dir, 'pages/*.vue'), r(this.dir, 'pages/**/*.vue') ]
|
||||
const options = {
|
||||
ignored: '**/_*.vue',
|
||||
// ignored: '**/_*.vue',
|
||||
ignoreInitial: true
|
||||
}
|
||||
const refreshFiles = _.debounce(() => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nuxt",
|
||||
"version": "0.5.3",
|
||||
"version": "0.6.0",
|
||||
"description": "A minimalistic framework for server-rendered Vue.js applications (inspired by Next.js)",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
|
Loading…
Reference in New Issue
Block a user