vite: manifest.json does not include assets

Describe the bug

Statically imported assets (SVG file in my case) will get bundled and created in the outDir, but are missing in the generated manifest.json.

vite.config.js

import { defineConfig } from 'vite';
import legacy from '@vitejs/plugin-legacy';

export default defineConfig({
    base: '/static/',
    publicDir: 'assets/static/',
    build: {
        manifest: true,
        outDir: 'public/static/',
        assetsInlineLimit: 0,
        rollupOptions: {
            input: 'assets/main.ts',
        },
    },
    plugins: [legacy()],
});

assets/main.ts

import './icon.svg';

manifest.json

{
  "assets/main-legacy.ts": {
    "file": "assets/main-legacy.dd90b2c6.js",
    "src": "assets/main-legacy.ts",
    "isEntry": true
  },
  "assets/main.ts": {
    "file": "assets/main.9470b65c.js",
    "src": "assets/main.ts",
    "isEntry": true
  }
}

output files

  • main.9470b65c.js
  • main-legacy.dd90b2c6.js
  • icon.a0d73e22.svg

System Info

  • vite version: 2.0.5
  • Operating System: Windows 10
  • Node version: 12.16.1
  • Package manager (npm/yarn/pnpm) and version: npm 7.5.3

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 3
  • Comments: 20 (6 by maintainers)

Most upvoted comments

Had a look and Vite is already tracking and outputting any assets regardless of their usage so I think it makes sense to expose them in the manifest so those integrating with a backend can take advantage of Vite for processing assets not directly used by Vite.

In my testing importing static assets via import.meta.glob is enough for Vite to process them even if not referenced.

src
├── entry.scss
├── files
│   └── favicon.svg
├── fonts
│   ├── my-font.woff
│   └── my-font.woff2
├── main.scss
└── main.ts

vite.config.js

export default defineConfig({
  build: {
    rollupOptions: {
      input: 'src/main.ts'
    }
  }
})

src/main.ts


import './main.scss'

let files = import.meta.glob('./files/**/*');

// The "files" variable doesn't need to used for imports to get processed.

src/main.scss

@font-face {
  font-family: "Font";
  font-style: normal;
  font-weight: 400;
  src:
    url("/src/fonts/my-font.woff2") format("woff2"),
    url("/src/fonts/my-font.woff") format("woff");
}

With the above Vite will process and output files matching the glob ./files/**/*. The only missing bit is a way to get the final path. Looking at generateBundle the asset chunks are present but not exposed so I propose we add a _assets entry within the manifest that contains all assets Vite has processed e.g.

{
  "_assets": {
    "src/files/favicon.svg": "assets/favicon.17e50649.svg",
    "src/fonts/my-font.woff2": "assets/my-font.bf47658a.woff2",
    "src/fonts/my-font.woff": "assets/my-font.80d4cac9.woff",
    "main.css": "assets/main.eac10a5f.css"
  },
  "src/main.ts": {
    "file": "assets/main.6097d7e4.js",
    "src": "src/main.ts",
    "isEntry": true,
    "css": [
      "assets/main.eac10a5f.css"
    ],
    "assets": [
      "assets/my-font.bf47658a.woff2",
      "assets/my-font.80d4cac9.woff"
    ]
  }
}

The _assets entry contains assets both used within chunks i.e. fonts and non used assets i.e. those imported via the glob. This is desirable as it then allows you to more easily reference files like fonts for things like generating preload directives and other assets such as favicons or static assets referenced in the frameworks templating language.

I’ve quickly modified vite/packages/vite/src/node/plugins/manifest.ts to achieve the above but I’m likely missing something.

if (chunk.type === 'asset') {
  if (!manifest['_assets']) {
    manifest['_assets'] = {}
  }
  let name = normalizePath(
    path.relative(config.root, chunk.name)
  )
  manifest['_assets'][name] = chunk.fileName
}

The only thing that isn’t quite right is how css is handled as Vite considers it an asset but the full path is stripped by the time the manifest is generated i.e. instead of src/main.scss it appears as main.css.

I’m not sure if we should remove the CSS properties or try to add the full path back in. If we did it keep it and use the source path it would then be possible to use css as an entry.

export default defineConfig({
  build: {
    rollupOptions: {
      input: [
        'src/main.ts',
        'src/entry.scss',
      ]
    }
  }
})

This is currently possible (not sure if intentional) but you’d have to remember to reference your css by the file name (entry.scss) rather than path (src/entry.scss) and I’m assuming that relying on filename only will result in conflicts.

<link rel="stylesheet" href="/assets/{{ manifest['_assets']['entry.css'] }}" />

But by keeping the original path things would work as expected.

