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

Docs: Document new addon bundling strategy #24776

Merged
merged 6 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions docs/addons/writing-addons.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,9 @@ Addons built in the Storybook ecosystem rely on [tsup](https://tsup.egoist.dev/)

<!-- prettier-ignore-end -->

When the build scripts run, it will look for the configuration file and pre-bundle the addon's code based on the configuration provided. However, there are a few properties that are worth referencing:
When the build scripts run, it will look for the configuration file and pre-bundle the addon's code based on the configuration provided. Addons can interact with Storybook in various ways. They can define presets to modify the configuration, add behavior to the manager UI, or add behavior to the preview iframe. These different use cases require different bundle outputs because they target different runtimes and environments. Presets are executed in a Node environment. Storybook's manager and preview environments provide certain packages in the global scope, so addons don't need to bundle them or include them as dependencies in their `package.json` file.

- **entry**: Configures the files to be processed by the bundler. It can be extended to include additional files using a regex pattern.
- **format**: Enables the generation of multiple output formats. In this case, we're generating a CommonJS and an ES Module version of our addon.
- **dts**: Auto-generates type definitions for our addon.
- **platform**: Specifies the target platform for our addon. In this case, we're targeting the browser. It can be set to `node` for Node.js environments or `neutral` for universal modules.
The `tsup` configuration handles these complexities by default, but you can customize it according to their requirements. For a detailed explanation of the bundling techniques used, please refer to [the README of the addon-kit](https://github.com/storybookjs/addon-kit#bundling).

## Register the addon

Expand Down
112 changes: 94 additions & 18 deletions docs/snippets/common/storybook-addon-toolkit-tsup-config.ts.mdx
Original file line number Diff line number Diff line change
@@ -1,22 +1,98 @@
```ts
// tsup.config.ts

import { defineConfig } from 'tsup';

export default defineConfig((options) => ({
entry: ['src/index.ts', 'src/preview.ts', 'src/manager.ts'],
splitting: false,
minify: !options.watch,
format: ['cjs', 'esm'],
dts: {
resolve: true,
},
treeshake: true,
sourcemap: true,
clean: true,
platform: 'browser',
esbuildOptions(options) {
options.conditions = ['module'];
},
}));
import { defineConfig, type Options } from "tsup";
JReinhold marked this conversation as resolved.
Show resolved Hide resolved
import { readFile } from "fs/promises";
import { globalPackages as globalManagerPackages } from "@storybook/manager/globals";
import { globalPackages as globalPreviewPackages } from "@storybook/preview/globals";

// The current browsers supported by Storybook v7
const BROWSER_TARGET: Options['target'] = ["chrome100", "safari15", "firefox91"];
const NODE_TARGET: Options['target'] = ["node16"];

type BundlerConfig = {
bundler?: {
exportEntries?: string[];
nodeEntries?: string[];
managerEntries?: string[];
previewEntries?: string[];
};
};

export default defineConfig(async (options) => {
// reading the three types of entries from package.json, which has the following structure:
// {
// ...
// "bundler": {
// "exportEntries": ["./src/index.ts"],
// "managerEntries": ["./src/manager.ts"],
// "previewEntries": ["./src/preview.ts"]
// }
// }
const packageJson = await readFile('./package.json', 'utf8').then(JSON.parse) as BundlerConfig;
const {
bundler: {
exportEntries = [],
managerEntries = [],
previewEntries = [],
} = {},
} = packageJson;

const commonConfig: Options = {
splitting: false,
minify: !options.watch,
treeshake: true,
sourcemap: true,
clean: true,
};

const configs: Options[] = [];

// export entries are entries meant to be manually imported by the user
// they are not meant to be loaded by the manager or preview
// they'll be usable in both node and browser environments, depending on which features and modules they depend on
if (exportEntries.length) {
configs.push({
...commonConfig,
entry: exportEntries,
dts: {
resolve: true,
},
format: ["esm", 'cjs'],
target: [...BROWSER_TARGET, ...NODE_TARGET],
platform: "neutral",
external: [...globalManagerPackages, ...globalPreviewPackages],
});
}

// manager entries are entries meant to be loaded into the manager UI
// they'll have manager-specific packages externalized and they won't be usable in node
// they won't have types generated for them as they're usually loaded automatically by Storybook
if (managerEntries.length) {
configs.push({
...commonConfig,
entry: managerEntries,
format: ["esm"],
target: BROWSER_TARGET,
platform: "browser",
external: globalManagerPackages,
});
}

// preview entries are entries meant to be loaded into the preview iframe
// they'll have preview-specific packages externalized and they won't be usable in node
// they won't have types generated for them as they're usually loaded automatically by Storybook
if (previewEntries.length) {
configs.push({
...commonConfig,
entry: previewEntries,
format: ["esm"],
target: BROWSER_TARGET,
platform: "browser",
external: globalPreviewPackages,
});
}

return configs;
});
```