How to wrap Vite build in IIFE and still have all the dependencies bundled into a single file?
Asked Answered
B

3

9

I'm building a chrome extension using Vite as my build tool. The main problem is during minification and mangling there are a lot of global variables created. After injecting my script to the page they conflict with already defined variables on window object.

I imagine the perfect solution would be to have my entire script wrapped in IIFE. I tried using esbuild.format = 'iife'. The resulting build is in fact wrapped in IIFE, however all the imports are not inlined. Instead resulting script is like 15 lines long with a bunch of require statements, which obviously does not work in the browser.

This is my config file:

export default defineConfig({
  plugins: [
    vue(),
  ],
  esbuild: {
    format: 'iife',
  },
  build: {
    emptyOutDir: false,
    rollupOptions: {
      input: resolve(__dirname, './src/web/index.ts'),
      output: {
        dir: resolve(__dirname, './dist'),
        entryFileNames: 'web.js',
        assetFileNames: 'style.css',
      },
    },
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
    },
  },
});

I'm currently using this hack so to say to wrap my build in IIFE (for this I removed the esbuild.format option).

Baliol answered 14/9, 2022 at 7:52 Comment(0)
W
8

Hey I am doing the exact same thing! And I also noticed the unminified variables and functions can clash with random code in a webpage.

From what I researched myself on this topic, you shouldn't change esbuild build options with Vite as that will prevent Rollup from transforming the output. Instead, you should use format: 'iife' in the rollupOptions of your vite.config. However, in my case (and yours I believe), I have to output multiple bundles since the extension code can't share modules amongst each other. Which will crash when you set the format to 'iife' due to:

Invalid value for option "output.inlineDynamicImports" - multiple inputs are not supported when "output.inlineDynamicImports" is true.

The only solution in my case seems to be to either use multiple vite.configs (I already have two) for each of my bundle with single input entry point and format as 'iife'. Or, as you did, just write the self-invoking function yourself with some hacky script. Seems though there aren't any perfect solutions as of now.

EDIT: Okay, got it working. This is my vite.config.ts (the project):

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

import path from 'path'

/** @type {import('vite').UserConfig} */
export default defineConfig({
  plugins: [svelte({}), tsconfigPaths()],
  build: {
    minify: false,
    rollupOptions: {
      output: {
        chunkFileNames: '[name].js',
        entryFileNames: '[name].js'
      },
      input: {
        inject: path.resolve('./src/inject.ts'),
        proxy: path.resolve('./src/proxy.ts'),
        'pop-up': path.resolve('./pop-up.html')
      },
      plugins: [
        {
          name: 'wrap-in-iife',
          generateBundle(outputOptions, bundle) {
            Object.keys(bundle).forEach((fileName) => {
              const file = bundle[fileName]
              if (fileName.slice(-3) === '.js' && 'code' in file) {
                file.code = `(() => {\n${file.code}})()`
              }
            })
          }
        }
      ]
    }
  }
})
Wilton answered 8/11, 2022 at 21:35 Comment(3)
I finally made it by adding the iife into rollup options. In my case I have separate vite config for each script (background, web and content). Honestly I'm not sure why I ended up with three configs, but as for now I'm happy with the solution.Baliol
For me this worked without iife option in rollup. I also have multiple inputs and iife does not seem to work with multiple inputs. But this custom plugin works for nowRovelli
It also worked for me by using this simple solution of using a custom plugin. I was looking for a Rollup "official" solution but I didn't find anything. Finally this an the AI gave me the same solution that I mixed to build the one that fits for me ;)Thao
B
1

Okay, I made it working with this config:

export default defineConfig({
  plugins: [
    vue(),
  ],
  build: {
    emptyOutDir: false,
    rollupOptions: {
      input: resolve(__dirname, './src/web/index.ts'),
      output: {
        format: 'iife',
        dir: resolve(__dirname, './dist'),
        entryFileNames: 'web.js',
        assetFileNames: 'style.css',
      },
    },
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
    },
  },
});

They key part is format: 'iife' inside build.rollupOptions.output.

Baliol answered 9/11, 2022 at 23:19 Comment(0)
P
1

I recently also created a standalone build script in IIFE format. Here's a simplified setup:

vite.config.ts

import { defineConfig } from "vite";
import packageJson from './package.json';

export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => {
  return {
    build: {
      outDir: "./dist",
      target: "esnext",
      minify: "terser",
      cssCodeSplit: false,
      terserOptions: {
        keep_classnames: true,
        keep_fnames: true,
      },
      rollupOptions: {
        input: "src/script.ts",
        output: {
          inlineDynamicImports: true,
          format: "iife",
          name: "TBD",
          entryFileNames: `[name].js`,
          assetFileNames: `[name][extname]`,
        },
      },
    },
  };
});

src/script.ts

Promise.all([
  // because this isn't an esmodule, you can't use ESM `import` syntax 
  // but dynamic imports work & are configured to be inlined by Rollup
  import("external-dep"),
  import("./api"),
]).then(async ([{ default: externalDep }, { api }]) => {
  // TODO: work
});

Poetize answered 8/8 at 20:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.