Sign inSign up
Setup
Interaction tests
Publish
Composition

TurboSnap dependency tracing

TurboSnap examines your project’s Git history and Webpack’s dependency graph to determine which stories might be affected by changes. If you’re new to these concepts, then this guide is for you.

It’ll help you better understand dependency tracing, enabling you to use TurboSnap more effectively. You’ll also learn how to trace dependencies in projects using Vite.

Tracing dependencies with Webpack

To start, let’s visit Webpack’s dependency graph concept:

❝ Any time one file depends on another, webpack treats this as a dependency. This allows webpack to take non-code assets, such as images or web fonts, and also provide them as dependencies for your application.

When webpack processes your application, it starts from a list of modules defined on the command line or in its configuration file. Starting from these entry points, webpack recursively builds a dependency graph that includes every module your application needs, then bundles all of those modules into a small number of bundles - often, only one - to be loaded by the browser. ❞

TurboSnap leverages Webpack’s Stats Data API to generate a JSON file with detailed statistics about a project’s modules. These statistics allow TurboSnap to analyze a project’s dependency graph by providing information on asset objects, related chunks (or grouped modules), and module dependencies linked to the assets.

If Chromatic builds your Storybook, it will automatically generate the stats file. But if you provide Chromatic with a prebuilt Storybook, you must add the --stats-json (or --webpack for Storybook versions 7 and lower) flag to the build-storybook command.

The stats file can be a bit challenging to read. Therefore, the Chromatic CLI offers a trim-stats-file option to make the file more human-readable. Use it like so:

npx chromatic trim-stats-file

Or, if you’re using a custom build directory:

npx chromatic trim-stats-file ./path/to/preview-stats.json

Reading the trimmed stats file

After running the trim-stats-file command, Chromatic will output a preview-stats.trimmed.json file. While this file is more readable, it can still be a bit daunting, so let’s break it down with an example.

preview-stats.trimmed
{
  "id": "./src/inputs/PinInput/PinInput.tsx",
  "name": "./src/inputs/PinInput/PinInput.tsx + 4 modules",
  "modules": [
    { "name": "./src/inputs/PinInput/PinInput.tsx" },
    { "name": "./src/inputs/PinInput/SeparatedPinInput.tsx" },
    { "name": "./src/inputs/PinInput/SinglePinInput.tsx" },
    { "name": "./src/inputs/utils.ts" },
    { "name": "./src/inputs/PinInput/PinCaret.tsx" }
  ],
  "reasons": [
    { "moduleName": "./src/data/DataGrid/cells/EditablePinInput.tsx" },
    { "moduleName": "./src/inputs/PinInput/index.ts" }
  ]
}

In this example, we have an asset with the chunkName of ./src/inputs/PinInput/PinInput.tsx + 4 modules. There’s a total of five modules within this chunk:

./src/inputs/PinInput/PinInput.tsx
./src/inputs/PinInput/SeparatedPinInput.tsx
./src/inputs/PinInput/SinglePinInput.tsx
./src/inputs/utils.ts
./src/inputs/PinInput/PinCaret.tsx

The "reasons" key provides information about the asset’s dependency graph. In other words, modules that depend on this asset. Any changes to the asset chunk’s modules may have an impact on its dependent modules:

./src/data/DataGrid/cells/EditablePinInput.tsx
./src/inputs/PinInput/index.ts

Asset object for a story file

The asset object for a story file usually looks a bit different:

preview-stats.trimmed
{
  "id": "./src/inputs/PinInput/stories/PinInput.stories.tsx",
  "name": "./src/inputs/PinInput/stories/PinInput.stories.tsx + 4 modules",
  "modules": [
    { "name": "./src/inputs/PinInput/stories/PinInput.stories.tsx" },
    {
      "name": "./src/inputs/PinInput/stories/PinInputWithNoCopyPaste.storyfile.tsx"
    },
    {
      "name": "./src/inputs/PinInput/stories/PinInputWithNoCopyPaste.storyfile.tsx?raw"
    },
    {
      "name": "./src/inputs/PinInput/stories/PinInputWithValidation.storyfile.tsx"
    },
    {
      "name": "./src/inputs/PinInput/stories/PinInputWithValidation.storyfile.tsx?raw"
    }
  ],
  "reasons": [
    {
      "moduleName": "./src/ lazy ^\\.\\/.*$ include: (?%21.*node_modules)(?:\\/src(?:\\/(?%21\\.)(?:(?:(?%21(?:^%7C\\/)\\.).)*?)\\/%7C\\/%7C$)(?%21\\.)(?=.)[^/]*?\\.stories\\.(js%7Cjsx%7Cts%7Ctsx))$ chunkName: [request] namespace object"
    }
  ]
}

Under “reasons,” you’ll notice a moduleName resembling a path pattern. In the full, untrimmed file, the issuerPath’s are "./storybook-config-entry.js" and "./storybook-stories.js". That’s because the dependency is related to this project’s Storybook configuration:

.storybook/main.js
/** @type { import('@storybook/react-webpack5').StorybookConfig } */
const config = {
  stories: [
    {
      directory: "../",
      files: "**/*.@(mdx|stories.@(js|jsx|ts|tsx))",
    },
  ],
  // ...
};
export default config;

Use the trimmed stats file to trace dependencies

If you’re trying to understand why a story file is being detected as changed, you can search for its file path in the trimmed stats file and trace its dependencies to see which modules were affected.

Tracing dependencies with Vite

Vite support for TurboSnap is available out of the box, starting with Storybook 8.0 and later, and does not require any additional configuration. However, if you’re using an older version of Storybook, you must install the vite-plugin-turbosnap plugin to enable TurboSnap support with Vite.

Similar to Webpack, when you run the build-storybook command, the preview-stats.json file is automatically generated. It contains information about each module being built and a mapping between each file and all the imported files. This structure mirrors that used by Wepack but only includes the information TurboSnap needs to perform dependency checks.