Preserving Module Structure to Allow Tree Shaking
hzhu opened this issue · 0 comments
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.
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
- Clone isolated test repo
- Run
yarn install
for the test repo - Run
yarn link
in localreact-components
repo:
a) Go tonode_modules/react
& runyarn link
b) Go tonode_modules/react-dom
& runyarn link
c) Go tosrc/packages/typographgy
& runyarn link
- 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
- Build local dist for
@zendeskgarden/react-typography
withyarn build:single
- 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
- Webpack's Tree Shaking
- Theodo's How To Make Tree Shakeable Libraries
- The technique of preserving module structure can be found in other projects like Shopify's Polaris