vitejs/vite

add an option to not to inline assets in library mode when format is 'es'

hronro opened this issue Β· 32 comments

Clear and concise description of the problem

Currently when using library mode, all the assets (images, videos...) are inlined using base64. When those assets are big, it takes a long for vite to build, and the browser also takes a long time to parse the javascript.

Suggested solution

When the library format is 'es', we can add an option to make vite emit those files, and import those files in javascript using new URL(import.meta.url, '...'), e.g.

import foo from './image.png';

const bar = foo; // new URL(import.meta.url, './assets/image.2d8efhg.png')

Alternative

No response

Additional context

No response

Validations

+1 for that feature,

In my case, I want to build a components library that includes fonts with that option, but now Vite includes the fonts in base64 string, after build, I see that my style.css: dist/style.css 1067.79 KiB / brotli: skipped (large chunk)

These are so huge for CSS and every new update of my CSS will be recached old file, but fonts are a more stable thing in the library and I can cache them for long days unlike style.css

@xesjkeee would you create another feature request for avoiding bundling the fonts in css files? Your proposal is a good idea to explore but it isn't directly related to this issue. Thanks!

@hronro we discussed your proposal with the team and it sounds good. As you said, it should only be applied to es, and lib mode should still bundle the assets for other configured formats. Adding the contribution welcome to this issue in case someone wants to work on this issue.

Hello @hronro. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!

Besides from checking es, this might help getting your assets emitted:
#3295 (comment)

Is this going to be fixed? I see the PR was closed and wondering if this issue is still something the maintainers want to fix. I have a library built with vite that has an icon font. I'd rather not have the font inlined in css as that makes for a very large css file and effectively means those files can't be cached long term.

It also means the browser is downloading all 3 font formats provided when it only needs to download the one it can/will use which is very inefficient.

I also need this urgently, as we're about to launch our component library built with Vite, and our security engineer doesn't want us to allow data: for font-src or image-src in our Content-Security-Policy.

Please upgrade the relevance of this feature as without it, people are forced to implement an inherently insecure setting for their Content-Security-Policy!

Are there any updates on that? It seems like a very common use-case for libraries – barely anyone wants fonts to be inlined in CSS.

I initialized a monorepo by rush recently. The monorepo has a vue app package and a vue component package.

The key problem is the vue component package depend on a logo image. The key code is as below.

import logo from './assets/logo.png';

<img :src="logo" alt="logo"/>

First time I use vue build --target lib to build the vue component package. I found that vue cli only support commonjs and umd. Though the vue app package can import the vue component package correctly. But the vue app package cannot find the logo image.

I have checked the vue component package's compiled code. The key code is as below.

module.exports = __webpack_require__.p + "img/logo.c83cee16.png";

So I guess webpack inside vue app package cannot detect the compiled vue component package depend on a logo png.

Second time I changed to use vite to build the vue component package. And vite document said that it supports es and umd.
So I thought webpack inside vue app package can detect the compiled(es code) vue component package depend on a logo png.

But finally I find this issue. Vite inline all assets in library mode. 😒

So as a summary, we cannot use any image or other assets inside vue component.
Vue cli will cause vue app cannot find the image.
Vite seems work properly, But vite inline all assets into js file.

g10 commented

…this works for css…

assets in public folder

public/fonts/Font.otf

in css, ./ dot prefix the path

@font-face {
  src: url("./fonts/Font.otf") format("opentype");
}

