egoist / tsup

The simplest and fastest way to bundle your TypeScript libraries.
https://tsup.egoist.dev
MIT License
8.99k stars 217 forks source link

Adding explicit ".js" extension to index files importing ESM modules #736

Closed eric-burel closed 2 years ago

eric-burel commented 2 years ago

Hi, In order to create a library of UI components exposed as ESM, I transpile an "index.ts" file that reexport each modules.

Here is the full config:

import { defineConfig, Format } from "tsup";
import path from "path";

const commonConfig = {
  clean: true,
  //splitting: false,
  // Skip until .d.ts.map is also supported https://github.com/egoist/tsup/issues/564
  // dts: true,
  sourcemap: true,
  tsconfig: path.resolve(__dirname, "./tsconfig.build.json"),
  outDir: "dist",
  format: ["esm" as Format],
};
export default defineConfig([
  // actual exposed modules = 1 per component
  {
    entry: [
      // basic reusable components
      "./components/core/!(index).ts?(x)",
      // sets of components eg "all form components", "all datatable components"
      "./components/VulcanComponents/liteVulcanComponents/!(index).ts?(x)",
      "./components/form/inputs/!(index).ts?(x)",
    ],
    ...commonConfig,
    // For debugging, will output ESbuild metafile
    // metafile: true,
    esbuildOptions(options, context) {
      // the directory structure will be the same as the source
      options.outbase = "./";
    },
  },
  // index files to allow named imports
  // inspired by react-bootstrap structure
  {
    entry: [
      "index.ts",
      "./components/core/index.ts",
      "./components/VulcanComponents/liteVulcanComponents/index.ts",
      "./components/form/inputs/index.ts",
      "./components/form/index.ts",
    ],
    ...commonConfig,
    esbuildOptions(options, context) {
      // the directory structure will be the same as the source
      options.outbase = "./";
    },
    // index files must NOT be bundled!
    // it acts as a map towards bundled components
    // but never rebundles them
    bundle: false,
  },

You can find more explanation on this config here: https://stackoverflow.com/a/73883783/5513532

Problem: for this to work, "index.ts" must add the ".js" extension in imports:

// index.ts
export { Foo } from "./Foobar.js"

This sounds weird, and it is, but it works both during development (TS basically seems to ignore the extension and correctly load "./Foobar.tsx") and when publishing the package (the .js is mandatory for ESM).

However, this breaks Storybook. This ticket at Storybook explains the same situation: https://github.com/storybookjs/storybook/issues/15962

The current solution seems to be altering Storybook Webpack config like so: https://github.com/storybookjs/storybook/issues/15962#issuecomment-1163459786, using this plugin: https://github.com/softwareventures/resolve-typescript-plugin

But it doesn't solve the underlying issue, we should not need the ".js" in the first place. To solve this issue in the context of Tsup, I would need to remove the ".js" from my source "index.ts", and add it only during transpilation of this file. Is there a way to achieve that with Tsup?

Related issue with Esbuild: https://github.com/evanw/esbuild/issues/622

eric-burel commented 2 years ago

I think I am going to close for now because the solution proposed at Storybook seems to work, and it seems to be perfectly ok to have explicit ".js" in the ".ts" file.