Developing in a large TypeScript Monorepo
Submitted by Jai Santhosh (@jaisanthosh) on Sunday, 1 September 2019
Section: Full talk (40 mins) Technical level: Intermediate
In Microsoft, we have a very large TypeScript-based git repository where over 250 developers build and write code for high-value frontend components which is used across all Microsoft365 products like Outlook, Office, Bing, SharePoint, etc.It contains over a 100 npm packages, containing over a million lines of TypeScript code. Co-locating these components encourages collaboration and sharing code across teams very easier.
In this talk, we’ll focus on the tooling used and code organization to make the development easier, fast and reliable and will not discuss in detail unit tests and automation for a better focus on development.
A closer look at package management
Since there are 100+ highly inter-connected internal npm packages which declare over 2,500 external dependencies, we will briefly take a look at how packages and dependency management. We will discuss the structure of each package in the monorepo and how dependencies are expressed across these packages. We will also look at the
.tsconfig file organization to understand package dependencies internally in the monorepo.
The packages contain transpiled TypeScript code and bundles generated using webpack and the modules from these bundles are used across various product endpoints.
Using yarn workspaces to handle external dependencies
We use yarn workspaces to install our external dependencies and link the Midgard-hosted packages together based on the dependencies expressed in the package.json files.
Faster type-checking across these packages
The fastest validation is type-checking; when we produce invalid TypeScript code, IntelliSense gives us instant visual feedback within the editor. This is probably the most useful and loved validation step. So we want to make sure it works and it works fast.
Using Test Apps to validate user behaviour
To validate more complex component behaviors, like user interactions, we use test-apps. Test apps are either webpages or native applications which load our bundles to render our components for manual testing. This is probably the second most popular validation workflow, therefore we care a lot about it.
Using Lerna to prepare packages
TSC will produce the files described in the package.json file, so now the package is ready to be consumed. The *.js files are the JS files containing the code ready to be consumed. The *.d.ts files (DTS) are the files containing type information, they are used to type-check the code consuming this package. The *.d.ts.map are used by IntelliSense to enable cross-package code navigation. Because producing these files is necessary before a package can be consumed, we call this step “prepare”.
Because a package can be prepared only if its dependencies are already prepared, we need to run the prepare script in all packages in topological order. We use Lerna to orchestrate this.We usually use Lerna to run the watch process in each package of the dependency tree of the component we work on.
Improving “watch” across packages
We will also briefly look at how we will manage watch processes across the packages and optimizing by exposing packages pubic interfaces from JS+DTS files to TS files for the monorepo-hosted packages. Its also important to discuss the Intellisense performance here.
We will go over a sample monorepo structure to explain this in detail.