JS code sharing tactics

We, coders, try to reuse code as much as possible. We extract utility functions, introduce new UI components. Motivation is clear – follow the DRY principle with all the benefits like less code, less maintenance or testing.

Refactoring code for reuse is easy when working within one project. However there are situations where we want to share code between different projects. One may want to share validation logic between client and server, to share model or API clients between multiple microservices or to enforce consistent design with a common UI library.
Code sharing can also be done between projects but there are multiple options how to do it – each with its own benefits and drawbacks. This blog should help you to choose the right one.

Tactic 1: Via file system

The simplest option to share code between projects is to share common files via file system directory and simply include them from multiple projects. Typical use-case for this tactic is to share models, validations and other utils between client and server code. Directory structure could be the following

Especially when you are using Create React App, you are forced to put common code “inside” client’s source folder. Luckily the server (usually) does not have restrictions on how to include files.


  • No (or minimal) setup. It is recommended to set up linting rules enforcing that common code does not import from client (nor server).
  • No indirection. Changes in common code are directly visible which provides quick developer feedback and makes debugging easier.


  • No versioning. Common code is directly coupled with dependent projects and if any breaking change happens, it must be fixed everywhere. But for someone it can be advantageous, as you can’t end up maintaining endless incompatible versions.
  • Not scalable in terms of number of projects. Although it is possible to share code between multiple projects in this way, it is unlikely that it will be more than a couple of projects.
  • Common code can use only a subset of language features common for all projects. E.g. your client code can use async/await syntax thanks to the transpilation but server code does not support it.
  • Suitable for mono-repo only. In special cases or with special handling this approach can also be used for multi-repo but it would bring its own challenges.

Tactic 2: Git submodules

Using git submodules feature, it is possible to compose multiple git repositories in order to mimic mono-repo structure. In essence it is almost identical to the previous approach but git allows you to use different branches to emulate different versions of common code.

Benefits and disadvantages are also similar (minimal setup, no indirection, subset of language features) with the following differences.


  • Independent versioning. It is possible to develop and use different versions of common code for multiple dependent projects. This also improves the scalability.


  • Error prone for developers. Using submodules can be tricky. Cloning, updating or pushing repositories requires special steps (or CLI arguments). Also managing multiple branches to emulate versions can add confusion.

Tactic 3: Npm packages

Npm packages are a standard way to share JS code. Code can be shared both internally inside the team or externally with the whole world. Using npm packages also makes collaboration between different teams possible, e.g. platform team preparing reusable packages and product teams working on final products.

With npm packages one must decide what is the correct size of a package. It is the same thing as with microservices – hard to define the right size in terms of line of codes. I suggest deciding it based on the semantics – code with high cohesion belongs together, loosely coupled things should be separated.
There might be a slight exception to this rule. It is okay to put a collection of different utility functions together into one package. Thanks to tree-shaking (if properly applied), we do not have to worry about the final bundle size if we want to use only one function out of hundreds. However if there are too many things together and some breaking change happens, it may be hard to figure out if the part I’m using is affected too.

This is the most versatile solution but it also comes with its cost.


  • Explicit versioning. Published npm packages must have (mostly) numeric versions (see semantic versioning). This approach to identify compatible packages is more natural (with help of package.json) than using git submodules.
  • Allows for heterogeneity. During the publishing process a package can be transpiled. Thus it is possible to use the TS library in a JS project or a package written with ES6 language features in an ES5 project.
  • Works fine with both mono- and multi-repo.


  • Complicated setup. We need to set up an extra project, possibly with transpilation etc. Different tools can help with it. And for internal packages, we need a private npm registry.
  • Indirection. Package and project code are separated and thus it is not so easy to change them at the same time. Also debugging code across the boundary can be cumbersome (especially when transpilation is used).
    • It is ideal when we can develop (and test) packages in isolation. Tools like Storybook can be very helpful.
    • If we are changing both library and dependent projects at the same time (e.g. we are in the middle of refactoring), we can use yarn/npm linking or use tools like lerna.


Which tactic to choose? Unless you are familiar with git submodules (even then it is quite exotic option which might have its benefits on the other hand), we would recommend choosing between the first and the last option.
If you are not sure that you need a full-blown solution with npm packages, you can start with simply sharing via file system. You might consider reexporting common code from common “root” and/or use absolute imports if you feel that you might switch to npm package soon. Then it should be quite easy to replace all imports with the new ones.

Related Post

Leave a Comment

© 2021 Instea, s.r.o. All rights reserved. Privacy policy

Contact us

Where to find us