Design: Overview
WARNING
This page is a work in progress.
Cloudpack aims to reduce the friction in building, testing, and shipping changes to applications and their dependencies.
The current focus is increasing inner loop speed and decreasing friction of cross-repo local development. (We also have many future ambitions for making the development, dependency management, and deployment experiences even easier!)
This page outlines some of the major benefits and fundamental concepts of Cloudpack.
Improved experiences
Faster startup times
For large enterprise apps with thousands of source files, library mode makes cloudpack start at least 2-5x faster than webpack-dev-server (often even better) for "cold start" with no cached bundles, "warm start" with a populated cache, and reload times in watch mode. Having fast inner-loop startup times helps developers build features and validate their changes quickly.
Startup performance can be further improved by enabling the sync feature to download most package bundles from a remote cache.
Testing cross-repo changes
cloudpack link makes the process of testing library changes against an app in another repo much easier.
Before:
- Clone the app repo
- Clone the library repo
- Run
npm linkto link the library to the app repo and hope it works - Use webpack dev server to start a local session of the app
- Debug why there are 2 copies of React in the graph
After:
- Clone the app repo
- Clone the library repo
- Run
cloudpack startin the app repo- In the future, you'll be able to link your library against an app from the cloud!
- Run
cloudpack linkin the library repo
See the client page for more details about how the Cloudpack client CLI works.
Concepts
Operation modes
Cloudpack can serve an app using two different modes:
- Library mode (default) - each package is served as individual isolated bundles from a remote bundle cache or your local machine.
- Optimized/production mode (not fully implemented) - the application is served to customers as bundled, tree-shaken, minified javascript optimized to minimize network traffic.
Library mode
In library mode, cloudpack start serves your web app by loading each entry point of each package in your dependency graph as a separate ES module (ESM) bundle, using an import map to convert imports to URLs. This allows us to bundle only the code the user is working on, while leveraging a local cache or (future) CDN for the bundles you're not working on. By isolating the libraries, we reduce the work required to get the app started and maximize caching opportunities.
This approach works best in packages with exports maps, so that valid package entry point files can be added to the import map ahead of time. (For more information on how to properly export multiple entry points and module types, see this guide.) Despite being an anti-pattern, deep imports into packages without exports maps are also supported, though you may need to run cloudpack init first to determine which entry point paths are imported and auto-generate exports maps.
In the future, library mode may also help facilitate updating dependencies, automated testing of dependency updates, and even rolling out changes to customers. (This is not currently implemented and not on our immediate roadmap.)
For a walkthrough of an older version of this concept, see this video.
Optimized mode
While library mode is great for inner loop development and upgrade evaluation, downloading thousands of bundles is not optimal for end-user application performance.
This is why Cloudpack can also create optimized production builds, including bundling and minifying all code, tree shaking, and splitting out common chunks. (As of summer 2025, a basic version of this is implemented with cloudpack bundle --mode production using Webpack, but it's still a work in progress.)
In the future, Cloudpack may support app bundle analysis to see how changes occur over time, and why they occur. Load the bundle graph, filter by packages, and see how sizes change over time.
Bundler abstraction
Cloudpack is designed to be bundler-agnostic, and internally it chooses between multiple bundlers depending on which one is best for the job:
- In most cases, we use Ori, a blazing-fast wrapper of esbuild
- Rollup is used for CommonJS packages
- Webpack is used for production bundles, AMD packages, or (as of writing) when bundling for Node not browser
- Rspack is also available
- ...and we can easily add more bundlers when the next new thing arrives!
WARNING
By design, Cloudpack does not respect existing bundler configs, e.g. webpack.config.js. This is because its bundling approach (especially in library mode) is fundamentally different than a normal monolithic bundle, so many settings simply aren't relevant. Our built-in bundler configs are also meant to handle many common scenarios. We recommend trying Cloudpack without any extra config first, then adding to your Cloudpack config as needed. (Custom bundler capabilities provide an "escape hatch" for advanced scenarios, but please talk to the team first to make sure we haven't covered your scenario another way.)
You've likely heard of the other bundlers, but Ori may be new. It's developed by another team at Microsoft, and the code and docs (requires MS github login) are still internal. As of writing, it's the fastest bundler option Cloudpack offers for most cases: at least 30% faster than Rspack.
Ori provides a binary with esbuild's Go code, plus Go versions of commonly-used plugins (such as CSS). This eliminates the IPC overhead of a native binary with JS plugins, which we discovered was a significant bottleneck when testing esbuild against a very large repo early on in Cloudpack's development. (Rspack is subject to the same issue.) The other key performance improvement is from a JS wrapper which runs builds via a reused service process, avoiding process startup overhead for each package.
The major downside of Ori for Cloudpack is that it doesn't handle CommonJS code well: specifically detecting export names, and require() of other packages. In that case, we fall back to generating an ESM stub to find the export names and then using Rollup. We also use Webpack for the specific case of packages which provide only AMD code for some reason (really shouldn't happen, but occasionally does), since Ori and Rollup don't handle that syntax well.
If you need to change the bundler, either for a specific package or the default, see the config docs.
ESM stubs
"ESM stubs" are a workaround for issues presented by CommonJS modules for Cloudpack's ESM library mode approach. The biggest problem is that CJS has many possible ways to declare exports, some of which are nontrivial or even impossible to find through static analysis (as detailed in the previous link). For an ESM bundle in library mode, all the export names must be declared as exports in the bundle output so they can be imported by name in consuming packages. Some bundlers may attempt to convert common patterns to named exports, but others will just provide a default export.
Cloudpack's workaround for this issue is to detect the actual exported names, create an ES module format stub file with the proper exports, then point the bundler at that. For example:
// Original file: index.js
module.exports.default = { value: 'I am the default export' }
// Example of a pattern that can't be statically analyzed (suppose it returns "a")
module.exports[computePropertyName()] = 'why'
module.exports.b = 'b'
// Generated stub file: index-stub.mjs
import moduleExport from './index.js'
const { a, b } = moduleExport
const defaultExport = moduleExport?.default?.default ?? moduleExport?.default
export default defaultExport
export { a, b }
// Exact bundle output varies, but it will include the export names "a", "b", "default"Currently, our primary approach for detecting export names is to run the code in a worker thread, with a browser-like environment provided for packages that need it. This has multiple significant downsides and we're actively considering other options (tracking issue), but running the code is the only way to approach 100% accuracy.
One alternative we've currently implemented is PackageSettings. unsafeCjsExportNames. This can be used to either provide hardcoded export names for a package, or attempt to detect them from types included with the package.