I had to look up isomorphic in a dictionary the first time I came across this word in web development. reports that Wikipedia Isomorphic JavaScript , also known as Universal JavaScript , describes JavaScript applications which run both on the client and the server . Building a package that works out of the box in both the server (ie Node) and the client (ie a browser) is hard. As soon as you start mixing in dependencies, if even one of them makes a call to a node-only package (think ) or browser-only object (think ) , it will tank your whole build. Worse yet, you may not even be able to reasonably infer this problem exists, as they may be buried in sub-sub-sub-dependencies that have no bearing on the code you are writing. [fs](https://nodejs.org/api/fs.html) [window](https://www.w3schools.com/jsref/obj_window.asp) This document describes a couple easy conventions for developing isomorphic node packages and ends with one opinionated missive about how they should be packaged. Ignore requires in webpack Even if you use a package like to create conditional node vs browser imports, does not care about conditionals because it has no clue how the runtime will resolve them. So, in the following scenario: [detect-node](https://www.npmjs.com/package/detect-node) webpack const foo = isNode ?r equire("fs") : require("fs-web"); it will happily try to package all of the code you require, meaning that in this case, it will try to package . It will then fail with the following error message. fs Module not found: Error: Can't resolve 'fs' in [insert file here] In response to my , Alex Rokabilis writes: question on how to get around this conundrum [check out] the not very well known function. This is a webpack specific function that will instruct the parser to avoid bundling this module that is being requested and assume that a global function is available. __non_webpack_require__ require While is a documented part of the API, it is buried deep within the documentation, thus its obscurity. However, it works like a charm with one minor tweak. [__non_webpack_require__](https://webpack.js.org/api/module-variables/) If you try to write: const foo = isNode ? [__non_webpack_require__](https://webpack.js.org/api/module-variables/)("fs") : require("fs-web"); Node will barf with the following error message: __non_webpack_require__ is not defined Typescript will also not recognize . __non_webpack_require__ This can be overcome in the following ways: For typescript users, require . Do not use for this, it will not work. [@types/webpack-env](https://www.npmjs.com/package/@types/webpack-env) [@types/webpack](https://www.npmjs.com/package/@types/webpack) In your code, somewhere before you use , write the following hack: __non_webpack_require__ if (isNode) {(global as any).__non_webpack_require__ = require;} There are a few different ways to accomplish this, most of which will not raise an error in an IDE and will compile with and but will fail in your node environment. So use this one — it’s been extensively tested by our team and works. babel typescript Use dependency injection and TS interfaces is a popular pattern that is famously evangelized by Uncle Bob in that I’ve mentioned several time on this blog. The idea is that dependencies should always import inwards, meaning that “core” code should never know about dependencies, but should make a contract with the calling code via interfaces, and the calling code implements classes that make good on this contract by, amongst other things, pulling in relevant dependencies. Dependency injection this article In , we use dependency injection for the logging and persistence mechanisms, both of which are defined in an options object that is injected into the main function at runtime. If no option is given, sensible defaults are chosen based on the package, which is the most popular and reliable way to detect if an environment is node or not. Let’s see how that works. unmock-js unmock detect-node In our dependency injection, parameter is defined to contain, amongst other things, the following interfaces: options export interface IUnmockOptions {// ... some stuff, then ...logger?: ILogger;persistence?: IPersistence;// ... more stuff} and themselves contain various methods, such as and , that call functions specific to node or the browser. For example, here is the difference between node and jsdom code for one method in the interface: ILogger IPersistence ILogger::log IPersistence::saveHeaders IPersistence // fs-persistence.ts export default class FSPersistence implements IPersistence {public saveHeaders(hash: string, headers: {[key: string]: string}){fs.writeFileSync(`${this.outdir(hash)}/response-header.json`, JSON.stringify(headers, null, 2));}} // local-storage-persistence.tsexport default class LocalStoragePersistence implements IPersistence {public saveHeaders(hash: string, headers: {[key: string]: string}){window.localStorage[`${this.outdir(hash)}/response-header.json`] = JSON.stringify(headers, null, 2);}} Don’t use separate packages I’ve flipped back and forth on this issue quite a lot. On one hand, using separate packages for different environment solves the “1980s Christmas lights” phenomenon where an error in one environment tanks all of the others. This can, of course, be toxic for package and project management. On the other hand, separate packages can lead to a usability problem if the developer is trying to develop an isomorphic package and now needs to include and manage multiple packages. This is currently the case, for example, with versus . If you are developing a package, for example, and are trying to reuse server and client code that relies on , this can lead to a huge mess of clauses. I’m sure that Sentry will fix this, but the general problem should be avoided if possible. [@sentry/node](https://www.npmjs.com/package/@sentry/node) [@sentry/browser](https://www.npmjs.com/package/@sentry/browser) Next.js Sentry if / then As a result of this philosophy, is a single package that works flawlessly across Node and browser environments, even in complicated scenarios that mix code from the two. [unmock-js](https://www.npmjs.com/package/unmock) The goal is to ease developer experience so that they have a batteries-included way to start working with mock data as they build out their web app. tl;dr When making isomorphic JS packages: Use . __non_webpack_require__ Use dependency injection. Thanks for reading! Who doesn’t like Morph?!?