Skip to content

Commit

Permalink
Fix component and data cache when using FS driver (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
dulnan committed Jun 16, 2024
1 parent 785b077 commit 41e8ea5
Show file tree
Hide file tree
Showing 16 changed files with 294 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ coverage
docs/.vitepress/cache
docs/.vitepress/dist
docs/.vitepress/.temp
playground-disk/cache
__cache__
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"scripts": {
"prepack": "nuxt-module-build build",
"dev": "nuxi dev playground",
"dev:playground-disk": "nuxi dev playground-disk",
"dev:build": "nuxi build playground",
"dev:serve": "node playground/.output/server/index.mjs",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
Expand Down
7 changes: 7 additions & 0 deletions playground-disk/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<div class="app">
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
67 changes: 67 additions & 0 deletions playground-disk/app/multiCache.serverOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { type H3Event, getQuery, getHeader } from 'h3'
import fsDriver from 'unstorage/drivers/fs'
import { defineMultiCacheOptions } from './../../src/runtime/serverOptions/defineMultiCacheOptions'

function getCacheKeyPrefix(event: H3Event): string {
const query = getQuery(event)
if (query.language && typeof query.language === 'string') {
return query.language
}

const acceptLanguage = getHeader(event, 'accept-language') || ''

if (
acceptLanguage &&
typeof acceptLanguage === 'string' &&
acceptLanguage.includes('de')
) {
return 'de'
}
return 'en'
}

export default defineMultiCacheOptions({
data: {
storage: {
driver: fsDriver({
base: './__cache__/data',
}),
},
},
route: {
alterCachedHeaders(headers) {
const cookie = headers['set-cookie']
// Remove the SESSION cookie.
if (cookie) {
if (typeof cookie === 'string') {
if (cookie.includes('SESSION')) {
headers['set-cookie'] = undefined
}
} else if (Array.isArray(cookie)) {
const remaining = cookie.filter((v) => !v.includes('SESSION'))
if (!remaining.length) {
headers['set-cookie'] = undefined
} else {
headers['set-cookie'] = remaining
}
}
}
return headers
},
storage: {
driver: fsDriver({
base: './__cache__/route',
}),
},
},
component: {
storage: {
driver: fsDriver({
base: './__cache__/component',
}),
},
},
cacheKeyPrefix: (event: H3Event): Promise<string> => {
return Promise.resolve(getCacheKeyPrefix(event))
},
})
16 changes: 16 additions & 0 deletions playground-disk/components/Test.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div>
<div id="cached-component-number">{{ random }}</div>
</div>
</template>

<script setup lang="ts">
import { useAsyncData } from '#imports'
const { data: random } = await useAsyncData(
'random_data_in_cached_component',
() => {
return Promise.resolve('RANDOM_NUMBER__' + Date.now())
},
)
</script>
7 changes: 7 additions & 0 deletions playground-disk/layouts/default.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<div class="main-layout">
<main>
<slot />
</main>
</div>
</template>
31 changes: 31 additions & 0 deletions playground-disk/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineNuxtConfig } from 'nuxt/config'
import NuxtMultiCache from './../src/module'

export default defineNuxtConfig({
modules: [NuxtMultiCache],
imports: {
autoImport: false,
},
multiCache: {
debug: true,
component: {
enabled: true,
},
route: {
enabled: true,
},

data: {
enabled: true,
},

cdn: {
enabled: true,
},
api: {
enabled: true,
cacheTagInvalidationDelay: 5000,
authorization: false,
},
},
})
16 changes: 16 additions & 0 deletions playground-disk/pages/cachedComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div>
<RenderCacheable :async-data-keys="['random_data_in_cached_component']">
<TestComponent />
</RenderCacheable>
</div>
</template>

<script lang="ts" setup>
import { useRouteCache } from '#imports'
import TestComponent from './../components/Test.vue'
useRouteCache((helper) => {
helper.setUncacheable()
})
</script>
15 changes: 15 additions & 0 deletions playground-disk/pages/cachedPageFromDisk.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div id="random-number">{{ random }}</div>
</template>

<script lang="ts" setup>
import { useRouteCache, useState } from '#imports'
const random = useState('random_data', () => {
return 'RANDOM_NUMBER__' + Math.round(Math.random() * 1000000000) + '__'
})
useRouteCache((helper) => {
helper.setCacheable()
})
</script>
15 changes: 15 additions & 0 deletions playground-disk/pages/uncacheablePage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<div id="random-number">{{ random }}</div>
</template>

<script lang="ts" setup>
import { useRouteCache, useState } from '#imports'
const random = useState('random_data', () => {
return 'RANDOM_NUMBER__' + Math.round(Math.random() * 1000000000) + '__'
})
useRouteCache((helper) => {
helper.setUncacheable()
})
</script>
3 changes: 3 additions & 0 deletions playground-disk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}
16 changes: 16 additions & 0 deletions playground/pages/cachedPageFromDisk.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div id="random-number">{{ random }}</div>
</template>

