First, the community is in this transition state where some packages only support CommonJS, others only ESM, and a few both module systems. In theory, ES modules can import CommonJS, but it doesn’t work that well because packages don’t follow the CommonJS conventions. For example, using
.cjs extensions or having a
main attribute in the
package.json. The bundling of your code alongside the external dependencies can output pure ESM and CJS bundles (as long as the CJS code is not using CJS-only features like dynamic requires).
Second, you flatten the dependency graph into a single package. When users install your CLI, they get a self-contained bundle that has everything it needs to run in it. We eliminate the installation of dependencies as a source of nondeterminism. This wouldn’t be necessary for other ecosystems like Ruby’s, where dependency graphs are less deep and fewer but larger dependencies are preferred over many but smaller packages. We reduce the likelihood of telling developers to delete
node_modules hoping for the new installation of dependencies to yield a valid dependency graph.
And last but not least, bundlers tree-shake the code, meaning that what users will install is only the code that the CLI uses, nothing else. It’s important to note that the effectiveness of the tree-shaking is tied to the staticness of the code. If we use dependencies that use dynamic requires, the bundler might include the entire dependency.
Gestalt and the Shopify CLI projects build upon this setup, which will positively impact the users. It’s taken a lot of tinkering with Typescript, Rollup, and Nx, but we seem to have found a setup that works incredibly well and plays nicely with VSCode. You can check Gestalt’s repository to see a tangible example.