├── .gitignore ├── firebase.json ├── functions ├── index.js ├── nuxt │ ├── App.js │ ├── client.js │ ├── components │ │ ├── no-ssr.js │ │ ├── nuxt-child.js │ │ ├── nuxt-error.vue │ │ ├── nuxt-link.js │ │ ├── nuxt-loading.vue │ │ └── nuxt.js │ ├── empty.js │ ├── index.js │ ├── loading.html │ ├── middleware.js │ ├── router.js │ ├── server.js │ ├── utils.js │ └── views │ │ ├── app.template.html │ │ └── error.html ├── package.json └── yarn.lock └── src ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── README.md ├── assets └── README.md ├── components ├── AppLogo.vue └── README.md ├── layouts ├── README.md └── default.vue ├── middleware └── README.md ├── nuxt.config.js ├── package.json ├── pages ├── README.md ├── about.vue ├── blog.vue └── index.vue ├── plugins └── README.md ├── static ├── README.md └── favicon.ico ├── store └── README.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | 7 | # Nuxt build 8 | .nuxt 9 | 10 | # Nuxt generate 11 | dist 12 | 13 | *.firebaserc 14 | *.directory 15 | *.log 16 | *.swp 17 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "functions/nuxt/dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [{ 10 | "source": "**", 11 | "function": "app" 12 | }] 13 | }, 14 | "functions": { 15 | "source": "functions" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions') 2 | const { Nuxt } = require('nuxt') 3 | const express = require('express') 4 | 5 | // Nuxt.js config 6 | const config = { 7 | dev: false, 8 | buildDir: 'nuxt', 9 | build: { 10 | publicPath: '/assets/' 11 | } 12 | } 13 | const nuxt = new Nuxt(config) 14 | const app = express() 15 | 16 | // render all requests with nuxt 17 | const handleAppRequest = (req, res) => { 18 | res.set('Cache-Control', 'public, max-age=150, s-maxage=150') 19 | return new Promise((resolve, reject) => { 20 | nuxt.render(req, res, promise => { 21 | promise.then(resolve).catch(reject) 22 | }) 23 | }) 24 | } 25 | 26 | app.use(handleAppRequest) 27 | exports.app = functions.https.onRequest(app) 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /functions/nuxt/App.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import NuxtLoading from './components/nuxt-loading.vue' 3 | 4 | 5 | let layouts = { 6 | 7 | "_default": () => import('../../src/layouts/default.vue' /* webpackChunkName: "layouts/default" */).then(m => m.default || m) 8 | 9 | } 10 | 11 | let resolvedLayouts = {} 12 | 13 | export default { 14 | head: {"title":"vue-vienna","meta":[{"charset":"utf-8"},{"name":"viewport","content":"width=device-width, initial-scale=1"},{"hid":"description","name":"description","content":"Nuxt.js project"}],"link":[{"rel":"icon","type":"image\u002Fx-icon","href":"\u002Ffavicon.ico"}],"style":[],"script":[]}, 15 | render(h, props) { 16 | const loadingEl = h('nuxt-loading', { ref: 'loading' }) 17 | const layoutEl = h(this.layout || 'nuxt') 18 | const templateEl = h('div', { 19 | domProps: { 20 | id: '__layout' 21 | }, 22 | key: this.layoutName 23 | }, [ layoutEl ]) 24 | 25 | const transitionEl = h('transition', { 26 | props: { 27 | name: 'layout', 28 | mode: 'out-in' 29 | } 30 | }, [ templateEl ]) 31 | 32 | return h('div',{ 33 | domProps: { 34 | id: '__nuxt' 35 | } 36 | }, [ 37 | loadingEl, 38 | transitionEl 39 | ]) 40 | }, 41 | data: () => ({ 42 | layout: null, 43 | layoutName: '' 44 | }), 45 | beforeCreate () { 46 | Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt) 47 | }, 48 | created () { 49 | // Add this.$nuxt in child instances 50 | Vue.prototype.$nuxt = this 51 | // add to window so we can listen when ready 52 | if (typeof window !== 'undefined') { 53 | window.$nuxt = this 54 | } 55 | // Add $nuxt.error() 56 | this.error = this.nuxt.error 57 | }, 58 | 59 | mounted () { 60 | this.$loading = this.$refs.loading 61 | }, 62 | watch: { 63 | 'nuxt.err': 'errorChanged' 64 | }, 65 | 66 | methods: { 67 | 68 | errorChanged () { 69 | if (this.nuxt.err && this.$loading) { 70 | if (this.$loading.fail) this.$loading.fail() 71 | if (this.$loading.finish) this.$loading.finish() 72 | } 73 | }, 74 | 75 | setLayout (layout) { 76 | if (!layout || !resolvedLayouts['_' + layout]) layout = 'default' 77 | this.layoutName = layout 78 | let _layout = '_' + layout 79 | this.layout = resolvedLayouts[_layout] 80 | return this.layout 81 | }, 82 | loadLayout (layout) { 83 | if (!layout || !(layouts['_' + layout] || resolvedLayouts['_' + layout])) layout = 'default' 84 | let _layout = '_' + layout 85 | if (resolvedLayouts[_layout]) { 86 | return Promise.resolve(resolvedLayouts[_layout]) 87 | } 88 | return layouts[_layout]() 89 | .then((Component) => { 90 | resolvedLayouts[_layout] = Component 91 | delete layouts[_layout] 92 | return resolvedLayouts[_layout] 93 | }) 94 | .catch((e) => { 95 | if (this.$nuxt) { 96 | return this.$nuxt.error({ statusCode: 500, message: e.message }) 97 | } 98 | }) 99 | } 100 | }, 101 | components: { 102 | NuxtLoading 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /functions/nuxt/client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import middleware from './middleware' 3 | import { createApp, NuxtError } from './index' 4 | import { 5 | applyAsyncData, 6 | sanitizeComponent, 7 | resolveRouteComponents, 8 | getMatchedComponents, 9 | getMatchedComponentsInstances, 10 | flatMapComponents, 11 | setContext, 12 | middlewareSeries, 13 | promisify, 14 | getLocation, 15 | compile, 16 | getQueryDiff 17 | } from './utils' 18 | 19 | const noopData = () => { return {} } 20 | const noopFetch = () => {} 21 | 22 | // Global shared references 23 | let _lastPaths = [] 24 | let app 25 | let router 26 | 27 | 28 | // Try to rehydrate SSR data from window 29 | const NUXT = window.__NUXT__ || {} 30 | 31 | 32 | 33 | // Create and mount App 34 | createApp() 35 | .then(mountApp) 36 | .catch(err => { 37 | if (err.message === 'ERR_REDIRECT') { 38 | return // Wait for browser to redirect... 39 | } 40 | console.error('[nuxt] Error while initializing app', err) 41 | }) 42 | 43 | function componentOption(component, key, ...args) { 44 | if (!component || !component.options || !component.options[key]) { 45 | return {} 46 | } 47 | const option = component.options[key] 48 | if (typeof option === 'function') { 49 | return option(...args) 50 | } 51 | return option 52 | } 53 | 54 | function mapTransitions(Components, to, from) { 55 | const componentTransitions = component => { 56 | const transition = componentOption(component, 'transition', to, from) || {} 57 | return (typeof transition === 'string' ? { name: transition } : transition) 58 | } 59 | 60 | return Components.map(Component => { 61 | // Clone original object to prevent overrides 62 | const transitions = Object.assign({}, componentTransitions(Component)) 63 | 64 | // Combine transitions & prefer `leave` transitions of 'from' route 65 | if (from && from.matched.length && from.matched[0].components.default) { 66 | const from_transitions = componentTransitions(from.matched[0].components.default) 67 | Object.keys(from_transitions) 68 | .filter(key => from_transitions[key] && key.toLowerCase().indexOf('leave') !== -1) 69 | .forEach(key => { transitions[key] = from_transitions[key] }) 70 | } 71 | 72 | return transitions 73 | }) 74 | } 75 | 76 | async function loadAsyncComponents (to, from, next) { 77 | // Check if route path changed (this._pathChanged), only if the page is not an error (for validate()) 78 | this._pathChanged = !!app.nuxt.err || from.path !== to.path 79 | this._queryChanged = JSON.stringify(to.query) !== JSON.stringify(from.query) 80 | this._diffQuery = (this._queryChanged ? getQueryDiff(to.query, from.query) : []) 81 | 82 | 83 | if (this._pathChanged && this.$loading.start) { 84 | this.$loading.start() 85 | } 86 | 87 | 88 | try { 89 | const Components = await resolveRouteComponents(to) 90 | 91 | if (!this._pathChanged && this._queryChanged) { 92 | // Add a marker on each component that it needs to refresh or not 93 | const startLoader = Components.some((Component) => { 94 | const watchQuery = Component.options.watchQuery 95 | if (watchQuery === true) return true 96 | if (Array.isArray(watchQuery)) { 97 | return watchQuery.some((key) => this._diffQuery[key]) 98 | } 99 | return false 100 | }) 101 | if (startLoader && this.$loading.start) { 102 | this.$loading.start() 103 | } 104 | } 105 | 106 | // Call next() 107 | next() 108 | } catch (err) { 109 | err = err || {} 110 | const statusCode = err.statusCode || err.status || (err.response && err.response.status) || 500 111 | this.error({ statusCode, message: err.message }) 112 | this.$nuxt.$emit('routeChanged', to, from, err) 113 | next(false) 114 | } 115 | } 116 | 117 | function applySSRData(Component, ssrData) { 118 | if (NUXT.serverRendered && ssrData) { 119 | applyAsyncData(Component, ssrData) 120 | } 121 | Component._Ctor = Component 122 | return Component 123 | } 124 | 125 | // Get matched components 126 | function resolveComponents(router) { 127 | const path = getLocation(router.options.base, router.options.mode) 128 | 129 | return flatMapComponents(router.match(path), async (Component, _, match, key, index) => { 130 | // If component is not resolved yet, resolve it 131 | if (typeof Component === 'function' && !Component.options) { 132 | Component = await Component() 133 | } 134 | // Sanitize it and save it 135 | const _Component = applySSRData(sanitizeComponent(Component), NUXT.data ? NUXT.data[index] : null) 136 | match.components[key] = _Component 137 | return _Component 138 | }) 139 | } 140 | 141 | function callMiddleware (Components, context, layout) { 142 | let midd = [] 143 | let unknownMiddleware = false 144 | 145 | // If layout is undefined, only call global middleware 146 | if (typeof layout !== 'undefined') { 147 | midd = [] // Exclude global middleware if layout defined (already called before) 148 | if (layout.middleware) { 149 | midd = midd.concat(layout.middleware) 150 | } 151 | Components.forEach(Component => { 152 | if (Component.options.middleware) { 153 | midd = midd.concat(Component.options.middleware) 154 | } 155 | }) 156 | } 157 | 158 | midd = midd.map(name => { 159 | if (typeof name === 'function') return name 160 | if (typeof middleware[name] !== 'function') { 161 | unknownMiddleware = true 162 | this.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 163 | } 164 | return middleware[name] 165 | }) 166 | 167 | if (unknownMiddleware) return 168 | return middlewareSeries(midd, context) 169 | } 170 | 171 | async function render (to, from, next) { 172 | if (this._pathChanged === false && this._queryChanged === false) return next() 173 | 174 | // nextCalled is true when redirected 175 | let nextCalled = false 176 | const _next = path => { 177 | if (from.path === path.path && this.$loading.finish) this.$loading.finish() 178 | if (from.path !== path.path && this.$loading.pause) this.$loading.pause() 179 | if (nextCalled) return 180 | nextCalled = true 181 | const matches = [] 182 | _lastPaths = getMatchedComponents(from, matches).map((Component, i) => compile(from.matched[matches[i]].path)(from.params)) 183 | next(path) 184 | } 185 | 186 | // Update context 187 | await setContext(app, { 188 | route: to, 189 | from, 190 | next: _next.bind(this) 191 | }) 192 | this._dateLastError = app.nuxt.dateErr 193 | this._hadError = !!app.nuxt.err 194 | 195 | // Get route's matched components 196 | const matches = [] 197 | const Components = getMatchedComponents(to, matches) 198 | 199 | // If no Components matched, generate 404 200 | if (!Components.length) { 201 | // Default layout 202 | await callMiddleware.call(this, Components, app.context) 203 | if (nextCalled) return 204 | // Load layout for error page 205 | const layout = await this.loadLayout(typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout) 206 | await callMiddleware.call(this, Components, app.context, layout) 207 | if (nextCalled) return 208 | // Show error page 209 | app.context.error({ statusCode: 404, message: 'This page could not be found' }) 210 | return next() 211 | } 212 | 213 | // Update ._data and other properties if hot reloaded 214 | Components.forEach(Component => { 215 | if (Component._Ctor && Component._Ctor.options) { 216 | Component.options.asyncData = Component._Ctor.options.asyncData 217 | Component.options.fetch = Component._Ctor.options.fetch 218 | } 219 | }) 220 | 221 | // Apply transitions 222 | this.setTransitions(mapTransitions(Components, to, from)) 223 | 224 | try { 225 | // Call middleware 226 | await callMiddleware.call(this, Components, app.context) 227 | if (nextCalled) return 228 | if (app.context._errored) return next() 229 | 230 | // Set layout 231 | let layout = Components[0].options.layout 232 | if (typeof layout === 'function') { 233 | layout = layout(app.context) 234 | } 235 | layout = await this.loadLayout(layout) 236 | 237 | // Call middleware for layout 238 | await callMiddleware.call(this, Components, app.context, layout) 239 | if (nextCalled) return 240 | if (app.context._errored) return next() 241 | 242 | // Call .validate() 243 | let isValid = true 244 | Components.forEach(Component => { 245 | if (!isValid) return 246 | if (typeof Component.options.validate !== 'function') return 247 | isValid = Component.options.validate({ 248 | params: to.params || {}, 249 | query : to.query || {}, 250 | 251 | }) 252 | }) 253 | // ...If .validate() returned false 254 | if (!isValid) { 255 | this.error({ statusCode: 404, message: 'This page could not be found' }) 256 | return next() 257 | } 258 | 259 | // Call asyncData & fetch hooks on components matched by the route. 260 | await Promise.all(Components.map((Component, i) => { 261 | // Check if only children route changed 262 | Component._path = compile(to.matched[matches[i]].path)(to.params) 263 | Component._dataRefresh = false 264 | // Check if Component need to be refreshed (call asyncData & fetch) 265 | // Only if its slug has changed or is watch query changes 266 | if (this._pathChanged && Component._path !== _lastPaths[i]) { 267 | Component._dataRefresh = true 268 | } else if (!this._pathChanged && this._queryChanged) { 269 | const watchQuery = Component.options.watchQuery 270 | if (watchQuery === true) { 271 | Component._dataRefresh = true 272 | } else if (Array.isArray(watchQuery)) { 273 | Component._dataRefresh = watchQuery.some((key) => this._diffQuery[key]) 274 | } 275 | } 276 | if (!this._hadError && this._isMounted && !Component._dataRefresh) { 277 | return Promise.resolve() 278 | } 279 | 280 | let promises = [] 281 | 282 | const hasAsyncData = Component.options.asyncData && typeof Component.options.asyncData === 'function' 283 | const hasFetch = !!Component.options.fetch 284 | const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45 285 | 286 | // Call asyncData(context) 287 | if (hasAsyncData) { 288 | const promise = promisify(Component.options.asyncData, app.context) 289 | .then(asyncDataResult => { 290 | applyAsyncData(Component, asyncDataResult) 291 | if(this.$loading.increase) this.$loading.increase(loadingIncrease) 292 | }) 293 | promises.push(promise) 294 | } 295 | 296 | // Call fetch(context) 297 | if (hasFetch) { 298 | let p = Component.options.fetch(app.context) 299 | if (!p || (!(p instanceof Promise) && (typeof p.then !== 'function'))) { 300 | p = Promise.resolve(p) 301 | } 302 | p.then(fetchResult => { 303 | if(this.$loading.increase) this.$loading.increase(loadingIncrease) 304 | }) 305 | promises.push(p) 306 | } 307 | 308 | return Promise.all(promises) 309 | })) 310 | 311 | // If not redirected 312 | if (!nextCalled) { 313 | if(this.$loading.finish) this.$loading.finish() 314 | _lastPaths = Components.map((Component, i) => compile(to.matched[matches[i]].path)(to.params)) 315 | next() 316 | } 317 | 318 | } catch (error) { 319 | if (!error) error = {} 320 | _lastPaths = [] 321 | error.statusCode = error.statusCode || error.status || (error.response && error.response.status) || 500 322 | 323 | // Load error layout 324 | let layout = NuxtError.layout 325 | if (typeof layout === 'function') { 326 | layout = layout(app.context) 327 | } 328 | await this.loadLayout(layout) 329 | 330 | this.error(error) 331 | this.$nuxt.$emit('routeChanged', to, from, error) 332 | next(false) 333 | } 334 | } 335 | 336 | // Fix components format in matched, it's due to code-splitting of vue-router 337 | function normalizeComponents (to, ___) { 338 | flatMapComponents(to, (Component, _, match, key) => { 339 | if (typeof Component === 'object' && !Component.options) { 340 | // Updated via vue-router resolveAsyncComponents() 341 | Component = Vue.extend(Component) 342 | Component._Ctor = Component 343 | match.components[key] = Component 344 | } 345 | return Component 346 | }) 347 | } 348 | 349 | function showNextPage(to) { 350 | // Hide error component if no error 351 | if (this._hadError && this._dateLastError === this.$options.nuxt.dateErr) { 352 | this.error() 353 | } 354 | 355 | // Set layout 356 | let layout = this.$options.nuxt.err ? NuxtError.layout : to.matched[0].components.default.options.layout 357 | if (typeof layout === 'function') { 358 | layout = layout(app.context) 359 | } 360 | this.setLayout(layout) 361 | } 362 | 363 | // When navigating on a different route but the same component is used, Vue.js 364 | // Will not update the instance data, so we have to update $data ourselves 365 | function fixPrepatch(to, ___) { 366 | if (this._pathChanged === false && this._queryChanged === false) return 367 | 368 | Vue.nextTick(() => { 369 | const matches = [] 370 | const instances = getMatchedComponentsInstances(to, matches) 371 | 372 | instances.forEach((instance, i) => { 373 | if (!instance) return 374 | // if (!this._queryChanged && to.matched[matches[i]].path.indexOf(':') === -1 && to.matched[matches[i]].path.indexOf('*') === -1) return // If not a dynamic route, skip 375 | if (instance.constructor._dataRefresh && _lastPaths[i] === instance.constructor._path && typeof instance.constructor.options.data === 'function') { 376 | const newData = instance.constructor.options.data.call(instance) 377 | for (let key in newData) { 378 | Vue.set(instance.$data, key, newData[key]) 379 | } 380 | } 381 | }) 382 | showNextPage.call(this, to) 383 | 384 | }) 385 | } 386 | 387 | function nuxtReady (_app) { 388 | window._nuxtReadyCbs.forEach((cb) => { 389 | if (typeof cb === 'function') { 390 | cb(_app) 391 | } 392 | }) 393 | // Special JSDOM 394 | if (typeof window._onNuxtLoaded === 'function') { 395 | window._onNuxtLoaded(_app) 396 | } 397 | // Add router hooks 398 | router.afterEach(function (to, from) { 399 | // Wait for fixPrepatch + $data updates 400 | Vue.nextTick(() => _app.$nuxt.$emit('routeChanged', to, from)) 401 | }) 402 | } 403 | 404 | 405 | 406 | async function mountApp(__app) { 407 | // Set global variables 408 | app = __app.app 409 | router = __app.router 410 | 411 | 412 | // Resolve route components 413 | const Components = await Promise.all(resolveComponents(router)) 414 | 415 | // Create Vue instance 416 | const _app = new Vue(app) 417 | 418 | 419 | // Load layout 420 | const layout = NUXT.layout || 'default' 421 | await _app.loadLayout(layout) 422 | _app.setLayout(layout) 423 | 424 | 425 | // Mounts Vue app to DOM element 426 | const mount = () => { 427 | _app.$mount('#__nuxt') 428 | 429 | // Listen for first Vue update 430 | Vue.nextTick(() => { 431 | // Call window.onNuxtReady callbacks 432 | nuxtReady(_app) 433 | 434 | }) 435 | } 436 | 437 | // Enable transitions 438 | _app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app) 439 | if (Components.length) { 440 | _app.setTransitions(mapTransitions(Components, router.currentRoute)) 441 | _lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params)) 442 | } 443 | 444 | // Initialize error handler 445 | _app.$loading = {} // To avoid error while _app.$nuxt does not exist 446 | if (NUXT.error) _app.error(NUXT.error) 447 | 448 | // Add router hooks 449 | router.beforeEach(loadAsyncComponents.bind(_app)) 450 | router.beforeEach(render.bind(_app)) 451 | router.afterEach(normalizeComponents) 452 | router.afterEach(fixPrepatch.bind(_app)) 453 | 454 | // If page already is server rendered 455 | if (NUXT.serverRendered) { 456 | mount() 457 | return 458 | } 459 | 460 | // First render on client-side 461 | render.call(_app, router.currentRoute, router.currentRoute, (path) => { 462 | // If not redirected 463 | if (!path) { 464 | normalizeComponents(router.currentRoute, router.currentRoute) 465 | showNextPage.call(_app, router.currentRoute) 466 | // Dont call fixPrepatch.call(_app, router.currentRoute, router.currentRoute) since it's first render 467 | mount() 468 | return 469 | } 470 | 471 | // Push the path and then mount app 472 | router.push(path, () => mount(), (err) => { 473 | if (!err) return mount() 474 | console.error(err) 475 | }) 476 | }) 477 | } 478 | -------------------------------------------------------------------------------- /functions/nuxt/components/no-ssr.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** From https://github.com/egoist/vue-no-ssr 3 | ** With the authorization of @egoist 4 | */ 5 | export default { 6 | name: 'no-ssr', 7 | props: ['placeholder'], 8 | data () { 9 | return { 10 | canRender: false 11 | } 12 | }, 13 | mounted () { 14 | this.canRender = true 15 | }, 16 | render (h) { 17 | if (this.canRender) { 18 | if ( 19 | process.env.NODE_ENV === 'development' && 20 | this.$slots.default && 21 | this.$slots.default.length > 1 22 | ) { 23 | throw new Error(' You cannot use multiple child components') 24 | } 25 | return this.$slots.default && this.$slots.default[0] 26 | } 27 | 28 | return h( 29 | 'div', 30 | { 31 | class: ['no-ssr-placeholder'] 32 | }, 33 | this.$slots.placeholder || this.placeholder 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /functions/nuxt/components/nuxt-child.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'nuxt-child', 3 | functional: true, 4 | props: ['keepAlive'], 5 | render (h, { parent, data, props }) { 6 | data.nuxtChild = true 7 | const _parent = parent 8 | const transitions = parent.$nuxt.nuxt.transitions 9 | const defaultTransition = parent.$nuxt.nuxt.defaultTransition 10 | 11 | let depth = 0 12 | while (parent) { 13 | if (parent.$vnode && parent.$vnode.data.nuxtChild) { 14 | depth++ 15 | } 16 | parent = parent.$parent 17 | } 18 | data.nuxtChildDepth = depth 19 | const transition = transitions[depth] || defaultTransition 20 | let transitionProps = {} 21 | transitionsKeys.forEach((key) => { 22 | if (typeof transition[key] !== 'undefined') { 23 | transitionProps[key] = transition[key] 24 | } 25 | }) 26 | let listeners = {} 27 | listenersKeys.forEach((key) => { 28 | if (typeof transition[key] === 'function') { 29 | listeners[key] = transition[key].bind(_parent) 30 | } 31 | }) 32 | // Add triggerScroll event on beforeEnter (fix #1376) 33 | let beforeEnter = listeners.beforeEnter 34 | listeners.beforeEnter = (el) => { 35 | window.$nuxt.$emit('triggerScroll') 36 | if (beforeEnter) return beforeEnter.call(_parent, el) 37 | } 38 | 39 | let routerView = [ 40 | h('router-view', data) 41 | ] 42 | if (typeof props.keepAlive !== 'undefined') { 43 | routerView = [ 44 | h('keep-alive', routerView) 45 | ] 46 | } 47 | return h('transition', { 48 | props: transitionProps, 49 | on: listeners 50 | }, routerView) 51 | } 52 | } 53 | 54 | const transitionsKeys = [ 55 | 'name', 56 | 'mode', 57 | 'appear', 58 | 'css', 59 | 'type', 60 | 'duration', 61 | 'enterClass', 62 | 'leaveClass', 63 | 'appearClass', 64 | 'enterActiveClass', 65 | 'enterActiveClass', 66 | 'leaveActiveClass', 67 | 'appearActiveClass', 68 | 'enterToClass', 69 | 'leaveToClass', 70 | 'appearToClass' 71 | ] 72 | 73 | const listenersKeys = [ 74 | 'beforeEnter', 75 | 'enter', 76 | 'afterEnter', 77 | 'enterCancelled', 78 | 'beforeLeave', 79 | 'leave', 80 | 'afterLeave', 81 | 'leaveCancelled', 82 | 'beforeAppear', 83 | 'appear', 84 | 'afterAppear', 85 | 'appearCancelled' 86 | ] 87 | -------------------------------------------------------------------------------- /functions/nuxt/components/nuxt-error.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 45 | 46 | 91 | -------------------------------------------------------------------------------- /functions/nuxt/components/nuxt-link.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'nuxt-link', 3 | functional: true, 4 | render (h, { data, children }) { 5 | return h('router-link', data, children) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /functions/nuxt/components/nuxt-loading.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 89 | 90 | 104 | -------------------------------------------------------------------------------- /functions/nuxt/components/nuxt.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import NuxtChild from './nuxt-child' 3 | import NuxtError from './nuxt-error.vue' 4 | import { compile } from '../utils' 5 | 6 | export default { 7 | name: 'nuxt', 8 | props: ['nuxtChildKey', 'keepAlive'], 9 | render(h) { 10 | // If there is some error 11 | if (this.nuxt.err) { 12 | return h('nuxt-error', { 13 | props: { 14 | error: this.nuxt.err 15 | } 16 | }) 17 | } 18 | // Directly return nuxt child 19 | return h('nuxt-child', { 20 | key: this.routerViewKey, 21 | props: this.$props 22 | }) 23 | }, 24 | beforeCreate () { 25 | Vue.util.defineReactive(this, 'nuxt', this.$root.$options.nuxt) 26 | }, 27 | computed: { 28 | routerViewKey () { 29 | // If nuxtChildKey prop is given or current route has children 30 | if (typeof this.nuxtChildKey !== 'undefined' || this.$route.matched.length > 1) { 31 | return this.nuxtChildKey || compile(this.$route.matched[0].path)(this.$route.params) 32 | } 33 | const Component = this.$route.matched[0] && this.$route.matched[0].components.default 34 | if (Component && Component.options && Component.options.key) { 35 | return (typeof Component.options.key === 'function' ? Component.options.key(this.$route) : Component.options.key) 36 | } 37 | return this.$route.path 38 | } 39 | }, 40 | components: { 41 | NuxtChild, 42 | NuxtError 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /functions/nuxt/empty.js: -------------------------------------------------------------------------------- 1 | // This file is intentionally left empty for noop aliases 2 | -------------------------------------------------------------------------------- /functions/nuxt/index.js: -------------------------------------------------------------------------------- 1 | import 'es6-promise/auto' 2 | import Vue from 'vue' 3 | import Meta from 'vue-meta' 4 | import { createRouter } from './router.js' 5 | import NoSSR from './components/no-ssr.js' 6 | import NuxtChild from './components/nuxt-child.js' 7 | import NuxtLink from './components/nuxt-link.js' 8 | import NuxtError from './components/nuxt-error.vue' 9 | import Nuxt from './components/nuxt.js' 10 | import App from './App.js' 11 | import { setContext, getLocation, getRouteData } from './utils' 12 | 13 | 14 | /* Plugins */ 15 | 16 | 17 | // Component: 18 | Vue.component(NoSSR.name, NoSSR) 19 | 20 | // Component: 21 | Vue.component(NuxtChild.name, NuxtChild) 22 | 23 | // Component: 24 | Vue.component(NuxtLink.name, NuxtLink) 25 | 26 | // Component: ` 27 | Vue.component(Nuxt.name, Nuxt) 28 | 29 | // vue-meta configuration 30 | Vue.use(Meta, { 31 | keyName: 'head', // the component option name that vue-meta looks for meta info on. 32 | attribute: 'data-n-head', // the attribute name vue-meta adds to the tags it observes 33 | ssrAttribute: 'data-n-head-ssr', // the attribute name that lets vue-meta know that meta info has already been server-rendered 34 | tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag 35 | }) 36 | 37 | const defaultTransition = {"name":"page","mode":"out-in","appear":false,"appearClass":"appear","appearActiveClass":"appear-active","appearToClass":"appear-to"} 38 | 39 | async function createApp (ssrContext) { 40 | const router = createRouter(ssrContext) 41 | 42 | 43 | 44 | // Create Root instance 45 | // here we inject the router and store to all child components, 46 | // making them available everywhere as `this.$router` and `this.$store`. 47 | const app = { 48 | router, 49 | 50 | nuxt: { 51 | defaultTransition, 52 | transitions: [ defaultTransition ], 53 | setTransitions (transitions) { 54 | if (!Array.isArray(transitions)) { 55 | transitions = [ transitions ] 56 | } 57 | transitions = transitions.map((transition) => { 58 | if (!transition) { 59 | transition = defaultTransition 60 | } else if (typeof transition === 'string') { 61 | transition = Object.assign({}, defaultTransition, { name: transition }) 62 | } else { 63 | transition = Object.assign({}, defaultTransition, transition) 64 | } 65 | return transition 66 | }) 67 | this.$options.nuxt.transitions = transitions 68 | return transitions 69 | }, 70 | err: null, 71 | dateErr: null, 72 | error (err) { 73 | err = err || null 74 | app.context._errored = !!err 75 | if (typeof err === 'string') err = { statusCode: 500, message: err } 76 | const nuxt = this.nuxt || this.$options.nuxt 77 | nuxt.dateErr = Date.now() 78 | nuxt.err = err 79 | // Used in lib/server.js 80 | if (ssrContext) ssrContext.nuxt.error = err 81 | return err 82 | } 83 | }, 84 | ...App 85 | } 86 | 87 | const next = ssrContext ? ssrContext.next : location => app.router.push(location) 88 | // Resolve route 89 | let route 90 | if (ssrContext) { 91 | route = router.resolve(ssrContext.url).route 92 | } else { 93 | const path = getLocation(router.options.base) 94 | route = router.resolve(path).route 95 | } 96 | 97 | // Set context to app.context 98 | await setContext(app, { 99 | route, 100 | next, 101 | error: app.nuxt.error.bind(app), 102 | 103 | payload: ssrContext ? ssrContext.payload : undefined, 104 | req: ssrContext ? ssrContext.req : undefined, 105 | res: ssrContext ? ssrContext.res : undefined, 106 | beforeRenderFns: ssrContext ? ssrContext.beforeRenderFns : undefined 107 | }) 108 | 109 | const inject = function (key, value) { 110 | if (!key) throw new Error('inject(key, value) has no key provided') 111 | if (!value) throw new Error('inject(key, value) has no value provided') 112 | key = '$' + key 113 | // Add into app 114 | app[key] = value 115 | 116 | // Check if plugin not already installed 117 | const installKey = '__nuxt_' + key + '_installed__' 118 | if (Vue[installKey]) return 119 | Vue[installKey] = true 120 | // Call Vue.use() to install the plugin into vm 121 | Vue.use(() => { 122 | if (!Vue.prototype.hasOwnProperty(key)) { 123 | Object.defineProperty(Vue.prototype, key, { 124 | get () { 125 | return this.$root.$options[key] 126 | } 127 | }) 128 | } 129 | }) 130 | } 131 | 132 | 133 | 134 | // Plugin execution 135 | 136 | 137 | 138 | // If server-side, wait for async component to be resolved first 139 | if (process.server && ssrContext && ssrContext.url) { 140 | await new Promise((resolve, reject) => { 141 | router.push(ssrContext.url, resolve, () => { 142 | // navigated to a different route in router guard 143 | const unregister = router.afterEach(async (to, from, next) => { 144 | ssrContext.url = to.fullPath 145 | app.context.route = await getRouteData(to) 146 | app.context.params = to.params || {} 147 | app.context.query = to.query || {} 148 | unregister() 149 | resolve() 150 | }) 151 | }) 152 | }) 153 | } 154 | 155 | return { 156 | app, 157 | router, 158 | 159 | } 160 | } 161 | 162 | export { createApp, NuxtError } 163 | -------------------------------------------------------------------------------- /functions/nuxt/loading.html: -------------------------------------------------------------------------------- 1 | 43 | 44 |
45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /functions/nuxt/middleware.js: -------------------------------------------------------------------------------- 1 | 2 | let files = require.context('@/middleware', false, /^\.\/(?!-)[^.]+\.(js)$/) 3 | let filenames = files.keys() 4 | 5 | function getModule (filename) { 6 | let file = files(filename) 7 | return file.default 8 | ? file.default 9 | : file 10 | } 11 | let middleware = {} 12 | 13 | // Generate the middleware 14 | for (let filename of filenames) { 15 | let name = filename.replace(/^\.\//, '').replace(/\.(js)$/, '') 16 | middleware[name] = getModule(filename) 17 | } 18 | 19 | export default middleware 20 | 21 | -------------------------------------------------------------------------------- /functions/nuxt/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | const _02e065f6 = () => import('../../src/pages/about.vue' /* webpackChunkName: "pages/about" */).then(m => m.default || m) 7 | const _77ef8fda = () => import('../../src/pages/blog.vue' /* webpackChunkName: "pages/blog" */).then(m => m.default || m) 8 | const _1e47a7ca = () => import('../../src/pages/index.vue' /* webpackChunkName: "pages/index" */).then(m => m.default || m) 9 | 10 | 11 | 12 | if (process.client) { 13 | window.history.scrollRestoration = 'manual' 14 | } 15 | const scrollBehavior = function (to, from, savedPosition) { 16 | // if the returned position is falsy or an empty object, 17 | // will retain current scroll position. 18 | let position = false 19 | 20 | // if no children detected 21 | if (to.matched.length < 2) { 22 | // scroll to the top of the page 23 | position = { x: 0, y: 0 } 24 | } else if (to.matched.some((r) => r.components.default.options.scrollToTop)) { 25 | // if one of the children has scrollToTop option set to true 26 | position = { x: 0, y: 0 } 27 | } 28 | 29 | // savedPosition is only available for popstate navigations (back button) 30 | if (savedPosition) { 31 | position = savedPosition 32 | } 33 | 34 | return new Promise(resolve => { 35 | // wait for the out transition to complete (if necessary) 36 | window.$nuxt.$once('triggerScroll', () => { 37 | // coords will be used if no selector is provided, 38 | // or if the selector didn't match any element. 39 | if (to.hash) { 40 | let hash = to.hash 41 | // CSS.escape() is not supported with IE and Edge. 42 | if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') { 43 | hash = '#' + window.CSS.escape(hash.substr(1)) 44 | } 45 | try { 46 | if (document.querySelector(hash)) { 47 | // scroll to anchor by returning the selector 48 | position = { selector: hash } 49 | } 50 | } catch (e) { 51 | console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).') 52 | } 53 | } 54 | resolve(position) 55 | }) 56 | }) 57 | } 58 | 59 | 60 | export function createRouter () { 61 | return new Router({ 62 | mode: 'history', 63 | base: '/', 64 | linkActiveClass: 'nuxt-link-active', 65 | linkExactActiveClass: 'nuxt-link-exact-active', 66 | scrollBehavior, 67 | routes: [ 68 | { 69 | path: "/about", 70 | component: _02e065f6, 71 | name: "about" 72 | }, 73 | { 74 | path: "/blog", 75 | component: _77ef8fda, 76 | name: "blog" 77 | }, 78 | { 79 | path: "/", 80 | component: _1e47a7ca, 81 | name: "index" 82 | } 83 | ], 84 | 85 | 86 | fallback: false 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /functions/nuxt/server.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import clone from 'clone' 3 | import { stringify } from 'querystring' 4 | import { omit } from 'lodash' 5 | import middleware from './middleware' 6 | import { createApp, NuxtError } from './index' 7 | import { applyAsyncData, sanitizeComponent, getMatchedComponents, getContext, middlewareSeries, promisify, urlJoin } from './utils' 8 | 9 | const debug = require('debug')('nuxt:render') 10 | debug.color = 4 // force blue color 11 | 12 | const isDev = false 13 | 14 | const noopApp = () => new Vue({ render: (h) => h('div') }) 15 | 16 | const createNext = ssrContext => opts => { 17 | ssrContext.redirected = opts 18 | // If nuxt generate 19 | if (!ssrContext.res) { 20 | ssrContext.nuxt.serverRendered = false 21 | return 22 | } 23 | opts.query = stringify(opts.query) 24 | opts.path = opts.path + (opts.query ? '?' + opts.query : '') 25 | if (opts.path.indexOf('http') !== 0 && ('/' !== '/' && opts.path.indexOf('/') !== 0)) { 26 | opts.path = urlJoin('/', opts.path) 27 | } 28 | // Avoid loop redirect 29 | if (opts.path === ssrContext.url) { 30 | ssrContext.redirected = false 31 | return 32 | } 33 | ssrContext.res.writeHead(opts.status, { 34 | 'Location': opts.path 35 | }) 36 | ssrContext.res.end() 37 | } 38 | 39 | // This exported function will be called by `bundleRenderer`. 40 | // This is where we perform data-prefetching to determine the 41 | // state of our application before actually rendering it. 42 | // Since data fetching is async, this function is expected to 43 | // return a Promise that resolves to the app instance. 44 | export default async ssrContext => { 45 | // Create ssrContext.next for simulate next() of beforeEach() when wanted to redirect 46 | ssrContext.redirected = false 47 | ssrContext.next = createNext(ssrContext) 48 | // Used for beforeNuxtRender({ Components, nuxtState }) 49 | ssrContext.beforeRenderFns = [] 50 | // Nuxt object (window.__NUXT__) 51 | ssrContext.nuxt = { layout: 'default', data: [], error: null, serverRendered: true } 52 | // Create the app definition and the instance (created for each request) 53 | const { app, router } = await createApp(ssrContext) 54 | const _app = new Vue(app) 55 | 56 | // Add meta infos (used in renderer.js) 57 | ssrContext.meta = _app.$meta() 58 | // Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext) 59 | ssrContext.asyncData = {} 60 | 61 | const beforeRender = async () => { 62 | // Call beforeNuxtRender() methods 63 | await Promise.all(ssrContext.beforeRenderFns.map((fn) => promisify(fn, { Components, nuxtState: ssrContext.nuxt }))) 64 | 65 | } 66 | const renderErrorPage = async () => { 67 | // Load layout for error page 68 | let errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout) 69 | ssrContext.nuxt.layout = errLayout || 'default' 70 | await _app.loadLayout(errLayout) 71 | _app.setLayout(errLayout) 72 | await beforeRender() 73 | return _app 74 | } 75 | const render404Page = () => { 76 | app.context.error({ statusCode: 404, path: ssrContext.url, message: 'This page could not be found' }) 77 | return renderErrorPage() 78 | } 79 | 80 | 81 | 82 | // Components are already resolved by setContext -> getRouteData (app/utils.js) 83 | const Components = getMatchedComponents(router.match(ssrContext.url)) 84 | 85 | 86 | 87 | /* 88 | ** Call global middleware (nuxt.config.js) 89 | */ 90 | let midd = [] 91 | midd = midd.map((name) => { 92 | if (typeof name === 'function') return name 93 | if (typeof middleware[name] !== 'function') { 94 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 95 | } 96 | return middleware[name] 97 | }) 98 | await middlewareSeries(midd, app.context) 99 | // ...If there is a redirect or an error, stop the process 100 | if (ssrContext.redirected) return noopApp() 101 | if (ssrContext.nuxt.error) return renderErrorPage() 102 | 103 | /* 104 | ** Set layout 105 | */ 106 | let layout = Components.length ? Components[0].options.layout : NuxtError.layout 107 | if (typeof layout === 'function') layout = layout(app.context) 108 | await _app.loadLayout(layout) 109 | layout = _app.setLayout(layout) 110 | // ...Set layout to __NUXT__ 111 | ssrContext.nuxt.layout = _app.layoutName 112 | 113 | /* 114 | ** Call middleware (layout + pages) 115 | */ 116 | midd = [] 117 | if (layout.middleware) midd = midd.concat(layout.middleware) 118 | Components.forEach((Component) => { 119 | if (Component.options.middleware) { 120 | midd = midd.concat(Component.options.middleware) 121 | } 122 | }) 123 | midd = midd.map((name) => { 124 | if (typeof name === 'function') return name 125 | if (typeof middleware[name] !== 'function') { 126 | app.context.error({ statusCode: 500, message: 'Unknown middleware ' + name }) 127 | } 128 | return middleware[name] 129 | }) 130 | await middlewareSeries(midd, app.context) 131 | // ...If there is a redirect or an error, stop the process 132 | if (ssrContext.redirected) return noopApp() 133 | if (ssrContext.nuxt.error) return renderErrorPage() 134 | 135 | /* 136 | ** Call .validate() 137 | */ 138 | let isValid = true 139 | Components.forEach((Component) => { 140 | if (!isValid) return 141 | if (typeof Component.options.validate !== 'function') return 142 | isValid = Component.options.validate({ 143 | params: app.context.route.params || {}, 144 | query: app.context.route.query || {}, 145 | 146 | }) 147 | }) 148 | // ...If .validate() returned false 149 | if (!isValid) { 150 | // Don't server-render the page in generate mode 151 | if (ssrContext._generate) ssrContext.nuxt.serverRendered = false 152 | // Render a 404 error page 153 | return render404Page() 154 | } 155 | 156 | // If no Components found, returns 404 157 | if (!Components.length) return render404Page() 158 | 159 | // Call asyncData & fetch hooks on components matched by the route. 160 | let asyncDatas = await Promise.all(Components.map(Component => { 161 | let promises = [] 162 | 163 | // Call asyncData(context) 164 | if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { 165 | let promise = promisify(Component.options.asyncData, app.context) 166 | promise.then(asyncDataResult => { 167 | ssrContext.asyncData[Component.cid] = asyncDataResult 168 | applyAsyncData(Component) 169 | return asyncDataResult 170 | }) 171 | promises.push(promise) 172 | } else { 173 | promises.push(null) 174 | } 175 | 176 | // Call fetch(context) 177 | if (Component.options.fetch) { 178 | promises.push(Component.options.fetch(app.context)) 179 | } 180 | else { 181 | promises.push(null) 182 | } 183 | 184 | return Promise.all(promises) 185 | })) 186 | 187 | 188 | 189 | // datas are the first row of each 190 | ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {}) 191 | 192 | // ...If there is a redirect or an error, stop the process 193 | if (ssrContext.redirected) return noopApp() 194 | if (ssrContext.nuxt.error) return renderErrorPage() 195 | 196 | // Call beforeNuxtRender methods & add store state 197 | await beforeRender() 198 | 199 | return _app 200 | } 201 | -------------------------------------------------------------------------------- /functions/nuxt/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const noopData = () => ({}) 4 | 5 | // window.onNuxtReady(() => console.log('Ready')) hook 6 | // Useful for jsdom testing or plugins (https://github.com/tmpvar/jsdom#dealing-with-asynchronous-script-loading) 7 | if (process.browser) { 8 | window._nuxtReadyCbs = [] 9 | window.onNuxtReady = function (cb) { 10 | window._nuxtReadyCbs.push(cb) 11 | } 12 | } 13 | 14 | export function applyAsyncData(Component, asyncData) { 15 | const ComponentData = Component.options.data || noopData 16 | // Prevent calling this method for each request on SSR context 17 | if (!asyncData && Component.options.hasAsyncData) { 18 | return 19 | } 20 | Component.options.hasAsyncData = true 21 | Component.options.data = function () { 22 | const data = ComponentData.call(this) 23 | if (this.$ssrContext) { 24 | asyncData = this.$ssrContext.asyncData[Component.cid] 25 | } 26 | return { ...data, ...asyncData } 27 | } 28 | if (Component._Ctor && Component._Ctor.options) { 29 | Component._Ctor.options.data = Component.options.data 30 | } 31 | } 32 | 33 | export function sanitizeComponent(Component) { 34 | // If Component already sanitized 35 | if (Component.options && Component._Ctor === Component) { 36 | return Component 37 | } 38 | if (!Component.options) { 39 | Component = Vue.extend(Component) // fix issue #6 40 | Component._Ctor = Component 41 | } else { 42 | Component._Ctor = Component 43 | Component.extendOptions = Component.options 44 | } 45 | // For debugging purpose 46 | if (!Component.options.name && Component.options.__file) { 47 | Component.options.name = Component.options.__file 48 | } 49 | return Component 50 | } 51 | 52 | export function getMatchedComponents(route, matches = false) { 53 | return [].concat.apply([], route.matched.map(function (m, index) { 54 | return Object.keys(m.components).map(function (key) { 55 | matches && matches.push(index) 56 | return m.components[key] 57 | }) 58 | })) 59 | } 60 | 61 | export function getMatchedComponentsInstances(route, matches = false) { 62 | return [].concat.apply([], route.matched.map(function (m, index) { 63 | return Object.keys(m.instances).map(function (key) { 64 | matches && matches.push(index) 65 | return m.instances[key] 66 | }) 67 | })) 68 | } 69 | 70 | export function flatMapComponents(route, fn) { 71 | return Array.prototype.concat.apply([], route.matched.map(function (m, index) { 72 | return Object.keys(m.components).map(function (key) { 73 | return fn(m.components[key], m.instances[key], m, key, index) 74 | }) 75 | })) 76 | } 77 | 78 | export function resolveRouteComponents(route) { 79 | return Promise.all( 80 | flatMapComponents(route, async (Component, _, match, key) => { 81 | // If component is a function, resolve it 82 | if (typeof Component === 'function' && !Component.options) { 83 | Component = await Component() 84 | } 85 | return match.components[key] = sanitizeComponent(Component) 86 | }) 87 | ) 88 | } 89 | 90 | export async function getRouteData(route) { 91 | // Make sure the components are resolved (code-splitting) 92 | await resolveRouteComponents(route) 93 | // Send back a copy of route with meta based on Component definition 94 | return { 95 | ...route, 96 | meta: getMatchedComponents(route).map((Component) => { 97 | return Component.options.meta || {} 98 | }) 99 | } 100 | } 101 | 102 | export async function setContext(app, context) { 103 | const route = (context.to ? context.to : context.route) 104 | // If context not defined, create it 105 | if (!app.context) { 106 | app.context = { 107 | get isServer() { 108 | console.warn('context.isServer has been deprecated, please use process.server instead.') 109 | return process.server 110 | }, 111 | get isClient() { 112 | console.warn('context.isClient has been deprecated, please use process.client instead.') 113 | return process.client 114 | }, 115 | isStatic: process.static, 116 | isDev: false, 117 | isHMR: false, 118 | app, 119 | 120 | payload: context.payload, 121 | error: context.error, 122 | base: '/', 123 | env: {} 124 | } 125 | // Only set once 126 | if (context.req) app.context.req = context.req 127 | if (context.res) app.context.res = context.res 128 | app.context.redirect = function (status, path, query) { 129 | if (!status) return 130 | app.context._redirected = true // Used in middleware 131 | // if only 1 or 2 arguments: redirect('/') or redirect('/', { foo: 'bar' }) 132 | let pathType = typeof path 133 | if (typeof status !== 'number' && (pathType === 'undefined' || pathType === 'object')) { 134 | query = path || {} 135 | path = status 136 | pathType = typeof path 137 | status = 302 138 | } 139 | if (pathType === 'object') { 140 | path = app.router.resolve(path).href 141 | } 142 | // "/absolute/route", "./relative/route" or "../relative/route" 143 | if (/(^[.]{1,2}\/)|(^\/(?!\/))/.test(path)) { 144 | app.context.next({ 145 | path: path, 146 | query: query, 147 | status: status 148 | }) 149 | } else { 150 | path = formatUrl(path, query) 151 | if (process.server) { 152 | app.context.next({ 153 | path: path, 154 | status: status 155 | }) 156 | } 157 | if (process.client) { 158 | // https://developer.mozilla.org/en-US/docs/Web/API/Location/replace 159 | window.location.replace(path) 160 | 161 | // Throw a redirect error 162 | throw new Error('ERR_REDIRECT') 163 | } 164 | } 165 | } 166 | if (process.server) app.context.beforeNuxtRender = (fn) => context.beforeRenderFns.push(fn) 167 | if (process.client) app.context.nuxtState = window.__NUXT__ 168 | } 169 | // Dynamic keys 170 | app.context.next = context.next 171 | app.context._redirected = false 172 | app.context._errored = false 173 | app.context.isHMR = !!context.isHMR 174 | if (context.route) app.context.route = await getRouteData(context.route) 175 | app.context.params = app.context.route.params || {} 176 | app.context.query = app.context.route.query || {} 177 | if (context.from) app.context.from = await getRouteData(context.from) 178 | } 179 | 180 | export function middlewareSeries(promises, appContext) { 181 | if (!promises.length || appContext._redirected || appContext._errored) { 182 | return Promise.resolve() 183 | } 184 | return promisify(promises[0], appContext) 185 | .then(() => { 186 | return middlewareSeries(promises.slice(1), appContext) 187 | }) 188 | } 189 | 190 | export function promisify(fn, context) { 191 | let promise 192 | if (fn.length === 2) { 193 | // fn(context, callback) 194 | promise = new Promise((resolve) => { 195 | fn(context, function (err, data) { 196 | if (err) { 197 | context.error(err) 198 | } 199 | data = data || {} 200 | resolve(data) 201 | }) 202 | }) 203 | } else { 204 | promise = fn(context) 205 | } 206 | if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) { 207 | promise = Promise.resolve(promise) 208 | } 209 | return promise 210 | } 211 | 212 | // Imported from vue-router 213 | export function getLocation(base, mode) { 214 | var path = window.location.pathname 215 | if (mode === 'hash') { 216 | return window.location.hash.replace(/^#\//, '') 217 | } 218 | if (base && path.indexOf(base) === 0) { 219 | path = path.slice(base.length) 220 | } 221 | return (path || '/') + window.location.search + window.location.hash 222 | } 223 | 224 | export function urlJoin() { 225 | return [].slice.call(arguments).join('/').replace(/\/+/g, '/') 226 | } 227 | 228 | // Imported from path-to-regexp 229 | 230 | /** 231 | * Compile a string to a template function for the path. 232 | * 233 | * @param {string} str 234 | * @param {Object=} options 235 | * @return {!function(Object=, Object=)} 236 | */ 237 | export function compile(str, options) { 238 | return tokensToFunction(parse(str, options)) 239 | } 240 | 241 | export function getQueryDiff(toQuery, fromQuery) { 242 | const diff = {} 243 | const queries = { ...toQuery, ...fromQuery } 244 | for (const k in queries) { 245 | if (String(toQuery[k]) !== String(fromQuery[k])) { 246 | diff[k] = true 247 | } 248 | } 249 | return diff 250 | } 251 | 252 | /** 253 | * The main path matching regexp utility. 254 | * 255 | * @type {RegExp} 256 | */ 257 | const PATH_REGEXP = new RegExp([ 258 | // Match escaped characters that would otherwise appear in future matches. 259 | // This allows the user to escape special characters that won't transform. 260 | '(\\\\.)', 261 | // Match Express-style parameters and un-named parameters with a prefix 262 | // and optional suffixes. Matches appear as: 263 | // 264 | // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] 265 | // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] 266 | // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] 267 | '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' 268 | ].join('|'), 'g') 269 | 270 | /** 271 | * Parse a string for the raw tokens. 272 | * 273 | * @param {string} str 274 | * @param {Object=} options 275 | * @return {!Array} 276 | */ 277 | function parse(str, options) { 278 | var tokens = [] 279 | var key = 0 280 | var index = 0 281 | var path = '' 282 | var defaultDelimiter = options && options.delimiter || '/' 283 | var res 284 | 285 | while ((res = PATH_REGEXP.exec(str)) != null) { 286 | var m = res[0] 287 | var escaped = res[1] 288 | var offset = res.index 289 | path += str.slice(index, offset) 290 | index = offset + m.length 291 | 292 | // Ignore already escaped sequences. 293 | if (escaped) { 294 | path += escaped[1] 295 | continue 296 | } 297 | 298 | var next = str[index] 299 | var prefix = res[2] 300 | var name = res[3] 301 | var capture = res[4] 302 | var group = res[5] 303 | var modifier = res[6] 304 | var asterisk = res[7] 305 | 306 | // Push the current path onto the tokens. 307 | if (path) { 308 | tokens.push(path) 309 | path = '' 310 | } 311 | 312 | var partial = prefix != null && next != null && next !== prefix 313 | var repeat = modifier === '+' || modifier === '*' 314 | var optional = modifier === '?' || modifier === '*' 315 | var delimiter = res[2] || defaultDelimiter 316 | var pattern = capture || group 317 | 318 | tokens.push({ 319 | name: name || key++, 320 | prefix: prefix || '', 321 | delimiter: delimiter, 322 | optional: optional, 323 | repeat: repeat, 324 | partial: partial, 325 | asterisk: !!asterisk, 326 | pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') 327 | }) 328 | } 329 | 330 | // Match any characters still remaining. 331 | if (index < str.length) { 332 | path += str.substr(index) 333 | } 334 | 335 | // If the path exists, push it onto the end. 336 | if (path) { 337 | tokens.push(path) 338 | } 339 | 340 | return tokens 341 | } 342 | 343 | /** 344 | * Prettier encoding of URI path segments. 345 | * 346 | * @param {string} 347 | * @return {string} 348 | */ 349 | function encodeURIComponentPretty(str) { 350 | return encodeURI(str).replace(/[\/?#]/g, function (c) { 351 | return '%' + c.charCodeAt(0).toString(16).toUpperCase() 352 | }) 353 | } 354 | 355 | /** 356 | * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. 357 | * 358 | * @param {string} 359 | * @return {string} 360 | */ 361 | function encodeAsterisk(str) { 362 | return encodeURI(str).replace(/[?#]/g, function (c) { 363 | return '%' + c.charCodeAt(0).toString(16).toUpperCase() 364 | }) 365 | } 366 | 367 | /** 368 | * Expose a method for transforming tokens into the path function. 369 | */ 370 | function tokensToFunction(tokens) { 371 | // Compile all the tokens into regexps. 372 | var matches = new Array(tokens.length) 373 | 374 | // Compile all the patterns before compilation. 375 | for (var i = 0; i < tokens.length; i++) { 376 | if (typeof tokens[i] === 'object') { 377 | matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$') 378 | } 379 | } 380 | 381 | return function(obj, opts) { 382 | var path = '' 383 | var data = obj || {} 384 | var options = opts || {} 385 | var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent 386 | 387 | for (var i = 0; i < tokens.length; i++) { 388 | var token = tokens[i] 389 | 390 | if (typeof token === 'string') { 391 | path += token 392 | 393 | continue 394 | } 395 | 396 | var value = data[token.name] 397 | var segment 398 | 399 | if (value == null) { 400 | if (token.optional) { 401 | // Prepend partial segment prefixes. 402 | if (token.partial) { 403 | path += token.prefix 404 | } 405 | 406 | continue 407 | } else { 408 | throw new TypeError('Expected "' + token.name + '" to be defined') 409 | } 410 | } 411 | 412 | if (Array.isArray(value)) { 413 | if (!token.repeat) { 414 | throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') 415 | } 416 | 417 | if (value.length === 0) { 418 | if (token.optional) { 419 | continue 420 | } else { 421 | throw new TypeError('Expected "' + token.name + '" to not be empty') 422 | } 423 | } 424 | 425 | for (var j = 0; j < value.length; j++) { 426 | segment = encode(value[j]) 427 | 428 | if (!matches[i].test(segment)) { 429 | throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') 430 | } 431 | 432 | path += (j === 0 ? token.prefix : token.delimiter) + segment 433 | } 434 | 435 | continue 436 | } 437 | 438 | segment = token.asterisk ? encodeAsterisk(value) : encode(value) 439 | 440 | if (!matches[i].test(segment)) { 441 | throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') 442 | } 443 | 444 | path += token.prefix + segment 445 | } 446 | 447 | return path 448 | } 449 | } 450 | 451 | /** 452 | * Escape a regular expression string. 453 | * 454 | * @param {string} str 455 | * @return {string} 456 | */ 457 | function escapeString(str) { 458 | return str.replace(/([.+*?=^!:()[\]|\/\\])/g, '\\$1') 459 | } 460 | 461 | /** 462 | * Escape the capturing group by escaping special characters and meaning. 463 | * 464 | * @param {string} group 465 | * @return {string} 466 | */ 467 | function escapeGroup(group) { 468 | return group.replace(/([=!:$\/()])/g, '\\$1') 469 | } 470 | 471 | /** 472 | * Format given url, append query to url query string 473 | * 474 | * @param {string} url 475 | * @param {string} query 476 | * @return {string} 477 | */ 478 | function formatUrl (url, query) { 479 | let protocol 480 | let index = url.indexOf('://') 481 | if (index !== -1) { 482 | protocol = url.substring(0, index) 483 | url = url.substring(index + 3) 484 | } else if (url.indexOf('//') === 0) { 485 | url = url.substring(2) 486 | } 487 | 488 | let parts = url.split('/') 489 | let result = (protocol ? protocol + '://' : '//') + parts.shift() 490 | 491 | let path = parts.filter(Boolean).join('/') 492 | let hash 493 | parts = path.split('#') 494 | if (parts.length === 2) { 495 | path = parts[0] 496 | hash = parts[1] 497 | } 498 | 499 | result += path ? '/' + path : '' 500 | 501 | if (query && JSON.stringify(query) !== '{}') { 502 | result += (url.split('?').length === 2 ? '&' : '?') + formatQuery(query) 503 | } 504 | result += hash ? '#' + hash : '' 505 | 506 | return result 507 | } 508 | 509 | /** 510 | * Transform data object to query string 511 | * 512 | * @param {object} query 513 | * @return {string} 514 | */ 515 | function formatQuery (query) { 516 | return Object.keys(query).sort().map(key => { 517 | var val = query[key] 518 | if (val == null) { 519 | return '' 520 | } 521 | if (Array.isArray(val)) { 522 | return val.slice().map(val2 => [key, '=', val2].join('')).join('&') 523 | } 524 | return key + '=' + val 525 | }).filter(Boolean).join('&') 526 | } 527 | -------------------------------------------------------------------------------- /functions/nuxt/views/app.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ HEAD }} 5 | 6 | 7 | {{ APP }} 8 | 9 | 10 | -------------------------------------------------------------------------------- /functions/nuxt/views/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server error 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |
Server error
16 |
An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details.
17 |
18 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "serve": "firebase serve --only functions", 6 | "shell": "firebase experimental:functions:shell", 7 | "start": "npm run shell", 8 | "deploy": "firebase deploy --only functions", 9 | "logs": "firebase functions:log" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.18.0", 13 | "express": "^4.16.3", 14 | "firebase-admin": "~5.10.0", 15 | "firebase-functions": "^0.9.0", 16 | "nuxt": "^1.4.0" 17 | }, 18 | "private": true 19 | } 20 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 4 6 | indent_style = tabs 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | extends: [ 11 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 12 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 13 | 'plugin:vue/essential' 14 | ], 15 | // required to lint *.vue files 16 | plugins: [ 17 | 'vue' 18 | ], 19 | // add your custom rules here 20 | rules: {} 21 | } 22 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | 7 | # Nuxt build 8 | .nuxt 9 | 10 | # Nuxt generate 11 | dist 12 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # vue-vienna 2 | 3 | > Nuxt.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm install # Or yarn install 10 | 11 | # serve with hot reload at localhost:3000 12 | $ npm run dev 13 | 14 | # build for production and launch server 15 | $ npm run build 16 | $ npm start 17 | 18 | # generate static project 19 | $ npm run generate 20 | ``` 21 | 22 | For detailed explanation on how things work, checkout the [Nuxt.js docs](https://github.com/nuxt/nuxt.js). 23 | -------------------------------------------------------------------------------- /src/assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/assets#webpacked 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /src/components/AppLogo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | -------------------------------------------------------------------------------- /src/components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | The components directory contains your Vue.js Components. 4 | Nuxt.js doesn't supercharge these components. 5 | 6 | **This directory is not required, you can delete it if you don't want to use it.** 7 | -------------------------------------------------------------------------------- /src/layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | This directory contains your Application Layouts. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/views#layouts 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 86 | -------------------------------------------------------------------------------- /src/middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | This directory contains your Application Middleware. 4 | The middleware lets you define custom function to be ran before rendering a page or a group of pages (layouts). 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing#middleware 8 | 9 | **This directory is not required, you can delete it if you don't want to use it.** 10 | -------------------------------------------------------------------------------- /src/nuxt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /* 3 | ** Headers of the page 4 | */ 5 | head: { 6 | title: 'vue-vienna', 7 | meta: [{ 8 | charset: 'utf-8' 9 | }, 10 | { 11 | name: 'viewport', 12 | content: 'width=device-width, initial-scale=1' 13 | }, 14 | { 15 | hid: 'description', 16 | name: 'description', 17 | content: 'Nuxt.js project' 18 | } 19 | ], 20 | link: [{ 21 | rel: 'icon', 22 | type: 'image/x-icon', 23 | href: '/favicon.ico' 24 | }] 25 | }, 26 | /* 27 | ** Customize the progress bar color 28 | */ 29 | loading: { 30 | color: '#3B8070' 31 | }, 32 | /* 33 | ** Build configuration 34 | */ 35 | buildDir: '../functions/nuxt', 36 | build: { 37 | publicPath: '/assets/', 38 | vendor: ['axios'], 39 | extractCSS: true, 40 | /* 41 | ** Run ESLint on save 42 | */ 43 | extend(config, { 44 | isDev, 45 | isClient 46 | }) { 47 | if (isDev && isClient) { 48 | config.module.rules.push({ 49 | enforce: 'pre', 50 | test: /\.(js|vue)$/, 51 | loader: 'eslint-loader', 52 | exclude: [ 53 | /(node_modules)/, 54 | /(functions)/ 55 | ] 56 | }) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-vienna", 3 | "version": "1.0.0", 4 | "description": "Nuxt.js project", 5 | "author": "david p ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate", 12 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", 13 | "precommit": "npm run lint" 14 | }, 15 | "dependencies": { 16 | "axios": "^0.18.0", 17 | "nuxt": "^1.0.0" 18 | }, 19 | "devDependencies": { 20 | "babel-eslint": "^8.2.1", 21 | "eslint": "^4.15.0", 22 | "eslint-friendly-formatter": "^3.0.0", 23 | "eslint-loader": "^1.7.1", 24 | "eslint-plugin-vue": "^4.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the .vue files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing 8 | -------------------------------------------------------------------------------- /src/pages/about.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /src/pages/blog.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | 37 | 48 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 34 | -------------------------------------------------------------------------------- /src/plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | This directory contains your Javascript plugins that you want to run before instantiating the root vue.js application. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/plugins 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /src/static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | This directory contains your static files. 4 | Each file inside this directory is mapped to /. 5 | 6 | Example: /static/robots.txt is mapped as /robots.txt. 7 | 8 | More information about the usage of this directory in the documentation: 9 | https://nuxtjs.org/guide/assets#static 10 | 11 | **This directory is not required, you can delete it if you don't want to use it.** 12 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pichsenmeister/nuxt-firebase/beaddb6c4a3a99c7cedc17f469d5cf399219559e/src/static/favicon.ico -------------------------------------------------------------------------------- /src/store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | This directory contains your Vuex Store files. 4 | Vuex Store option is implemented in the Nuxt.js framework. 5 | Creating a index.js file in this directory activate the option in the framework automatically. 6 | 7 | More information about the usage of this directory in the documentation: 8 | https://nuxtjs.org/guide/vuex-store 9 | 10 | **This directory is not required, you can delete it if you don't want to use it.** 11 | --------------------------------------------------------------------------------