Start here. This is the direct spoken answer to practice first.
Overview
Tree shaking is a build optimization, not an architectural guarantee. Clear static module boundaries make both reuse and analysis easier.
I organize modules around cohesive responsibilities and expose a small public API with static ESM imports and exports. I keep initialization side effects separate from reusable definitions, because bundlers can remove unused exports only when they can analyze the graph safely. Consumers should depend on the public boundary rather than reaching into internal files.