zendeskgarden/react-components

Preserving Module Structure to Allow Tree Shaking

hzhu opened this issue · 0 comments

hzhu commented

Overview

Garden packages may not be optimized for consumer tree-shaking. As a result, consumers may include unused Garden package code in their final production bundles. This issue attempts to provide an explanation and demo of the problem.

Problem

Consumers of Garden packages cannot drop unused Garden code with tree shaking because there are no modules to drop.

Garden packages distribute two bundle files: 1) CJS and 2) ESM. This issue focuses on the ESM bundle because tree shaking uses ESM’s static structure to determine which ESM modules to drop during tree shaking.

Garden's ESM bundle groups the code into a single file without modules. For example, Garden's react-typography distribution file index.esm.js includes code for: SM, MD, LG, XL, XXL, XXXL, Code, Blockquote, CodeBlock, Span, Ellipsis, Paragraph, OrderedList, and UnorderedList. Since there are no modules, no modules can be dropped during tree shaking.

When a consumer only uses a single export in their app:

import { XXL } from '@zendeskgarden/react-typography'

const App = () => <XXL>hello world</XXL>

Then runs the application code through tree shaking, the final bundle will include code from every module used to generate the bundle in the react-typography package. In other words, SM, MD, LG, CodeBlock, Paragraph, etc. is not used by the consumer's application code, but still included in the consumer's production bundle.

Solution

Preserve the module structure of Garden packages in the distribution so that consumers can take advantage of tree-shaking. Rollup provides an option for preserving module structure.

Demo

I created an isolated demo app to analyze the outcomes of tree shaking, a minimal React app using Garden's typography package. The app can be found here.

⚠️ To run this analysis locally, you will need to yarn link a local version of @zendeskgarden/react-typography distribution. Also, to prevent conflicting React versions, you will need to yarn link both react and react-dom.

Non-preserved modules

  1. Clone isolated test repo
  2. Run yarn install for the test repo
  3. Run yarn link in local react-components repo:
    a) Go to node_modules/react & run yarn link
    b) Go to node_modules/react-dom & run yarn link
    c) Go to src/packages/typographgy & run yarn link
  4. Link dependencies in the test repo to point to react-components. In root test repo, run:
    a) yarn link react
    b) yarn link react-dom
    c) yarn link @zendeskgarden/react-typography
  5. Build local dist for @zendeskgarden/react-typography with yarn build:single
  6. Navigate to test repo and run yarn build to generate app bundle

Preserved modules

Same steps as "Non-preserved modules". Except with rollup.config updated to preserve module structure. Update rollup.config.js output to:

output: [
  { file: pkg.main, format: 'cjs' },
  { dir: 'dist', format: 'esm', preserveModules: true, entryFileNames: '[name].esm.js' }
]

Bundle Size Analysis

❌ Non-preserved modules

asset main.js 311 KiB [emitted] [minimized] [big] (name: main) 1 related asset

Search for text "CodeBlock" in main.js bundle. Unused CodeBlock code found and was not dropped during tree shaking.

✅ Preserved modules

asset main.js 234 KiB [emitted] [minimized] (name: main) 1 related asset

Search for text "CodeBlock" in main.js bundle. Unused CodeBlock cannot be found and was dropped during tree shaking.

Tree Shaking Results

The consumer application bundle size is reduced by 24.75% with tree shaking of preserved modules:

Command Non-preserved Modules Preserved Modules
Unused modules dropped No 🙅🏽 Yes 🎉
Bundle size 311 KiB 234 KiB

Further Investigation

Further investigation on other packages is needed. There may be other factors not considered in this current issue ⚠️ .

Resources