Skip to content

Commit 5dde2d1

Browse files
authored
refactor!: move resource parsing -> manifest stage and expose /runtime export (#33)
1 parent 78f9777 commit 5dde2d1

20 files changed

+226
-129
lines changed

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,25 @@ pnpm add vue-bundle-renderer
2222
### `createRenderer`
2323

2424
```ts
25-
import { createRenderer } from 'vue-bundle-renderer'
25+
import { createRenderer } from 'vue-bundle-renderer/runtime'
2626

27-
declare function createRenderer(createApp:, renderOptions: RenderOptions)
27+
declare function createRenderer(createApp, renderOptions: RenderOptions)
2828
```
2929

30-
### `normalizeClientManifest`
30+
### `normalizeViteManifest`
3131

32-
If using a webpack manifest, you should normalize it with this function before passing it to `createRenderer`.
32+
```ts
33+
import { normalizeViteManifest } from 'vue-bundle-renderer'
34+
35+
declare function normalizeViteManifest(manifest: ViteManifest)
36+
```
37+
38+
### `normalizeWebpackManifest`
3339

3440
```ts
35-
import { normalizeClientManifest } from 'vue-bundle-renderer/legacy'
41+
import { normalizeWebpackManifest } from 'vue-bundle-renderer'
3642
37-
declare function normalizeClientManifest(manifest: ClientManifest | LegacyClientManifest)
43+
declare function normalizeWebpackManifest(manifest: ViteManifest)
3844
```
3945

4046
## Credits

legacy.d.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@
99
"require": "./dist/index.cjs",
1010
"import": "./dist/index.mjs"
1111
},
12-
"./legacy": {
13-
"require": "./dist/legacy.cjs",
14-
"import": "./dist/legacy.mjs"
12+
"./runtime": {
13+
"require": "./dist/runtime.cjs",
14+
"import": "./dist/runtime.mjs"
1515
}
1616
},
1717
"main": "./dist/index.cjs",
1818
"module": "./dist/index.mjs",
1919
"types": "./dist/index.d.ts",
2020
"files": [
2121
"dist",
22-
"legacy.d.ts"
22+
"runtime.d.ts"
2323
],
2424
"scripts": {
2525
"build": "unbuild",
2626
"dev": "vitest",
2727
"lint": "eslint --ext .ts src",
2828
"prerelease": "pnpm test && pnpm build && standard-version -p && git push --follow-tags && pnpm publish --tag prerelease",
2929
"release": "pnpm test && pnpm build && standard-version && git push --follow-tags && pnpm publish",
30-
"test": "pnpm lint && pnpm vitest run --coverage"
30+
"test": "pnpm lint && pnpm vitest run --coverage && tsc --noEmit"
3131
},
3232
"dependencies": {
3333
"ufo": "^0.8.3"
@@ -36,6 +36,7 @@
3636
"@nuxtjs/eslint-config-typescript": "latest",
3737
"c8": "^7.12.0",
3838
"eslint": "latest",
39+
"expect-type": "^0.13.0",
3940
"standard-version": "latest",
4041
"typescript": "latest",
4142
"unbuild": "latest",

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './dist/runtime'

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
export * from './renderer'
1+
import { Manifest } from './manifest'
2+
3+
export { normalizeViteManifest } from './vite'
4+
export { normalizeWebpackManifest } from './webpack'
5+
export type { Manifest, ResourceMeta } from './manifest'
6+
7+
export function defineManifest (manifest: Manifest): Manifest {
8+
return manifest
9+
}

src/manifest.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export interface ResourceMeta {
2+
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/manifest.ts#L8-L19
3+
src?: string
4+
file: string
5+
css?: string[]
6+
assets?: string[]
7+
isEntry?: boolean
8+
isDynamicEntry?: boolean
9+
imports?: string[]
10+
dynamicImports?: string[]
11+
// Augmentation for vue-bundle-renderer
12+
module?: boolean
13+
// https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#what_types_of_content_can_be_preloaded
14+
resourceType?: 'audio' | 'document' | 'embed' | 'fetch' | 'font' | 'image' | 'object' | 'script' | 'style' | 'track' | 'worker' | 'video'
15+
mimeType?: string
16+
}
17+
18+
export interface Manifest {
19+
[key: string]: ResourceMeta
20+
}

src/renderer.ts renamed to src/runtime.ts

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import type { Manifest, ManifestChunk } from 'vite'
21
import { withLeadingSlash } from 'ufo'
3-
import { renderLinkToString, renderLinkToHeader, renderScriptToString, parseResource } from './utils'
4-
import type { LinkAttributes, ParsedResource } from './utils'
2+
import type { Manifest, ResourceMeta } from './manifest'
3+
import { LinkAttributes, renderLinkToHeader, renderLinkToString, renderScriptToString } from './runtime/utils'
54

65
export interface ModuleDependencies {
7-
scripts: Record<string, ParsedResource>
8-
styles: Record<string, ParsedResource>
9-
preload: Record<string, ParsedResource>
10-
prefetch: Record<string, ParsedResource>
6+
scripts: Record<string, ResourceMeta>
7+
styles: Record<string, ResourceMeta>
8+
preload: Record<string, ResourceMeta>
9+
prefetch: Record<string, ResourceMeta>
1110
}
1211

1312
export interface SSRContext {
@@ -24,22 +23,21 @@ export interface SSRContext {
2423
}
2524

2625
export interface RenderOptions {
27-
shouldPrefetch?: (resource: ParsedResource) => boolean
28-
shouldPreload?: (resource: ParsedResource) => boolean
26+
shouldPrefetch?: (resource: ResourceMeta) => boolean
27+
shouldPreload?: (resource: ResourceMeta) => boolean
2928
buildAssetsURL?: (id: string) => string
3029
manifest: Manifest
3130
}
3231

3332
export interface RendererContext extends Required<RenderOptions> {
3433
_dependencies: Record<string, ModuleDependencies>
3534
_dependencySets: Record<string, ModuleDependencies>
36-
_parsedResources: Record<string, ParsedResource>
3735
_entrypoints: string[]
3836
updateManifest: (manifest: Manifest) => void
3937
}
4038

4139
const defaultShouldPrefetch = () => true
42-
const defaultShouldPreload = (resource: ParsedResource) => ['module', 'script', 'style'].includes(resource.asType || '')
40+
const defaultShouldPreload = (resource: ResourceMeta) => ['module', 'script', 'style'].includes(resource.resourceType || '')
4341

4442
export function createRendererContext ({ manifest, buildAssetsURL, shouldPrefetch, shouldPreload }: RenderOptions): RendererContext {
4543
const ctx: RendererContext = {
@@ -53,16 +51,14 @@ export function createRendererContext ({ manifest, buildAssetsURL, shouldPrefetc
5351
// Internal cache
5452
_dependencies: undefined!,
5553
_dependencySets: undefined!,
56-
_parsedResources: undefined!,
5754
_entrypoints: undefined!
5855
}
5956

6057
function updateManifest (manifest: Manifest) {
61-
const manifestEntries = Object.entries(manifest) as [string, ManifestChunk][]
58+
const manifestEntries = Object.entries(manifest) as [string, ResourceMeta][]
6259
ctx.manifest = manifest
6360
ctx._dependencies = {}
6461
ctx._dependencySets = {}
65-
ctx._parsedResources = {}
6662
ctx._entrypoints = manifestEntries.filter(e => e[1].isEntry).map(([module]) => module)
6763
}
6864

@@ -92,16 +88,16 @@ export function getModuleDependencies (id: string, rendererContext: RendererCont
9288

9389
// Add to scripts + preload
9490
if (meta.file) {
95-
dependencies.scripts[id] = dependencies.preload[id] = rendererContext._parsedResources[meta.file] || parseResource(meta.file)
91+
dependencies.scripts[id] = dependencies.preload[id] = rendererContext.manifest[id]
9692
}
9793

9894
// Add styles + preload
9995
for (const css of meta.css || []) {
100-
dependencies.styles[css] = dependencies.preload[css] = dependencies.prefetch[css] = rendererContext._parsedResources[css] || parseResource(css)
96+
dependencies.styles[css] = dependencies.preload[css] = dependencies.prefetch[css] = rendererContext.manifest[css]
10197
}
10298
// Add assets as preload
10399
for (const asset of meta.assets || []) {
104-
dependencies.preload[asset] = dependencies.prefetch[asset] = rendererContext._parsedResources[asset] || parseResource(asset)
100+
dependencies.preload[asset] = dependencies.prefetch[asset] = rendererContext.manifest[asset]
105101
}
106102
// Resolve nested dependencies and merge
107103
for (const depId of meta.imports || []) {
@@ -194,7 +190,7 @@ export function getRequestDependencies (ssrContext: SSRContext, rendererContext:
194190
export function renderStyles (ssrContext: SSRContext, rendererContext: RendererContext): string {
195191
const { styles } = getRequestDependencies(ssrContext, rendererContext)
196192
return Object.values(styles).map(resource =>
197-
renderLinkToString({ rel: 'stylesheet', href: rendererContext.buildAssetsURL(resource.path) })
193+
renderLinkToString({ rel: 'stylesheet', href: rendererContext.buildAssetsURL(resource.file) })
198194
).join('')
199195
}
200196

@@ -216,31 +212,31 @@ export function getPreloadLinks (ssrContext: SSRContext, rendererContext: Render
216212
const { preload } = getRequestDependencies(ssrContext, rendererContext)
217213
return Object.values(preload)
218214
.map(resource => ({
219-
rel: resource.isModule ? 'modulepreload' : 'preload',
220-
as: resource.asType,
221-
type: resource.contentType,
222-
crossorigin: resource.asType === 'font' || resource.isModule ? '' : null,
223-
href: rendererContext.buildAssetsURL(resource.path)
215+
rel: resource.module ? 'modulepreload' : 'preload',
216+
as: resource.resourceType,
217+
type: resource.mimeType ?? null,
218+
crossorigin: resource.resourceType === 'font' || resource.module ? '' : null,
219+
href: rendererContext.buildAssetsURL(resource.file)
224220
}))
225221
}
226222

227223
export function getPrefetchLinks (ssrContext: SSRContext, rendererContext: RendererContext): LinkAttributes[] {
228224
const { prefetch } = getRequestDependencies(ssrContext, rendererContext)
229225
return Object.values(prefetch).map(resource => ({
230-
rel: 'prefetch' + (resource.asType === 'style' ? ' stylesheet' : ''),
231-
as: resource.asType !== 'style' ? resource.asType : null,
232-
type: resource.contentType,
233-
crossorigin: resource.asType === 'font' || resource.isModule ? '' : null,
234-
href: rendererContext.buildAssetsURL(resource.path)
226+
rel: 'prefetch' + (resource.resourceType === 'style' ? ' stylesheet' : ''),
227+
as: resource.resourceType !== 'style' ? resource.resourceType : null,
228+
type: resource.mimeType ?? null,
229+
crossorigin: resource.resourceType === 'font' || resource.module ? '' : null,
230+
href: rendererContext.buildAssetsURL(resource.file)
235231
}))
236232
}
237233

238234
export function renderScripts (ssrContext: SSRContext, rendererContext: RendererContext): string {
239235
const { scripts } = getRequestDependencies(ssrContext, rendererContext)
240236
return Object.values(scripts).map(resource => renderScriptToString({
241-
type: resource.isModule ? 'module' : null,
242-
src: rendererContext.buildAssetsURL(resource.path),
243-
defer: resource.isModule ? null : '',
237+
type: resource.module ? 'module' : null,
238+
src: rendererContext.buildAssetsURL(resource.file),
239+
defer: resource.module ? null : '',
244240
crossorigin: ''
245241
})).join('')
246242
}

src/runtime/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Utilities to render script and link tags, and link headers
2+
export const renderScriptToString = (attrs: Record<string, string | null>) =>
3+
`<script${Object.entries(attrs).map(([key, value]) => value === null ? '' : value ? ` ${key}="${value}"` : ' ' + key).join('')}></script>`
4+
5+
export type LinkAttributes = {
6+
rel: string | null
7+
href: string
8+
as?: string | null
9+
type?: string | null
10+
crossorigin?: '' | null
11+
}
12+
13+
export const renderLinkToString = (attrs: LinkAttributes) =>
14+
`<link${Object.entries(attrs).map(([key, value]) => value === null ? '' : value ? ` ${key}="${value}"` : ' ' + key).join('')}>`
15+
16+
export const renderLinkToHeader = (attrs: LinkAttributes) =>
17+
`<${attrs.href}>${Object.entries(attrs).map(([key, value]) => key === 'href' || value === null ? '' : value ? `; ${key}="${value}"` : `; ${key}`).join('')}`

src/utils.ts

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ResourceMeta } from './manifest'
2+
13
const IS_JS_RE = /\.[cm]?js(\?[^.]+)?$/
24
const HAS_EXT_RE = /[^./]+\.[^./]+$/
35
const IS_CSS_RE = /\.(css|postcss|sass|scss|less|stylus|styl)(\?[^.]+)?$/
@@ -15,21 +17,13 @@ const FONT_RE = /^woff2?|ttf|otf|eot$/
1517
const AUDIO_RE = /^mp3|wav|ogg|flac|aac|m4a|wma|aiff|aif|au|raw|vox|opus$/
1618
const VIDEO_RE = /^mp4|webm|ogv|mkv|avi|mov|flv|wmv|mpg|mpeg|m4v|3gp|3g2|mxf|rm|rmvb|asf|asx|m3u8|m3u|pls|cue|m3u8$/
1719

18-
export interface ParsedResource {
19-
path: string
20-
isModule: boolean
21-
// https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#what_types_of_content_can_be_preloaded
22-
asType: 'audio' | 'document' | 'embed' | 'fetch' | 'font' | 'image' | 'object' | 'script' | 'style' | 'track' | 'worker' | 'video' | null
23-
contentType: string | null
24-
}
25-
2620
const contentTypeMap: Record<string, string> = {
2721
ico: 'image/x-icon',
2822
jpg: 'image/jpeg',
2923
svg: 'image/svg+xml'
3024
}
3125

32-
export function getContentType (asType: ParsedResource['asType'], extension: string) {
26+
export function getContentType (asType: ResourceMeta['resourceType'], extension: string) {
3327
if (asType === 'font') {
3428
return `font/${extension}`
3529
}
@@ -38,7 +32,7 @@ export function getContentType (asType: ParsedResource['asType'], extension: str
3832
}
3933
}
4034

41-
export function getAsType (ext: string): ParsedResource['asType'] {
35+
export function getAsType (ext: string): ResourceMeta['resourceType'] {
4236
if (ext === 'js' || ext === 'cjs' || ext === 'mjs') {
4337
return 'script'
4438
} else if (ext === 'css') {
@@ -51,37 +45,28 @@ export function getAsType (ext: string): ParsedResource['asType'] {
5145
return 'audio'
5246
} else if (VIDEO_RE.test(ext)) {
5347
return 'video'
54-
} else {
55-
// not exhausting all possibilities here, but above covers common cases
56-
return null
5748
}
49+
// not exhausting all possibilities here, but above covers common cases
5850
}
5951

60-
// Utilities to render script and link tags, and link headers
61-
export const renderScriptToString = (attrs: Record<string, string | null>) =>
62-
`<script${Object.entries(attrs).map(([key, value]) => value === null ? '' : value ? ` ${key}="${value}"` : ' ' + key).join('')}></script>`
52+
export const parseResource = (path: string) => {
53+
const chunk: Omit<ResourceMeta, 'file'> = {}
6354

64-
export type LinkAttributes = {
65-
rel: string | null
66-
href: string
67-
as?: string | null
68-
type?: string | null
69-
crossorigin?: '' | null
70-
}
55+
const extension = path.replace(/\?.*/, '').split('.').pop() || ''
7156

72-
export const renderLinkToString = (attrs: LinkAttributes) =>
73-
`<link${Object.entries(attrs).map(([key, value]) => value === null ? '' : value ? ` ${key}="${value}"` : ' ' + key).join('')}>`
57+
const asType = getAsType(extension)
58+
if (asType) {
59+
chunk.resourceType = asType
7460

75-
export const renderLinkToHeader = (attrs: LinkAttributes) =>
76-
`<${attrs.href}>${Object.entries(attrs).map(([key, value]) => key === 'href' || value === null ? '' : value ? `; ${key}="${value}"` : `; ${key}`).join('')}`
61+
if (asType === 'script') {
62+
chunk.module = true
63+
}
64+
}
7765

78-
export const parseResource = (path: string): ParsedResource => {
79-
const extension = path.replace(/\?.*/, '').split('.').pop() || ''
80-
const asType = getAsType(extension)
81-
return {
82-
path,
83-
asType,
84-
isModule: extension === 'mjs',
85-
contentType: getContentType(asType, extension) || null
66+
const contentType = getContentType(asType, extension)
67+
if (contentType) {
68+
chunk.mimeType = contentType
8669
}
70+
71+
return chunk
8772
}

0 commit comments

Comments
 (0)