{
  "_assets": {
    "src/files/favicon.svg": "assets/favicon.17e50649.svg",
    "src/fonts/my-font.woff2": "assets/my-font.bf47658a.woff2",
    "src/fonts/my-font.woff": "assets/my-font.80d4cac9.woff",
    "src/main.scss": "assets/main.eac10a5f.css",
    "src/entry.scss": "assets/entry.wef646xf.css"
  },
  "src/main.ts": {
    "file": "assets/main.6097d7e4.js",
    "src": "src/main.ts",
    "isEntry": true,
    "css": [
      "assets/main.eac10a5f.css"
    ],
    "assets": [
      "assets/my-font.bf47658a.woff2",
      "assets/my-font.80d4cac9.woff"
    ]
  }
}
<link rel="stylesheet" href="/assets/{{ manifest['_assets']['src/entry.scss'] }}" />

To summarise there are two things to look at:

  • Exposing all assets in the manifest via something like _assets
  • Providing the full source path for css

I feel like a “web bundle manifest” specification/standard would be really helpful. As some already mentioned, I would also assume all files, which end up in the final bundle/dist/build directory, to be present in the manifest.

It seems that with the latest vite@2.3.x version, even without the legacy plugin, the issue now pops up. Statically imported images will be copied to the outDir folder, but do not show up in the generated manifest.json file.

I realize that this behavior is probably something which is not needed in modern, JS-based (PWA) applications, but in our case, when using vite in a more classic backend-rendered (PHP) application, not having all imported assets in the manifest.json file, results in not being able to render/access those assets in the first place.

Is there any chance it would be considered for vite to list all imported assets in manifest.json for this reason? Thanks a lot!

No, I checked both manifest files, and neither includes required assets unless imported AND used in a JS file. Further, the keys are wrong in that case, as the keys are literally “…/images/something” which is not consistent with they way it worked with webpacker. All of these JS bundlers are so configuration heavy. They say they work, but not for images, not for SVGs, not for Vue components… Isn’t there one that actually works as a drop-in replacement for webpacker and isn’t so brittle? (The whole reason for switching away from webpacker/webpack is because it’s always breaking things and not intuitive, but then if all the alternatives are just as troublesome to figure out, and the docs are just as bad, what is the incentive to switch?)

I have encounter same question. use build.cssCodeSplit: false would miss css in the manifest.json

Also seeing this, and like @thasmo we’re using Vite in a hybrid approach where once built a PHP back-end takes over. The files are indeed copied to the dist directory (in our case, we want to run them through imagemin), but are missing from the manifest.

vite.config.js

import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    manifest: true,
  },
})

src/main.js

import './icon.svg';

manifest.json

{
  "index.html": {
    "file": "assets/index.aa78867d.js",
    "src": "index.html",
    "isEntry": true
  }
}

That’s despite the actual dist folder containing the asset.

assets/index.aa78867d.js
assets/icon.03d6d6da.svg
index.html
manifest.json

I’ve gone all the way back to Vite v2.0.0 and this has never worked it seems?

The only way I have got this to work is if I actively use the asset somehow. For instance, doing:

import icon from './icon.svg';
console.log(icon)

Will add it to the manifest exactly as I expect. Not sure if this is a bug, or expected behaviour?

Fixed in #6649

If I had to guess, imports are probably promises that are lazily resolved. So it isn’t until an import is actually used (thus the behavior that @engram-design is seeing) that it’d be added to any manifest, just as a resource that can’t be resolved (404 for example) isn’t added either.

So I’m guessing this probably is a feature, not a bug, in that unused resources (unresolved promises) are not included in the production build.

The behavioral expectation likely comes from experience with task runners, rather than loaders.

It seems this happens when using the legacy plugin. Removing the legacy plugin resolves the issue. In addition, I noticed when setting build.cssCodeSplit: false CSS files won’t be generated/created if the legacy plugin is used. On top of that, if the legacy plugin is disabled and build.cssCodeSplit: false is still set, the CSS files will be generated, but are still missing in the manifest.json.

@Shinigami92 This behavior is intentional, at least at the time of this comment: https://github.com/vitejs/vite/pull/1774#issuecomment-769447478

What threw me was the fact that it was processing the file - moving it to dist but not adding it to the manifest. If the file is deemed not required, should it even end up in dist?

I’ll say that in my case, I mention the use of imagemin. As @khalwat rightly guessed, I’m coming from Webpack (Laravel Mix) where I would have as part of my production build process, a task that scans the src/img/**/* folder for any and all images, processes them, and copies them to dist/img.

I’m trying to replicate that by using vite-plugin-imagemin which works perfectly, but only for assets used in Vue components, or CSS files. There are cases I want to use these assets in Twig templates, outside of Vite’s awareness. Hence, my journey to try and “add” every asset to the manifest.

But I’ve pivoted back to my original goal, and forked the vite-plugin-imagemin plugin to allow direct copying from src/img to dist/img. In my Twig templates, I can now refer to these specifically via /dist/img/my-image.png upon build. During development these are resolved to use the dev server, http://localhost:3000/img/my-image.png.

It’s quite likely me scenario is different, but just thought I’d chime in - just in case anyone has a similar scenario and how to approach this. My approach using Craft CMS for the project involves Twig server-side code, but also a sprinkling of Vue, so it’s very much a hybrid approach, similar to a lot of people in the Laravel community.

@Shinigami92 Would you be okay with removing the plugin: legacy tag as I think the issue now also occurs without it? Thanks!