Sharing code between projects

Yash Mahalwal
12 min readOct 30, 2021

--

Photo by Alexander Sinn on Unsplash

You are a happy developer. You wake up everyday, work on your React web app and log off. npm and 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
Pros
Cons
· Approach 2: Use a tool with less structure
Pros
Cons
· Approach 3: Switch to a monorepo
Pros
Cons
· Approach 4: Use git submodules and subtrees
Submodules
Subtrees
Cons
Pros
· Approach 5: Keep synchronised copies of code
Pros
Cons
· Conclusion

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

Photo by Paul Esch-Laurent on Unsplash

This approach is pretty simple yet effective. It goes like this:

  1. You create a new repository for the shared code.
  2. You create a new npm project.
  3. You publish this package either on npm registry or a private registry managed by tools like Verdaccio or GitHub packages. This is optional and you don’t have to do this. But it makes code management (think versioning) and sharing easier.
  4. 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.

Pros

  1. 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.
  2. npm provides first-class support for package versioning, releasing and other management steps. That enables you and your team to have good processes with minimal effort.

Cons

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:

  1. Clone the shared code repository.
  2. Remove the log statement.
  3. Create a pull request for this change. After all, we are a team.
  4. PR gets reviewed and then merged.
  5. 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.
  6. 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:

  1. Bit comes with two parts, a cli tool and a hosting service.
  2. 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 bit keeps track of it. It also keeps a dependency graph (similar to webpack) for your component. This means that bit knows all the dependencies for your code.
  3. 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.
  4. You can either install your component as an npm package or you can import your component to other target projects (say web or mobile). Installing npm package 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.
  5. 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.

Pros

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.

Cons

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:

  1. You have 3 packages deployed to a registry: web, mobile, shared.
  2. package.json for web and mobile will contain an entry for shared.
  3. Whenever you deploy your web or mobile code, npm will be responsible for fetching shared from 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:

  1. Lerna allows you to initialise a monorepo. You can add packages (projects) inside the monorepo.
  2. Whenever one project relies on another, you can link it. This is done by a simple command. Lerna creates 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.
  3. 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.
  4. Lerna allows you to version and release web, mobile and shared code as npm packages independently.
  5. When you deploy your web or mobile codebase, shared code is installed from the npm repository 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.

Pros

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.

Cons

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

Photo by Roman Synkevych on Unsplash

Every developer worth their salt has heard of git submodules. 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.

Submodules

By using 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.

Subtrees

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.

Cons

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.

Pros

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

Photo by Cas Holmes on Unsplash

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.

Pros

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 .yml file.

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.

Cons

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.

Conclusion

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 : )

--

--