vite
vite copied to clipboard
Library mode output not tree-shakeable when consumed by webpack
Describe the bug
When writing a library using vite "library mode," the output is expected to be tree-shakeable regardless of where it is consumed. When consumed by vite, things work properly, but when consumed by webpack, the output is not tree-shakeable.
The repo provided has a folder for a very simple vue library built by vite, which should be tree shakeable. It also has two more folders with sample apps (one vite app, one vue cli app) that attempt to use the library. The sample repo's README should explain in more detail. Both sample apps are minimal, using the default output from npm init vite or vue create. The sample library uses the recommended configuration from the vite docs for library mode.
A bit of history behind tracking down this bug can be found here - the TL;DR is that the namespaceToStringTag: true option in the vite build process emits [Symbol.toStringTag]: "Module", in the output, and webpack chokes on it. Removing/commenting that line makes webpack in the vue cli app happy again, and I can't see any ill effects in the vite app, though my tests are rudimentary.
Reproduction
https://github.com/mgdodge/vue-compiler-treeshaking-bug
System Info
System:
OS: Linux 4.19 Ubuntu 20.04 LTS (Focal Fossa)
CPU: (4) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
Memory: 1.41 GB / 7.77 GB
Container: Yes
Shell: 5.8 - /usr/bin/zsh
Binaries:
Node: 14.15.4 - ~/.nvm/versions/node/v14.15.4/bin/node
npm: 6.14.10 - ~/.nvm/versions/node/v14.15.4/bin/npm
Browsers:
Firefox: 85.0
npmPackages:
@vitejs/plugin-vue: ^1.9.2 => 1.9.2
vite: ^2.6.2 => 2.6.2
Used Package Manager
npm
Logs
No response
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to https://github.com/vuejs/vue-next instead.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [X] The provided reproduction is a minimal reproducible example of the bug.
Maybe this is related: https://github.com/vitejs/vite/issues/2071#issuecomment-783601579
I am seeing this too with a library build with vite and consumed in an app bootstrapped using CRA5 (which includes webpack@5)
Happy to provide an example repo if it helps. But FYI my library config looks like this:
import path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import dts from "vite-dts";
const isExternal = (id: string) => !id.startsWith(".") && !path.isAbsolute(id);
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react({
babel: {
plugins: ["formatjs"],
},
}),
dts(),
],
build: {
lib: {
entry: "./src/index.ts",
formats: ["es", "cjs"],
fileName: (format) => `index.${format}.js`,
},
rollupOptions: {
external: isExternal,
output: {
// Since we publish our ./src folder, there's no point
// in bloating sourcemaps with another copy of it.
sourcemapExcludeSources: true,
},
},
sourcemap: true,
minify: false,
},
});
My libraries package.json includes sideEffects: false.
The generated ES code seems to be littered with /* @__PURE__ */ but it isn't working and the entire file is being bundled by webpack. In a Vite app it seems to work OK though but it would be great to figure out a solution to this as I am out of my depth.
Has anyone found a solution to this? I'm in the same pickle with tree shaking vite library into a webpack project.
I have sideEffects: false set in package.json within the component library.
For background into Webpack's behavior, sideEffects only optimizes module imports/exports -- this has no effect when everything is bundled into a single file (see https://github.com/webpack/webpack/issues/9337#issuecomment-507074474). Also see Clarifying tree shaking and sideEffects.
A solution to this is to not bundle, via Rollup's preserveModules:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
build: {
minify: false,
target: 'esnext', // use w/e here but don't set it too loose
lib: {
fileName: '[name]',
},
rollupOptions: {
output: {
preserveModules: true,
},
},
},
})
The unused code is not removed in Webpack because terser does not remove it.
var cat = /* @__PURE__ */ a("cat");
var dog = /* @__PURE__ */ a("dog");
var fish = /* @__PURE__ */ a("fish");
var components = /* @__PURE__ */ Object.freeze({
__proto__: null,
[Symbol.toStringTag]: "Module",
Cat: cat,
Dog: dog,
Fish: fish
});
This is the output from terser for the code above.
var g=a("cat"),o=a("dog"),t=a("fish");Symbol.toStringTag;
If you set compress.passes = 2, most thing are removed:
Symbol.toStringTag;
// vue.config.js
module.exports = {
chainWebpack: config => {
config.optimization.minimizer('terser').tap(args => {
const { terserOptions } = args[0]
terserOptions.compress.passes = 2
return args
})
}
}
Vite could set output.generatedCode.symbols = false/output.namespaceToStringTag = false as default. But that will cause other problems (#764).
Has anyone found a solution to this?
I was experiencing two issues when building with vite. I am using react and vite instead of vue and webpack but I am leaving this comment because I believe the solution to be the same and it took me so long to figure out.
esbuilddoesn't mark some top-level exports as/* @__PURE__ */by default. -- esbuild Pure -- ignore annotations- The bundle was being bundled into a single file and the consuming repo was unable to
tree shakeas expected as mentioned by above @CodyJasonBennett.
tl;dr to enable tree-shaking
- Mark all functions that are able to be dropped if unused,
tree-shaken, with/* @__PURE__ */. (use caution)
// src/icon.tsx
/* @__PURE__ */
export const Icon = React.forwardRef<SVGSVGElement, IconProps>(
(props, forwardedRef) => (
<svg {...props} ref={forwardedRef}>
<path
d="M14.5"
fill={props.color}
/>
</svg>
),
)
- Preserve modules in your build step:
// vite.config.ts
rollupOptions: {
output: {
preserveModules: true,
},
},
How to validate:
In your dist/index.es.js check for the annotation /* @__PURE__ */
Will be tree shaken:
const Icon = /* @__PURE__ */ React.forwardRef(
(props, forwardedRef) => React.createElement("svg", { ...props, ref: forwardedRef }, /* @__PURE__ */ React.createElement(
"path",
{
d: "M14.5",
fill: color
}
))
);
Will Not be tree-shaken
const Icon = React.forwardRef(
(props, forwardedRef) => React.createElement("svg", { ...props, ref: forwardedRef }, /* @__PURE__ */ React.createElement(
"path",
{
d: "M14.5",
fill: color
}
))
);
I had the same problem building my library with Vite lib mode ..
Either, @hoop71 solution works but you have to mark everything with /* @__PURE__ */ or use rollup-plugin-pure to help with that.
Either, don't use lib mode and use rollup like I did :
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import dts from "vite-plugin-dts";
import pkg from "./package.json";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
esbuild: {
minifyIdentifiers: false,
},
build: {
rollupOptions: {
preserveEntrySignatures: "strict",
input: ["src/index.ts", "src/nav/index.ts"],
external: [...Object.keys(pkg.peerDependencies)],
output: [
{
dir: "dist",
format: "esm",
preserveModules: true,
preserveModulesRoot: "src",
entryFileNames: ({ name: fileName }) => {
return `${fileName}.js`;
},
},
],
},
},
plugins: [
react({
jsxRuntime: "classic",
}),
visualizer(),
dts(),
],
});
⚠️ jsxRuntime : "classic" is removed from @vitejs/[email protected]...
I have not upgrade it to v4 yet and handle automatic runtime. Read more