Css font imports in manifest.json
Description
Currently fonts are properly added to the manifest.json but you cannot see which e.g. css/vue files require this font.
This makes automatic preloading difficult as we cannot determine which file requires the font, and IF we need to require the font for the initial page load.
https://stackblitz.com/edit/github-citlf9?file=package.json,src%2Findex.css&terminal=build
Suggested solution
I suggest adding an imports key to css files within the manifest.json just like is done with JS.
This may contain imported css (which is not relevant as everything currently gets compiled to a single css file)
and fonts required by the current css or vue file.
This way when (pre)loading the css or vue file, we know we should also (pre)load those font files
Alternative
Preloading of the fonts either needs to be done manually, or ALL fonts in the manifest.json must be loaded. Which isn't ideal since likely not all fonts are actually used on the current page.
Additional context
No response
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
An example, currently the Vite just adds all the fonts with no relation to the css file where the font was discovered in the manifest:
"resources/css/app.css": {
"file": "assets/app-DE7dJou-.css",
"src": "resources/css/app.css",
"isEntry": true
},
"resources/css/fonts/Nunito-Bold.woff": {
"file": "assets/Nunito-Bold-C6dW0HYt.woff",
"src": "resources/css/fonts/Nunito-Bold.woff"
},
Where Javascript files do have a reference with dynamic imports:
"resources/js/app.js": {
"file": "assets/app-CNshmeXx.js",
"name": "app",
"src": "resources/js/app.js",
"isEntry": true,
"dynamicImports": [
"_Listing-DnGV7WH4.js"
],
},
"_Listing-DnGV7WH4.js": {
"file": "assets/Listing-DnGV7WH4.js",
"name": "Listing",
"isDynamicEntry": true,
"imports": [
"resources/js/app.js"
]
},
It would be nice to have something like:
"resources/css/app.css": {
"file": "assets/app-DE7dJou-.css",
"src": "resources/css/app.css",
"isEntry": true,
"fonts": [
"resources/css/fonts/Nunito-Bold.woff"
]
},
"resources/css/fonts/Nunito-Bold.woff": {
"file": "assets/Nunito-Bold-C6dW0HYt.woff",
"src": "resources/css/fonts/Nunito-Bold.woff",
"foundIn": [
"resources/css/app.css"
]
},
With that we can use the new fonts array to add preload tags in the html and only get fonts from that CSS file instead of all fonts from all CSS files which aren't always used. For example multisite with a CSS per store.
I'm guessing somewhere here we need to have access to the bundle so we can set the bundle.imports to the files we find here
https://github.com/vitejs/vite/blob/ccee3d7c7d34fc66854029f27f6cc89de7dcf3c5/packages/vite/src/node/plugins/css.ts#L643
I've published a vite plugin that adds information about font imports to the manifest: vite-plugin-font-manifest. Working great in our use case, but would appreciate feedback if anybody wants to give it a try!
import { defineConfig } from 'vite'
import fontManifest from 'vite-plugin-font-manifest'
export default defineConfig({
plugins: [
fontManifest(),
]
})
{
"src/fonts/Inter-Regular.woff2": {
"file": "assets/Inter-Regular-CnZ_CWUo.woff2",
"src": "src/fonts/Inter-Regular.woff2",
"fontFace": {
"family": "Inter",
"weight": "400",
"style": "normal",
"mime": "font/woff2",
"css": "@font-face { /* */ }",
"definedIn": ["src/css/display.css"]
}
},
"src/css/display.css": {
"fonts": ["src/fonts/Inter-Regular.woff2"]
}
}
Automatic <link rel=preload> for fonts via the manifest.json Backend Integration already works in Vite v7.1.12, as long as there is some JavaScript entry point that includes the CSS entry point via import statement.
I think it should be a bug. The "assets" manifest.json property should not behave differently depending if there happens to be some other JavaScript entry point or not.
For the specific use case where I stumbled over this issue, I was able to just combine the CSS and JavaScript entry points as they were always used together.
In details, the following works in v7.1.12:
// vite.config.ts
export default defineConfig({
base: "./",
build: {
manifest: "manifest.json",
rollupOptions: {
input: ["test.css", "test.js"],
},
},
});
// test.js
import "./test.css"
/* test.css */
@font-face {
font-family: "my-font";
src: url("./font.woff2") format("woff2");
}
The resulting `manifest.json` contains the "assets" key for the CSS entry point:
{
"font.woff2": {
"file": "assets/font-BRpGEcXM.woff2",
"src": "font.woff2"
},
"test.css": {
"file": "assets/test-Bz9E2gFy.js",
"name": "test",
"src": "test.css",
"isEntry": true,
"css": [
"assets/test-BQohd8LT.css"
],
"assets": [
"assets/font-BRpGEcXM.woff2"
]
},
"test.js": {
"file": "assets/test-C4i5TFBK.js",
"name": "test",
"src": "test.js",
"isEntry": true,
"imports": [
"test.css"
]
}
}
If the JavaScript entry point is missing:
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
base: "./",
build: {
manifest: "manifest.json",
rollupOptions: {
input: ["test.css"],
},
},
});
The "asset" key will not be set on the CSS entry point in the generated `manifest.json`:
{
"font.woff2": {
"file": "assets/font-BRpGEcXM.woff2",
"src": "font.woff2"
},
"test.css": {
"file": "assets/test-BQohd8LT.css",
"src": "test.css",
"isEntry": true,
"names": [
"test.css"
]
}
}
If only the JavaScript entry point is specified,
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
base: "./",
build: {
manifest: "manifest.json",
rollupOptions: {
input: ["test.js"],
},
},
});
no empty JavaScript dist file will be generated for the CSS file:
{
"font.woff2": {
"file": "assets/font-BRpGEcXM.woff2",
"src": "font.woff2"
},
"test.js": {
"file": "assets/test-Bz9E2gFy.js",
"name": "test",
"src": "test.js",
"isEntry": true,
"css": [
"assets/test-BQohd8LT.css"
],
"assets": [
"assets/font-BRpGEcXM.woff2"
]
}
}