Skip to content

Commit

Permalink
feat: http caching for vue requests
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 27, 2020
1 parent fd1ddaa commit ecc7219
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/node/serverPluginHmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const hmrPlugin: Plugin = ({ root, app, server, watcher, resolver }) => {
}
debug('serving hmr client')
ctx.type = 'js'
ctx.body = await cachedRead(hmrClientFilePath)
await cachedRead(ctx, hmrClientFilePath)
})

// start a websocket server to send hmr notifications to the client
Expand Down
20 changes: 10 additions & 10 deletions src/node/serverPluginModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ const fileToIdMap = new Map()
const webModulesMap = new Map()
const rewriteCache = new LRUCache({ max: 65535 })

export const modulesPlugin: Plugin = ({ root, app, watcher }) => {
export const modulesPlugin: Plugin = ({ root, app, watcher, resolver }) => {
// bust module rewrite cache on file change
watcher.on('change', (file) => {
// TODO also need logic for reverse mapping file to servedPath
const servedPath = '/' + path.relative(root, file)
debugImportRewrite(`${servedPath}: cache busted`)
rewriteCache.del(servedPath)
// TODO also need logic for reverse mapping file to publicPath
const publicPath = resolver.fileToPublic(file)
debugImportRewrite(`${publicPath}: cache busted`)
rewriteCache.del(publicPath)
})

// rewrite named module imports to `/@modules/:id` requests
Expand Down Expand Up @@ -99,16 +99,16 @@ export const modulesPlugin: Plugin = ({ root, app, watcher }) => {
// special handling for vue's runtime.
if (id === 'vue') {
const vuePath = resolveVue(root).vue
ctx.body = await cachedRead(vuePath)
await cachedRead(ctx, vuePath)
debugModuleResolution(`vue -> ${getDebugPath(vuePath)}`)
return
}

// already resolved and cached
const cachedPath = idToFileMap.get(id)
if (cachedPath) {
await cachedRead(ctx, cachedPath)
debugModuleResolution(`(cached) ${id} -> ${getDebugPath(cachedPath)}`)
ctx.body = await cachedRead(cachedPath)
return
}

Expand All @@ -135,8 +135,8 @@ export const modulesPlugin: Plugin = ({ root, app, watcher }) => {
path.basename(sourceMapRequest)
)
idToFileMap.set(sourceMapRequest, sourceMapPath)
await cachedRead(ctx, sourceMapPath)
ctx.type = 'application/json'
ctx.body = await cachedRead(sourceMapPath)
debugModuleResolution(
`(source map) ${id} -> ${getDebugPath(sourceMapPath)}`
)
Expand All @@ -149,7 +149,7 @@ export const modulesPlugin: Plugin = ({ root, app, watcher }) => {
if (webModulePath) {
idToFileMap.set(id, webModulePath)
fileToIdMap.set(path.basename(webModulePath), id)
ctx.body = await cachedRead(webModulePath)
await cachedRead(ctx, webModulePath)
debugModuleResolution(`${id} -> ${getDebugPath(webModulePath)}`)
return
}
Expand Down Expand Up @@ -179,7 +179,7 @@ export const modulesPlugin: Plugin = ({ root, app, watcher }) => {
idToFileMap.set(id, modulePath)
fileToIdMap.set(path.basename(modulePath), id)
debugModuleResolution(`${id} -> ${getDebugPath(modulePath)}`)
ctx.body = await cachedRead(modulePath)
await cachedRead(ctx, modulePath)
} catch (e) {
console.error(
chalk.red(`[vite] Error while resolving node_modules with id "${id}":`)
Expand Down
52 changes: 37 additions & 15 deletions src/node/serverPluginVue.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { promises as fs } from 'fs'
import { Plugin } from './server'
import {
SFCDescriptor,
Expand All @@ -7,11 +8,13 @@ import {
} from '@vue/compiler-sfc'
import { resolveCompiler } from './resolveVue'
import hash_sum from 'hash-sum'
import { cachedRead } from './utils'
import LRUCache from 'lru-cache'
import { hmrClientPublicPath } from './serverPluginHmr'
import resolve from 'resolve-from'
import { Context } from 'koa'
import { cachedRead } from './utils'

const getEtag = require('etag')
const debug = require('debug')('vite:sfc')

interface CacheEntry {
Expand All @@ -34,7 +37,10 @@ export const vuePlugin: Plugin = ({ root, app, resolver }) => {
const query = ctx.query
const publicPath = ctx.path
const filePath = resolver.publicToFile(publicPath)
const descriptor = await parseSFC(root, filePath)
const timestamp = query.t

await cachedRead(ctx, filePath)
const descriptor = await parseSFC(root, filePath, ctx.body)

if (!descriptor) {
debug(`${ctx.url} - 404`)
Expand All @@ -44,23 +50,20 @@ export const vuePlugin: Plugin = ({ root, app, resolver }) => {

if (!query.type) {
ctx.type = 'js'
ctx.body = compileSFCMain(
descriptor,
filePath,
publicPath,
query.t as string
)
ctx.body = compileSFCMain(descriptor, filePath, publicPath, timestamp)
return
}

if (query.type === 'template') {
ctx.type = 'js'
ctx.body = compileSFCTemplate(
ctx,
root,
descriptor.template!,
filePath,
publicPath,
descriptor.styles.some((s) => s.scoped)
descriptor.styles.some((s) => s.scoped),
timestamp
)
return
}
Expand All @@ -69,11 +72,13 @@ export const vuePlugin: Plugin = ({ root, app, resolver }) => {
const index = Number(query.index)
const styleBlock = descriptor.styles[index]
const result = await compileSFCStyle(
ctx,
root,
styleBlock,
index,
filePath,
publicPath
publicPath,
timestamp
)
if (query.module != null) {
ctx.type = 'js'
Expand Down Expand Up @@ -101,7 +106,7 @@ export async function parseSFC(

if (!content) {
try {
content = await cachedRead(filename, 'utf-8')
content = await fs.readFile(filename, 'utf-8')
} catch (e) {
return
}
Expand Down Expand Up @@ -191,17 +196,25 @@ function compileSFCMain(
}

function compileSFCTemplate(
ctx: Context,
root: string,
template: SFCTemplateBlock,
filename: string,
pathname: string,
scoped: boolean
scoped: boolean,
timestamp: string | undefined
): string {
let cached = vueCache.get(filename)
if (cached && cached.template) {
if (timestamp) {
ctx.status = 200
} else {
ctx.etag = getEtag(cached.template)
}
return cached.template
}

ctx.status = 200
const { code, errors } = resolveCompiler(root).compileTemplate({
source: template.content,
filename,
Expand All @@ -222,17 +235,26 @@ function compileSFCTemplate(
}

async function compileSFCStyle(
ctx: Context,
root: string,
style: SFCStyleBlock,
index: number,
filename: string,
pathname: string
pathname: string,
timestamp: string | undefined
): Promise<SFCStyleCompileResults> {
let cached = vueCache.get(filename)
if (cached && cached.styles && cached.styles[index]) {
return cached.styles[index]
const cachedEntry = cached && cached.styles && cached.styles[index]
if (cachedEntry) {
if (timestamp) {
ctx.status = 200
} else {
ctx.etag = getEtag(cachedEntry.code)
}
return cachedEntry
}

ctx.status = 200
const id = hash_sum(pathname)
const result = await resolveCompiler(root).compileStyleAsync({
source: style.content,
Expand Down
29 changes: 21 additions & 8 deletions src/node/utils.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
import path from 'path'
import { promises as fs } from 'fs'
import LRUCache from 'lru-cache'
import os from 'os'
import { Context } from 'koa'

const getETag = require('etag')

interface CacheEntry {
lastModified: number
etag: string
content: Buffer | string
}

const moduleReadCache = new LRUCache<string, CacheEntry>({
max: 10000
})

export function cachedRead(path: string): Promise<Buffer>
export function cachedRead(path: string, encoding: string): Promise<string>
export async function cachedRead(path: string, encoding?: string) {
const lastModified = (await fs.stat(path)).mtimeMs
const cached = moduleReadCache.get(path)
export async function cachedRead(ctx: Context, file: string) {
const lastModified = (await fs.stat(file)).mtimeMs
const cached = moduleReadCache.get(file)
ctx.set('Cache-Control', 'no-cache')
ctx.type = path.basename(file)
if (cached && cached.lastModified === lastModified) {
ctx.etag = cached.etag
ctx.lastModified = new Date(cached.lastModified)
ctx.status = 304
return cached.content
}
const content = await fs.readFile(path, encoding)
moduleReadCache.set(path, {
const content = await fs.readFile(file, 'utf-8')
const etag = getETag(content)
moduleReadCache.set(file, {
content,
etag,
lastModified
})
return content
ctx.etag = etag
ctx.lastModified = new Date(lastModified)
ctx.body = content
ctx.status = 200
}

export function getIPv4AddressList(): string[] {
Expand Down

0 comments on commit ecc7219

Please sign in to comment.