Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify filename of emitted CSS when build.cssCodeSplit: false #4863

Closed
4 tasks done
vwkd opened this issue Sep 6, 2021 · 21 comments · Fixed by #18488
Closed
4 tasks done

Allow to specify filename of emitted CSS when build.cssCodeSplit: false #4863

vwkd opened this issue Sep 6, 2021 · 21 comments · Fixed by #18488

Comments

@vwkd
Copy link
Contributor

vwkd commented Sep 6, 2021

Clear and concise description of the problem

When building in library mode for a .js file that imports styles, Vite generates a style.css file in the target directory alongside the .js file.

Using build.lib.fileName we can already specify the filename for the .js file. Currently there seems to be no way to specify the filename for the .css file, as it always defaults to style.css.

Suggested solution

Maybe a build.lib.fileNameCSS option?

Alternative

A workaround is renaming the style.css in a postbuild script. But this isn't ideal since it involves yet another build step, and doing it in Vite itself would be more coherent.

Additional context

No response

Validations

@vwkd vwkd changed the title Allow to specify filename of style.css Allow to specify filename of emitted CSS in Library Mode Sep 19, 2021
@uipoet
Copy link

uipoet commented Nov 9, 2021

Finally found this issue after several attempts to configure the CSS filename.

Thought this would work...

	css: { postcss: { to: "custom.css" } },

...but nope.

@koooge
Copy link

koooge commented Jan 5, 2022

Me as a vite's newbie. Following config works but not elegant

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === 'style.css') return 'custom.css';
          return assetInfo.name;
        },
      },
    },
  },
});

@17px
Copy link

17px commented Oct 11, 2022

rollupOptions: {
  output: [
    assetFileNames: (assetInfo) => {
      const { source } = assetInfo
      return `[name][hash][extname]`
    }
  ]
}

You can try these two methods

  • rename ${somewhere}/${cssFilename}.[hash][extname]

  • get the compiled css text from assetInfo.source, an then fs.writeFileSync('somewhere',cssString)

@johnnyshankman
Copy link

neither solutions work for me at the moment. it's as if the css file is never seen by assetFileNames. going to log...

@milahu

This comment was marked as spam.

@milahu
Copy link
Contributor

milahu commented Nov 25, 2022

long story short

-  config.build.lib.rollupOptions.output.assetFileNames = (assetInfo) => { ... }
+  config.build    .rollupOptions.output.assetFileNames = (assetInfo) => { ... }

so this works

#4863 (comment)

@aleen42
Copy link
Contributor

aleen42 commented Dec 30, 2022

How can we use the entry name to construct two files with the name L.css and X.css when specifying an entry like this:

export default {
    build : {
        rollupOptions : {
            input : {
                L : 'login/index.less',
                X : 'X/index.less'
            }
        }
    }
};

Inside the assetFileNames hook, we cannot distinguish two assets with the same name index.css.

@amirgalor-gong
Copy link

Having the same issue with library-mode of multi-entry. each entry has css (LESS actually) inclusion along the "import" lane.
Yet i get a single "styles.css" file at the end.

tried using "config.build.rollupOptions.output.assetFileNames" but assetInfo is always:
{
name: 'style.css',
type: 'asset',
source: 'vite internal call, ignore'
}

@sapphi-red sapphi-red changed the title Allow to specify filename of emitted CSS in Library Mode Allow to specify filename of emitted CSS when build.cssCodeSplit: false Jun 7, 2023
@psociety
Copy link

How can we use the entry name to construct two files with the name L.css and X.css when specifying an entry like this:

export default {
    build : {
        rollupOptions : {
            input : {
                L : 'login/index.less',
                X : 'X/index.less'
            }
        }
    }
};

Inside the assetFileNames hook, we cannot distinguish two assets with the same name index.css.

I'm quite amazed at the amount of bullshit code i had to write to make this possible, when old solutions like grunt where just 1 line. I'm leaving it here in case someone else still needs it:

import { defineConfig } from "vite";

const inputs = {
    "css/built.css": "./htdocs/css/src/main.scss",
    "unlogged/css/main.css": "./htdocs/unlogged/css/main.scss",
    "js/built": "./htdocs/js/index.js",
};
let i = 0;

export default defineConfig({
    build: {
        emptyOutDir: false,
        outDir: "htdocs/",
        sourcemap: true,
        cssCodeSplit: true,
        rollupOptions: {
            input: inputs,
            output: {
                assetFileNames: (file) => {
                    // those are input file names, we don't care about them
                    if (Object.values(inputs).includes(file.name) || Object.values(inputs).includes("./" + file.name)) {
                        return `[name].[ext]`;
                    }

                    const endPath = Object.keys(inputs)[i];
                    i++;

                    return endPath;
                },
                entryFileNames: `[name].js`, // otherwise it will add the hash
                chunkFileNames: `[name].js`, // otherwise it will add the hash
            },
        },
    },
});

@LuisSevillano
Copy link

