Skip to content

Commit

Permalink
feat: add support for preloading assets in production
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien-R44 committed Mar 2, 2024
1 parent ba88a39 commit 5045c81
Show file tree
Hide file tree
Showing 3 changed files with 347 additions and 25 deletions.
91 changes: 80 additions & 11 deletions src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ export class Vite {
})
}

/**
* Generate an asset URL for a given asset path
*/
#generateAssetUrl(path: string): string {
return `${this.#options.assetsUrl}/${path}`
}

/**
* Generate a HTML tag for the given asset
*/
Expand All @@ -125,7 +132,7 @@ export class Vite {
if (this.isViteRunning) {
url = `/${asset}`
} else {
url = `${this.#options.assetsUrl}/${asset}`
url = this.#generateAssetUrl(asset)
}

if (this.#isCssPath(asset)) {
Expand All @@ -139,7 +146,7 @@ export class Vite {
* Generate style and script tags for the given entrypoints
* Also adds the @vite/client script
*/
#generateEntryPointsTagsForHotMode(
#generateEntryPointsTagsForDevMode(
entryPoints: string[],
attributes?: Record<string, any>
): AdonisViteElement[] {
Expand All @@ -154,16 +161,36 @@ export class Vite {
/**
* Get a chunk from the manifest file for a given file name
*/
#chunk(manifest: Manifest, fileName: string) {
const chunk = manifest[fileName]
#chunk(manifest: Manifest, entrypoint: string) {
const chunk = manifest[entrypoint]

if (!chunk) {
throw new Error(`Cannot find "${fileName}" chunk in the manifest file`)
throw new Error(`Cannot find "${entrypoint}" chunk in the manifest file`)
}

return chunk
}

/**
* Get a list of chunks for a given filename
*/
#chunksByFile(manifest: Manifest, file: string) {
return Object.entries(manifest)
.filter(([, chunk]) => chunk.file === file)
.map(([_, chunk]) => chunk)
}

/**
* Generate preload tag for a given url
*/
#makePreloadTagForUrl(url: string) {
const attributes = this.#isCssPath(url)
? { rel: 'preload', as: 'style', href: url }
: { rel: 'modulepreload', href: url }

return this.#generateElement({ tag: 'link', attributes })
}

/**
* Generate style and script tags for the given entrypoints
* using the manifest file
Expand All @@ -174,22 +201,64 @@ export class Vite {
): AdonisViteElement[] {
const manifest = this.manifest()
const tags: { path: string; tag: AdonisViteElement }[] = []
const preloads: Array<{ path: string }> = []

for (const entryPoint of entryPoints) {
/**
* 1. We generate tags + modulepreload for the entrypoint
*/
const chunk = this.#chunk(manifest, entryPoint)
preloads.push({ path: this.#generateAssetUrl(chunk.file) })
tags.push({
path: chunk.file,
tag: this.#generateTag(chunk.file, { ...attributes, integrity: chunk.integrity }),
})

/**
* 2. We go through the CSS files that are imported by the entrypoint
* and generate tags + preload for them
*/
for (const css of chunk.css || []) {
preloads.push({ path: this.#generateAssetUrl(css) })
tags.push({ path: css, tag: this.#generateTag(css) })
}

/**
* 3. We go through every import of the entrypoint and generate preload
*/
for (const importNode of chunk.imports || []) {
preloads.push({ path: this.#generateAssetUrl(manifest[importNode].file) })

/**
* 4. Finally, we generate tags + preload for the CSS files imported by the import
* of the entrypoint
*/
for (const css of manifest[importNode].css || []) {
const subChunk = this.#chunksByFile(manifest, css)

preloads.push({ path: this.#generateAssetUrl(css) })
tags.push({
path: this.#generateAssetUrl(css),
tag: this.#generateTag(css, {
...attributes,
integrity: subChunk[0]?.integrity,
}),
})
}
}
}

return uniqBy(tags, 'path')
.sort((a) => (a.path.endsWith('.css') ? -1 : 1))
.map((tag) => tag.tag)
/**
* We sort the preload to ensure that CSS files are preloaded first
*/
const preloadsElements = uniqBy(preloads, 'path')
.sort((preload) => (this.#isCssPath(preload.path) ? -1 : 1))
.map((preload) => this.#makePreloadTagForUrl(preload.path))

/**
* And finally, we return the preloads + script and link tags
*/
return preloadsElements.concat(tags.map(({ tag }) => tag))
}

/**
Expand All @@ -202,7 +271,7 @@ export class Vite {
entryPoints = Array.isArray(entryPoints) ? entryPoints : [entryPoints]

if (this.isViteRunning) {
return this.#generateEntryPointsTagsForHotMode(entryPoints, attributes)
return this.#generateEntryPointsTagsForDevMode(entryPoints, attributes)
}

return this.#generateEntryPointsTagsWithManifest(entryPoints, attributes)
Expand All @@ -216,15 +285,15 @@ export class Vite {
}

/**
* Returns path to a given asset file
* Returns path to a given asset file using the manifest file
*/
assetPath(asset: string): string {
if (this.isViteRunning) {
return `/${asset}`
}

const chunk = this.#chunk(this.manifest(), asset)
return `${this.#options.assetsUrl}/${chunk.file}`
return this.#generateAssetUrl(chunk.file)
}

/**
Expand Down
182 changes: 182 additions & 0 deletions tests/backend/fixtures/adonis_packages_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
{
"__plugin-vue_export-helper-DlAUqK2U.js": {
"file": "_plugin-vue_export-helper-DlAUqK2U.js"
},
"_default-!~{00h}~.js": {
"file": "default-CzWQScon.css",
"src": "_default-!~{00h}~.js"
},
"_default-Do-xftcX.js": {
"file": "default-Do-xftcX.js",
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"],
"css": ["default-CzWQScon.css"]
},
"_index-C1JNlH7D.js": {
"file": "index-C1JNlH7D.js",
"imports": ["resources/app.ts"]
},
"_main_section-!~{004}~.js": {
"file": "main_section-QGbeXyUe.css",
"src": "_main_section-!~{004}~.js"
},
"_main_section-CT1dtBDn.js": {
"file": "main_section-CT1dtBDn.js",
"isDynamicEntry": true,
"imports": [
"resources/app.ts",
"resources/pages/home/components/package_card.vue",
"__plugin-vue_export-helper-DlAUqK2U.js"
],
"css": ["main_section-QGbeXyUe.css"]
},
"_package_stats-BzvH-KEP.js": {
"file": "package_stats-BzvH-KEP.js",
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/app.ts": {
"file": "app-CGO3UiiC.js",
"src": "resources/app.ts",
"isEntry": true,
"dynamicImports": [
"resources/pages/home/components/button_group.vue",
"resources/pages/home/components/filters.vue",
"_main_section-CT1dtBDn.js",
"resources/pages/home/components/order.vue",
"resources/pages/home/components/package_card.vue",
"resources/pages/home/components/pagination.vue",
"resources/pages/home/components/search_bar.vue",
"resources/pages/home/components/select_menu.vue",
"resources/pages/home/main.vue",
"resources/pages/package/components/heading.vue",
"resources/pages/package/components/links.vue",
"resources/pages/package/components/toc.vue",
"resources/pages/package/main.vue"
],
"css": ["app-2kD3K4XR.css"],
"assets": ["PolySans-Neutral-DB_poC01.ttf", "Graphik-Regular-Cn31DaBb.ttf"]
},
"resources/assets/fonts/Graphik-Regular.ttf": {
"file": "Graphik-Regular-Cn31DaBb.ttf",
"src": "resources/assets/fonts/Graphik-Regular.ttf"
},
"resources/assets/fonts/PolySans-Neutral.ttf": {
"file": "PolySans-Neutral-DB_poC01.ttf",
"src": "resources/assets/fonts/PolySans-Neutral.ttf"
},
"resources/assets/noise.webp": {
"file": "noise-C0lGURVU.webp",
"src": "resources/assets/noise.webp"
},
"resources/assets/topography.svg": {
"file": "topography-CdPJiSxy.svg",
"src": "resources/assets/topography.svg"
},
"resources/pages/home/components/button_group.vue": {
"file": "button_group-BrQ8CWuu.js",
"src": "resources/pages/home/components/button_group.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/home/components/filters.vue": {
"file": "filters-Dmvaqb5E.js",
"src": "resources/pages/home/components/filters.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/home/components/order.vue": {
"file": "order-D6tpsh_Z.js",
"src": "resources/pages/home/components/order.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/home/components/package_card.vue": {
"file": "package_card-COzCRM-8.js",
"src": "resources/pages/home/components/package_card.vue",
"isDynamicEntry": true,
"imports": [
"resources/app.ts",
"__plugin-vue_export-helper-DlAUqK2U.js",
"_package_stats-BzvH-KEP.js"
],
"css": ["package_card-JrVjtBKi.css"]
},
"resources/pages/home/components/pagination.vue": {
"file": "pagination-FizlBWAv.js",
"src": "resources/pages/home/components/pagination.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/home/components/search_bar.vue": {
"file": "search_bar-DO-fkAsH.js",
"src": "resources/pages/home/components/search_bar.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/home/components/select_menu.vue": {
"file": "select_menu-SuftI0p0.js",
"src": "resources/pages/home/components/select_menu.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/home/main.vue": {
"file": "main-CKiOIoD7.js",
"src": "resources/pages/home/main.vue",
"isDynamicEntry": true,
"imports": [
"resources/app.ts",
"_index-C1JNlH7D.js",
"_main_section-CT1dtBDn.js",
"__plugin-vue_export-helper-DlAUqK2U.js",
"_default-Do-xftcX.js",
"resources/pages/home/components/order.vue",
"resources/pages/home/components/filters.vue",
"resources/pages/home/components/search_bar.vue",
"resources/pages/home/components/pagination.vue",
"resources/pages/home/components/select_menu.vue",
"resources/pages/home/components/button_group.vue",
"resources/pages/home/components/package_card.vue",
"_package_stats-BzvH-KEP.js"
],
"css": ["main-BcGYH63d.css"],
"assets": ["noise-C0lGURVU.webp", "topography-CdPJiSxy.svg"]
},
"resources/pages/package/components/heading.vue": {
"file": "heading-CsYW2tca.js",
"src": "resources/pages/package/components/heading.vue",
"isDynamicEntry": true,
"imports": [
"resources/app.ts",
"_package_stats-BzvH-KEP.js",
"__plugin-vue_export-helper-DlAUqK2U.js"
]
},
"resources/pages/package/components/links.vue": {
"file": "links-Dp_Qnu9y.js",
"src": "resources/pages/package/components/links.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/package/components/toc.vue": {
"file": "toc-C4T6XXuW.js",
"src": "resources/pages/package/components/toc.vue",
"isDynamicEntry": true,
"imports": ["resources/app.ts", "_index-C1JNlH7D.js", "__plugin-vue_export-helper-DlAUqK2U.js"]
},
"resources/pages/package/main.vue": {
"file": "main-Sqg_PXSY.js",
"src": "resources/pages/package/main.vue",
"isDynamicEntry": true,
"imports": [
"resources/app.ts",
"resources/pages/package/components/toc.vue",
"_default-Do-xftcX.js",
"resources/pages/package/components/links.vue",
"resources/pages/package/components/heading.vue",
"__plugin-vue_export-helper-DlAUqK2U.js",
"_index-C1JNlH7D.js",
"_package_stats-BzvH-KEP.js"
],
"css": ["main-LIhZCNSE.css"],
"assets": ["noise-C0lGURVU.webp", "topography-CdPJiSxy.svg"]
}
}
Loading

0 comments on commit 5045c81

Please sign in to comment.