Bundle JS and CSS into single file with Vite
Asked Answered
F

9

37

I'm having a devil of a time figuring out how to build a single .js file from Vite in my Svelte project that includes all of the built javascript and CSS from my Svelte projects. By default, Vite bundles the app into one html file (this is ok), two .js files (why??), and one .css file (just want this bundled into the one js file).

I ran this very basic command to get a starter project:
npx degit sveltejs/template myproject

I tried adding a couple of plugins, but nothing I added achieved the results I wanted. Primarily, the plugins I found seem to want to create a single HTML file with everything in it. It seems like PostCSS might be able to help, but I don't understand what configuration I can set via Vite to get it to do what I want.

What is the magic set of plugins and config that will output a single HTML file with a single js file that renders my Svelte app and its CSS onto the page?

Fledgy answered 31/5, 2021 at 23:36 Comment(1)
What's your project structure look like? Your vite configuration? Are you using any dynamic imports?Sexual
M
35

Two steps,

Final result,

import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";

export default defineConfig({
  plugins: [cssInjectedByJsPlugin()],
  build: {
    rollupOptions: {
      output: {
        manualChunks: undefined,
      },
    },
  },
});

As suggested by @TheRockerRush below, you may want to use vite-plugin-singlefile to bundle all code into a single .html file, although the OP is asking for a single .js file.

Maxi answered 1/3, 2022 at 6:36 Comment(2)
What should the import look like? I'm struggling to get this to work with tailwind, vue, postcss. Also, that output is not assignable to type 'RollupOptions'Gorrono
manualChunks undefined doesn't seem to work (anymore?), but defining a function does (see my other answer)Haaf
H
11

If you're looking for a solution to this, you might want to take a look at vite-plugin-singlefile.

Hidden answered 23/6, 2022 at 21:48 Comment(4)
This solution does not support multiple output filesGalosh
> This solution does not support multiple output files As per the name of the plug-in lolAlta
Note: this plugin specifically outputs only an html file with all assets embedded, mostly for exceptional use cases or offline distribution and usage, whereas the OP is asking for assets to be bundled into a single javascript file. The plugin in the accepted answer satisfies the OP requirement.Salve
This comment thread is fantastic 🍿just to add something, the docs for the plugin actually say "Last but not least, this is a single file plugin. As in, it creates one HTML file. Hence the name -- maybe this is a common mixupAudun
K
5

That doesn't come out of the box for vite but you can write a quick plugin which will be doing exactly that

const bundle_filename = ''
const css_filename = 'style.css'

defineConfig({
  build: {
    lib: {
      entry: 'src/mycomponent.js',
      name: 'mycomponent.js',
      fileName: () => 'mycomponent.js',
      formats: ['iife'],
    },
    cssCodeSplit: false,
    rollupOptions: {
      plugins: [
        {
          apply: 'build',
          enforce: 'post',
          name: 'pack-css',
          generateBundle(opts, bundle) {
            const {
              [css_filename]: { source: rawCss },
              [bundle_filename]: component,
            } = bundle

            const IIFEcss = `
            (function() {
              try {
                  var elementStyle = document.createElement('style');
                  elementStyle.innerText = ${JSON.stringify(rawCss)}
                  document.head.appendChild(elementStyle)
              } catch(error) {
                console.error(error, 'unable to concat style inside the bundled file')
              }
            })()`

            component.code += IIFEcss

            // remove from final bundle
            delete bundle[css_filename]
          },
        },
      ],
    },
  },
})
Kephart answered 27/8, 2021 at 14:18 Comment(3)
This code is now outdated, build fails.Maxi
Yes, answer needs some editing but the general idea is solid and works as expected.Unto
any hint on what to change that this is working again?Sphenic
D
2

I created a boilerplate Vite project for this problem:
https://github.com/mvsde/svelte-micro-frontend

Maybe the configuration helps with your case:

import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [
    svelte({
      emitCss: false
    })
  ],

  build: {
    assetsDir: '',
    sourcemap: true,
    lib: {
      entry: 'src/main.js',
      formats: ['iife'],
      name: 'SvelteMicroFrontend',
      fileName: 'svelte-micro-frontend'
    }
  }
})
Donaldson answered 17/6, 2021 at 5:32 Comment(1)
This config does not insert the CSS into the single .js asset emitted, as requested by OP.Maxi
M
2

I had the same issue and was able to fix by editing vite.config.ts as followed (tested on [email protected]):

export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: undefined,
      },
    },
  },
};
Mccue answered 22/6, 2021 at 13:39 Comment(1)
This config does not insert the CSS into the single .js asset emitted, as requested by OP.Maxi
S
2

If you are working with Svelte, you can use emitCss:

export default defineConfig({
    plugins: [svelte({
        emitCss: false,
    })],
})
Sommer answered 17/4, 2022 at 9:5 Comment(0)
C
1

Now you can use:

https://www.npmjs.com/package/vite-plugin-singlefile

npm i vite-plugin-singlefile

import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import { viteSingleFile } from "vite-plugin-singlefile"

export default defineConfig({   
plugins: [vue(), viteSingleFile()]
})
Cochise answered 1/1, 2024 at 22:28 Comment(0)
T
0

As manualChunks are no longer working in a latest versions of Vite, there's no any way to combine all the chunks into one.

But found a hacky solution to have an index.html + bundle.js after the build: https://github.com/d-velopment/SvelteKit-One-Bundle - it rewraps the project's initial .js files to go from bundle.js, which could be loaded from index.html or external project.

Toxicosis answered 26/1, 2023 at 21:40 Comment(0)
H
0

For combining the JS files, you can specify a function for manualChunks that returns the same value for all files:

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: (filename) =>
          'x'
      },
    },
  },
});
Haaf answered 22/7, 2024 at 10:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.