This worked for me:

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, "src/main.js"),
      name: "libraryName",
      fileName: "libraryName",
    },
    rollupOptions: {
      output: {
        assetFileNames: "libraryName.[ext]",
      },
    },
  },
});

@matty-at-rdc
Copy link

matty-at-rdc commented Oct 25, 2023

Here is one potentially dangerous way to solve for multiple entrypoint apps (instead of auto-incrementing index.css files. Ex: index1.css, index2.css) it if you're into potentially dangerous solves. If you have a better way to do this (and there has to be one, right?) please share it.

You can see it working as intended here.

import { resolve } from "path"
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react-swc"

const inputNames = ["door_one", "door_two", "door_three"]

export const mapCSSFileNames = {
  name: 'mapCSSFileNames',
  generateBundle(opts, bundle) {
    const cssNamesMap = {}

    // This is dumb... why do I have to do this... oh golly im so clueless..
    Object.entries(bundle).forEach(([fileName, fileInfo]) => {
      if (inputNames.includes(fileInfo.name)) {        
        const ambiguous_css_name = [...fileInfo.viteMetadata.importedCss][0]
        const mapped_css_name = `${fileInfo.name}.css`
        bundle[ambiguous_css_name].fileName = mapped_css_name
        bundle[ambiguous_css_name].name = mapped_css_name
        cssNamesMap[ambiguous_css_name] = mapped_css_name
      }
    })
  },
  writeBundle(opts, bundle) {
    const cssNamesMap = {}

    Object.keys(bundle).forEach((key) => {
      const file = bundle[key]
      if(file.fileName.endsWith(".css")) {
        cssNamesMap[key] = file.fileName
      }
    })

    Object.entries(bundle).forEach(([fileName, fileInfo]) => {
      const cssFileNames = Object.keys(cssNamesMap)
      if(fileName.endsWith(".html")) {
        cssFileNames.forEach((cssFileName) => {

          if(fileInfo.source.includes(cssFileName)) {
            console.log(`Pattern: ${cssFileName} found in ${fileName} replacing it with: ${cssNamesMap[cssFileName]}`);
            fileInfo.source = fileInfo.source.replace(cssFileName, cssNamesMap[cssFileName])
          }
        })
      }
    })
  },
}

export default defineConfig(({ mode }) => {
  const config = {
    root: "src",
    build: {
      outDir: "../static/",
      emptyOutDir: true,
      rollupOptions: {
        maxParallelFileOps: 100,
        input: {
          door_one: resolve(__dirname, "src", "DoorOne", "index.html"),
          door_two: resolve(__dirname, "src", "DoorTwo", "index.html"),
          door_three: resolve(__dirname, "src", "DoorThree", "index.html"),
        },
        output: {
          entryFileNames: "[name].js",
        },
      },
      commonjsOptions: { include: [] },
    },
    plugins: [
      react({
        include: [/\.jsx?$/, /\.js$/],
      }),
      mapCSSFileNames,
    ],
    resolve: {
      alias: {
        "~": resolve(__dirname, "src/"),
      },
    },
    optimizeDeps: {
      disabled: false,
    },
  }
  return config
})

@sapphi-red sapphi-red modified the milestone: 6.0 Nov 15, 2023
@sapphi-red
Copy link
Member

We've discussed this in the last meeting. We think deriving the CSS file name from build.lib.fileName would be good.
But unfortunately this will be a breaking change and we'll need to wait for Vite 6 to change it.

For now, you can use the workaround in #4863 (comment).


Maybe we can have a experimental.enableCssFilenameDerivation flag that opt-in the behavior above.

@kytta
Copy link

kytta commented Nov 20, 2023

In Vite 5, the build step now prints the CSS filename for each JS format:

$ vite build

vite v5.0.0 building for production...
✓ 3 modules transformed.
dist/libraryName.css  17.22 kB │ gzip: 6.70 kB
dist/libraryName.js    4.22 kB │ gzip: 1.29 kB
dist/libraryName.css     17.22 kB │ gzip: 6.70 kB
dist/libraryName.umd.js   3.48 kB │ gzip: 1.33 kB
dist/libraryName.css      17.22 kB │ gzip: 6.70 kB
dist/libraryName.iife.js   3.31 kB │ gzip: 1.24 kB
✓ built in 220ms

I hope it doesn't mean that the same file is being build three times... 😅 (edit: it does 😕)

@sapphi-red
Copy link
Member

@kytta Would you create a new issue with reproduction?

@kytta
Copy link

kytta commented Nov 21, 2023

Would you create a new issue with reproduction?

Will do later today 👌

@styfrombrest
Copy link

Me as a vite's newbie. Following config works but not elegant

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === 'style.css') return 'custom.css';
          return assetInfo.name;
        },
      },
    },
  },
});

Doesn't work for me anymore in Vite 4.5.2: asset keeps it's name doesn't matter what I return in assetFileNames function

@baxterw3b
Copy link

baxterw3b commented Apr 2, 2024

if someone is not injecting an index.css file, and use the default name generated by the styleX plugin, here a plugin i created for my case to rename the file.

