javascriptnode.jses6-modulesisomorphic-javascriptnode-streams

Using stream or any Node.js library in a Node-Browser(Isomorphic) library


I am working on a Node-Browser (Isomorphic) library where the source code is written in TypeScript and transpiled into CJS and ES modules. The transpiled code is shipped as is and not bundled using a module bundler before publishing.

I need to use the stream module from node core and I plan to use the stream-browserify package for the browser part.

  1. What is the best way to include the use of Node.js stream or any node specific library in the library?
  2. How can I include stream-browserify without having to rewrite the same code or with minimum duplication?
  3. I came across some examples where the browser field in package.json is used as follows -
   "browser" : {
       "stream":"stream-browserify";
   } 

How does this work?


Solution

  • You're going to need to need to configure whatever tool you use to transpile your code to understand installed modules.

    If you're not bundling your code, that means you're loading each individual file as it's being imported (I'm not sure how you do or intend to do this with CJS, since there's no require() API in the browser). However, if I import foo from "some-library"; and some-library is a third-party package, the JavaScript file that needs to be loaded needs to somehow be imported.

    This means three things:

    1. You need the module's code, which means you need to actually install it (you'll do this with the dependencies hash in your package.json).
    2. You need to be able to import the code. Which means your import statement needs to be rewritten by your transpiler/bundler/etc. to point at an accessible path. import foo from 'some-library'; needs to translate 'some-library' into something like node_modules/some-library/src/index.js. Otherwise how is the browser going to know what to load and from where?
    3. You need to do the same thing for the internals of the libraries you're importing, since they will also need to have their imports rewritten.

    The browser hash you're mentioning maps imported module names in the code to the browser-friendly version of the module. The example you listed means require('stream') becomes require('stream-browserify') when compiled for a browser and require('stream') when compiled for Node.

    For #2 and #3 above, TypeScript (or rather, tsc) isn't going to do that for you. It sounds like you want a tool like Snowpack. However, there's some things to note.

    First, you can't run any Node module in the browser, or at least not without a more advanced tool like Webpack to mock/rewrite/replace dependencies that require Node and cannot run in the browser. If you import a module that requires another module that's not browser-safe, you won't know that it's not browser-safe until it throws an error at runtime (be it a JS error or a 404 from your server).

    Second, you probably do want a bundler. If you're using Node modules, the dependency tree is going to get pretty deep. If you have 5,000 JS files in your node_modules/ directory, that's 5,000 network requests—many of which need to load, parse, and execute serially (i.e., after the module that imports them loads and parses). Optimizations like tree shaking don't come without a bundler: even Snowpack recommends creating an optimized build for production purposes.

    That said, if you are publishing a package (that is, you're uploading this to NPM rather than deploying it to your own servers), this is mostly moot. Just write your code how you normally write it, list your dependencies in dependencies and the browser-safe equivalents in browser, and publish your transpiled source. The folks actually using your library will worry about how to load it. If you're not publishing to NPM (that is, you're putting this code on the web yourself), do consider a bundler. Nothing about a bundler precludes you from having an isomorphic library: you can have a single JS file that runs in the browser and Node, or you can have two output files (one browser, one Node) which are compiled from the same source. How you achieve this depends on your goals.