Do Any JavaScript Test Frameworks Support ES Modules - javascript

Do any test frameworks support tests that use the new ES Modules syntax? I have a JS application which heavily uses .mjs files with ES Modules. I tried Jest and Jasmine, both of which throw errors when I try to write run tests for my app. I need to test this file:
math.mjs
export function add(a, b) {
return a + b;
}

As of February 2022, this is what I saw:
Jasmine: built-in support.
Jest: built-in experimental support, requires configuration to code transformations and a node flag.
Mocha: built-in experimental support lacks some features such as watch mode.
Tape: built-in support (not mentioned in docs).
AVA: built-in support (not mentioned in released docs, but there's a recent commit to the docs switching all examples to esm)
UVU: built-in support explicitly mentioned in the docs

Other test runners with support for ES Modules are:
AVA: AVA 4 explicitly mentions ESM, but I've also used AVA 3 with ES modules without problems
UVU: A lightweight test runner that supports ES Modules and does not pull tons of dependencies into the project

You can use the esm package with Jasmine. Not sure about Jest though 🤔
math.spec.js
import { add } from './math.mjs';
describe('Add', () => {
it('should add 3 and 2', () => {
expect(add(3,2)).toBe(5);
});
});
Install and run
$ yarn global add jasmine esm
$ jasmine --require=esm
Randomized with seed 44366
Started
.
1 spec, 0 failures
Finished in 0.004 seconds

I tried Vitest and it worked like a charm. My goal was to build a publishable library with typescript. The library was part of a monorepo built with Nxw with "type": "module" in package.json and the following in my tsconfig.json:
"compilerOptions": {
"module": "ESNext",
"target": "ES6",
"moduleResolution": "node"
}
It worked with 0 configuration, and I tried testing functions that used both CJS and ESM type imports from 3rd party packages.
On a side note, I really like the clean syntax. Plus, vitest also supports testing components built with React/Vue.

The project Japa works with ESM and Typescript, and works perfectly in my set-up (Node.js 16.x with "type": "module" and .js files). The only issue I got was easy to solve, so you should give it a try

Related

How can I use "exports" in package.json today for nested submodules and typescript

Latest Update (2022-06-06): TS 4.7 supports "exports"
tl;dr
// package.json
"type": "module"
// tsconfig.json
"module": "node12" // or "nodenext"
Update: TS 4.5 does not support "exports" (also see this issue):
... support for Node.js 12 has been deferred to a future release, and is now only available as an experimental flag in nightly releases. This was not an easy decision, but our team had a combination of concerns around ecosystem readiness and general guidance for how/when to use the feature.
[2022-04-01] The feature is still not available in TS 4.6.
Original question:
I am wanting to take advantage of the new-ish "exports" feature of NodeJS/package.json so that I can do the following:
"exports": {
".": "./dist/index.js",
"./foo": "./dist/path/to/foo.js"
}
And users can do the following:
import { foo } from 'my-package/foo';
Typescript 4.5 should support the "exports" field, yet it does not seem to work. I am building a simple package using TS 4.5.2, and I am consuming that package in a project using TS 4.5.2. I have looked at other SO questions and this github thread and this bug report but can't seem to find a consensus on the issue and whether it should work today.
Note 1: I am still able to import using the more verbose syntax:
import { foo } from 'my-package/dist/path/to/foo.js';
Note 2: I have also tried the object notation for exports, to no avail:
"exports": {
".": { "require": "./dist/index.js", "import": "./dist/index.js" },
"./foo": { "require": "./dist/path/to/foo.js", "import": "./dist/path/to/foo.js" }
}
Question(s):
Is this feature ready to be used with typescript projects today? If not, I just want to know.
If yes to #1, what am I missing? Specifics about tsconfig would be useful for both the source project and consuming project. The TS compiler complains about node12/nodenext being used for either the module or moduleResolution fields (I am definitely using TS 4.5.2).
I've been looking to use nested folders for a design system package we created at Pipefy, and after deep research, I found how to do it.
The package exports React components, and we use to import them like this import { Button } from '#mypackage/design-system;
Later on, we've added tokens to our design system library, like Colors, Spacing, and Fonts, but we don't want to import everything from the index, it isn't productive.
After some exhaustive research, I found how to export nested folders using TypeScript. My purpose is to use tokens like this import { Colors } from '#mypackage/design-system/tokens;
To use your TypeScript lib like this you should use the typesVersions inside the package.json file.
Here I use it like this
"typesVersions": {
"*": {
"index": [
"lib/components/index.d.ts"
],
"tokens": [
"lib/tokens/index.d.ts"
]
}
},
It worked like a charm for me, it would work for you too!
Without knowing what error you are getting, or in what other way TypeScript doesn't seem to be working for you (not sure why you would not want to share such crucial information), I can tell that your exports section appears to be missing types information. Typically, if your .d.ts files were located next to their respective .js files, your exports section would look like this:
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./foo": {
"types": "./dist/path/to/foo.d.ts",
"default": "./dist/path/to/foo.js"
}
}
I'm posting my own answer because there has been a lot of confusion around this topic.
NodeJS has supported exports since v12.7.0 (Jul. 2019)
When I asked this question (Dec. 2021), NodeJS had supported the exports field for nearly 2.5 years. It seemed reasonable to assume that Typescript supported it.
Typescript did not support the exports field when I asked this question.
When I asked this question (Dec. 2021), the exports field in package.json was not supported by the current version of Typescript at the time (v4.5). This was particularly confusing because the TS 4.5 beta announcement said that it would support the package.json exports.
Typescript 4.7 (Jun. 2022) finally supported package.json exports
After much testing and quite a bit of secrecy, typescript finally supported the exports field for package.json. Wine and whiskey were consumed with this announcement.
Using typesVersions in package.json is not the solution
Several people suggested using typesVersions - but thats a completely separate feature which is specific to typescript only (read more about it here). The exports field in package.json is supported by ANY npm package - typescript or not.
What you need in order for this to work
Your typescript project must be using TS v4.7 or later
Your tsconfig should be using moduleResolution: node, node16, or nodenext.
You don't have to set moduleResultion if you are using module with a value of CommonJS, ES2015, ES6, ES2020, or ESNEXT
TypeScript only respects export maps in package.json if you use "moduleResolution": "NodeNext"(or "Node16") instead of the widespread "moduleResolution": "Node". (I guess "moduleResolution" defaults to the same value as "module", but it's hard to find documentation of this?
See: https://www.typescriptlang.org/docs/handbook/esm-node.html
However a number of TS libraries out there (including many of Microsoft's own) have errors with "moduleResolution": "NodeNext" right now because of things like relative imports without explicit file extensions.

Angular 10 Upgrade - Fix CommonJS or AMD dependencies can cause optimization bailouts

I am trying to upgrade my Angular 9 app to Angular 10 version, but getting below warning after the upgrade
WARNING in calendar.reducer.ts depends on lodash/keys. CommonJS or AMD dependencies can cause optimization bailouts.
I have added below line to my angular.json file but issue is not resolved
"allowedCommonJsDependencies": ["lodash"]
How can I fix above issue.
The npm package lodash itself is not an ECMAScript module and therefore produces the warning.
There are multiple ways to fix this:
Replace with ES modulized library (recommended)
Some libraries offer ES modulized builds. In case of lodash, you can replace it with lodash-es.
Run npm install --save lodash-es.
Now replace all imports from lodash with lodash-es.
Also make sure to import the library with ES import statements:
import { keys } from 'lodash-es';
Whitelist CommonJS dependency
If there is no ES modulized build available for your library, or if you for some reason don't care, you can allow specific CommonJS dependencies in the angular.json file:
"architect": {
"build": {
"builder": "#angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": ["lodash"]
}
}
}
Since Angular CLI Version 10.0.1 you can use globs in allowedCommonJsDependencies.
This means that if you pass lodash, the sub-paths (e.g. lodash/keys) will also be allowed.
Docs reference: https://angular.io/guide/build#configuring-commonjs-dependencies

How to use npm module in DENO?

Deno is super cool. I saw it in the morning and want to migrate to deno now. I was trying to move my existing nodejs script to deno. Can any one help me on how to use npm modules in deno. I need esprima module. This one has the package https://github.com/denoland/deno_third_party/tree/master/node_modules but i am not able to figure out how to use that.
Deno provides a Node Compatibility Library, that allows using some NPM packages that do not use non-polyfilled Node.js APIs.
As of Deno 1.25 there's an experimental NPM support by using npm: specifier
npm:<package-name>[#<version-requirement>][/<sub-path>]
import express from "npm:express";
const app = express();
app.get("/", function (req, res) {
res.send("Hello World");
});
app.listen(3000);
console.log("listening on http://localhost:3000/");
The --unstable flag is required.
When doing this, no npm install is necessary and no node_modules folder is created
You can also require and installed npm package by using https://deno.land/std/node/module.ts
The following works on deno >= 1.0.0
npm install esprima
import { createRequire } from "https://deno.land/std/node/module.ts";
const require = createRequire(import.meta.url);
const esprima = require("esprima");
const program = 'const answer = 42';
console.log(esprima.tokenize(program))
The above code will use esprima from node_modules/.
To run it, you'll need --allow-read && --allow-env flag
# you can also use --allow-all
deno run --allow-read --allow-env esprima.js
You can restrict it only to node_modules
deno run --allow-read=node_modules esprima.js
Which outputs:
[
{ type: "Keyword", value: "const" },
{ type: "Identifier", value: "answer" },
{ type: "Punctuator", value: "=" },
{ type: "Numeric", value: "42" }
]
Note: many APIs used by std/ are still unstable, so you may need to run it with --unstable flag.
Although since that whole project is written in TypeScript already, and it's not using any dependencies, it will be very easy for them to adapt it to Deno. All they need to do is use .ts extension on their imports.
You can also fork the project and do the changes.
// import { CommentHandler } from './comment-handler';
import { CommentHandler } from './comment-handler.ts';
// ...
Once they do, you'll be able to just do:
// Ideally they would issue a tagged release and you'll use that instead of master
import esprima from 'https://raw.githubusercontent.com/jquery/esprima/master/src/esprima.ts';
const program = 'const answer = 42';
console.log(esprima.tokenize(program))
Alternative
You can also use https://jspm.io/ which will convert NPM modules to ES Modules
All modules on npm are converted into ES modules handling full
CommonJS compatibility including strict mode conversions.
import esprima from "https://dev.jspm.io/esprima";
const program = 'const answer = 42';
console.log(esprima.tokenize(program))
For packages that use Node.js modules not supported by jspm it will throw an error:
Uncaught Error: Node.js fs module is not supported by jspm core.
Deno support here is tracking in
https://github.com/jspm/jspm-core/issues/4, +1's are appreciated!
To polyfill those Node.js APIs you'll have to include std/node.
// import so polyfilled Buffer is exposed
import "https://deno.land/std/node/module.ts";
import BJSON from 'https://dev.jspm.io/buffer-json';
const str = BJSON.stringify({ buf: Buffer.from('hello') })
console.log(str);
Issue
In general, there are two issues with npm packages in Deno:
ES Module (ESM) conformity is not given.
Bare imports like import _ from "lodash" don't work - no "magic" node_modules resolution
All import specifiers need to include the file extension - .ts,.js etc.
CommonJS module system is not usable in Deno
The npm package uses native Node.js builtins like fs or path.
Solutions to issue 1
1.1: Third party modules
The Third Party Modules section is the quickest way to discover compatible packages.
1.2: ESM CDN providers
Also take a look at CDN providers, that can auto-convert npm packages to ES Modules (ESM):
Skypack CDN
jspm.io
unpkg.com with ?module query parameter
Skypack CDN can deliver auto-converted packages, that e.g. have set a "module" entrypoint in package.json. For TypeScript users: It fetches .d.ts type definitions along with .js files (via X-TypeScript-Types HTTP headers used by Deno).
unpkg.com describes its ?module flag as follows: "Expands all 'bare' import specifiers in JavaScript modules to unpkg URLs. This feature is very experimental".
Esprima does not depend on Node.js builtins, so we can simplify its import by a CDN URL:
import esprima from "https://cdn.skypack.dev/esprima#^4.0.1"; // Option 1: Skypack
import esprima from "https://dev.jspm.io/esprima"; // Option 2: jspm
// your program
const tokens = esprima.tokenize("const foo = 'bar'"); // works
jspm would be a good choice here - Skypack TS types didn't work for me in this particular case.
1.3: Other approaches
You might also try to import an ESM compatible version directly from repository sources (e.g. an ESM branch). Though for Esprima it won't work because of missing file extensions in code.
Snowpack and jspm stand in for a more manual approach to convert CommonJS → ESM. The rollup plugin #rollup/plugin-commonjs (internally used by Snowpack) is even a more low-level tool.
Solution to issue 2
Deno provides a Node compatibility layer, see Marcos Casagrande's answer. However, not all native Node.js built-ins are fully supported.
As Esprima doesn't rely on Node builtins, you can go with the simpler CDN option.
As of version Deno 1.25 (released today) deno is now included with experimental npm support.
// main.ts
import express from "npm:express";
const app = express();
app.get("/", function (req, res) {
res.send("Hello World");
});
app.listen(3000);
console.log("listening on http://localhost:3000/");
You can now run deno run --unstable --A main.ts and express will be downloaded.
Starting with v1.15 Deno provides Node compatibility mode that makes it possible to run a subset of programs authored for Node.js directly in Deno. Compatibility mode can be activated by passing --compat flag in CLI.
deno run --compat --unstable --allow-read test.js
Currently, not all node.js built-in modules are supported and many are partially supported.
The following modules are not yet implemented:
cluster, dgram, http2, https, repl, tls, vm, lib

How to write unit tests supporting Javascript 6 modules

My Javascript codebase is based on new ES6 Modules.
So I have Javascript files like this for example:
export class MyClass {
constructor() {
this.list = [];
}
add(el) { this.list.push(el); }
}
As a module, I import this file in other Javascript files like this:
import * as lists from "./myclass";
And inside an HTML page, the following syntax has to be used:
<script src="myclass.js" type="module"></script>
Unit testing
I need a framework for testing my code. The problem is that I am using Javascript 6 modules, so modern frameworks like karma have problems as they import the files not as modules:
module.exports = function(config) {
config.set({
files: [
'src/**/*.js',
'test/**/*.js'
],
...
})
}
Above is an example of karma.conf.js. In the specific case of Karma, the runner will not import the files as modules, thus the injection in page fails.
What unit test frameworks can I use for testing Javascript 6 modules?
I'm using a combination of Mocha and Babel - Babel transpiles the ES6 modules to code Mocha can work with, and so you can use import in the test files.
To run mocha with the Babel transpiler:
mocha --compilers js:babel-core/register --recursive test/*
I'm pretty sure other frameworks have a similar solution.
You can check out Jest, it's Facebook's test framework that allows you to run your tests on Node (with JSDOM).
It runs your tests in parallel and without browser, therefore suppose to be much faster.
I'm not sure Jest actually supports modules, right? I.e. I want my test files .. those with the assertions .. to be able to import modules which also import other modules.
It seems the best you can do with node based testing frameworks, with I think may be all of them, is us pretty awful babel/webpack stunts which I'd really prefer not to use.
Headless Chrome certainly seems to be interesting, as does Puppeteer
https://github.com/GoogleChrome/puppeteer
https://developers.google.com/web/updates/2017/06/headless-karma-mocha-chai

How to publish a module written in ES6 to NPM?

I was about to publish a module to NPM, when I thought about rewriting it in ES6, to both future-proof it, and learn ES6. I've used Babel to transpile to ES5, and run tests. But I'm not sure how to proceed:
Do I transpile, and publish the resulting /out folder to NPM?
Do I include the result folder in my Github repo?
Or do I maintain 2 repos, one with the ES6 code + gulp script for Github, and one with the transpiled results + tests for NPM?
In short: what steps do I need to take to publish a module written in ES6 to NPM, while still allowing people to browse/fork the original code?
The pattern I have seen so far is to keep the es6 files in a src directory and build your stuff in npm's prepublish to the lib directory.
You will need an .npmignore file, similar to .gitignore but ignoring src instead of lib.
I like José's answer. I've noticed several modules follow that pattern already. Here's how you can easily implement it with Babel6. I install babel-cli locally so the build doesn't break if I ever change my global babel version.
.npmignore
/src/
.gitignore
/lib/
/node_modules/
Install Babel
npm install --save-dev babel-core babel-cli babel-preset-es2015
package.json
{
"main": "lib/index.js",
"scripts": {
"prepublish": "babel src --out-dir lib"
},
"babel": {
"presets": ["es2015"]
}
}
TL;DR - Don't, until ~October 2019. The Node.js Modules Team has asked:
Please do not publish any ES module packages intended for use by Node.js until [October 2019]
2019 May update
Since 2015 when this question was asked, JavaScript support for modules has matured significantly, and is hopefully going to be officially stable in October 2019. All other answers are now obsolete or overly complicated. Here is the current situation and best practice.
ES6 support
99% of ES6 (aka 2015) has been supported by Node since version 6. The current version of Node is 12. All evergreen browsers support the vast majority of ES6 features. ECMAScript is now at version 2019, and the versioning scheme now favors using years.
ES Modules (aka ECMAScript modules) in browsers
All evergreen browsers have been supporting import-ing ES6 modules since 2017. Dynamic imports are supported by Chrome (+ forks like Opera and Samsung Internet) and Safari. Firefox support is slated for the next version, 67.
You no longer need Webpack/rollup/Parcel etc. to load modules. They may be still useful for other purposes, but are not required to load your code. You can directly import URLs pointing to ES modules code.
ES modules in Node
ES modules (.mjs files with import/export) have been supported since Node v8.5.0 by calling node with the --experimental-modules flag. Node v12, released in April 2019, rewrote the experimental modules support. The most visible change is that the file extension needs to be specified by default when importing:
// lib.mjs
export const hello = 'Hello world!';
// index.mjs:
import { hello } from './lib.mjs';
console.log(hello);
Note the mandatory .mjs extensions throughout. Run as:
node --experimental-modules index.mjs
The Node 12 release is also when the Modules Team asked developers to not publish ES module packages intended for use by Node.js until a solution is found for using packages via both require('pkg') and import 'pkg'. You can still publish native ES modules intended for browsers.
Ecosystem support of native ES modules
As of May 2019, ecosystem support for ES Modules is immature. For example, test frameworks like Jest and Ava don't support --experimental-modules. You need to use a transpiler, and must then decide between using the named import (import { symbol }) syntax (which won't work with most npm packages yet), and the default import syntax (import Package from 'package'), which does work, but not when Babel parses it for packages authored in TypeScript (graphql-tools, node-influx, faast etc.) There is however a workaround that works both with --experimental-modules and if Babel transpiles your code so you can test it with Jest/Ava/Mocha etc:
import * as ApolloServerM from 'apollo-server'; const ApolloServer = ApolloServerM.default || ApolloServerM;
Arguably ugly, but this way you can write your own ES modules code with import/export and run it with node --experimental-modules, without transpilers. If you have dependencies that aren't ESM-ready yet, import them as above, and you'll be able to use test frameworks and other tooling via Babel.
Previous answer to the question - remember, don't do this until Node solves the require/import issue, hopefully around October 2019.
Publishing ES6 modules to npm, with backwards compatibility
To publish an ES module to npmjs.org so that it can be imported directly, without Babel or other transpilers, simply point the main field in your package.json to the .mjs file, but omit the extension:
{
"name": "mjs-example",
"main": "index"
}
That's the only change. By omitting the extension, Node will look first for an mjs file if run with --experimental-modules. Otherwise it will fall back to the .js file, so your existing transpilation process to support older Node versions will work as before — just make sure to point Babel to the .mjs file(s).
Here's the source for a native ES module with backwards compatibility for Node < 8.5.0 that I published to NPM. You can use it right now, without Babel or anything else.
Install the module:
npm install local-iso-dt
# or, yarn add local-iso-dt
Create a test file test.mjs:
import { localISOdt } from 'local-iso-dt/index.mjs';
console.log(localISOdt(), 'Starting job...');
Run node (v8.5.0+) with the --experimental-modules flag:
node --experimental-modules test.mjs
TypeScript
If you develop in TypeScript, you can generate ES6 code and use ES6 modules:
tsc index.js --target es6 --modules es2015
Then, you need to rename *.js output to .mjs, a known issue that will hopefully get fixed soon so tsc can output .mjs files directly.
#Jose is right. There's nothing wrong with publishing ES6/ES2015 to NPM but that may cause trouble, specially if the person using your package is using Webpack, for instance, because normally people ignore the node_modules folder while preprocessing with babel for performance reasons.
So, just use gulp, grunt or simply Node.js to build a lib folder that is ES5.
Here's my build-lib.js script, which I keep in ./tools/ (no gulpor grunt here):
var rimraf = require('rimraf-promise');
var colors = require('colors');
var exec = require('child-process-promise').exec;
console.log('building lib'.green);
rimraf('./lib')
.then(function (error) {
let babelCli = 'babel --optional es7.objectRestSpread ./src --out-dir ./lib';
return exec(babelCli).fail(function (error) {
console.log(colors.red(error))
});
}).then(() => console.log('lib built'.green));
Here's a last advice: You need to add a .npmignore to your project. If npm publish doesn't find this file, it will use .gitignore instead, which will cause you trouble because normally your .gitignore file will exclude ./lib and include ./src, which is exactly the opposite of what you want when you are publishing to NPM. The .npmignore file has basically the same syntax of .gitignore (AFAIK).
Following José and Marius's approach, (with update of Babel's latest version in 2019): Keep the latest JavaScript files in a src directory, and build with npm's prepublish script and output to the lib directory.
.npmignore
/src
.gitignore
/lib
/node_modules
Install Babel (version 7.5.5 in my case)
$ npm install #babel/core #babel/cli #babel/preset-env --save-dev
package.json
{
"name": "latest-js-to-npm",
"version": "1.0.0",
"description": "Keep the latest JavaScript files in a src directory and build with npm's prepublish script and output to the lib directory.",
"main": "lib/index.js",
"scripts": {
"prepublish": "babel src -d lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"#babel/cli": "^7.5.5",
"#babel/core": "^7.5.5",
"#babel/preset-env": "^7.5.5"
},
"babel": {
"presets": [
"#babel/preset-env"
]
}
}
And I have src/index.js which uses the arrow function:
"use strict";
let NewOneWithParameters = (a, b) => {
console.log(a + b); // 30
};
NewOneWithParameters(10, 20);
Here is the repo on GitHub.
Now you can publish the package:
$ npm publish
...
> latest-js-to-npm#1.0.0 prepublish .
> babel src -d lib
Successfully compiled 1 file with Babel.
...
Before the package is published to npm, you will see that lib/index.js has been generated, which is transpiled to es5:
"use strict";
var NewOneWithParameters = function NewOneWithParameters(a, b) {
console.log(a + b); // 30
};
NewOneWithParameters(10, 20);
[Update for Rollup bundler]
As asked by #kyw, how would you integrate Rollup bundler?
First, install rollup and rollup-plugin-babel
npm install -D rollup rollup-plugin-babel
Second, create rollup.config.js in the project root directory
import babel from "rollup-plugin-babel";
export default {
input: "./src/index.js",
output: {
file: "./lib/index.js",
format: "cjs",
name: "bundle"
},
plugins: [
babel({
exclude: "node_modules/**"
})
]
};
Lastly, update prepublish in package.json
{
...
"scripts": {
"prepublish": "rollup -c"
},
...
}
Now you can run npm publish, and before the package is published to npm, you will see that lib/index.js has been generated, which is transpiled to es5:
'use strict';
var NewOneWithParameters = function NewOneWithParameters(a, b) {
console.log(a + b); // 30
};
NewOneWithParameters(10, 20);
Note: by the way, you no longer need #babel/cli if you are using the Rollup bundler. You can safely uninstall it:
npm uninstall #babel/cli
If you want to see this in action in a very simple small open source Node module then take a look at nth-day (which I started - also other contributors). Look in the package.json file and at the prepublish step which will lead you to where and how to do this. If you clone that module you can run it locally and use it as a template for yous.
Node.js 13.2.0+ supports ESM without the experimental flag and there're a few options to publish hybrid (ESM and CommonJS) NPM packages (depending on the level of backward compatibility needed): https://2ality.com/2019/10/hybrid-npm-packages.html
I recommend going the full backward compatibility way to make the usage of your package easier. This could look as follows:
The hybrid package has the following files:
mypkg/
package.json
esm/
entry.js
commonjs/
package.json
entry.js
mypkg/package.json
{
"type": "module",
"main": "./commonjs/entry.js",
"exports": {
"./esm": "./esm/entry.js"
},
"module": "./esm/entry.js",
···
}
mypkg/commonjs/package.json
{
"type": "commonjs"
}
Importing from CommonJS:
const {x} = require('mypkg');
Importing from ESM:
import {x} from 'mypkg/esm';
We did an investigation into ESM support in 05.2019 and found that a lot of libraries were lacking support (hence the recommendation for backward compatibility):
esm package's support doesn't align with Node's which causes issues
"Builtin require cannot sideload .mjs files." https://github.com/standard-things/esm#loading, https://github.com/standard-things/esm/issues/498#issuecomment-403496745
"The .mjs file extension should not be the thing developers reach for if they want interop or ease of use. It's available since it's in --experimental-modules but since it's not fully baked I can't commit to any enhancements to it." https://github.com/standard-things/esm/issues/498#issuecomment-403655466
mocha doesn't have native support for .mjs files
Update 2020-01-13: Mocha released experimental support in mocha#7.0.0-esm1
Many high-profile projects had issues with .mjs files:
create-react-app
react-apollo
graphql-js
inferno
The main key in package.json decides the entry point to the package once it's published. So you can put your Babel's output wherever you want and just have to mention the right path in main key.
"main": "./lib/index.js",
Here's a well written article on how to publish an npm package
https://codeburst.io/publish-your-own-npm-package-ff918698d450
Here's a sample repo you can use for reference
https://github.com/flexdinesh/npm-module-boilerplate
The two criteria of an NPM package is that it is usable with nothing more than a require( 'package' ) and does something software-ish.
If you fulfill those two requirements, you can do whatever you wish.
Even if the module is written in ES6, if the end user doesn't need to know that, I would transpile it for now to get maximum support.
However, if like koa, your module requires compatibility with users using ES6 features, then perhaps the two package solution would be a better idea.
Takeaway
Only publish as much code as you need to make require( 'your-package' ) work.
Unless the between ES5 & 6 matters to the user, only publish 1 package. Transpile it if you must.
A few extra notes for anyone, using own modules directly from github, not going through published modules:
The (widely used) "prepublish" hook is not doing anything for you.
Best thing one can do (if plans to rely on github repos, not published stuff):
unlist src from .npmignore (in other words: allow it). If you don't have an .npmignore, remember: A copy of .gitignore will be used instead in the installed location, as ls node_modules/yourProject will show you.
make sure, babel-cli is a depenency in your module, not just a devDepenceny since you are indeed building on the consuming machine aka at the App developers computer, who is using your module
do the build thing, in the install hook i.e.:
"install": "babel src -d lib -s"
(no added value in trying anything "preinstall", i.e. babel-cli might be missing)
Deppending on the anatomy of your module, this solution may not work, but if your module is contained inside a single file, and has no dependencies (does not make use of import), using the following pattern you can release your code as it is, and will be able to be imported with import (Browser ES6 Modules) and require (Node CommonJS Modules)
As a bonus, it will be suittable to be imported using a SCRIPT HTML Element.
main.js :
(function(){
'use strict';
const myModule = {
helloWorld : function(){ console.log('Hello World!' )}
};
// if running in NODE export module using NODEJS syntax
if(typeof module !== 'undefined') module.exports = myModule ;
// if running in Browser, set as a global variable.
else window.myModule = myModule ;
})()
my-module.js :
// import main.js (it will declare your Object in the global scope)
import './main.js';
// get a copy of your module object reference
let _myModule = window.myModule;
// delete the the reference from the global object
delete window.myModule;
// export it!
export {_myModule as myModule};
package.json :`
{
"name" : "my-module", // set module name
"main": "main.js", // set entry point
/* ...other package.json stuff here */
}
To use your module, you can now use the regular syntax ...
When imported in NODE ...
let myModule = require('my-module');
myModule.helloWorld();
// outputs 'Hello World!'
When imported in BROWSER ...
import {myModule} from './my-module.js';
myModule.helloWorld();
// outputs 'Hello World!'
Or even when included using an HTML Script Element...
<script src="./main.js"></script>
<script>
myModule.helloWorld();
// outputs 'Hello World!'
</script>

Categories