import { rename, readdirSync } from "fs";

const renameCss = ({
  folder,
  filename,
}: {
  folder: string;
  filename: string;
}) => ({
  name: "vite-plugin-rename-css",
  writeBundle() {
    const files = readdirSync(folder);
    const file = files[0];
    rename(`${folder}/${file}`, `${folder}/${filename}`, () => {
      console.log("renamed css!");
    });
  },
});

then use it as a normal plugin after the styleX() plugin call passing the build folder the css it's in and the new filename you want.

plugins: [
    react(),
    styleX(),
    renameCss({
      folder: "./lib/assets",
      filename: "style.css",
    }),
    dts(),
  ]

Of course you can extend it or change it as you wish, was just a quick one for my case, hope it helps!

@TheJaredWilcurt
Copy link

Me as a vite's newbie. Following config works but not elegant

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === 'style.css') return 'custom.css';
          return assetInfo.name;
        },
      },
    },
  },
});

Doesn't work for me anymore in Vite 4.5.2: asset keeps it's name doesn't matter what I return in assetFileNames function

Tried it with Vite 5.2.9 and it worked fine.

@bluwy

This comment was marked as duplicate.

@bluwy bluwy added enhancement New feature or request and removed enhancement: pending triage labels May 20, 2024
@jxh150535011
Copy link

这个问题, 将我这边终结,不可不说 vite 这个构建配置是恶心到我了。 好了 请看我的解决方案
1 首先要用到为什么要用到插件,在代码注释中会说明 vite-plugin-style-inject

import type { Plugin } from 'vite';

export interface PreRenderedAsset {
  name: string;
  type: string;
  source: string;
}

export interface VitePluginStyleInjectOptions {
  fileName?: string;
  assetFileNames?: (chunkInfo: PreRenderedAsset) =>  string | void;
}
export default function VitePluginStyleInject(options?: VitePluginStyleInjectOptions): Plugin {

  const { fileName = 'style.css', assetFileNames } = options || {}
  
  let viteConfig;

  return {
    name: 'vite-plugin-style-inject',
    apply: 'build',
    enforce: 'post',
    configResolved(resolvedConfig) {
      viteConfig = resolvedConfig;
    },
    generateBundle(config, bundles) {

      // cssCodeSplit 提取出 css 之后,这里将所有的css 进行汇总
      const chunks = Object.values(bundles);

      // 需要处理的全部css
      const cssChunks = chunks.filter(p => p.type === 'asset' &&  /[.](css|less|scss)$/.test(p.fileName));


      const cssChunkGroup = cssChunks.reduce((memo: any, chunk: any) => {
        const newFileName = assetFileNames?.(chunk) || fileName;
        const assetChunks = memo[newFileName] || [];
        assetChunks.push(chunk);
        memo[newFileName] = assetChunks;
        return memo
      }, {})

      const cssChunkGroupKeys = Object.keys(cssChunkGroup);
      // create new asset bundles
      const newAssetBundles = cssChunkGroupKeys.map((name) => {
        const cssContent = cssChunkGroup[name].map(p => p.source || '').join('\n');
        return {
          fileName: name,
          name: name,
          needsCodeReference: false,
          source: cssContent,
          type: 'asset'
        }
      })

      // delete old css bundles
      cssChunks.forEach(chunk => {
        delete bundles[chunk.fileName]
      })

      newAssetBundles.forEach((bundle) => {
        // @ts-ignore
        // inject new asset bundles
        bundles[bundle.fileName] = bundle;
      })

    },
  }
}

2 使用这个插件

plugins: [
      vitePluginStyleInject({
        assetFileNames(chunk){
           return /xxx/.test(chunk.fileName) ? 'custom.css' : 'style.css'
        }
      }),
]

3 开启 cssCodeSplit, 为什么要开启呢?看注释

  build: {
      /** 
       * 如果为false, 会先对css 进行聚合,最终只能获取到多个默认的 chunks name = style.css 无法区分
       * 但是开启为true ,assetFileNames 中的chunks 就可以获取到各自的fileName
       * 
       * 同名的 css name ,会生成同名style2, 因此 需要通过插件 vitePluginStyleInject ,对名称重新分类
       */
      cssCodeSplit: true,
}

4 重要 禁用掉已有的 build.rollupOptions.output.assetFileNames 设置

build: {
  rollupOptions: {
     output: {
          /**
           * 1 为了防止这边的修改 导致 在 vitePluginStyleInject 中的 assetFileNames 出现冲突,建议这里不做处理
           * 2 简单的方式 在这边修改名称 是可以的,其实不用插件。但是: assetFileNames 如果返回两个重名的定义
           * 例如: style1.css style1.css ,最终输出的时候 会生成style1.css 和 style12.css,所以才用到上面插件
           * vitePluginStyleInject 自定义实现
           */
        // assetFileNames
     }
  }
}

@junaga
Copy link
Contributor

junaga commented Sep 8, 2024

what a heavy thread

@github-actions github-actions bot locked and limited conversation to collaborators Nov 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet