mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nuxt): allow configuring spa loading indicator (#21640)
This commit is contained in:
parent
343a46d5f9
commit
c66c82e6a0
@ -67,6 +67,11 @@ export default defineNuxtConfig({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
::alert{type=info}
|
||||||
|
If you do use `ssr: false`, you should also place an HTML file in `~/app/spa-loading-template.html` with some HTML you would like to use to render a loading screen that will be rendered until your app is hydrated.
|
||||||
|
:ReadMore{link="/docs/api/configuration/nuxt-config#spaloadingindicator"}
|
||||||
|
::
|
||||||
|
|
||||||
## Hybrid Rendering
|
## Hybrid Rendering
|
||||||
|
|
||||||
Hybrid rendering allows different caching rules per route using **Route Rules** and decides how the server should respond to a new request on a given URL.
|
Hybrid rendering allows different caching rules per route using **Route Rules** and decides how the server should respond to a new request on a given URL.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { existsSync, promises as fsp } from 'node:fs'
|
import { existsSync, promises as fsp, readFileSync } from 'node:fs'
|
||||||
import { join, relative, resolve } from 'pathe'
|
import { join, relative, resolve } from 'pathe'
|
||||||
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
|
import { build, copyPublicAssets, createDevServer, createNitro, prepare, prerender, scanHandlers, writeTypes } from 'nitropack'
|
||||||
import type { Nitro, NitroConfig } from 'nitropack'
|
import type { Nitro, NitroConfig } from 'nitropack'
|
||||||
@ -10,6 +10,8 @@ import { dynamicEventHandler } from 'h3'
|
|||||||
import { createHeadCore } from '@unhead/vue'
|
import { createHeadCore } from '@unhead/vue'
|
||||||
import { renderSSRHead } from '@unhead/ssr'
|
import { renderSSRHead } from '@unhead/ssr'
|
||||||
import type { Nuxt } from 'nuxt/schema'
|
import type { Nuxt } from 'nuxt/schema'
|
||||||
|
// @ts-expect-error TODO: add legacy type support for subpath imports
|
||||||
|
import { template as defaultSpaLoadingTemplate } from '@nuxt/ui-templates/templates/spa-loading-icon.mjs'
|
||||||
|
|
||||||
import { distDir } from '../dirs'
|
import { distDir } from '../dirs'
|
||||||
import { ImportProtectionPlugin } from './plugins/import-protection'
|
import { ImportProtectionPlugin } from './plugins/import-protection'
|
||||||
@ -29,6 +31,13 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
? [new RegExp(`node_modules\\/(?!${excludePaths.join('|')})`)]
|
? [new RegExp(`node_modules\\/(?!${excludePaths.join('|')})`)]
|
||||||
: [/node_modules/]
|
: [/node_modules/]
|
||||||
|
|
||||||
|
const spaLoadingTemplatePath = nuxt.options.spaLoadingTemplate ?? resolve(nuxt.options.srcDir, 'app/spa-loading-template.html')
|
||||||
|
if (spaLoadingTemplatePath !== false && !existsSync(spaLoadingTemplatePath)) {
|
||||||
|
if (nuxt.options.spaLoadingTemplate) {
|
||||||
|
console.warn(`[nuxt] Could not load custom \`spaLoadingTemplate\` path as it does not exist: \`${spaLoadingTemplatePath}\`.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const nitroConfig: NitroConfig = defu(_nitroConfig, <NitroConfig>{
|
const nitroConfig: NitroConfig = defu(_nitroConfig, <NitroConfig>{
|
||||||
debug: nuxt.options.debug,
|
debug: nuxt.options.debug,
|
||||||
rootDir: nuxt.options.rootDir,
|
rootDir: nuxt.options.rootDir,
|
||||||
@ -75,7 +84,15 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
|||||||
devHandlers: [],
|
devHandlers: [],
|
||||||
baseURL: nuxt.options.app.baseURL,
|
baseURL: nuxt.options.app.baseURL,
|
||||||
virtual: {
|
virtual: {
|
||||||
'#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config']
|
'#internal/nuxt.config.mjs': () => nuxt.vfs['#build/nuxt.config'],
|
||||||
|
'#spa-template': () => {
|
||||||
|
try {
|
||||||
|
if (spaLoadingTemplatePath) {
|
||||||
|
return `export const template = ${JSON.stringify(readFileSync(spaLoadingTemplatePath, 'utf-8'))}`
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return `export const template = ${JSON.stringify(defaultSpaLoadingTemplate({}))}`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
routeRules: {
|
routeRules: {
|
||||||
'/__nuxt_error': { cache: false }
|
'/__nuxt_error': { cache: false }
|
||||||
|
@ -112,9 +112,12 @@ const getSSRRenderer = lazyCachedFunction(async () => {
|
|||||||
const getSPARenderer = lazyCachedFunction(async () => {
|
const getSPARenderer = lazyCachedFunction(async () => {
|
||||||
const manifest = await getClientManifest()
|
const manifest = await getClientManifest()
|
||||||
|
|
||||||
|
// @ts-expect-error virtual file
|
||||||
|
const spaTemplate = await import('#spa-template').then(r => r.template).catch(() => '')
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
manifest,
|
manifest,
|
||||||
renderToString: () => `<${appRootTag} id="${appRootId}"></${appRootTag}>`,
|
renderToString: () => `<${appRootTag} id="${appRootId}">${spaTemplate}</${appRootTag}>`,
|
||||||
buildAssetsURL
|
buildAssetsURL
|
||||||
}
|
}
|
||||||
// Create SPA renderer and cache the result for all requests
|
// Create SPA renderer and cache the result for all requests
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { defineUntypedSchema } from 'untyped'
|
import { defineUntypedSchema } from 'untyped'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
|
import { resolve } from 'pathe'
|
||||||
import type { AppHeadMetaObject } from '../types/head'
|
import type { AppHeadMetaObject } from '../types/head'
|
||||||
|
|
||||||
export default defineUntypedSchema({
|
export default defineUntypedSchema({
|
||||||
@ -177,6 +178,65 @@ export default defineUntypedSchema({
|
|||||||
rootTag: 'div',
|
rootTag: 'div',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** A path to an HTML file, the contents of which will be inserted into any HTML page
|
||||||
|
* rendered with `ssr: false`.
|
||||||
|
*
|
||||||
|
* By default Nuxt will look in `~/app/spa-loading-template.html` for this file.
|
||||||
|
*
|
||||||
|
* You can set this to `false` to disable any loading indicator.
|
||||||
|
*
|
||||||
|
* Some good sources for spinners are [SpinKit](https://github.com/tobiasahlin/SpinKit) or [SVG Spinners](https://icones.js.org/collection/svg-spinners).
|
||||||
|
*
|
||||||
|
* @example ~/app/spa-loading-template.html
|
||||||
|
* ```html
|
||||||
|
* <!-- https://github.com/barelyhuman/snips/blob/dev/pages/css-loader.md -->
|
||||||
|
* <div class="loader"></div>
|
||||||
|
* <style>
|
||||||
|
* .loader {
|
||||||
|
* display: block;
|
||||||
|
* position: fixed;
|
||||||
|
* z-index: 1031;
|
||||||
|
* top: 50%;
|
||||||
|
* left: 50%;
|
||||||
|
* transform: translate(-50%, -50%);
|
||||||
|
* width: 18px;
|
||||||
|
* height: 18px;
|
||||||
|
* box-sizing: border-box;
|
||||||
|
* border: solid 2px transparent;
|
||||||
|
* border-top-color: #000;
|
||||||
|
* border-left-color: #000;
|
||||||
|
* border-bottom-color: #efefef;
|
||||||
|
* border-right-color: #efefef;
|
||||||
|
* border-radius: 50%;
|
||||||
|
* -webkit-animation: loader 400ms linear infinite;
|
||||||
|
* animation: loader 400ms linear infinite;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* \@-webkit-keyframes loader {
|
||||||
|
* 0% {
|
||||||
|
* -webkit-transform: rotate(0deg);
|
||||||
|
* }
|
||||||
|
* 100% {
|
||||||
|
* -webkit-transform: rotate(360deg);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* \@keyframes loader {
|
||||||
|
* 0% {
|
||||||
|
* transform: rotate(0deg);
|
||||||
|
* }
|
||||||
|
* 100% {
|
||||||
|
* transform: rotate(360deg);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </style>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @type {string | false}
|
||||||
|
*/
|
||||||
|
spaLoadingTemplate: {
|
||||||
|
$resolve: async (val, get) => typeof val === 'string' ? resolve(await get('srcDir'), val) : (val ?? null)
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of nuxt app plugins.
|
* An array of nuxt app plugins.
|
||||||
*
|
*
|
||||||
|
@ -5310,6 +5310,17 @@ packages:
|
|||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/globby@13.1.4:
|
||||||
|
resolution: {integrity: sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
dependencies:
|
||||||
|
dir-glob: 3.0.1
|
||||||
|
fast-glob: 3.2.12
|
||||||
|
ignore: 5.2.4
|
||||||
|
merge2: 1.4.1
|
||||||
|
slash: 4.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/globby@13.2.0:
|
/globby@13.2.0:
|
||||||
resolution: {integrity: sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==}
|
resolution: {integrity: sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
@ -6425,7 +6436,7 @@ packages:
|
|||||||
defu: 6.1.2
|
defu: 6.1.2
|
||||||
esbuild: 0.17.19
|
esbuild: 0.17.19
|
||||||
fs-extra: 11.1.1
|
fs-extra: 11.1.1
|
||||||
globby: 13.2.0
|
globby: 13.1.4
|
||||||
jiti: 1.18.2
|
jiti: 1.18.2
|
||||||
mlly: 1.3.0
|
mlly: 1.3.0
|
||||||
mri: 1.2.0
|
mri: 1.2.0
|
||||||
@ -8452,7 +8463,7 @@ packages:
|
|||||||
consola: 3.1.0
|
consola: 3.1.0
|
||||||
defu: 6.1.2
|
defu: 6.1.2
|
||||||
esbuild: 0.17.19
|
esbuild: 0.17.19
|
||||||
globby: 13.2.0
|
globby: 13.1.4
|
||||||
hookable: 5.5.3
|
hookable: 5.5.3
|
||||||
jiti: 1.18.2
|
jiti: 1.18.2
|
||||||
magic-string: 0.30.0
|
magic-string: 0.30.0
|
||||||
|
@ -35,7 +35,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
|||||||
|
|
||||||
it('default server bundle size', async () => {
|
it('default server bundle size', async () => {
|
||||||
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||||
expect.soft(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"61.3k"')
|
expect.soft(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"62.1k"')
|
||||||
|
|
||||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2295k"')
|
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2295k"')
|
||||||
|
Loading…
Reference in New Issue
Block a user