vite icon indicating copy to clipboard operation
vite copied to clipboard

Css font imports in manifest.json

Open indykoning opened this issue 1 year ago • 7 comments

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

indykoning avatar Oct 18 '24 09:10 indykoning

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.

royduin avatar Oct 18 '24 10:10 royduin

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

indykoning avatar Dec 03 '24 09:12 indykoning

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"]
  }
}

daun avatar Jun 16 '25 15:06 daun

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"
    ]
  }
}

adamkoppede avatar Oct 27 '25 23:10 adamkoppede