Search code examples
javascriptoutputvitesubdirectory

Problem Organizing Assets in Vite Build Output


I'm facing an issue with organizing assets in the output directory while using Vite for my project. I've configured the output.assetFileNames option to organize assets into subdirectories based on their types (css, fonts, img, js), but it's not working as expected.

this my Vite Configuration:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import autoprefixer from 'autoprefixer';
import imagemin from 'imagemin';
import imageminWebp from 'imagemin-webp';
// Specify the input and output directories for img minify
const inputDir = './src/assets/img';
const outputDir = './dist/assets';

export default defineConfig({
    server: {
        host: true,
        port: 8080,
        open: true,
        https: false,
        proxy: {
            '/api': {
                target: 'https://jsonplaceholder.typicode.com',
                changeOrigin: true,
                rewrite: path => path.replace(/^\/api/, ''),
            },
        },
    },
    css: {
        postcss: {
            plugins: [
                autoprefixer(), // Add CSS prefixes
            ],
        },
    },
    build: {
        plugins: [
            vue(),// 
            autoprefixer(), // Add CSS prefixes
            // Minify images
            imagemin([`${inputDir}/*.{jpg,png}`], {
                destination: outputDir,
                plugins: [
                    imageminWebp({
                        quality: 75, // Adjust the quality (0 to 100)
                    }),
                ],
            }),
        ],
        assetsDir: 'assets',
        minify: 'terser', // or 'esbuild' for esbuild-based minification
        sourcemap: true,
        cssCodeSplit: true,
        rollupOptions: {
            output: {
                assetFileNames: ({ name, ext }) => {
                    // Organize assets based on file type
                    if (ext === '.css') {
                        return `assets/css/${name}`;
                    } else if (ext === '.woff' || ext === '.woff2') {
                        return `assets/fonts/${name}`;
                    } else if (ext === '.jpg' || ext === '.webp') {
                        return `assets/img/${name}`;
                    } else if (ext === '.js') {
                        return `assets/js/${name}`;
                    }
                    // For other file types, use the default output format with hash
                    return `assets/${name}`;
                },
            },
        },
    },

});

i was tring many thing but this is the colse structure with this config

Project Structure:

dist
 ┣ assets
 ┃ ┣ arslaner.jpg
 ┃ ┣ arslaner.webp
 ┃ ┣ index-gT3gkYz_.js.map
 ┃ ┣ index.css
 ┃ ┣ rawert.jpg
 ┃ ┣ rawert.webp
 ┃ ┣ SegoeUI-VF.woff
 ┃ ┗ SegoeUI-VF.woff2
 ┣ index.html
 ┗ vite.svg

Expected Output:

dist
 ┣ assets
 ┃ ┣ css
 ┃ ┃ ┗ index.css
 ┃ ┣ fonts
 ┃ ┃ ┣ SegoeUI-VF.woff
 ┃ ┃ ┗ SegoeUI-VF.woff2
 ┃ ┣ img
 ┃ ┃ ┣ arslaner.jpg
 ┃ ┃ ┣ arslaner.webp
 ┃ ┃ ┣ rawert.jpg
 ┃ ┃ ┗ rawert.webp
 ┃ ┗ js
 ┃ ┃ ┗ index-gT3gkYz_.js.map
 ┣ index.html
 ┗ vite.svg

Issue: Despite configuring the output.assetFileNames function, the assets are not being organized into the specified subdirectories. I've checked the file extensions, reviewed the Vite configuration, and tried running a clean build, but the issue persists.

I appreciate any help in resolving this issue. If you need more information, please let me know. Thank you!


Solution

  • Assets

    For the files considered assets – which s all files apart from the index-….js and index.html file – their locations can indeed be modified via the assetFileNames. If we look at the documentation form output.assetFileNames:

    Type: Type: string| ((assetInfo: AssetInfo) => string)

    […]

    When using a function, assetInfo is a reduced version of the one in generateBundle without the fileName.

    And when we look at generateBundle:

    interface OutputAsset {
      fileName: string;
      name?: string;
      needsCodeReference: boolean;
      source: string | Uint8Array;
      type: 'asset';
    }
    

    And if we look at the source code of Rollup where assetFileNames is called:

    typeof outputOptions.assetFileNames === 'function'
      ? outputOptions.assetFileNames({ name, source, type: 'asset' })
      : outputOptions.assetFileNames,
    

    We see that ext is not a property that exists in the object parameter passed to the assetFileNames. This means that for your function:

    assetFileNames: ({ name, ext }) => {
    

    ext will be undefined for all asset files and thus none of the code paths within the if statements will be visited. Instead, consider querying the extension yourself like:

    // …
    import path from 'node:path';
    // …
    export default defineConfig({
      // …
      build: {
        // …
        rollupOptions: {
          output: {
            assetFileNames: ({ name }) => {
              const ext = path.extname(name);
    

    Sourcemaps

    For the index-gT3gkYz_.js.map sourcemap file, you'd need to use the output.sourcemapFileNames Rollup option like:

    // …
    export default defineConfig({
      // …
      build: {
        // …
        rollupOptions: {
          output: {
            // …
            sourcemapFileNames: 'assets/js/[name].[hash].js.map',