I'm currently having a weird issue:
Uncaught (in promise) ReferenceError: Cannot access 'Agile' before initialization,
which is probably caused by webpack trying to correctly transform the ESM part of my library into CommonJs. Unfortunately, it doesn't do a good job and I end up with the error mentioned above.
My library supports both: CommonJs and ESM. So I'm wondering why webpack uses the ESM part and transforms it into CommonJs, instead of directly using the CommonJs part of my library?!
Why am I so sure that it has something to do with the ESM to CommonJs transformation?
Well, I once forced webpack to use the CommonJs part of my library by removing the ESM support. By doing so, I simply deleted the path to the ESM module in the package.json of my library.
"module": "dist/esm/index.js"
After unsupporting ESM, webpack was forced to use the CommonJs part
and it works as expected, since webpack doesn't have to transform anything anymore. (see image)
ESM transformed to CommonJS [not working]
Untransformed CommonJS [working]
Since my library should support both: ESM and CommonJS,
simply unsupporting ESM is no solution.
Here is the package.json of my library:
{
"name": "#agile-ts/core",
"version": "0.2.0+17b078aa",
"author": "BennoDev",
"license": "MIT",
"homepage": "https://agile-ts.org/",
"description": "Spacy, Simple, Scalable State Management Framework",
"keywords": [
// ..
],
"main": "dist/index.js", // <!-- CommonJs
"module": "dist/esm/index.js", // <!-- ESM
"types": "dist/index.d.ts",
"scripts": {
// ..
},
"dependencies": {
"#agile-ts/utils": "^0.0.8"
},
"peerDependencies": {
"#agile-ts/logger": "^0.0.8"
},
"peerDependenciesMeta": {
"#agile-ts/logger": {
"optional": true
}
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/agile-ts/agile.git"
},
"bugs": {
"url": "https://github.com/agile-ts/agile/issues"
},
"files": [
"dist",
"LICENSE",
"README.md",
"CHANGELOG.md"
],
"sideEffects": false
}
Maybe I've misconfigured something in the package.json
and thus webpack uses ESM although it requires CommonJs?
Node Version: v16.3.0
Github Repo: https://github.com/agile-ts/agile/tree/master/packages/core
Ok, I fixed this issue by resolving all circular dependencies ^^
See: https://github.com/agile-ts/agile/issues/193
Related
Context
Our product is (sadly) still on Webpack 3. Hope to upgrade, but in the meantime, I am trying to use an npm library that bundles cjs, mjs and iffe files. (See package.json excerpt below).
The problem is that Webpack complains when I import from these files.
import { styled } from '#stitches/react';
This throws the following error:
ERROR in ./node_modules/#stitches/react/dist/index.cjs
Module parse failed: Unexpected token (1:9356)
You may need an appropriate loader to handle this file type.
The unexpected token is something random like n or a variable name. The source code is used widely in the community so it's likely not a bug in the code.
Works when copy-pasting the code into src
In our Babel setup, we don't touch anything in node_modules, but when I copy the contents of dist/index.cjs or dist/index.mjs into our src directory and import it there, everything works fine.
This looks to me like the source code has issues with either a) how it's imported as a module, or b) it requires some kind of transpilation to work with our setup.
The question
Is this somehow related to Webpack 3? Are mjs and cjs files problematic?
package.json
{
"name": "#stitches/react",
"version": "0.2.3",
"description": "The modern CSS-in-JS library",
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "types/index.d.ts",
"typesVersions": {
">= 4.1": {
"*": [
"types/index.d.ts"
]
}
},
"jsdelivr": "dist/index.iife.js",
"unpkg": "dist/index.iife.js",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs",
"types": "./types/index.d.ts"
}
},
I use Node.js v14.17.0, Mocha 9.0.2.
My project is a bare-bone project configured to use ES6 js module made for the understanding of this issue.
{
"name": "mocha-tlp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"author": "",
"license": "ISC",
"type": "module"
}
With the test case (test/foo.js):
import assert from 'assert/strict';
describe('MyObject', function () {
describe('#myFunction()', function () {
it('should work', function () {
assert(true, "All good");
});
});
});
I get the following error :
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: C:\xxxxxxxxxxxxxx\test\foo.js
require() of ES modules is not supported.
require() of C:\xxxxxxxxxxxxxx\test\foo.js from C:\xxxxxxxxxxxxxx\node_modules\mocha\lib\esm-utils.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename foo.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from C:\projects\svelte\dgb-app\package.json.
It does not seems to be a issue that esm and mocha --require esm could solve because both Node.js and Mocha supports ESM for those version.
How can I use Nodejs Assert with Mocha when ESM project ?
Since assert is a built in node module, you may use:
import assert from 'node:assert/strict'
I maintain a library that has has a /dist folder with versions for CommonJS, ES6 modules, and direct <script> loading.
The package.json file is configured to identify each version:
"type": "module",
"main": "dist/pretty-print-json.umd.cjs",
"module": "dist/pretty-print-json.esm.js",
"browser": "dist/pretty-print-json.min.js",
When the library is installed:
$ npm install pretty-print-json
and imported:
import { prettyPrintJson } from 'pretty-print-json';
into a project with webpack, IDEs correctly interpret the ES6 module version plus the TypeScript declaration file (dist/pretty-print-json.d.ts).
However, the build process fails:
./src/app/app.component.ts:74:13-35 - Error: export 'prettyPrintJson' (imported
as 'prettyPrintJson') was not found in 'pretty-print-json' (module has no
exports)
The ES6 module version has an export statement:
export { prettyPrintJson };
After a bunch of experiments with a simple Angular project, I figured out that webpack is using the "browser" version instead of the "module" version.
How do you configure a library so that webpack correctly picks up the "module" version without breaking support for the other versions?
The browser field takes priority over module and main if your target is the browser.
I found this solution for your problem:
{
"name": "main-module-browser",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"browser": {
"./dist/index.js": "./dist/index.browser.js",
"./dist/index.esm.js": "./dist/index.browser.esm.js"
}
}
I have a module I want to publish to npm. I have found some "solutions" that are 4+ years old, examples using babel 5.x, and other problems that made the examples not work as shown.
Ideally I want to write my code using es6 and build/transpile with babel such that it can be imported with require() in scripts or import in modules.
Right now, here's my (sample) module that shows what I've tried.
// index.js
export default function speak () {
console.log("Hello, World!");
}
// .babelrc
{
"comments":false,
"presets": [
["#babel/env", {"modules": "commonjs"}]
],
"plugins": [
"#babel/plugin-transform-modules-commonjs",
"add-module-exports"
]
}
// package.json
{
"name": "foo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "babel index.js -d dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"#babel/cli": "^7.10.5",
"#babel/core": "^7.10.5",
"#babel/plugin-transform-modules-commonjs": "^7.10.4",
"#babel/preset-env": "^7.10.4",
"babel-plugin-add-module-exports": "^1.0.2"
}
}
And, finally, a demo script (demo.js)
// demo.js
const speak = require('./dist/index.js');
speak();
When I do npm run build and then node demo.js, it works as expected:
Hello, World!
I would also like to be able to have a module (add "type":"module" to package.json) and then use a demo.js file this way:
import speak from './dist/index.js';
speak();
However, I get this an error that a default export isn't available:
import speak from './dist/index.js';
^^^^^
SyntaxError: The requested module './dist/index.js' does not provide an export named 'default'
I don't really care what the answer is, I'd just like to know what the best practices are. Should I just export as ES6? Should I just require commonjs? Is there a way of transpiling with two available targets?
Note:
node v14.5.0
npm v6.14.6
#babel/core v7.10.5
You can use a bundler like webpack or rollup in combination with babel. They provide options to compile to multiple targets. Normally any library code is compiled to below targets:
ESM or MJS (Ecmascript modules)
UMD (Universal Modules)
You can also compile to CJS (CommonJS module) or IIFE (Immediately invoked function expression).
UMD and ESM are pretty much standard these days (esp. UMD because it is combination of IIFE, CJS and AMD).
You can explore official documentation for Webpack or Rollup. However, I have created a tool which you can use to achieve the output you are looking for. https://www.npmjs.com/package/rollup-boilerplate
You can check it out, play around with it, hack it. I think it can be good starting point. You can checkout this article to get started: https://medium.com/#contactsachinsingh/setting-up-a-rollup-project-under-two-minutes-fc838be02d0e
With the jsnext:main property in package.json and tree-shaking, it seems like it would be preferable to use the es6 version and babel-loader to get a smaller build than to always import the whole UMD package from main.
I know not every package supports jsnext:main, but for the ones that do I want to apply babel-loader and cherry-pick imports.
Example of es6 package:
{
"name": "module",
"main": "dist/index.js",
"jsnext:main": "src/index.js"
}
Example of commonjs or UMD dependency:
{
"name": "module",
"main": "lib/index.js"
}
Right now I exclude all node modules in webpack.
{
test: /\.(jsx|js)$/,
exclude: /node_modules/,
loader: "babel-loader"
}
Is there some way to configure the loader to include if there is a jsnext:main and exclude otherwise? This might be overkill but it seems objectively better to use es6 imports when supported.