0

Is there an existing API or library that can be used to load a JSON file in both the browser and Node?

I'm working on a module that I intend to run both from the command-line in NodeJS, and through the browser. I'm using the latest language features common to both (and don't need to support older browsers), including class keywords and the ES6 import syntax. The class in question needs to load a series of JSON files (where the first file identifies others that need to be loaded), and my preference is to access them as-is (they are externally defined and shared with other tools).

The "import" command looks like it might work for the first JSON file, except that I don't see a way of using that to load a series of files (determined by the first file) into variables.

One option is to pass in a helper function to the class for loading files, which the root script would populate as appropriate for NodeJS or the browser.

Alternatively, my current leading idea, but still not ideal in my mind, is to define a separate module with a "async function loadFile(fn)" function that can be imported, and set the paths such that a different version of that file loads for browser vs NodeJS.

This seems like something that should have a native option, or that somebody else would have already written a module for, but I've yet to find either.

Digicrat
  • 581
  • 5
  • 13
  • 1
    axios , fetch & superagent can be used in both environments. In modern browsers fetch is built in – charlietfl Aug 22 '20 at 23:59
  • Frustratingly, not really. Your best bet is fetch for browsers and a fetch polyfill for node. Fetch is [part of the web api](https://stackoverflow.com/q/44058726) and not something node has to implement for ECMA. Their specific network interface is the http module. – zero298 Aug 23 '20 at 00:04

2 Answers2

0

For node, install the node-fetch module from npm.

Note that browser fetch can't talk directly to your filesystem -- it requires an HTTP server on the other side of the request. Node can talk to your filesystem, as well as making HTTP calls to servers.

Tom
  • 8,509
  • 7
  • 49
  • 78
  • That looks promising, but doesn't quite do it for working seamlessly in either environment (counting the import part). – Digicrat Aug 24 '20 at 01:08
  • If you want to ship a single codebase to both platforms, you'll have to set up a transpilation process using something like Babel. This is pretty much standard these days in the JS world. – Tom Aug 24 '20 at 03:13
  • I may need to do transpilation at some point, but so far I've been successful in avoiding it (with the assumption this only works in the latest browsers), and only a single 'cp' needed if hosting outside of ExpressJS. Being able to refresh a page without needing a transpilation step makes development much easier. – Digicrat Aug 25 '20 at 15:06
  • Well, you're going to run into a problem where node doesn't understand `import` and browsers don't understand `require`. So I suspect your luck has run out. There is no fetch API shared by both browserland and node, and I wouldn't bet on that changing in the near future. – Tom Aug 25 '20 at 17:49
  • NodeJS supports import natively in v14+ as long as you set your package.json to "type":"module", though you can't intermix ES and commonjs module syntax within the same package. – Digicrat Aug 26 '20 at 18:25
0

It sounds like as of now, there is no perfect solution here. The 'fetch' API is the most promising, but only if Node implements it some day.

In the meantime I've settled for a simple solution that works seamlessly with minimal dependencies, requiring only a little magic with my ExpressJS server paths to point the served web instance to a different version of utils.js.

Note: To use the ES-style import syntax for includes in NodeJS (v14+) you must set "type":"module" in your package.json. See https://nodejs.org/api/esm.html#esm_package_json_type_field for details. This is necessary for true shared code bases.

Module Using it (NodeJS + Browser running the same file):

import * as utils from "../utils.js";
...
var data = await utils.loadJSON(filename);
...

utils.js for browser:

async function loadJSON(fn) { 
    return $.getJSON(fn); // Only because I'm using another JQuery-dependent lib
    /* Or natively something like
    let response = await fetch(fn);
    return response.json();
    */
}

export { loadJSON };

utils.js for nodeJS

import * as fs from 'fs';

async function loadJSON(fn) {
    return JSON.parse(await fs.promises.readFile(fn));
}

export { loadJSON };
Digicrat
  • 581
  • 5
  • 13
  • The problem with this solution is that the code comment "or natively, something like `await fetch`") is a non-starter for anyone who isn't using jQuery. Folks who are not using jQuery won't be helped by setting 'type' to 'module', because it doesn't address the fundamental fact that there is no API shared by both browsers and Node for making internet requests. Browsers may use fetch, but Node must use a third-party module like node-fetch, axios, or similar. The lightest-weight strategy for a shared codebase would be use node-fetch in Node, to mirror the browser API. – Tom Aug 29 '20 at 00:43
  • node-fetch doesn't use the same import statement for browser and nodejs. Here, I simply have two versions of util.js with the server configured to give the browser a different file than node imports. No other modifications or transpilation needed. As you say fetch is a native API for the browser, I just had jquery available so I put that comment in for reference since I haven't tested the non-jquery version. If this were a C project, the two targets would use a single header file and the compiler would use different source paths (a common paradigm for handling multiple platforms). – Digicrat Aug 30 '20 at 01:57