Handling dependencies in es6 module, intended for node and browser

Viewed 853

I've written a javascript library as an ES6 module, primarily intended for use in the browser. Now I want to package it for node. I'm ok with restricting to node v 14+. I don't want to transpile.

I'm not sure how to handle the dependencies. One in particular, moment-js, is giving me trouble.

In the browser (tried latest FF + Chrome), I reference the moment library in a <script> tag, and then in my code, I can just use the variable moment, without having had to first import it.

my-module.js:

function testFn() {
  return moment.duration(300).asSeconds();
}

export { testFn }

browser code:

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.26.0/moment.min.js"></script>
<script type="module" src="my-module.js"></script>
<script>

import { testFn } from 'my-module.js';
let val = testFn();
console.log(val); 

</script>

However, in node, I need the module to include an import in order for it to work:

my-module.js:

import moment from 'moment';

function testFn() {
  return moment.duration(300).asSeconds();
}

export { testFn }

Then I can use my-module in node:

import { testFn } from 'my-module.js';

let val = testFn();
console.log(val); 

But, now the browser code is broken: TypeError: Error resolving module specifier: moment

I've tried different imports (import * as ..., import { moment } from ...) but no luck - the browser still complains.

Moment-js is just an example here - I'd like a generic solution as I have other dependencies too.

This must be an issue that other people have had and solved without resorting to awful workarounds like detecting the environment and conditionally importing. Does anyone have a good solution or reference? Thanks!!

3 Answers

Did you try import * as moment from "./moment.js"; (you may need to fiddle with the path based on where you are installing it and how moment handles the file)?

Browsers do not support bare imports and I believe some older libraries like moment are still bare.

Also I would just say, this is exactly what transpiling and build tooling can be great for. It can make dealing with this type of situation much simpler. Certain things can not be conditional checked for and you just have to transpile to the lowest compatibility.

This should kind-of work with the caveat below:

const M = typeof moment !== 'undefined' ? moment
  : (await import('moment')).default
  • as of now, top level await only works in chrome, node and firefox canary
  • officially, top level await is still only a stage 3 proposal

Set the main:main.js and browser:browser.js fields in package.json that set-up whatever environment-specific elements before importing and re-exporting the common index.js module.

//main.js fix for nodejs
import {something} from 'nodejsSpecificModule'
global.something = something //or export whatever is needed
export {...} from './index.js'
//browser.js fix for browser
import {something} from 'browserSpecificModule'
window.something = something //or export whatever is needed
export {...} from './index.js'

this avoids dynamic imports with top-level-await

Related