Skip to content
This repository was archived by the owner on Apr 6, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/nuxt/src/app/composables/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ interface LoadPayloadOptions {

export function loadPayload (url: string, opts: LoadPayloadOptions = {}) {
if (process.server) { return null }
if (!_hasPayload(url)) { return null }

const payloadURL = _getPayloadURL(url, opts)
const nuxtApp = useNuxtApp()
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
Expand All @@ -26,6 +28,8 @@ export function loadPayload (url: string, opts: LoadPayloadOptions = {}) {
}

export function preloadPayload (url: string, opts: LoadPayloadOptions = {}) {
if (!_hasPayload(url)) { return }

const payloadURL = _getPayloadURL(url, opts)
useHead({
link: [
Expand All @@ -45,6 +49,11 @@ function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
return joinURL(parsed.pathname, hash ? `_payload.${hash}.js` : '_payload.js')
}

function _hasPayload (url: string) {
const nuxtApp = useNuxtApp()
return nuxtApp._manifest.static.some(route => typeof route === 'string' ? route === url : route.test(url))
}

async function _importPayload (payloadURL: string) {
if (process.server) { return null }
const res = await import(/* webpackIgnore: true */ /* @vite-ignore */ payloadURL).catch((err) => {
Expand Down
6 changes: 6 additions & 0 deletions packages/nuxt/src/app/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export interface NuxtSSRContext extends SSRContext {
renderMeta?: () => Promise<NuxtMeta> | NuxtMeta
}

export interface RouteManifest {
static: Array<string | RegExp>
}

interface _NuxtApp {
vueApp: App<Element>
globalName: string
Expand All @@ -72,6 +76,8 @@ interface _NuxtApp {
error: Ref<any>
} | undefined>,

_manifest: RouteManifest

ssrContext?: NuxtSSRContext
payload: {
serverRendered?: boolean
Expand Down
18 changes: 12 additions & 6 deletions packages/nuxt/src/app/plugins/payload.client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { defineNuxtPlugin, loadPayload, isPrerendered, useRouter } from '#app'
import escapeRE from 'escape-string-regexp'
import { defineNuxtPlugin, loadPayload, useRouter } from '#app'

export default defineNuxtPlugin((nuxtApp) => {
// Only enable behavior if initial page is prerendered
// TOOD: Support hybrid and dev
if (!isPrerendered()) {
return
export default defineNuxtPlugin(async (nuxtApp) => {
const manifest = await $fetch<{ static: string[] }>('/manifest.json', {
cache: 'no-cache'
}).catch(() => ({ static: [] }))

nuxtApp._manifest = {
...manifest,
static: manifest.static.map(r => r.endsWith('/**') ? new RegExp(escapeRE(r.slice(0, -3)) + '.*') : r)
}

if (nuxtApp._manifest.static.length === 0) { return }

// Load payload into cache
nuxtApp.hooks.hook('link:prefetch', to => loadPayload(to))

Expand Down
14 changes: 14 additions & 0 deletions packages/nuxt/src/core/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
],
prerender: {
crawlLinks: nuxt.options._generate ? nuxt.options.generate.crawler : false,
ignore: ['/manifest.json'],
routes: ([] as string[])
.concat(nuxt.options._generate ? ['/', '/200.html', ...nuxt.options.generate.routes] : [])
.concat(nuxt.options.ssr === false ? ['/index.html', '/200.html', '/404.html'] : [])
Expand Down Expand Up @@ -119,6 +120,14 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
nitroConfig.virtual!['#build/dist/server/styles.mjs'] = 'export default {}'
}

// Add manifest handler
if (nuxt.options.experimental.payloadExtraction) {
nitroConfig.handlers!.push({
route: '/manifest.json',
handler: resolve(distDir, 'core/runtime/nitro/manifest')
})
}

// Register nuxt protection patterns
nitroConfig.rollupConfig!.plugins!.push(ImportProtectionPlugin.rollup({
rootDir: nuxt.options.rootDir,
Expand Down Expand Up @@ -176,6 +185,11 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
if (!nuxt.options._generate) {
await build(nitro)
} else {
await fsp.writeFile(join(nitro.options.output.publicDir, 'manifest.json'), JSON.stringify({
routes: nitro._prerenderedRoutes
?.filter(r => r.route.endsWith('_payload.js'))
.map(r => r.route.replace(/\/_payload\.js$/, '') || '/')
}))
const distDir = resolve(nuxt.options.rootDir, 'dist')
if (!existsSync(distDir)) {
await fsp.symlink(nitro.options.output.publicDir, distDir, 'junction').catch(() => {})
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async function initNuxt (nuxt: Nuxt) {
})

// Add prerender payload support
if (!nuxt.options.dev && nuxt.options.experimental.payloadExtraction) {
if (nuxt.options.experimental.payloadExtraction) {
addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
}

Expand Down
8 changes: 8 additions & 0 deletions packages/nuxt/src/core/runtime/nitro/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineCachedEventHandler } from '#internal/nitro'

export default defineCachedEventHandler(() => {
return {
// TODO: support route rules for payload extraction
static: ['/**']
}
})
1 change: 1 addition & 0 deletions packages/nuxt/src/core/runtime/nitro/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export default defineRenderHandler(async (event) => {
head: normalizeChunks([
renderedMeta.headTags,
_PAYLOAD_EXTRACTION ? `<link rel="modulepreload" href="${payloadURL}">` : null,
process.env.NUXT_PAYLOAD_EXTRACTION ? '<link rel="preload" href="/manifest.json" as="fetch" crossorigin>' : null,
_rendered.renderResourceHints(),
_rendered.renderStyles(),
inlinedStyles,
Expand Down
4 changes: 3 additions & 1 deletion packages/schema/src/config/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export default defineUntypedSchema({
/**
* When this option is enabled (by default) payload of pages generated with `nuxt generate` are extracted
*/
payloadExtraction: true,
payloadExtraction: {
$resolve: async (val, get) => val ?? !(await get('dev'))
},
}
})
8 changes: 4 additions & 4 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ describe.runIf(process.env.NUXT_TEST_DEV)('detecting invalid root nodes', () =>
describe.skipIf(process.env.NUXT_TEST_DEV)('dynamic paths', () => {
it('should work with no overrides', async () => {
const html: string = await $fetch('/assets')
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
for (const match of html.matchAll(/<(?!link)[^>]*(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
const url = match[2] || match[3]
expect(url.startsWith('/_nuxt/') || url === '/public.svg').toBeTruthy()
}
Expand Down Expand Up @@ -523,7 +523,7 @@ describe.skipIf(process.env.NUXT_TEST_DEV)('dynamic paths', () => {
await startServer()

const html = await $fetch('/foo/assets')
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
for (const match of html.matchAll(/<(?!link)[^>]*(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
const url = match[2] || match[3]
expect(
url.startsWith('/foo/_other/') ||
Expand All @@ -540,7 +540,7 @@ describe.skipIf(process.env.NUXT_TEST_DEV)('dynamic paths', () => {
await startServer()

const html = await $fetch('/assets')
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
for (const match of html.matchAll(/<(?!link)[^>]*(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
const url = match[2] || match[3]
expect(
url.startsWith('./_nuxt/') ||
Expand Down Expand Up @@ -568,7 +568,7 @@ describe.skipIf(process.env.NUXT_TEST_DEV)('dynamic paths', () => {
await startServer()

const html = await $fetch('/foo/assets')
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
for (const match of html.matchAll(/<(?!link)[^>]*(href|src)="(.*?)"|url\(([^)]*?)\)/g)) {
const url = match[2] || match[3]
expect(
url.startsWith('https://example.com/_cdn/') ||
Expand Down