in vite.config, build lib with es format

  build: {
    lib: {
      formats: ["es"],
      …
    },
   outDir: "./lib",
   …

the public folder content is copied to lib and paths in css are preserved β†’ no inline assets

package.json

  …
  "files": [
    "lib/*",
    …
  ],
  "exports": {
    ".": {
      "import": "./lib/index.es.js"
    },
    "./style.css": "./lib/style.css"
    …
  },

This would be amazing to have, for situations like the one I'm facing where I have a monorepo, with a shared codebase for frontend app logic and a web app and a separate electron app that both render that same shared codebase

What @g10 suggested worked like a charm for the lib we're working on.

Placing files in public folder and referencing them with an absolute path worked great in my case (font files).
But, make sure to update your vite.config.ts with the following or otherwise it won't work

   root: './',
   publicDir: 'public',

Looking forward to have this option supported.

We are currently working on a library including a large webassembly file(10M plus).
It's not possible to publish this library as a single JS file bundling with out wasm file.

+1

+1

Have the same problem, but with an external library.
I am using KaTeX in my project, and it has around 1.2 MB of fonts.
When compiled as library, every font gets included into a single style.css, and it grows to a size of 1.5 MB.

I have tried the public workaround above, setting it to node_modules/katex/dist/fonts, however it did not work: fonts were copied into the dist folder, but style.css still inlined all of them.

EDIT: I would have assumed that setting build.assetsInlineLimit to 0 would work, but it did not -- assets are still inlined.

EDIT 2: I have resorted to adding ["katex", /katex\/dist/] (yes, both) to build.rollupOptions.external and moving katex to peerDependencies, but this is really not ideal.
Would love this to be fixed ASAP. PR #9734 seems to be abandoned though, and no maintainers have even left a comment there.

seems to be abandoned though, and no maintainers have even left a comment there.

Same for the RFC discussion I started :(

https://github.com/vitejs/rfcs/discussions/16

I have found https://github.com/ManBearTM/vite-plugin-no-bundle, but was unable to make it work.

What I've actually done for now:

export default defineConfig({
  build: {
    rollupOptions: {
      external: (source, _, isResolved) => !(isResolved || /^[./]/.test(source)),
    },
  },
});

Now all dependencies that do not look local when resolving (examples of local: /project/src/common.js, ./common.js, examples of external: ws, expressjs, katex/dist/style.css) are just excluded from the project.

This is basically all I want, because dependencies of the lib will be installed with it, and everything will be further optimized by the consumer projects build system.

I have found https://github.com/ManBearTM/vite-plugin-no-bundle, but was unable to make it work.

As the author of that plugin, I would love to know more about the issues you encountered so that I may attempt to fix them πŸ™‚

The plugin does something very much like your solution, but it goes even further and doesn’t actually bundle anything - so unless you want that, it is probably not a good fit.

Also, the plugin does not aim to resolve the problem in this issue, though it does have a copy option that simply marks local files as external and then copies the raw files to the build folder. But this only works for simple use cases where you don’t need Vite to touch the files at all.

I wrote a plugin to "external" assets in library mode:
https://gist.github.com/ZKHelloworld/4d518583b90ddcb4762d49c102a42054

usage:

    plugins: [
      viteLibraryAssetExternal({
        test: /.*(png|jpg|jpeg)$/, // assets to external
        include: ['.ts', '.tsx', '.js', '.jsx'], // source file to check
        dir: 'assets' // output directory for assets
      }),
    ],

Pretty significant that Vite doesn't have this feature yet. Webpack had file-loader since v0.5 which was able to emit external files (font files, svgs, jpegs, etcs) without having to inline them in CSS, etc. Hoping this gets some love soon! πŸ˜€

This would be amazing to have, for situations like the one I'm facing where I have a monorepo, with a shared codebase for frontend app logic and a web app and a separate electron app that both render that same shared codebase

I had the same problem, I tried to compile a vue3 project and keep the directory structure, publish to npm repository. I split a project into monorepo. no idea yet

This problem has been bothering me for a long time. Is there any update?

Any updates on the issue? Can't start using vite because of that. Thank you!

@grybykm Feel free to comment on my RFC with your use case: #13172

Thank you!

In meanwhile I've managed to workaround by using rollupOptions.external feature and using vite-plugin-static-copy plugin. I have to manually specify every file I want to use as url, ex './my-icons.svg?url' and then copying it with vite-plugin-static-copy.

I have a monorepository with several UI libraries in react used by applications outside this monorepository.

What would be the simplest workaround for a use case where I use images, fonts and videos from within the same library? I thought about leaving the files in the public folder, and in the application, changing the path of the public folder to node_modules/@my-org/my-lib/assets, but then I would need to create another assets folder inside public, otherwise when building the library folders inside public will go to the root of the outDir, and the vite public folder configuration parameter only accepts a single string and that would prevent the application from adding more files.

I remember reading somewhere that maybe using an exclusive package for these assets might work, but I don't understand how.

https://github.com/vitejs/rfcs/discussions/16

I wrote a plugin to emit external files in library mode: vite-plugin-lib-assets

Using the following syntax manages to create seperate file for each asset but it modularizes them and in doing so it encodes them in base64 again. Extra steps for the same thing?

foo.js here is emscripten generated file so it needs to be treated as an asset

await import("../bin/foo.js?url").then(m => m.default)

Weirdly enough, I have a worker file that's being imported in the following syntax. That however, places the related worker file under assets/ directory and loads it via absolute path.

import FooWorker from "./worker?worker&url"

new Worker(new URL(FooWorker, import.meta.url).href, {
  type: "module"
})

Now, to an extent I could understand why library mode differs from standalone mode. However, within same mode why does this asset bundling differ? Either putting the worker under assets was a mistake or this is possible from the start but the first syntax I've pointed out is being neglected. Or there is another syntax which would work in the same way as how workers are being bundled.

If someone could shine a light on this that would be delightful.

Have the same problem, but with an external library. I am using KaTeX in my project, and it has around 1.2 MB of fonts. When compiled as library, every font gets included into a single style.css, and it grows to a size of 1.5 MB.

I have tried the public workaround above, setting it to node_modules/katex/dist/fonts, however it did not work: fonts were copied into the dist folder, but style.css still inlined all of them.

EDIT: I would have assumed that setting build.assetsInlineLimit to 0 would work, but it did not -- assets are still inlined.

EDIT 2: I have resorted to adding ["katex", /katex\/dist/] (yes, both) to build.rollupOptions.external and moving katex to peerDependencies, but this is really not ideal. Would love this to be fixed ASAP. PR #9734 seems to be abandoned though, and no maintainers have even left a comment there.

@rijenkii Have you managed to do something with that? I've noticed that we have exactly the same problem that you mentioned :/

@zgrybus Well, kind of, but not really. Currently using something like this:

import pkg from "./package.json";

const deps = [...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies)];

export default defineConfig({
  build: {
    rollupOptions: {
      external: (source) => {
        for (const dep of deps) {
          if (source === dep || source.startsWith(`${dep}/`)) {
            return true;
          }
        }
        return false;
      },
    },
  },
});

This allows the usage of aliases, but may fail if you accidentally import something from a transitive dependency that is not explicitly defined in the package.json.

My setup consist of two Vite build configs. Ended up disabling lib mode and using rollupOptions as a workaround for me. This is currently in my config for asset files:

export default defineConfig({
  build: {
    lib: false,
    rollupOptions: {
      input: { /* ...assets */ },
      output: {
          assetFileNames: ({ name }) =>
            name ? name : 'assets/[name]-[hash][extname]'
      }
    },
  },
});

And then I have another config for the non assets, making sure I correctly alias the paths to the asset build files. As long you don't bundle those files into your JS, you're good to go...