Skip to content

Instantly share code, notes, and snippets.

@SilverMira
Last active May 27, 2022 02:11
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SilverMira/206f7b352ed771ae5bb30de707284c42 to your computer and use it in GitHub Desktop.
Save SilverMira/206f7b352ed771ae5bb30de707284c42 to your computer and use it in GitHub Desktop.
Vite manifest to include CSS entrypoints

Related issue

How

Turns out, Vite's built in manifest plugin IS able to generate CSS entrypoints in manifest.json. In fact, I accidentally discovered this when trying to adapt the internal plugin with minimal changes.

The reason why we don't see this behavior is because the manifest plugin runs at the very end of the plugin chain. Pure CSS entrypoints chunk is removed by another built-in plugin.

Therefore, to simply have CSS entrypoints to show up in manifest.json, just use the built-in manifest plugin as a user plugin since they run before any built-in plugins run.

The below shows example on how to workaround the current limitation of the built in plugin

I am not going to publish an npm package for this workaround, since it seems like this issue is soon to be fixed [vitejs/vite#6649]. Feel free to do so if you find it easier.

Some notes

  1. The css entrypoints generated in the manifest.json has a file field that points to a NON-EXISTING js file, not a problem since manifest.json is only used in backend integrations, just generate the <link/> tags from the css array.

  2. It is OK to disable the built in manifest file, since the outputs of both manifest are IDENTICAL, except for the css entries (or maybe other assets entrypoints)

  3. If you use build.cssCodeSplit = false, please note that this workaround WILL NOT work. The entrypoint still shows up, but without css field.

{
"src/index.ts": {
"file": "assets/index.aba780b6.js",
"src": "src/index.ts",
"isEntry": true,
"css": [
"assets/index.61292221.css" // <----- another css file imported in index.ts
]
},
"src/index2.scss": {
"file": "assets/index2.0610dba2.js", // not exist
"src": "src/index2.scss",
"isEntry": true,
"css": [
"assets/index2.9f0c3dd7.css" // <-----
]
}
}
import path from 'path';
import { normalizePath } from 'vite';
export default function AssetsManifestPlugin({ manifestFile }) {
manifestFile = manifestFile ?? 'manifest-assets.json';
const manifest = {};
let outputCount;
let config;
return {
name: 'vite-assets-manifest-plugin',
configResolved(resolvedConfig) {
config = resolvedConfig;
},
buildStart() {
outputCount = 0;
},
generateBundle({ format }, bundle) {
function getChunkName(chunk) {
if (chunk.facadeModuleId) {
let name = normalizePath(path.relative(config.root, chunk.facadeModuleId));
if (format === 'system' && !chunk.name.includes('-legacy')) {
const ext = path.extname(name);
name = name.slice(0, -ext.length) + `-legacy` + ext;
}
return name.replace(/\0/g, '');
}
else {
return `_` + path.basename(chunk.fileName);
}
}
function getInternalImports(imports) {
const filteredImports = [];
for (const file of imports) {
if (bundle[file] === undefined) {
continue;
}
filteredImports.push(getChunkName(bundle[file]));
}
return filteredImports;
}
function createChunk(chunk) {
const manifestChunk = {
file: chunk.fileName,
};
if (chunk.facadeModuleId) {
manifestChunk.src = getChunkName(chunk);
}
if (chunk.isEntry) {
manifestChunk.isEntry = true;
}
if (chunk.isDynamicEntry) {
manifestChunk.isDynamicEntry = true;
}
if (chunk.imports.length) {
const internalImports = getInternalImports(chunk.imports);
if (internalImports.length > 0) {
manifestChunk.imports = internalImports;
}
}
if (chunk.dynamicImports.length) {
const internalImports = getInternalImports(chunk.dynamicImports);
if (internalImports.length > 0) {
manifestChunk.dynamicImports = internalImports;
}
}
// @ts-ignore
if (chunk.viteMetadata.importedCss.size) {
// @ts-ignore
manifestChunk.css = [...chunk.viteMetadata.importedCss];
}
// @ts-ignore
if (chunk.viteMetadata.importedAssets.size) {
// @ts-ignore
manifestChunk.assets = [...chunk.viteMetadata.importedAssets];
}
return manifestChunk;
}
for (const file in bundle) {
const chunk = bundle[file];
if (chunk.type === 'chunk') {
manifest[getChunkName(chunk)] = createChunk(chunk);
}
}
outputCount++;
const output = config.build.rollupOptions?.output;
const outputLength = Array.isArray(output) ? output.length : 1;
if (outputCount >= outputLength) {
this.emitFile({
fileName: manifestFile,
type: 'asset',
source: JSON.stringify(manifest, null, 2),
});
}
},
};
}
import { defineConfig } from 'vite';
import AssetsManifestPlugin from './vite-assets-manifest-plugin';
export default defineConfig({
plugins: [AssetsManifestPlugin({manifestFile: 'manifest-assets.json'})],
build: {
emptyOutDir: true,
// manifest: true, // see notes 2
rollupOptions: {
input: ['src/index.ts', 'src/index2.scss'],
},
cssCodeSplit: true, // important. see notes 3
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment