Sharing code between projects
You are a happy developer. You wake up everyday, work on your
React web app and log off.
git are your best friends and you tame your code every day. Life couldn’t be any better!
One fine day, the CEO of your company realises that you are missing out on an important market — Mobile. Like a good developer, you step up to the job and suggest
React Native to build for mobile. This is where the trouble begins. You find yourself rewriting most of the application again for the mobile and life isn’t fun anymore. You feel like you’re wasting a lot of time and lethargy takes over whenever you start to work. You lie back in your chair, looking at the fan and think back to your good old childhood days. And then all of a sudden, you remember an important lesson from school:
Sharing is caring
Why not share logic between the web and mobile codebase? That is better than rewriting network calls, business logic, type interfaces, tests cases and whatnot. To be honest, if you didn’t think of it before, you might’ve missed out on your software engineering course.
Table of contents
This section was generated by
toc-medium extension for chrome. You can find out more about it here.
· Understanding the problem
· Approach 1: Create an installable package
· Approach 2: Use a tool with less structure
· Approach 3: Switch to a monorepo
· Approach 4: Use git submodules and subtrees
· Approach 5: Keep synchronised copies of code
Understanding the problem
You wish to share code between multiple codebases. There are a variety of approaches to do that. Some are simple but limited in functionality while others are very flexible but also complicated. This article is a summary of different ways to share code between code repositories and compares their pros and cons. While most of the conversation is around
nodejs applications, these approaches are more or less the same for different ecosystems.
Approach 1: Create an installable package
This approach is pretty simple yet effective. It goes like this:
- You create a new repository for the shared code.
- You create a new
- You publish this package either on
npmregistry or a private registry managed by tools like
GitHubpackages. This is optional and you don’t have to do this. But it makes code management (think versioning) and sharing easier.
- Install this package in your web and mobile codebase. If the package is published to a registry, you can install it directly. If it is published to a remote repository (like
GitHub) you can install the package via the url.
- Granular control over shared code. You can have different versions of shared code on web and mobile (via package version if published to a. registry or via commit hash if published to a remote repository). So if some change breaks the web or mobile, you can always revert to the previous version for that platform.
npmprovides first-class support for package versioning, releasing and other management steps. That enables you and your team to have good processes with minimal effort.
Packages complicate the development process. Say you find that someone left out a
console.log statement in the shared code. Here are the steps to remove it:
- Clone the shared code repository.
- Remove the log statement.
- Create a pull request for this change. After all, we are a team.
- PR gets reviewed and then merged.
- A new version of this package is published to the registry. This part is usually automated but there’s still some time delay. When you’re in the zone, even a few minutes matter. This step is not present if you are not using a package registry.
- Now you need pull requests for web and mobile where you update the shared package in the project. PR creation can be automated but they still need to be reviewed and merged.
Imagine doing this for every change. Doesn’t sound too fun, does it? You can navigate around this using
npm link but it is a little complex to manage. There’s another drawback to using a package. Source code usually needs to be built before it is used. So you have to either build the code before it goes to the npm registry or remote repository (if you rely on the latter). Otherwise, you need to add support for building your package (present inside
node_modules ) in your web and mobile codebases.
The problem here is that
npm packages are meant to be highly shareable and therefore they involve a long process to allow for dependency management, versioning and other semantics. This is not ideal for code that is changed frequently.
Approach 2: Use a tool with less structure
There are tools that allow sharing code components while keeping the process simple. One such tool is
bit. Here’s how it works:
- Bit comes with two parts, a cli tool and a hosting service.
- You use cli to create a component in your project (web or mobile). It involves running a simple command. This essentially creates a folder where you can keep your code and
bitkeeps track of it. It also keeps a dependency graph (similar to
webpack) for your component. This means that
bitknows all the dependencies for your code.
- You can export your code to the hosting service. This allows you to share your component. This again is done by a simple command. Bit hosting (
bit.dev) comes with some cool features like component demos, building and testing, creating pull requests in your projects for changes in the component and other tools to manage your components.
- You can either install your component as an
npmpackage or you can import your component to other target projects (say web or mobile). Installing
npmpackage works like it normally does. If you import the component in your project (again, a simple command), you get the source code of the component in your project. Now your component is present in your projects.
- Now that you have the source code of your component in your projects, you can also change your components in any project and export it from there. Simply change the code and run the export command. It is sent to the hosting service. PRs are made in all your projects which depend on the component to reflect this update.
Bit allows you to make changes frequently in your shared code with ease. There’s a little learning curve but that can be easily managed. You can also host demos for your components in isolation. Plus you get all the advantages of
npm packages including versioning. It also comes with templates for creating components which helps with the boilerplate.
The bit hosting service is not completely free. It has a pricing scheme if you are not using it for basic personal work. You can have your own hosting service (which is a simple
node.js server) but then you lose out on features like demos and PR creation.
Approach 3: Switch to a monorepo
While this may seem like a radical approach, monorepos make it extremely easy to share code between projects. A lot of awesome projects, including our beloved
babel, are hosted in a monorepo.
Remember how I said that you can use
npm packages along with
npm link for ease of workflow? This is based on that approach. The idea is to keep a single repository for all your projects. Each project is a separate folder in the repository. You can then use a tool like
Lerna to manage versioning and release of each project.
Lerna also helps you link projects for development.
Lerna expects you to deploy all your code to a registry. This is how it will look in production:
- You have 3 packages deployed to a registry:
mobilewill contain an entry for
- Whenever you deploy your
npmwill be responsible for fetching
sharedfrom the registry and install it in your project as it is a dependency.
This is pretty much straightforward. You might already be familiar with this if you read Approach 1. If you skimmed over it, I feel you. I’d probably do the same if I were you.
Thinks get interesting when we talk about the development workflow. Here’s how the process goes:
Lernaallows you to initialise a monorepo. You can add packages (projects) inside the monorepo.
- Whenever one project relies on another, you can link it. This is done by a simple command.
Lernacreates symlinks between projects on your local machine. So if you’re working on web codebase and then you change the shared code, that is reflected immediately.
- In the
package.json, all linked dependencies are specified by their package version. So if you check the web or mobile
package.json, you see the version of shared code (
xx.xx.xx). This version is usually the version of shared code in the monorepo when linking command was run.
Lernaallows you to version and release web, mobile and shared code as
- When you deploy your web or mobile codebase, shared code is installed from the
npmrepository as a dependency.
In a nutshell, you can change shared code while development and see the impact on web and mobile codebases without having to release the shared package and installing it over and over.
Monorepos allow you to manage versioning while allowing simultaneous development of different components. Tools like
Lerna can also
prune dependencies which means that common dependencies of all the projects can be kept in one location instead of keeping them in
node_modules for each project. This can reduce the storage requirement drastically. You can also have testing, linting and formatting of the codebase in one place.
Lerna comes with a learning curve. Plus it might not be feasible to switch an existing application to monorepo. Monorepos also present problems for larger teams where access control plays an important role.
Approach 4: Use git submodules and subtrees
Every developer worth their salt has heard of
git subtrees is a newer feature that is meant to enable code sharing but makes management easier as compared to
git submodule. Essentially,
git allows you to have other repositories inside your project repository. Here’s a basic description of both of these. Going deeper into them is beyond the scope of this article. You can google about them if you wish to understand everything in more detail.
git submodules, you can have an independent repository inside a parent repository. So you have your web and mobile repository and then inside it, in a folder, the shared code repository. If you
cd into the shared code folder, you can pull, commit, make branches and push back to the shared code. Just how you would do in any normal repository. If you
cd back into the project, your git commands only track the project files and not the shared code files. Whenever you commit in the mobile or web codebase,
git will simply note the commit hash of shared code. That means
git only remembers that at this point in web or mobile codebase, the shared code was at a given state (the commit hash). Branches and commits of the project and the shared code are managed independently.
git provides commands to manage this workflow.
Submodules work by keeping a reference of shared code in the project (via commit hash). While this allows for consistency, it makes managing the codebase very hard. You need to learn complex commands to make changes to your code. With subtrees, you can add all the files of shared code as children of the project repository. When you run git commands in web or mobile codebase, these files will be tracked as if they were a part of the project repository.
git allows you to pull and push shared code files. But it is not as simple as running a
git pull or a
git push. Instead, you need to learn new concepts, commands and flows.
Let us start with the cons first. Both of these approaches will make your normal
git workflow very complex. You’ll probably need to run multiple git commands to make changes and it is very easy to mess up.
git submodules are especially hard to manage and work with. If you have a team of multiple developers, all of them need to be fluent with the concepts in order to work on the codebase. Experienced developers usually stay away from submodules and use subtrees only if they have to. That being said, it relies on the use case and both of them are pretty strong candidates for code sharing.
Now to the good part. You get the power of
git while sharing code. You can have commit messages, branches, access control and other cool features that
git enables. People usually prefer submodules and subtrees when there’s a need for access control. A junior developer trying to edit license keys for your product? Not on your watch!
Approach 5: Keep synchronised copies of code
This is a very simple approach and one of my favourites. It is ideal when you need to share small codebases. Imagine having to go through the ordeal of Approaches 1 to 4 just to share a couple of functions. Definitely isn’t worth the effort!
The idea is simple. You keep a copy of the shared code in web and mobile codebases. Then you use an automated tool like Repo file sync action. This is a
GitHub action. Basically, you have a configuration file in web codebase which has a list of files and folders that need to be synced with mobile repository. You have a similar file in the mobile codebase which lists code to be synced with web repository. Whenever any change is made to the shared code in web repository, a pull request is created in the mobile codebase to update the code. Whenever a change is made to the shared code in mobile repository, a pull request is created in the web codebase to update the code. Everything is completely automated and you don’t have to worry about anything else.
It is extremely simple. All the other approaches require you to alter your development workflow. You need to run some commands and analyse flows to start working. Imagine doing that but with a team of developers. Sucks, doesn’t it? With this approach, no one needs to learn anything. All anyone needs to know is how to write the configuration file for synchronising which is a very basic
This action is also highly customisable. You can sync different branches across different repositories. You can format the commit messages, assign PRs to people and manage branch names for the PRs — all inside the configuration file.
Since the approach is pretty basic, it cannot be used for complex code management like versioning of shared code or dependency management. If your shared code grows and you feel that this approach is limiting you, it might be time to switch to a different one.
There is another subtle drawback to this. Say a change was made to shared code in web repository. PR was made to the mobile codebase. Someone looks at the PR and approves it. But they miss out on a line which can cause the mobile app to crash. Now this ticking time bomb is in your mobile codebase and no one really ran the mobile app to make sure if it works fine. Ideally, someone should pull in the mobile PR branch and confirm that the change is correct. But let’s face it, everyone messes up some days.
bit suffers from a similar drawback. With the rest of the approaches, you’ll need to have the mobile codebase on the local machine and make some changes to it. You’ll probably run the app and check when you do that. That reduces chances for this sort to messes. It is clearly not a deal-breaker and can be accounted for easily.
All the approaches have their benefits and flaws. There’s no correct way to go about it. It most probably depends on your use case.
Do you have another approach that you used to share code? Did you spot a mistake? Do you have different opinions about these approaches? Let me know in the comments : )