mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 06:05:11 +00:00
feat(vue-app): support configurable features (#6287)
This commit is contained in:
parent
05a6efd1eb
commit
174c13d56c
3
examples/minimal-features/README.md
Normal file
3
examples/minimal-features/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# A minimal Hello World Nuxt.js app
|
||||
|
||||
https://nuxtjs.org/examples
|
28
examples/minimal-features/nuxt.config.js
Normal file
28
examples/minimal-features/nuxt.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
export default {
|
||||
loading: false,
|
||||
loadingIndicator: false,
|
||||
fetch: {
|
||||
client: false,
|
||||
server: false
|
||||
},
|
||||
features: {
|
||||
store: false,
|
||||
layouts: false,
|
||||
meta: false,
|
||||
middleware: false,
|
||||
transitions: false,
|
||||
deprecations: false,
|
||||
validate: false,
|
||||
asyncData: false,
|
||||
fetch: false,
|
||||
clientOnline: false,
|
||||
clientPrefetch: false,
|
||||
clientUseUrl: true,
|
||||
componentAliases: false,
|
||||
componentClientOnly: false
|
||||
},
|
||||
build: {
|
||||
indicator: false,
|
||||
terser: true
|
||||
}
|
||||
}
|
12
examples/minimal-features/package.json
Normal file
12
examples/minimal-features/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "example-minimal-features",
|
||||
"dependencies": {
|
||||
"nuxt": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nuxt",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start",
|
||||
"post-update": "yarn upgrade --latest"
|
||||
}
|
||||
}
|
21
examples/minimal-features/pages/about.vue
Executable file
21
examples/minimal-features/pages/about.vue
Executable file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>Hi from {{ name }}</p>
|
||||
<nuxt-link to="/">
|
||||
Home page
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
name: process.static ? 'static' : (process.server ? 'server' : 'client')
|
||||
}
|
||||
},
|
||||
head: {
|
||||
title: 'About page'
|
||||
}
|
||||
}
|
||||
</script>
|
16
examples/minimal-features/pages/index.vue
Executable file
16
examples/minimal-features/pages/index.vue
Executable file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Welcome!</h1>
|
||||
<nuxt-link to="/about">
|
||||
About Page
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
head: {
|
||||
title: 'Home page'
|
||||
}
|
||||
}
|
||||
</script>
|
@ -238,6 +238,8 @@ export default class Builder {
|
||||
this.resolveMiddleware(templateContext)
|
||||
])
|
||||
|
||||
this.addOptionalTemplates(templateContext)
|
||||
|
||||
await this.resolveCustomTemplates(templateContext)
|
||||
|
||||
await this.resolveLoadingIndicator(templateContext)
|
||||
@ -303,6 +305,16 @@ export default class Builder {
|
||||
)
|
||||
}
|
||||
|
||||
addOptionalTemplates (templateContext) {
|
||||
if (this.options.build.indicator) {
|
||||
templateContext.templateFiles.push('components/nuxt-build-indicator.vue')
|
||||
}
|
||||
|
||||
if (this.options.loading !== false) {
|
||||
templateContext.templateFiles.push('components/nuxt-loading.vue')
|
||||
}
|
||||
}
|
||||
|
||||
async resolveFiles (dir, cwd = this.options.srcDir) {
|
||||
return this.ignore.filter(await glob(this.globPathWithExtensions(dir), {
|
||||
cwd,
|
||||
@ -316,6 +328,10 @@ export default class Builder {
|
||||
}
|
||||
|
||||
async resolveLayouts ({ templateVars, templateFiles }) {
|
||||
if (!this.options.features.layouts) {
|
||||
return
|
||||
}
|
||||
|
||||
if (await fsExtra.exists(path.resolve(this.options.srcDir, this.options.dir.layouts))) {
|
||||
for (const file of await this.resolveFiles(this.options.dir.layouts)) {
|
||||
const name = file
|
||||
@ -409,7 +425,7 @@ export default class Builder {
|
||||
|
||||
async resolveStore ({ templateVars, templateFiles }) {
|
||||
// Add store if needed
|
||||
if (!this.options.store) {
|
||||
if (!this.options.features.store || !this.options.store) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -428,17 +444,20 @@ export default class Builder {
|
||||
templateFiles.push('store.js')
|
||||
}
|
||||
|
||||
async resolveMiddleware ({ templateVars }) {
|
||||
// -- Middleware --
|
||||
async resolveMiddleware ({ templateVars, templateFiles }) {
|
||||
if (!this.options.features.middleware) {
|
||||
return
|
||||
}
|
||||
|
||||
const middleware = await this.resolveRelative(this.options.dir.middleware)
|
||||
|
||||
const extRE = new RegExp(`\\.(${this.supportedExtensions.join('|')})$`)
|
||||
|
||||
templateVars.middleware = middleware.map(({ src }) => {
|
||||
const name = src.replace(extRE, '')
|
||||
const dst = this.relativeToBuild(this.options.srcDir, this.options.dir.middleware, src)
|
||||
return { name, src, dst }
|
||||
})
|
||||
|
||||
templateFiles.push('middleware.js')
|
||||
}
|
||||
|
||||
async resolveCustomTemplates (templateContext) {
|
||||
|
@ -11,6 +11,7 @@ export default class TemplateContext {
|
||||
this.templateFiles = Array.from(builder.template.files)
|
||||
this.templateVars = {
|
||||
nuxtOptions: options,
|
||||
features: options.features,
|
||||
extensions: options.extensions
|
||||
.map(ext => ext.replace(/^\./, ''))
|
||||
.join('|'),
|
||||
@ -27,7 +28,7 @@ export default class TemplateContext {
|
||||
router: options.router,
|
||||
env: options.env,
|
||||
head: options.head,
|
||||
store: options.store,
|
||||
store: options.features.store ? options.store : false,
|
||||
globalName: options.globalName,
|
||||
globals: builder.globals,
|
||||
css: options.css,
|
||||
|
@ -48,12 +48,14 @@ describe('builder: builder generate', () => {
|
||||
},
|
||||
watch: []
|
||||
}
|
||||
|
||||
const builder = new Builder(nuxt, BundleBuilder)
|
||||
builder.normalizePlugins = jest.fn(() => [{ name: 'test_plugin', src: '/var/somesrc' }])
|
||||
builder.resolveLayouts = jest.fn(() => 'resolveLayouts')
|
||||
builder.resolveRoutes = jest.fn(() => 'resolveRoutes')
|
||||
builder.resolveStore = jest.fn(() => 'resolveStore')
|
||||
builder.resolveMiddleware = jest.fn(() => 'resolveMiddleware')
|
||||
builder.addOptionalTemplates = jest.fn()
|
||||
builder.resolveCustomTemplates = jest.fn()
|
||||
builder.resolveLoadingIndicator = jest.fn()
|
||||
builder.compileTemplates = jest.fn()
|
||||
@ -77,6 +79,7 @@ describe('builder: builder generate', () => {
|
||||
'resolveStore',
|
||||
'resolveMiddleware'
|
||||
])
|
||||
expect(builder.addOptionalTemplates).toBeCalledTimes(1)
|
||||
expect(builder.resolveCustomTemplates).toBeCalledTimes(1)
|
||||
expect(builder.resolveLoadingIndicator).toBeCalledTimes(1)
|
||||
expect(builder.options.build.watch).toEqual(['/var/nuxt/src/template/**/*.{vue,js}'])
|
||||
@ -124,6 +127,7 @@ describe('builder: builder generate', () => {
|
||||
|
||||
test('should resolve store modules', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { store: true }
|
||||
nuxt.options.store = true
|
||||
nuxt.options.dir = {
|
||||
store: '/var/nuxt/src/store'
|
||||
@ -153,8 +157,26 @@ describe('builder: builder generate', () => {
|
||||
expect(templateFiles).toEqual(['store.js'])
|
||||
})
|
||||
|
||||
test('should disable store resolving', async () => {
|
||||
test('should disable store resolving when not set', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { store: false }
|
||||
nuxt.options.dir = {
|
||||
store: '/var/nuxt/src/store'
|
||||
}
|
||||
const builder = new Builder(nuxt, BundleBuilder)
|
||||
|
||||
const templateVars = {}
|
||||
const templateFiles = []
|
||||
await builder.resolveStore({ templateVars, templateFiles })
|
||||
|
||||
expect(templateVars.storeModules).toBeUndefined()
|
||||
expect(templateFiles).toEqual([])
|
||||
})
|
||||
|
||||
test('should disable store resolving when feature disabled', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { store: false }
|
||||
nuxt.options.store = true
|
||||
nuxt.options.dir = {
|
||||
store: '/var/nuxt/src/store'
|
||||
}
|
||||
@ -170,6 +192,7 @@ describe('builder: builder generate', () => {
|
||||
|
||||
test('should resolve middleware', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { middleware: true }
|
||||
nuxt.options.store = false
|
||||
nuxt.options.srcDir = '/var/nuxt/src'
|
||||
nuxt.options.dir = {
|
||||
@ -183,13 +206,31 @@ describe('builder: builder generate', () => {
|
||||
builder.relativeToBuild = jest.fn().mockReturnValue(middlewarePath)
|
||||
|
||||
const templateVars = {}
|
||||
await builder.resolveMiddleware({ templateVars })
|
||||
const templateFiles = []
|
||||
await builder.resolveMiddleware({ templateVars, templateFiles })
|
||||
|
||||
expect(templateVars.middleware).toEqual([{
|
||||
name: 'subfolder/midd',
|
||||
src: 'subfolder/midd.js',
|
||||
dst: 'subfolder/midd.js'
|
||||
}])
|
||||
expect(templateVars.middleware).toEqual([
|
||||
{
|
||||
name: 'subfolder/midd',
|
||||
src: 'subfolder/midd.js',
|
||||
dst: 'subfolder/midd.js'
|
||||
}
|
||||
])
|
||||
expect(templateFiles).toEqual(['middleware.js'])
|
||||
})
|
||||
|
||||
test('should disable middleware when feature disabled', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { middleware: false }
|
||||
nuxt.options.store = false
|
||||
nuxt.options.dir = {
|
||||
middleware: '/var/nuxt/src/middleware'
|
||||
}
|
||||
const builder = new Builder(nuxt, BundleBuilder)
|
||||
const templateVars = {}
|
||||
const templateFiles = []
|
||||
await builder.resolveMiddleware({ templateVars, templateFiles })
|
||||
expect(templateFiles).toEqual([])
|
||||
})
|
||||
|
||||
test('should custom templates', async () => {
|
||||
@ -414,6 +455,7 @@ describe('builder: builder generate', () => {
|
||||
describe('builder: builder resolveLayouts', () => {
|
||||
test('should resolve layouts', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { layouts: true }
|
||||
nuxt.options.srcDir = '/var/nuxt/src'
|
||||
nuxt.options.buildDir = '/var/nuxt/build'
|
||||
nuxt.options.dir = {
|
||||
@ -466,6 +508,7 @@ describe('builder: builder generate', () => {
|
||||
|
||||
test('should resolve error layouts', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { layouts: true }
|
||||
nuxt.options.srcDir = '/var/nuxt/src'
|
||||
nuxt.options.dir = {
|
||||
layouts: '/var/nuxt/src/layouts'
|
||||
@ -493,6 +536,7 @@ describe('builder: builder generate', () => {
|
||||
|
||||
test('should not resolve layouts if layouts dir does not exist', async () => {
|
||||
const nuxt = createNuxt()
|
||||
nuxt.options.features = { layouts: true }
|
||||
nuxt.options.srcDir = '/var/nuxt/src'
|
||||
nuxt.options.dir = {
|
||||
layouts: '/var/nuxt/src/layouts'
|
||||
|
@ -20,6 +20,9 @@ TemplateContext {
|
||||
],
|
||||
"env": "test_env",
|
||||
"extensions": "test|ext",
|
||||
"features": Object {
|
||||
"store": true,
|
||||
},
|
||||
"fetch": undefined,
|
||||
"globalName": "test_global",
|
||||
"globals": Array [
|
||||
@ -59,6 +62,9 @@ TemplateContext {
|
||||
"test",
|
||||
"ext",
|
||||
],
|
||||
"features": Object {
|
||||
"store": true,
|
||||
},
|
||||
"globalName": "test_global",
|
||||
"head": "test_head",
|
||||
"layoutTransition": Object {
|
||||
|
@ -16,6 +16,7 @@ describe('builder: buildContext', () => {
|
||||
relativeToBuild: jest.fn((...args) => `relativeBuild(${args.join(', ')})`)
|
||||
}
|
||||
const options = {
|
||||
features: { store: true },
|
||||
extensions: [ 'test', 'ext' ],
|
||||
messages: { test: 'test message' },
|
||||
build: {
|
||||
|
@ -56,5 +56,22 @@ export default () => ({
|
||||
layoutTransition: {
|
||||
name: 'layout',
|
||||
mode: 'out-in'
|
||||
},
|
||||
|
||||
features: {
|
||||
store: true,
|
||||
layouts: true,
|
||||
meta: true,
|
||||
middleware: true,
|
||||
transitions: true,
|
||||
deprecations: true,
|
||||
validate: true,
|
||||
asyncData: true,
|
||||
fetch: true,
|
||||
clientOnline: true,
|
||||
clientPrefetch: true,
|
||||
clientUseUrl: false,
|
||||
componentAliases: true,
|
||||
componentClientOnly: true
|
||||
}
|
||||
})
|
||||
|
@ -166,6 +166,22 @@ Object {
|
||||
"js",
|
||||
"mjs",
|
||||
],
|
||||
"features": Object {
|
||||
"asyncData": true,
|
||||
"clientOnline": true,
|
||||
"clientPrefetch": true,
|
||||
"clientUseUrl": false,
|
||||
"componentAliases": true,
|
||||
"componentClientOnly": true,
|
||||
"deprecations": true,
|
||||
"fetch": true,
|
||||
"layouts": true,
|
||||
"meta": true,
|
||||
"middleware": true,
|
||||
"store": true,
|
||||
"transitions": true,
|
||||
"validate": true,
|
||||
},
|
||||
"fetch": Object {
|
||||
"client": true,
|
||||
"server": true,
|
||||
|
@ -143,6 +143,22 @@ Object {
|
||||
"env": Object {},
|
||||
"extendPlugins": null,
|
||||
"extensions": Array [],
|
||||
"features": Object {
|
||||
"asyncData": true,
|
||||
"clientOnline": true,
|
||||
"clientPrefetch": true,
|
||||
"clientUseUrl": false,
|
||||
"componentAliases": true,
|
||||
"componentClientOnly": true,
|
||||
"deprecations": true,
|
||||
"fetch": true,
|
||||
"layouts": true,
|
||||
"meta": true,
|
||||
"middleware": true,
|
||||
"store": true,
|
||||
"transitions": true,
|
||||
"validate": true,
|
||||
},
|
||||
"fetch": Object {
|
||||
"client": true,
|
||||
"server": true,
|
||||
@ -474,6 +490,22 @@ Object {
|
||||
"env": Object {},
|
||||
"extendPlugins": null,
|
||||
"extensions": Array [],
|
||||
"features": Object {
|
||||
"asyncData": true,
|
||||
"clientOnline": true,
|
||||
"clientPrefetch": true,
|
||||
"clientUseUrl": false,
|
||||
"componentAliases": true,
|
||||
"componentClientOnly": true,
|
||||
"deprecations": true,
|
||||
"fetch": true,
|
||||
"layouts": true,
|
||||
"meta": true,
|
||||
"middleware": true,
|
||||
"store": true,
|
||||
"transitions": true,
|
||||
"validate": true,
|
||||
},
|
||||
"fetch": Object {
|
||||
"client": true,
|
||||
"server": true,
|
||||
|
@ -8,15 +8,12 @@ export const template = {
|
||||
'App.js',
|
||||
'client.js',
|
||||
'index.js',
|
||||
'middleware.js',
|
||||
'router.js',
|
||||
'router.scrollBehavior.js',
|
||||
'server.js',
|
||||
'utils.js',
|
||||
'empty.js',
|
||||
'components/nuxt-build-indicator.vue',
|
||||
'components/nuxt-error.vue',
|
||||
'components/nuxt-loading.vue',
|
||||
'components/nuxt-child.js',
|
||||
'components/nuxt-link.server.js',
|
||||
'components/nuxt-link.client.js',
|
||||
|
@ -1,11 +1,18 @@
|
||||
import Vue from 'vue'
|
||||
import { getMatchedComponentsInstances, promisify, globalHandleError } from './utils'
|
||||
<% if (features.asyncData || features.fetch) { %>
|
||||
import {
|
||||
getMatchedComponentsInstances,
|
||||
promisify,
|
||||
globalHandleError
|
||||
} from './utils'
|
||||
<% } %>
|
||||
<% if (loading) { %>import NuxtLoading from '<%= (typeof loading === "string" ? loading : "./components/nuxt-loading.vue") %>'<% } %>
|
||||
<%if (buildIndicator) { %>import NuxtBuildIndicator from './components/nuxt-build-indicator'<% } %>
|
||||
<% if (buildIndicator) { %>import NuxtBuildIndicator from './components/nuxt-build-indicator'<% } %>
|
||||
<% css.forEach((c) => { %>
|
||||
import '<%= relativeToBuild(resolvePath(c.src || c, { isStyle: true })) %>'
|
||||
<% }) %>
|
||||
|
||||
<% if (features.layouts) { %>
|
||||
<%= Object.keys(layouts).map((key) => {
|
||||
if (splitChunks.layouts) {
|
||||
return `const _${hash(key)} = () => import('${layouts[key]}' /* webpackChunkName: "${wChunk('layouts/' + key)}" */).then(m => m.default || m)`
|
||||
@ -17,13 +24,17 @@ import '<%= relativeToBuild(resolvePath(c.src || c, { isStyle: true })) %>'
|
||||
const layouts = { <%= Object.keys(layouts).map(key => `"_${key}": _${hash(key)}`).join(',') %> }<%= isTest ? '// eslint-disable-line' : '' %>
|
||||
|
||||
<% if (splitChunks.layouts) { %>let resolvedLayouts = {}<% } %>
|
||||
<% } %>
|
||||
|
||||
export default {
|
||||
<% if (features.meta) { %>
|
||||
<%= isTest ? '/* eslint-disable quotes, semi, indent, comma-spacing, key-spacing, object-curly-spacing, space-before-function-paren */' : '' %>
|
||||
head: <%= serializeFunction(head) %>,
|
||||
<%= isTest ? '/* eslint-enable quotes, semi, indent, comma-spacing, key-spacing, object-curly-spacing, space-before-function-paren */' : '' %>
|
||||
<% } %>
|
||||
render(h, props) {
|
||||
<% if (loading) { %>const loadingEl = h('NuxtLoading', { ref: 'loading' })<% } %>
|
||||
<% if (features.layouts) { %>
|
||||
const layoutEl = h(this.layout || 'nuxt')
|
||||
const templateEl = h('div', {
|
||||
domProps: {
|
||||
@ -31,7 +42,11 @@ export default {
|
||||
},
|
||||
key: this.layoutName
|
||||
}, [ layoutEl ])
|
||||
<% } else { %>
|
||||
const templateEl = h('nuxt')
|
||||
<% } %>
|
||||
|
||||
<% if (features.transitions) { %>
|
||||
const transitionEl = h('transition', {
|
||||
props: {
|
||||
name: '<%= layoutTransition.name %>',
|
||||
@ -46,18 +61,29 @@ export default {
|
||||
}
|
||||
}
|
||||
}, [ templateEl ])
|
||||
<% } %>
|
||||
|
||||
return h('div', {
|
||||
domProps: {
|
||||
id: '<%= globals.id %>'
|
||||
}
|
||||
}, [<% if (loading) { %>loadingEl, <% } %><%if (buildIndicator) { %>h(NuxtBuildIndicator), <% } %>transitionEl])
|
||||
}, [
|
||||
<% if (loading) { %>loadingEl, <% } %>
|
||||
<% if (buildIndicator) { %>h(NuxtBuildIndicator), <% } %>
|
||||
<% if (features.transitions) { %>transitionEl<% } else { %>templateEl<% } %>
|
||||
])
|
||||
},
|
||||
<% if (features.clientOnline || features.layouts) { %>
|
||||
data: () => ({
|
||||
<% if (features.clientOnline) { %>
|
||||
isOnline: true,
|
||||
<% } %>
|
||||
<% if (features.layouts) { %>
|
||||
layout: null,
|
||||
layoutName: ''
|
||||
<% } %>
|
||||
}),
|
||||
<% } %>
|
||||
beforeCreate() {
|
||||
Vue.util.defineReactive(this, 'nuxt', this.$options.nuxt)
|
||||
},
|
||||
@ -67,10 +93,12 @@ export default {
|
||||
// add to window so we can listen when ready
|
||||
if (process.client) {
|
||||
window.<%= globals.nuxt %> = <%= (globals.nuxt !== '$nuxt' ? 'window.$nuxt = ' : '') %>this
|
||||
<% if (features.clientOnline) { %>
|
||||
this.refreshOnlineStatus()
|
||||
// Setup the listeners
|
||||
window.addEventListener('online', this.refreshOnlineStatus)
|
||||
window.addEventListener('offline', this.refreshOnlineStatus)
|
||||
<% } %>
|
||||
}
|
||||
// Add $nuxt.error()
|
||||
this.error = this.nuxt.error
|
||||
@ -85,12 +113,15 @@ export default {
|
||||
'nuxt.err': 'errorChanged'
|
||||
},
|
||||
<% } %>
|
||||
<% if (features.clientOnline) { %>
|
||||
computed: {
|
||||
isOffline() {
|
||||
return !this.isOnline
|
||||
}
|
||||
},
|
||||
<% } %>
|
||||
methods: {
|
||||
<% if (features.clientOnline) { %>
|
||||
refreshOnlineStatus() {
|
||||
if (process.client) {
|
||||
if (typeof window.navigator.onLine === 'undefined') {
|
||||
@ -103,19 +134,25 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
<% } %>
|
||||
async refresh() {
|
||||
<% if (features.asyncData || features.fetch) { %>
|
||||
const pages = getMatchedComponentsInstances(this.$route)
|
||||
|
||||
if (!pages.length) {
|
||||
return
|
||||
}
|
||||
<% if (loading) { %>this.$loading.start()<% } %>
|
||||
|
||||
const promises = pages.map(async (page) => {
|
||||
const p = []
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
if (page.$options.fetch) {
|
||||
p.push(promisify(page.$options.fetch, this.context))
|
||||
}
|
||||
<% } %>
|
||||
<% if (features.asyncData) { %>
|
||||
if (page.$options.asyncData) {
|
||||
p.push(
|
||||
promisify(page.$options.asyncData, this.context)
|
||||
@ -126,6 +163,7 @@ export default {
|
||||
})
|
||||
)
|
||||
}
|
||||
<% } %>
|
||||
return Promise.all(p)
|
||||
})
|
||||
try {
|
||||
@ -136,6 +174,7 @@ export default {
|
||||
this.error(error)
|
||||
}
|
||||
<% if (loading) { %>this.$loading.finish()<% } %>
|
||||
<% } %>
|
||||
},
|
||||
<% if (loading) { %>
|
||||
errorChanged() {
|
||||
@ -145,6 +184,7 @@ export default {
|
||||
}
|
||||
},
|
||||
<% } %>
|
||||
<% if (features.layouts) { %>
|
||||
<% if (splitChunks.layouts) { %>
|
||||
setLayout(layout) {
|
||||
<% if (debug) { %>
|
||||
@ -193,9 +233,12 @@ export default {
|
||||
}
|
||||
return Promise.resolve(layouts['_' + layout])
|
||||
}
|
||||
<% } %>
|
||||
<% } /* splitChunks.layouts */ %>
|
||||
<% } /* features.layouts */ %>
|
||||
},
|
||||
<% if (loading) { %>
|
||||
components: {
|
||||
<%= (loading ? 'NuxtLoading' : '') %>
|
||||
NuxtLoading
|
||||
}
|
||||
<% } %>
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import Vue from 'vue'
|
||||
<% if (fetch.client) { %>import fetch from 'unfetch'<% } %>
|
||||
import middleware from './middleware.js'
|
||||
<% if (features.middleware) { %>import middleware from './middleware.js'<% } %>
|
||||
import {
|
||||
applyAsyncData,
|
||||
<% if (features.asyncData) { %>applyAsyncData,<% } %>
|
||||
<% if (features.middleware) { %>middlewareSeries,<% } %>
|
||||
sanitizeComponent,
|
||||
resolveRouteComponents,
|
||||
getMatchedComponents,
|
||||
getMatchedComponentsInstances,
|
||||
flatMapComponents,
|
||||
setContext,
|
||||
middlewareSeries,
|
||||
promisify,
|
||||
getLocation,
|
||||
compile,
|
||||
@ -17,7 +17,7 @@ import {
|
||||
globalHandleError
|
||||
} from './utils.js'
|
||||
import { createApp, NuxtError } from './index.js'
|
||||
import NuxtLink from './components/nuxt-link.<%= router.prefetchLinks ? "client" : "server" %>.js' // should be included after ./index.js
|
||||
import NuxtLink from './components/nuxt-link.<%= features.clientPrefetch && router.prefetchLinks ? "client" : "server" %>.js' // should be included after ./index.js
|
||||
<% if (isDev) { %>import consola from 'consola'<% } %>
|
||||
|
||||
<% if (isDev) { %>consola.wrapConsole()
|
||||
@ -26,7 +26,7 @@ console.log = console.__log
|
||||
|
||||
// Component: <NuxtLink>
|
||||
Vue.component(NuxtLink.name, NuxtLink)
|
||||
Vue.component('NLink', NuxtLink)
|
||||
<% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
|
||||
|
||||
<% if (fetch.client) { %>if (!global.fetch) { global.fetch = fetch }<% } %>
|
||||
|
||||
@ -94,6 +94,7 @@ const errorHandler = Vue.config.errorHandler || console.error
|
||||
// Create and mount App
|
||||
createApp().then(mountApp).catch(errorHandler)
|
||||
|
||||
<% if (features.transitions) { %>
|
||||
function componentOption(component, key, ...args) {
|
||||
if (!component || !component.options || !component.options[key]) {
|
||||
return {}
|
||||
@ -126,7 +127,7 @@ function mapTransitions(Components, to, from) {
|
||||
return transitions
|
||||
})
|
||||
}
|
||||
|
||||
<% } %>
|
||||
async function loadAsyncComponents(to, from, next) {
|
||||
// Check if route path changed (this._pathChanged), only if the page is not an error (for validate())
|
||||
this._pathChanged = Boolean(app.nuxt.err) || from.path !== to.path
|
||||
@ -184,9 +185,11 @@ async function loadAsyncComponents(to, from, next) {
|
||||
}
|
||||
|
||||
function applySSRData(Component, ssrData) {
|
||||
<% if (features.asyncData) { %>
|
||||
if (NUXT.serverRendered && ssrData) {
|
||||
applyAsyncData(Component, ssrData)
|
||||
}
|
||||
<% } %>
|
||||
Component._Ctor = Component
|
||||
return Component
|
||||
}
|
||||
@ -206,11 +209,12 @@ function resolveComponents(router) {
|
||||
return _Component
|
||||
})
|
||||
}
|
||||
|
||||
<% if (features.middleware) { %>
|
||||
function callMiddleware(Components, context, layout) {
|
||||
let midd = <%= devalue(router.middleware) %><%= isTest ? '// eslint-disable-line' : '' %>
|
||||
let unknownMiddleware = false
|
||||
|
||||
<% if (features.layouts) { %>
|
||||
// If layout is undefined, only call global middleware
|
||||
if (typeof layout !== 'undefined') {
|
||||
midd = [] // Exclude global middleware if layout defined (already called before)
|
||||
@ -224,6 +228,7 @@ function callMiddleware(Components, context, layout) {
|
||||
}
|
||||
})
|
||||
}
|
||||
<% } %>
|
||||
|
||||
midd = midd.map((name) => {
|
||||
if (typeof name === 'function') return name
|
||||
@ -237,7 +242,14 @@ function callMiddleware(Components, context, layout) {
|
||||
if (unknownMiddleware) return
|
||||
return middlewareSeries(midd, context)
|
||||
}
|
||||
|
||||
<% } else if (isDev) {
|
||||
// This is a placeholder function mainly so we dont have to
|
||||
// refactor the promise chain in addHotReload()
|
||||
%>
|
||||
function callMiddleware() {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
<% } %>
|
||||
async function render(to, from, next) {
|
||||
if (this._pathChanged === false && this._queryChanged === false) return next()
|
||||
// Handle first render on SPA mode
|
||||
@ -282,51 +294,71 @@ async function render(to, from, next) {
|
||||
|
||||
// If no Components matched, generate 404
|
||||
if (!Components.length) {
|
||||
<% if (features.middleware) { %>
|
||||
// Default layout
|
||||
await callMiddleware.call(this, Components, app.context)
|
||||
if (nextCalled) return
|
||||
<% } %>
|
||||
|
||||
<% if (features.layouts) { %>
|
||||
// Load layout for error page
|
||||
const layout = await this.loadLayout(
|
||||
typeof NuxtError.layout === 'function'
|
||||
? NuxtError.layout(app.context)
|
||||
: NuxtError.layout
|
||||
)
|
||||
<% } %>
|
||||
|
||||
<% if (features.middleware) { %>
|
||||
await callMiddleware.call(this, Components, app.context, layout)
|
||||
if (nextCalled) return
|
||||
<% } %>
|
||||
|
||||
// Show error page
|
||||
app.context.error({ statusCode: 404, message: `<%= messages.error_404 %>` })
|
||||
return next()
|
||||
}
|
||||
|
||||
<% if (features.asyncData || features.fetch) { %>
|
||||
// Update ._data and other properties if hot reloaded
|
||||
Components.forEach((Component) => {
|
||||
if (Component._Ctor && Component._Ctor.options) {
|
||||
Component.options.asyncData = Component._Ctor.options.asyncData
|
||||
Component.options.fetch = Component._Ctor.options.fetch
|
||||
<% if (features.asyncData) { %>Component.options.asyncData = Component._Ctor.options.asyncData<% } %>
|
||||
<% if (features.fetch) { %>Component.options.fetch = Component._Ctor.options.fetch<% } %>
|
||||
}
|
||||
})
|
||||
<% } %>
|
||||
|
||||
<% if (features.transitions) { %>
|
||||
// Apply transitions
|
||||
this.setTransitions(mapTransitions(Components, to, from))
|
||||
|
||||
<% } %>
|
||||
try {
|
||||
<% if (features.middleware) { %>
|
||||
// Call middleware
|
||||
await callMiddleware.call(this, Components, app.context)
|
||||
if (nextCalled) return
|
||||
if (app.context._errored) return next()
|
||||
<% } %>
|
||||
|
||||
<% if (features.layouts) { %>
|
||||
// Set layout
|
||||
let layout = Components[0].options.layout
|
||||
if (typeof layout === 'function') {
|
||||
layout = layout(app.context)
|
||||
}
|
||||
layout = await this.loadLayout(layout)
|
||||
<% } %>
|
||||
|
||||
<% if (features.middleware) { %>
|
||||
// Call middleware for layout
|
||||
await callMiddleware.call(this, Components, app.context, layout)
|
||||
if (nextCalled) return
|
||||
if (app.context._errored) return next()
|
||||
<% } %>
|
||||
|
||||
|
||||
<% if (features.validate) { %>
|
||||
// Call .validate()
|
||||
let isValid = true
|
||||
try {
|
||||
@ -355,7 +387,9 @@ async function render(to, from, next) {
|
||||
this.error({ statusCode: 404, message: `<%= messages.error_404 %>` })
|
||||
return next()
|
||||
}
|
||||
<% } %>
|
||||
|
||||
<% if (features.asyncData || features.fetch) { %>
|
||||
let instances
|
||||
// Call asyncData & fetch hooks on components matched by the route.
|
||||
await Promise.all(Components.map((Component, i) => {
|
||||
@ -380,20 +414,31 @@ async function render(to, from, next) {
|
||||
}
|
||||
}
|
||||
if (!this._hadError && this._isMounted && !Component._dataRefresh) {
|
||||
return Promise.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const promises = []
|
||||
|
||||
<% if (features.asyncData) { %>
|
||||
const hasAsyncData = (
|
||||
Component.options.asyncData &&
|
||||
typeof Component.options.asyncData === 'function'
|
||||
)
|
||||
<% } else { %>
|
||||
const hasAsyncData = false
|
||||
<% } %>
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
const hasFetch = Boolean(Component.options.fetch)
|
||||
<% } else { %>
|
||||
const hasFetch = false
|
||||
<% } %>
|
||||
|
||||
<% if (loading) { %>
|
||||
const loadingIncrease = (hasAsyncData && hasFetch) ? 30 : 45
|
||||
<% } %>
|
||||
|
||||
<% if (features.asyncData) { %>
|
||||
// Call asyncData(context)
|
||||
if (hasAsyncData) {
|
||||
const promise = promisify(Component.options.asyncData, app.context)
|
||||
@ -407,10 +452,12 @@ async function render(to, from, next) {
|
||||
})
|
||||
promises.push(promise)
|
||||
}
|
||||
<% } %>
|
||||
|
||||
// Check disabled page loading
|
||||
this.$loading.manual = Component.options.loading === false
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
// Call fetch(context)
|
||||
if (hasFetch) {
|
||||
let p = Component.options.fetch(app.context)
|
||||
@ -426,9 +473,11 @@ async function render(to, from, next) {
|
||||
})
|
||||
promises.push(p)
|
||||
}
|
||||
<% } %>
|
||||
|
||||
return Promise.all(promises)
|
||||
}))
|
||||
<% } %>
|
||||
|
||||
// If not redirected
|
||||
if (!nextCalled) {
|
||||
@ -449,12 +498,14 @@ async function render(to, from, next) {
|
||||
|
||||
globalHandleError(error)
|
||||
|
||||
<% if (features.layouts) { %>
|
||||
// Load error layout
|
||||
let layout = NuxtError.layout
|
||||
if (typeof layout === 'function') {
|
||||
layout = layout(app.context)
|
||||
}
|
||||
await this.loadLayout(layout)
|
||||
<% } %>
|
||||
|
||||
this.error(error)
|
||||
this.<%= globals.nuxt %>.$emit('routeChanged', to, from, error)
|
||||
@ -481,6 +532,7 @@ function showNextPage(to) {
|
||||
this.error()
|
||||
}
|
||||
|
||||
<% if (features.layouts) { %>
|
||||
// Set layout
|
||||
let layout = this.$options.nuxt.err
|
||||
? NuxtError.layout
|
||||
@ -490,6 +542,7 @@ function showNextPage(to) {
|
||||
layout = layout(app.context)
|
||||
}
|
||||
this.setLayout(layout)
|
||||
<% } %>
|
||||
}
|
||||
|
||||
// When navigating on a different route but the same component is used, Vue.js
|
||||
@ -581,7 +634,9 @@ function addHotReload($component, depth) {
|
||||
$component.$vnode.context.$forceUpdate = async () => {
|
||||
let Components = getMatchedComponents(router.currentRoute)
|
||||
let Component = Components[depth]
|
||||
if (!Component) return _forceUpdate()
|
||||
if (!Component) {
|
||||
return _forceUpdate()
|
||||
}
|
||||
if (typeof Component === 'object' && !Component.options) {
|
||||
// Updated via vue-router resolveAsyncComponents()
|
||||
Component = Vue.extend(Component)
|
||||
@ -599,29 +654,45 @@ function addHotReload($component, depth) {
|
||||
next: next.bind(this)
|
||||
})
|
||||
const context = app.context
|
||||
|
||||
<% if (loading) { %>
|
||||
if (this.$loading.start && !this.$loading.manual) this.$loading.start()
|
||||
if (this.$loading.start && !this.$loading.manual) {
|
||||
this.$loading.start()
|
||||
}
|
||||
<% } %>
|
||||
|
||||
callMiddleware.call(this, Components, context)
|
||||
.then(() => {
|
||||
<% if (features.layouts) { %>
|
||||
// If layout changed
|
||||
if (depth !== 0) return Promise.resolve()
|
||||
if (depth !== 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let layout = Component.options.layout || 'default'
|
||||
if (typeof layout === 'function') {
|
||||
layout = layout(context)
|
||||
}
|
||||
if (this.layoutName === layout) return Promise.resolve()
|
||||
if (this.layoutName === layout) {
|
||||
return
|
||||
}
|
||||
let promise = this.loadLayout(layout)
|
||||
promise.then(() => {
|
||||
this.setLayout(layout)
|
||||
Vue.nextTick(() => hotReloadAPI(this))
|
||||
})
|
||||
return promise
|
||||
<% } else { %>
|
||||
return
|
||||
<% } %>
|
||||
})
|
||||
<% if (features.layouts) { %>
|
||||
.then(() => {
|
||||
return callMiddleware.call(this, Components, context, this.layout)
|
||||
})
|
||||
<% } %>
|
||||
.then(() => {
|
||||
<% if (features.asyncData) { %>
|
||||
// Call asyncData(context)
|
||||
let pAsyncData = promisify(Component.options.asyncData || noopData, context)
|
||||
pAsyncData.then((asyncDataResult) => {
|
||||
@ -629,12 +700,16 @@ function addHotReload($component, depth) {
|
||||
<%= (loading ? 'this.$loading.increase && this.$loading.increase(30)' : '') %>
|
||||
})
|
||||
promises.push(pAsyncData)
|
||||
<% } %>
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
// Call fetch()
|
||||
Component.options.fetch = Component.options.fetch || noopFetch
|
||||
let pFetch = Component.options.fetch(context)
|
||||
if (!pFetch || (!(pFetch instanceof Promise) && (typeof pFetch.then !== 'function'))) { pFetch = Promise.resolve(pFetch) }
|
||||
<%= (loading ? 'pFetch.then(() => this.$loading.increase && this.$loading.increase(30))' : '') %>
|
||||
promises.push(pFetch)
|
||||
<% } %>
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(() => {
|
||||
@ -658,7 +733,7 @@ async function mountApp(__app) {
|
||||
// Create Vue instance
|
||||
const _app = new Vue(app)
|
||||
|
||||
<% if (mode !== 'spa') { %>
|
||||
<% if (features.layouts && mode !== 'spa') { %>
|
||||
// Load layout
|
||||
const layout = NUXT.layout || 'default'
|
||||
await _app.loadLayout(layout)
|
||||
@ -683,14 +758,14 @@ async function mountApp(__app) {
|
||||
<% } %>
|
||||
})
|
||||
}
|
||||
|
||||
<% if (features.transitions) { %>
|
||||
// Enable transitions
|
||||
_app.setTransitions = _app.$options.nuxt.setTransitions.bind(_app)
|
||||
if (Components.length) {
|
||||
_app.setTransitions(mapTransitions(Components, router.currentRoute))
|
||||
_lastPaths = router.currentRoute.matched.map(route => compile(route.path)(router.currentRoute.params))
|
||||
}
|
||||
|
||||
<% } %>
|
||||
// Initialize error handler
|
||||
_app.$loading = {} // To avoid error while _app.$nuxt does not exist
|
||||
if (NUXT.error) _app.error(NUXT.error)
|
||||
|
@ -14,6 +14,7 @@ export default {
|
||||
}
|
||||
},
|
||||
render(h, { parent, data, props }) {
|
||||
<% if (features.transitions) { %>
|
||||
data.nuxtChild = true
|
||||
const _parent = parent
|
||||
const transitions = parent.<%= globals.nuxt %>.nuxt.transitions
|
||||
@ -69,20 +70,25 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<% } %>
|
||||
let routerView = h('routerView', data)
|
||||
|
||||
if (props.keepAlive) {
|
||||
routerView = h('keep-alive', { props: props.keepAliveProps }, [routerView])
|
||||
}
|
||||
|
||||
<% if (features.transitions) { %>
|
||||
return h('transition', {
|
||||
props: transitionProps,
|
||||
on: listeners
|
||||
}, [routerView])
|
||||
<% } else { %>
|
||||
return routerView
|
||||
<% } %>
|
||||
}
|
||||
}
|
||||
|
||||
<% if (features.transitions) { %>
|
||||
const transitionsKeys = [
|
||||
'name',
|
||||
'mode',
|
||||
@ -116,3 +122,4 @@ const listenersKeys = [
|
||||
'afterAppear',
|
||||
'appearCancelled'
|
||||
]
|
||||
<% } %>
|
||||
|
@ -9,7 +9,7 @@ import NuxtError from '<%= "../" + components.ErrorPage %>'
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
import NuxtError from './nuxt-error.vue'
|
||||
<% } %>
|
||||
<% } /* components */ %>
|
||||
import NuxtChild from './nuxt-child'
|
||||
|
||||
<%= isTest ? '// @vue/component' : '' %>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import Meta from 'vue-meta'
|
||||
import ClientOnly from 'vue-client-only'
|
||||
import NoSsr from 'vue-no-ssr'
|
||||
<% if (features.meta) { %>import Meta from 'vue-meta'<% } %>
|
||||
<% if (features.componentClientOnly) { %>import ClientOnly from 'vue-client-only'<% } %>
|
||||
<% if (features.deprecations) { %>import NoSsr from 'vue-no-ssr'<% } %>
|
||||
import { createRouter } from './router.js'
|
||||
import NuxtChild from './components/nuxt-child.js'
|
||||
import NuxtError from '<%= components.ErrorPage ? components.ErrorPage : "./components/nuxt-error.vue" %>'
|
||||
@ -16,8 +16,11 @@ import { setContext, getLocation, getRouteData, normalizeError } from './utils'
|
||||
<% }) %>
|
||||
<%= isTest ? '/* eslint-enable camelcase */' : '' %>
|
||||
|
||||
<% if (features.componentClientOnly) { %>
|
||||
// Component: <ClientOnly>
|
||||
Vue.component(ClientOnly.name, ClientOnly)
|
||||
<% } %>
|
||||
<% if (features.deprecations) { %>
|
||||
// TODO: Remove in Nuxt 3: <NoSsr>
|
||||
Vue.component(NoSsr.name, {
|
||||
...NoSsr,
|
||||
@ -29,16 +32,17 @@ Vue.component(NoSsr.name, {
|
||||
return NoSsr.render(h, ctx)
|
||||
}
|
||||
})
|
||||
|
||||
<% } %>
|
||||
// Component: <NuxtChild>
|
||||
Vue.component(NuxtChild.name, NuxtChild)
|
||||
Vue.component('NChild', NuxtChild)
|
||||
<% if (features.componentAliases) { %>Vue.component('NChild', NuxtChild)<% } %>
|
||||
|
||||
// Component NuxtLink is imported in server.js or client.js
|
||||
|
||||
// Component: <Nuxt>`
|
||||
Vue.component(Nuxt.name, Nuxt)
|
||||
|
||||
<% if (features.meta) { %>
|
||||
// vue-meta configuration
|
||||
Vue.use(Meta, {
|
||||
keyName: 'head', // the component option name that vue-meta looks for meta info on.
|
||||
@ -46,7 +50,9 @@ Vue.use(Meta, {
|
||||
ssrAttribute: 'data-n-head-ssr', // the attribute name that lets vue-meta know that meta info has already been server-rendered
|
||||
tagIDKeyName: 'hid' // the property name that vue-meta uses to determine whether to overwrite or append a tag
|
||||
})
|
||||
<% } %>
|
||||
|
||||
<% if (features.transitions) { %>
|
||||
const defaultTransition = <%=
|
||||
serialize(pageTransition)
|
||||
.replace('beforeEnter(', 'function(').replace('enter(', 'function(').replace('afterEnter(', 'function(')
|
||||
@ -54,6 +60,7 @@ const defaultTransition = <%=
|
||||
.replace('afterLeave(', 'function(').replace('leaveCancelled(', 'function(').replace('beforeAppear(', 'function(')
|
||||
.replace('appear(', 'function(').replace('afterAppear(', 'function(').replace('appearCancelled(', 'function(')
|
||||
%><%= isTest ? '// eslint-disable-line' : '' %>
|
||||
<% } %>
|
||||
|
||||
async function createApp(ssrContext) {
|
||||
const router = await createRouter(ssrContext)
|
||||
@ -74,9 +81,10 @@ async function createApp(ssrContext) {
|
||||
// here we inject the router and store to all child components,
|
||||
// making them available everywhere as `this.$router` and `this.$store`.
|
||||
const app = {
|
||||
router,
|
||||
<% if (store) { %>store,<% } %>
|
||||
router,
|
||||
nuxt: {
|
||||
<% if (features.transitions) { %>
|
||||
defaultTransition,
|
||||
transitions: [ defaultTransition ],
|
||||
setTransitions(transitions) {
|
||||
@ -96,6 +104,7 @@ async function createApp(ssrContext) {
|
||||
this.$options.nuxt.transitions = transitions
|
||||
return transitions
|
||||
},
|
||||
<% } %>
|
||||
err: null,
|
||||
dateErr: null,
|
||||
error(err) {
|
||||
@ -128,10 +137,10 @@ async function createApp(ssrContext) {
|
||||
|
||||
// Set context to app.context
|
||||
await setContext(app, {
|
||||
<% if (store) { %>store,<% } %>
|
||||
route,
|
||||
next,
|
||||
error: app.nuxt.error.bind(app),
|
||||
<% if (store) { %>store,<% } %>
|
||||
payload: ssrContext ? ssrContext.payload : undefined,
|
||||
req: ssrContext ? ssrContext.req : undefined,
|
||||
res: ssrContext ? ssrContext.res : undefined,
|
||||
@ -213,8 +222,8 @@ async function createApp(ssrContext) {
|
||||
}
|
||||
|
||||
return {
|
||||
app,
|
||||
<% if(store) { %>store,<% } %>
|
||||
app,
|
||||
router
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,29 @@
|
||||
import { stringify } from 'querystring'
|
||||
import Vue from 'vue'
|
||||
<% if (fetch.server) { %>import fetch from 'node-fetch'<% } %>
|
||||
import middleware from './middleware.js'
|
||||
import { applyAsyncData, getMatchedComponents, middlewareSeries, promisify, urlJoin, sanitizeComponent } from './utils.js'
|
||||
<% if (features.middleware) { %>import middleware from './middleware.js'<% } %>
|
||||
import {
|
||||
<% if (features.asyncData) { %>applyAsyncData,<% } %>
|
||||
<% if (features.middleware) { %>middlewareSeries,<% } %>
|
||||
getMatchedComponents,
|
||||
promisify,
|
||||
sanitizeComponent
|
||||
} from './utils.js'
|
||||
import { createApp, NuxtError } from './index.js'
|
||||
import NuxtLink from './components/nuxt-link.server.js' // should be included after ./index.js
|
||||
|
||||
// Component: <NuxtLink>
|
||||
Vue.component(NuxtLink.name, NuxtLink)
|
||||
Vue.component('NLink', NuxtLink)
|
||||
<% if (features.componentAliases) { %>Vue.component('NLink', NuxtLink)<% } %>
|
||||
|
||||
<% if (fetch.server) { %>if (!global.fetch) { global.fetch = fetch }<% } %>
|
||||
|
||||
const noopApp = () => new Vue({ render: h => h('div') })
|
||||
|
||||
function urlJoin() {
|
||||
return Array.prototype.slice.call(arguments).join('/').replace(/\/+/g, '/')
|
||||
}
|
||||
|
||||
const createNext = ssrContext => (opts) => {
|
||||
ssrContext.redirected = opts
|
||||
// If nuxt generate
|
||||
@ -50,32 +60,39 @@ export default async (ssrContext) => {
|
||||
// Used for beforeNuxtRender({ Components, nuxtState })
|
||||
ssrContext.beforeRenderFns = []
|
||||
// Nuxt object (window{{globals.context}}, defaults to window.__NUXT__)
|
||||
ssrContext.nuxt = { layout: 'default', data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
|
||||
ssrContext.nuxt = { <% if (features.layouts) { %>layout: 'default', <% } %>data: [], error: null<%= (store ? ', state: null' : '') %>, serverRendered: true }
|
||||
// Create the app definition and the instance (created for each request)
|
||||
const { app, router<%= (store ? ', store' : '') %> } = await createApp(ssrContext)
|
||||
const _app = new Vue(app)
|
||||
|
||||
<% if (features.meta) { %>
|
||||
// Add meta infos (used in renderer.js)
|
||||
ssrContext.meta = _app.$meta()
|
||||
<% } %>
|
||||
<% if (features.asyncData) { %>
|
||||
// Keep asyncData for each matched component in ssrContext (used in app/utils.js via this.$ssrContext)
|
||||
ssrContext.asyncData = {}
|
||||
<% } %>
|
||||
|
||||
const beforeRender = async () => {
|
||||
// Call beforeNuxtRender() methods
|
||||
await Promise.all(ssrContext.beforeRenderFns.map(fn => promisify(fn, { Components, nuxtState: ssrContext.nuxt })))
|
||||
ssrContext.rendered = () => {
|
||||
<% if (store) { %>
|
||||
ssrContext.rendered = () => {
|
||||
// Add the state from the vuex store
|
||||
ssrContext.nuxt.state = store.state
|
||||
<% } %>
|
||||
}
|
||||
<% } %>
|
||||
}
|
||||
|
||||
const renderErrorPage = async () => {
|
||||
<% if (features.layouts) { %>
|
||||
// Load layout for error page
|
||||
const errLayout = (typeof NuxtError.layout === 'function' ? NuxtError.layout(app.context) : NuxtError.layout)
|
||||
ssrContext.nuxt.layout = errLayout || 'default'
|
||||
await _app.loadLayout(errLayout)
|
||||
_app.setLayout(errLayout)
|
||||
<% } %>
|
||||
await beforeRender()
|
||||
return _app
|
||||
}
|
||||
@ -106,6 +123,7 @@ export default async (ssrContext) => {
|
||||
if (ssrContext.nuxt.error) return renderErrorPage()
|
||||
<% } %>
|
||||
|
||||
<% if (features.middleware) { %>
|
||||
/*
|
||||
** Call global middleware (nuxt.config.js)
|
||||
*/
|
||||
@ -121,7 +139,9 @@ export default async (ssrContext) => {
|
||||
// ...If there is a redirect or an error, stop the process
|
||||
if (ssrContext.redirected) return noopApp()
|
||||
if (ssrContext.nuxt.error) return renderErrorPage()
|
||||
<% } %>
|
||||
|
||||
<% if (features.layouts) { %>
|
||||
/*
|
||||
** Set layout
|
||||
*/
|
||||
@ -131,13 +151,19 @@ export default async (ssrContext) => {
|
||||
if (ssrContext.nuxt.error) return renderErrorPage()
|
||||
layout = _app.setLayout(layout)
|
||||
ssrContext.nuxt.layout = _app.layoutName
|
||||
<% } %>
|
||||
|
||||
<% if (features.middleware) { %>
|
||||
/*
|
||||
** Call middleware (layout + pages)
|
||||
*/
|
||||
midd = []
|
||||
<% if (features.layouts) { %>
|
||||
layout = sanitizeComponent(layout)
|
||||
if (layout.options.middleware) midd = midd.concat(layout.options.middleware)
|
||||
if (layout.options.middleware) {
|
||||
midd = midd.concat(layout.options.middleware)
|
||||
}
|
||||
<% } %>
|
||||
Components.forEach((Component) => {
|
||||
if (Component.options.middleware) {
|
||||
midd = midd.concat(Component.options.middleware)
|
||||
@ -154,7 +180,9 @@ export default async (ssrContext) => {
|
||||
// ...If there is a redirect or an error, stop the process
|
||||
if (ssrContext.redirected) return noopApp()
|
||||
if (ssrContext.nuxt.error) return renderErrorPage()
|
||||
<% } %>
|
||||
|
||||
<% if (features.validate) { %>
|
||||
/*
|
||||
** Call .validate()
|
||||
*/
|
||||
@ -187,14 +215,17 @@ export default async (ssrContext) => {
|
||||
// Render a 404 error page
|
||||
return render404Page()
|
||||
}
|
||||
<% } %>
|
||||
|
||||
// If no Components found, returns 404
|
||||
if (!Components.length) return render404Page()
|
||||
|
||||
<% if (features.asyncData || features.fetch) { %>
|
||||
// Call asyncData & fetch hooks on components matched by the route.
|
||||
const asyncDatas = await Promise.all(Components.map((Component) => {
|
||||
const promises = []
|
||||
|
||||
<% if (features.asyncData) { %>
|
||||
// Call asyncData(context)
|
||||
if (Component.options.asyncData && typeof Component.options.asyncData === 'function') {
|
||||
const promise = promisify(Component.options.asyncData, app.context)
|
||||
@ -207,13 +238,16 @@ export default async (ssrContext) => {
|
||||
} else {
|
||||
promises.push(null)
|
||||
}
|
||||
<% } %>
|
||||
|
||||
<% if (features.fetch) { %>
|
||||
// Call fetch(context)
|
||||
if (Component.options.fetch) {
|
||||
promises.push(Component.options.fetch(app.context))
|
||||
} else {
|
||||
promises.push(null)
|
||||
}
|
||||
<% } %>
|
||||
|
||||
return Promise.all(promises)
|
||||
}))
|
||||
@ -222,6 +256,7 @@ export default async (ssrContext) => {
|
||||
|
||||
// datas are the first row of each
|
||||
ssrContext.nuxt.data = asyncDatas.map(r => r[0] || {})
|
||||
<% } %>
|
||||
|
||||
// ...If there is a redirect or an error, stop the process
|
||||
if (ssrContext.redirected) return noopApp()
|
||||
|
@ -21,6 +21,7 @@ export function interopDefault(promise) {
|
||||
return promise.then(m => m.default || m)
|
||||
}
|
||||
|
||||
<% if (features.asyncData) { %>
|
||||
export function applyAsyncData(Component, asyncData) {
|
||||
if (
|
||||
// For SSR, we once all this function without second param to just apply asyncData
|
||||
@ -47,6 +48,7 @@ export function applyAsyncData(Component, asyncData) {
|
||||
Component._Ctor.options.data = Component.options.data
|
||||
}
|
||||
}
|
||||
<% } %>
|
||||
|
||||
export function sanitizeComponent(Component) {
|
||||
// If Component already sanitized
|
||||
@ -67,22 +69,17 @@ export function sanitizeComponent(Component) {
|
||||
return Component
|
||||
}
|
||||
|
||||
export function getMatchedComponents(route, matches = false) {
|
||||
export function getMatchedComponents(route, matches = false, prop = 'components') {
|
||||
return Array.prototype.concat.apply([], route.matched.map((m, index) => {
|
||||
return Object.keys(m.components).map((key) => {
|
||||
return Object.keys(m[prop]).map((key) => {
|
||||
matches && matches.push(index)
|
||||
return m.components[key]
|
||||
return m[prop][key]
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export function getMatchedComponentsInstances(route, matches = false) {
|
||||
return Array.prototype.concat.apply([], route.matched.map((m, index) => {
|
||||
return Object.keys(m.instances).map((key) => {
|
||||
matches && matches.push(index)
|
||||
return m.instances[key]
|
||||
})
|
||||
}))
|
||||
return getMatchedComponents(route, matches, 'instances')
|
||||
}
|
||||
|
||||
export function flatMapComponents(route, fn) {
|
||||
@ -215,11 +212,11 @@ export async function setContext(app, context) {
|
||||
app.context.next = context.next
|
||||
app.context._redirected = false
|
||||
app.context._errored = false
|
||||
app.context.isHMR = Boolean(context.isHMR)
|
||||
app.context.isHMR = <% if(isDev) { %>Boolean(context.isHMR)<% } else { %>false<% } %>
|
||||
app.context.params = app.context.route.params || {}
|
||||
app.context.query = app.context.route.query || {}
|
||||
}
|
||||
|
||||
<% if (features.middleware) { %>
|
||||
export function middlewareSeries(promises, appContext) {
|
||||
if (!promises.length || appContext._redirected || appContext._errored) {
|
||||
return Promise.resolve()
|
||||
@ -229,8 +226,9 @@ export function middlewareSeries(promises, appContext) {
|
||||
return middlewareSeries(promises.slice(1), appContext)
|
||||
})
|
||||
}
|
||||
|
||||
<% } %>
|
||||
export function promisify(fn, context) {
|
||||
<% if (features.deprecations) { %>
|
||||
let promise
|
||||
if (fn.length === 2) {
|
||||
<% if (isDev) { %>
|
||||
@ -251,10 +249,13 @@ export function promisify(fn, context) {
|
||||
} else {
|
||||
promise = fn(context)
|
||||
}
|
||||
if (!promise || (!(promise instanceof Promise) && (typeof promise.then !== 'function'))) {
|
||||
promise = Promise.resolve(promise)
|
||||
<% } else { %>
|
||||
const promise = fn(context)
|
||||
<% } %>
|
||||
if (promise && promise instanceof Promise && typeof promise.then === 'function') {
|
||||
return promise
|
||||
}
|
||||
return promise
|
||||
return Promise.resolve(promise)
|
||||
}
|
||||
|
||||
// Imported from vue-router
|
||||
@ -269,10 +270,6 @@ export function getLocation(base, mode) {
|
||||
return (path || '/') + window.location.search + window.location.hash
|
||||
}
|
||||
|
||||
export function urlJoin() {
|
||||
return Array.prototype.slice.call(arguments).join('/').replace(/\/+/g, '/')
|
||||
}
|
||||
|
||||
// Imported from path-to-regexp
|
||||
|
||||
/**
|
||||
@ -412,8 +409,9 @@ function parse(str, options) {
|
||||
* @param {string}
|
||||
* @return {string}
|
||||
*/
|
||||
function encodeURIComponentPretty(str) {
|
||||
return encodeURI(str).replace(/[/?#]/g, (c) => {
|
||||
function encodeURIComponentPretty(str, slashAllowed) {
|
||||
const re = slashAllowed ? /[?#]/g : /[/?#]/g
|
||||
return encodeURI(str).replace(re, (c) => {
|
||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
||||
})
|
||||
}
|
||||
@ -425,9 +423,27 @@ function encodeURIComponentPretty(str) {
|
||||
* @return {string}
|
||||
*/
|
||||
function encodeAsterisk(str) {
|
||||
return encodeURI(str).replace(/[?#]/g, (c) => {
|
||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase()
|
||||
})
|
||||
return encodeURIComponentPretty(str, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a regular expression string.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeString(str) {
|
||||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape the capturing group by escaping special characters and meaning.
|
||||
*
|
||||
* @param {string} group
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeGroup(group) {
|
||||
return group.replace(/([=!:$/()])/g, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -514,26 +530,6 @@ function tokensToFunction(tokens) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a regular expression string.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeString(str) {
|
||||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape the capturing group by escaping special characters and meaning.
|
||||
*
|
||||
* @param {string} group
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeGroup(group) {
|
||||
return group.replace(/([=!:$/()])/g, '\\$1')
|
||||
}
|
||||
|
||||
/**
|
||||
* Format given url, append query to url query string
|
||||
*
|
||||
@ -542,6 +538,24 @@ function escapeGroup(group) {
|
||||
* @return {string}
|
||||
*/
|
||||
function formatUrl(url, query) {
|
||||
<% if (features.clientUseUrl) { %>
|
||||
url = new URL(url, top.location.href)
|
||||
for (const key in query) {
|
||||
const value = query[key]
|
||||
if (value == null) {
|
||||
continue
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const arrayValue of value) {
|
||||
url.searchParams.append(key, arrayValue)
|
||||
}
|
||||
continue
|
||||
}
|
||||
url.searchParams.append(key, value)
|
||||
}
|
||||
url.searchParams.sort()
|
||||
return url.toString()
|
||||
<% } else { %>
|
||||
let protocol
|
||||
const index = url.indexOf('://')
|
||||
if (index !== -1) {
|
||||
@ -569,8 +583,9 @@ function formatUrl(url, query) {
|
||||
result += hash ? '#' + hash : ''
|
||||
|
||||
return result
|
||||
<% } %>
|
||||
}
|
||||
|
||||
<% if (!features.clientUseUrl) { %>
|
||||
/**
|
||||
* Transform data object to query string
|
||||
*
|
||||
@ -589,3 +604,4 @@ function formatQuery(query) {
|
||||
return key + '=' + val
|
||||
}).filter(Boolean).join('&')
|
||||
}
|
||||
<% } %>
|
||||
|
@ -47,50 +47,52 @@ export default class SPARenderer extends BaseRenderer {
|
||||
BODY_SCRIPTS: ''
|
||||
}
|
||||
|
||||
// Get vue-meta context
|
||||
let head
|
||||
if (typeof this.options.head === 'function') {
|
||||
head = this.options.head()
|
||||
} else {
|
||||
head = this.options.head
|
||||
if (this.options.features.meta) {
|
||||
// Get vue-meta context
|
||||
let head
|
||||
if (typeof this.options.head === 'function') {
|
||||
head = this.options.head()
|
||||
} else {
|
||||
head = this.options.head
|
||||
}
|
||||
|
||||
const m = VueMeta.generate(head || {}, this.vueMetaConfig)
|
||||
|
||||
// HTML_ATTRS
|
||||
meta.HTML_ATTRS = m.htmlAttrs.text()
|
||||
|
||||
// HEAD_ATTRS
|
||||
meta.HEAD_ATTRS = m.headAttrs.text()
|
||||
|
||||
// BODY_ATTRS
|
||||
meta.BODY_ATTRS = m.bodyAttrs.text()
|
||||
|
||||
// HEAD tags
|
||||
meta.HEAD =
|
||||
m.title.text() +
|
||||
m.meta.text() +
|
||||
m.link.text() +
|
||||
m.style.text() +
|
||||
m.script.text() +
|
||||
m.noscript.text()
|
||||
|
||||
// BODY_SCRIPTS (PREPEND)
|
||||
meta.BODY_SCRIPTS_PREPEND =
|
||||
m.meta.text({ pbody: true }) +
|
||||
m.link.text({ pbody: true }) +
|
||||
m.style.text({ pbody: true }) +
|
||||
m.script.text({ pbody: true }) +
|
||||
m.noscript.text({ pbody: true })
|
||||
|
||||
// BODY_SCRIPTS (APPEND)
|
||||
meta.BODY_SCRIPTS =
|
||||
m.meta.text({ body: true }) +
|
||||
m.link.text({ body: true }) +
|
||||
m.style.text({ body: true }) +
|
||||
m.script.text({ body: true }) +
|
||||
m.noscript.text({ body: true })
|
||||
}
|
||||
|
||||
const m = VueMeta.generate(head || {}, this.vueMetaConfig)
|
||||
|
||||
// HTML_ATTRS
|
||||
meta.HTML_ATTRS = m.htmlAttrs.text()
|
||||
|
||||
// HEAD_ATTRS
|
||||
meta.HEAD_ATTRS = m.headAttrs.text()
|
||||
|
||||
// BODY_ATTRS
|
||||
meta.BODY_ATTRS = m.bodyAttrs.text()
|
||||
|
||||
// HEAD tags
|
||||
meta.HEAD =
|
||||
m.title.text() +
|
||||
m.meta.text() +
|
||||
m.link.text() +
|
||||
m.style.text() +
|
||||
m.script.text() +
|
||||
m.noscript.text()
|
||||
|
||||
// BODY_SCRIPTS (PREPEND)
|
||||
meta.BODY_SCRIPTS_PREPEND =
|
||||
m.meta.text({ pbody: true }) +
|
||||
m.link.text({ pbody: true }) +
|
||||
m.style.text({ pbody: true }) +
|
||||
m.script.text({ pbody: true }) +
|
||||
m.noscript.text({ pbody: true })
|
||||
|
||||
// BODY_SCRIPTS (APPEND)
|
||||
meta.BODY_SCRIPTS =
|
||||
m.meta.text({ body: true }) +
|
||||
m.link.text({ body: true }) +
|
||||
m.style.text({ body: true }) +
|
||||
m.script.text({ body: true }) +
|
||||
m.noscript.text({ body: true })
|
||||
|
||||
// Resources Hints
|
||||
meta.resourceHints = ''
|
||||
|
||||
|
@ -83,15 +83,19 @@ export default class SSRRenderer extends BaseRenderer {
|
||||
APP = `<div id="${this.serverContext.globals.id}"></div>`
|
||||
}
|
||||
|
||||
let HEAD = ''
|
||||
|
||||
// Inject head meta
|
||||
const m = renderContext.meta.inject()
|
||||
let HEAD =
|
||||
m.title.text() +
|
||||
m.meta.text() +
|
||||
m.link.text() +
|
||||
m.style.text() +
|
||||
m.script.text() +
|
||||
m.noscript.text()
|
||||
// (this is unset when features.meta is false in server template)
|
||||
const meta = renderContext.meta && renderContext.meta.inject()
|
||||
if (meta) {
|
||||
HEAD += meta.title.text() +
|
||||
meta.meta.text() +
|
||||
meta.link.text() +
|
||||
meta.style.text() +
|
||||
meta.script.text() +
|
||||
meta.noscript.text()
|
||||
}
|
||||
|
||||
// Check if we need to inject scripts and state
|
||||
const shouldInjectScripts = this.options.render.injectScripts !== false
|
||||
@ -109,15 +113,17 @@ export default class SSRRenderer extends BaseRenderer {
|
||||
// Inject styles
|
||||
HEAD += renderContext.renderStyles()
|
||||
|
||||
const BODY_PREPEND =
|
||||
m.meta.text({ pbody: true }) +
|
||||
m.link.text({ pbody: true }) +
|
||||
m.style.text({ pbody: true }) +
|
||||
m.script.text({ pbody: true }) +
|
||||
m.noscript.text({ pbody: true })
|
||||
if (meta) {
|
||||
const BODY_PREPEND =
|
||||
meta.meta.text({ pbody: true }) +
|
||||
meta.link.text({ pbody: true }) +
|
||||
meta.style.text({ pbody: true }) +
|
||||
meta.script.text({ pbody: true }) +
|
||||
meta.noscript.text({ pbody: true })
|
||||
|
||||
if (BODY_PREPEND) {
|
||||
APP = `${BODY_PREPEND}${APP}`
|
||||
if (BODY_PREPEND) {
|
||||
APP = `${BODY_PREPEND}${APP}`
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize state
|
||||
@ -152,18 +158,20 @@ export default class SSRRenderer extends BaseRenderer {
|
||||
APP += this.renderScripts(renderContext)
|
||||
}
|
||||
|
||||
// Append body scripts
|
||||
APP += m.meta.text({ body: true })
|
||||
APP += m.link.text({ body: true })
|
||||
APP += m.style.text({ body: true })
|
||||
APP += m.script.text({ body: true })
|
||||
APP += m.noscript.text({ body: true })
|
||||
if (meta) {
|
||||
// Append body scripts
|
||||
APP += meta.meta.text({ body: true })
|
||||
APP += meta.link.text({ body: true })
|
||||
APP += meta.style.text({ body: true })
|
||||
APP += meta.script.text({ body: true })
|
||||
APP += meta.noscript.text({ body: true })
|
||||
}
|
||||
|
||||
// Template params
|
||||
const templateParams = {
|
||||
HTML_ATTRS: m.htmlAttrs.text(true /* addSrrAttribute */),
|
||||
HEAD_ATTRS: m.headAttrs.text(),
|
||||
BODY_ATTRS: m.bodyAttrs.text(),
|
||||
HTML_ATTRS: meta ? meta.htmlAttrs.text(true /* addSrrAttribute */) : '',
|
||||
HEAD_ATTRS: meta ? meta.headAttrs.text() : '',
|
||||
BODY_ATTRS: meta ? meta.bodyAttrs.text() : '',
|
||||
HEAD,
|
||||
APP,
|
||||
ENV: this.options.env
|
||||
|
38
test/fixtures/unicode-base/nuxt.config.js
vendored
38
test/fixtures/unicode-base/nuxt.config.js
vendored
@ -1,5 +1,43 @@
|
||||
export default {
|
||||
modern: 'server',
|
||||
router: {
|
||||
base: '/%C3%B6/'
|
||||
},
|
||||
loading: false,
|
||||
loadingIndicator: false,
|
||||
fetch: {
|
||||
client: false,
|
||||
server: false
|
||||
},
|
||||
features: {
|
||||
store: false,
|
||||
layouts: false,
|
||||
meta: false,
|
||||
middleware: false,
|
||||
transitions: false,
|
||||
deprecations: false,
|
||||
validate: false,
|
||||
asyncData: false,
|
||||
fetch: false,
|
||||
clientOnline: false,
|
||||
clientPrefetch: false,
|
||||
clientUseUrl: true,
|
||||
componentAliases: false,
|
||||
componentClientOnly: false
|
||||
},
|
||||
build: {
|
||||
indicator: false,
|
||||
terser: true,
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
nuxtApp: {
|
||||
test: /[\\/]\.nuxt[\\/]/,
|
||||
filename: 'vue-app.nuxt.js',
|
||||
enforce: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,8 @@
|
||||
|
||||
import { resolve } from 'path'
|
||||
import zlib from 'zlib'
|
||||
import fs from 'fs-extra'
|
||||
import pify from 'pify'
|
||||
|
||||
const gzip = pify(zlib.gzip)
|
||||
const brotli = pify(zlib.brotliCompress)
|
||||
const compressSize = (input, compressor) => compressor(input).then(data => data.length)
|
||||
import { getResourcesSize } from '../utils'
|
||||
|
||||
const distDir = resolve(__dirname, '../fixtures/async-config/.nuxt/dist')
|
||||
|
||||
const getResourcesSize = async (mode) => {
|
||||
const { all } = await import(resolve(distDir, 'server', `${mode}.manifest.json`))
|
||||
const resources = all.filter(filename => filename.endsWith('.js'))
|
||||
const sizes = { uncompressed: 0, gzip: 0, brotli: 0 }
|
||||
for (const resource of resources) {
|
||||
const file = resolve(distDir, 'client', resource)
|
||||
const stat = await fs.stat(file)
|
||||
sizes.uncompressed += stat.size / 1024
|
||||
const fileContent = await fs.readFile(file)
|
||||
sizes.gzip += await compressSize(fileContent, gzip) / 1024
|
||||
sizes.brotli += await compressSize(fileContent, brotli) / 1024
|
||||
}
|
||||
return sizes
|
||||
}
|
||||
|
||||
describe('nuxt basic resources size limit', () => {
|
||||
expect.extend({
|
||||
toBeWithinSize (received, size) {
|
||||
@ -40,7 +18,7 @@ describe('nuxt basic resources size limit', () => {
|
||||
})
|
||||
|
||||
it('should stay within the size limit range in legacy mode', async () => {
|
||||
const legacyResourcesSize = await getResourcesSize('client')
|
||||
const legacyResourcesSize = await getResourcesSize(distDir, 'client', { gzip: true, brotli: true })
|
||||
|
||||
const LEGACY_JS_RESOURCES_KB_SIZE = 194
|
||||
expect(legacyResourcesSize.uncompressed).toBeWithinSize(LEGACY_JS_RESOURCES_KB_SIZE)
|
||||
@ -53,7 +31,7 @@ describe('nuxt basic resources size limit', () => {
|
||||
})
|
||||
|
||||
it('should stay within the size limit range in modern mode', async () => {
|
||||
const modernResourcesSize = await getResourcesSize('modern')
|
||||
const modernResourcesSize = await getResourcesSize(distDir, 'modern', { gzip: true, brotli: true })
|
||||
|
||||
const MODERN_JS_RESOURCES_KB_SIZE = 172
|
||||
expect(modernResourcesSize.uncompressed).toBeWithinSize(MODERN_JS_RESOURCES_KB_SIZE)
|
||||
|
27
test/unit/unicode-base.size-limit.test.js
Normal file
27
test/unit/unicode-base.size-limit.test.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { resolve } from 'path'
|
||||
import { getResourcesSize } from '../utils'
|
||||
|
||||
const distDir = resolve(__dirname, '../fixtures/unicode-base/.nuxt/dist')
|
||||
|
||||
describe('nuxt minimal vue-app bundle size limit', () => {
|
||||
expect.extend({
|
||||
toBeWithinSize (received, size) {
|
||||
const maxSize = size * 1.02
|
||||
const minSize = size * 0.98
|
||||
const pass = received >= minSize && received <= maxSize
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
`expected ${received} to be within range ${minSize} - ${maxSize}`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should stay within the size limit range', async () => {
|
||||
const filter = filename => filename === 'vue-app.nuxt.js'
|
||||
const legacyResourcesSize = await getResourcesSize(distDir, 'client', { filter })
|
||||
|
||||
const LEGACY_JS_RESOURCES_KB_SIZE = 15.8
|
||||
expect(legacyResourcesSize.uncompressed).toBeWithinSize(LEGACY_JS_RESOURCES_KB_SIZE)
|
||||
})
|
||||
})
|
@ -1,7 +1,7 @@
|
||||
import { getPort, loadFixture, Nuxt } from '../utils'
|
||||
import { getPort, loadFixture, Nuxt, rp } from '../utils'
|
||||
|
||||
let port
|
||||
const url = route => 'http://localhost:' + port + route
|
||||
const url = route => 'http://localhost:' + port + encodeURI(route)
|
||||
|
||||
let nuxt = null
|
||||
|
||||
@ -16,10 +16,9 @@ describe('unicode-base', () => {
|
||||
})
|
||||
|
||||
test('/ö/ (router base)', async () => {
|
||||
const window = await nuxt.server.renderAndGetWindow(url('/ö/'))
|
||||
const response = await rp(url('/ö/'))
|
||||
|
||||
const html = window.document.body.innerHTML
|
||||
expect(html).toContain('<h1>Unicode base works!</h1>')
|
||||
expect(response).toContain('<h1>Unicode base works!</h1>')
|
||||
})
|
||||
|
||||
// Close server and ask nuxt to stop listening to file changes
|
||||
|
@ -5,6 +5,7 @@ export { default as getPort } from 'get-port'
|
||||
export { default as rp } from 'request-promise-native'
|
||||
|
||||
export * from './nuxt'
|
||||
export * from './resource-size'
|
||||
|
||||
export const listPaths = function listPaths (dir, pathsBefore = [], options = {}) {
|
||||
if (Array.isArray(pathsBefore) && pathsBefore.length) {
|
||||
|
35
test/utils/resource-size.js
Normal file
35
test/utils/resource-size.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { resolve } from 'path'
|
||||
import zlib from 'zlib'
|
||||
import fs from 'fs-extra'
|
||||
import pify from 'pify'
|
||||
|
||||
const gzipCompressor = pify(zlib.gzip)
|
||||
const brotliCompressor = pify(zlib.brotliCompress)
|
||||
const compressSize = (input, compressor) => compressor(input).then(data => data.length)
|
||||
|
||||
export const getResourcesSize = async (distDir, mode, { filter, gzip, brotli } = {}) => {
|
||||
if (!filter) {
|
||||
filter = filename => filename.endsWith('.js')
|
||||
}
|
||||
const { all } = await import(resolve(distDir, 'server', `${mode}.manifest.json`))
|
||||
const resources = all.filter(filter)
|
||||
const sizes = { uncompressed: 0, gzip: 0, brotli: 0 }
|
||||
for (const resource of resources) {
|
||||
const file = resolve(distDir, 'client', resource)
|
||||
|
||||
const stat = await fs.stat(file)
|
||||
sizes.uncompressed += stat.size / 1024
|
||||
|
||||
if (gzip || brotli) {
|
||||
const fileContent = await fs.readFile(file)
|
||||
|
||||
if (gzip) {
|
||||
sizes.gzip += await compressSize(fileContent, gzipCompressor) / 1024
|
||||
}
|
||||
if (brotli) {
|
||||
sizes.brotli += await compressSize(fileContent, brotliCompressor) / 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
return sizes
|
||||
}
|
Loading…
Reference in New Issue
Block a user