<script lang="ts" setup>
import { computed } from 'vue'
import { useRouteCache } from '#imports'
const random = computed(() => {
return 'RANDOM_NUMBER__' + Math.round(Math.random() * 1000000000) + '__'
})
useRouteCache((helper) => {
helper.setCacheable()
})
</script>
11 changes: 8 additions & 3 deletions src/runtime/components/RenderCacheable/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import type {
Slots,
ComponentInternalInstance,
} from 'vue'
import { decodeComponentCacheItem } from '../../../helpers/cacheItem'
import {
decodeComponentCacheItem,
handleRawCacheData,
} from '../../../helpers/cacheItem'
import { logger } from '../../../helpers/logger'
import type { ComponentCacheItem } from './../../../types'

Expand Down Expand Up @@ -155,9 +158,11 @@ export async function getCachedComponent(
cacheKey: string,
): Promise<ComponentCacheItem | void> {
// Get the cached item from the storage.
const cachedRaw = await storage.getItemRaw<string>(cacheKey)
const cachedRaw = handleRawCacheData(
await storage.getItemRaw<string>(cacheKey),
)

if (cachedRaw && typeof cachedRaw === 'string') {
if (cachedRaw) {
return decodeComponentCacheItem(cachedRaw)
}
}
16 changes: 16 additions & 0 deletions src/runtime/helpers/cacheItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,19 @@ export function decodeComponentCacheItem(
}
} catch (e) {}
}

/**
* Handle the return value from cache.getItemRaw().
*
* Not all drivers return strings, so this method handles the case where a
* driver returns other types such as buffers.
*/
export function handleRawCacheData(
data: string | Buffer | undefined | null,
): string | undefined {
if (typeof data === 'string') {
return data
} else if (data instanceof Buffer) {
return data.toString()
}
}
8 changes: 5 additions & 3 deletions src/runtime/serverHandler/serveCachedRoute.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineEventHandler, setResponseHeaders } from 'h3'
import { decodeRouteCacheItem } from '../helpers/cacheItem'
import { decodeRouteCacheItem, handleRawCacheData } from '../helpers/cacheItem'
import { logger } from '../helpers/logger'
import {
getMultiCacheContext,
Expand Down Expand Up @@ -29,8 +29,10 @@ export default defineEventHandler(async (event) => {
? serverOptions.route.buildCacheKey(event)
: getCacheKeyWithPrefix(encodeRouteCacheKey(event.path), event)

const cachedRaw = await multiCache.route.getItemRaw(fullKey)
if (cachedRaw && typeof cachedRaw === 'string') {
const cachedRaw = handleRawCacheData(
await multiCache.route.getItemRaw(fullKey),
)
if (cachedRaw) {
const decoded = decodeRouteCacheItem(cachedRaw)
if (decoded) {
// Check if the item is stale.
Expand Down
69 changes: 69 additions & 0 deletions test/fileSystemDriver.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import path from 'path'
import { setup } from '@nuxt/test-utils/e2e'
import { describe, test, expect } from 'vitest'
import type { NuxtMultiCacheOptions } from '../src/runtime/types'
import { createPageWithoutHydration } from './__helpers__'
import purgeAll from './__helpers__/purgeAll'

const multiCache: NuxtMultiCacheOptions = {
route: {
enabled: true,
},
data: {
enabled: true,
},
api: {
enabled: true,
authorization: false,
cacheTagInvalidationDelay: 5000,
},
}

const nuxtConfig: any = {
multiCache,
}

await setup({
server: true,
logLevel: 0,
runner: 'vitest',
build: true,
// browser: true,
rootDir: path.resolve(__dirname, './../playground-disk'),
nuxtConfig,
})

describe('Caching with the file system driver', () => {
test('correctly serves a cached page', async () => {
await purgeAll()
const page1 = await createPageWithoutHydration('/cachedPageFromDisk', 'en')
const text1 = await page1.locator('#random-number').innerText()
const page2 = await createPageWithoutHydration('/cachedPageFromDisk', 'en')
const text2 = await page2.locator('#random-number').innerText()

expect(text1).toEqual(text2)

await purgeAll()

const page3 = await createPageWithoutHydration('/cachedPageFromDisk', 'en')
const text3 = await page3.locator('#random-number').innerText()
expect(text3).to.not.equal(text1)
})

test('correctly returns a cached component', async () => {
await purgeAll()

const page1 = await createPageWithoutHydration('/cachedComponent', 'en')
const text1 = await page1.locator('#cached-component-number').innerText()
const page2 = await createPageWithoutHydration('/cachedComponent', 'en')
const text2 = await page2.locator('#cached-component-number').innerText()

expect(text1).toEqual(text2)

await purgeAll()

const page3 = await createPageWithoutHydration('/cachedComponent', 'en')
const text3 = await page3.locator('#cached-component-number').innerText()
expect(text3).to.not.equal(text1)
})
})

0 comments on commit 41e8ea5

Please sign in to comment.