Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library mode output not tree-shakeable when consumed by webpack #5174

Open
7 tasks done
mgdodge opened this issue Oct 1, 2021 · 8 comments
Open
7 tasks done

Library mode output not tree-shakeable when consumed by webpack #5174

mgdodge opened this issue Oct 1, 2021 · 8 comments

Comments

@mgdodge
Copy link

mgdodge commented Oct 1, 2021

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

@mgdodge mgdodge changed the title Library mode output not tree-shakeable Library mode output not tree-shakeable when consumed by webpack Oct 12, 2021
@fsblemos
Copy link
Contributor

Maybe this is related: #2071 (comment)

@robcaldecott
Copy link

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.

@kinoli
Copy link

kinoli commented Apr 15, 2022

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.

@CodyJasonBennett
Copy link

CodyJasonBennett commented Jul 24, 2022

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 webpack/webpack#9337 (comment)). 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,
      },
    },
  },
})

@sapphi-red
Copy link
Member

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).

@wuyuweixin
Copy link

Has anyone found a solution to this?

@hoop71
Copy link

hoop71 commented Feb 15, 2023

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.

  • esbuild doesn'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 shake as expected as mentioned by above @CodyJasonBennett.

tl;dr to enable tree-shaking

  1. 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>
  ),
)
  1. 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
    }
  ))
);

@clementcreusat
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants