I'm straggling on how to make two ESM working together in a browser. I have written a first ESM named 'esm1' written in Typescript that emits both a 'es6' module (with tsc) and a bundle for the browser (with webpack). Everything seems to work since I am able to use the bundle in a web page:
esm1$ npm run webtest
and I am as well able to use the generated 'es6' code from another ts file (
esm1$ npm run test:esm
Now I want to use the 'esm1' module from another module names 'esm2'. esm2 module has the same structure of esm1, but it imports esm1 as dependency in the package.json. In esm2 I have created a Typescript file that uses classes coming from esm1:
// esm2/src/index.ts
import {Hammer} from 'esm1/lib-esm/Hammer.js';
import { BoxObject } from 'esm1/lib-esm/BoxObject.js';
import {Box} from 'esm1/lib-esm/Box-node.js';
export class Interop {
constructor() {
}
doSomethingWithEsm1() {
console.log("Into doSomethingWithEsm5");
const p = new Hammer("blue");
console.log(p);
const box = new Box("studio");
box.addObject(p);
console.log(box);
box.getFile("http://skies.esac.esa.int/Herschel/PACS-color/properties").then( (data) => console.log(data));
}
}
const ip = new Interop();
ip.doSomethingWithEsm1();
On esm2 if I run
esm2$ npm run test:esm
everything goes well. The problem raises when I include the esm2 bundle in a web page:
<!-- esm2/webtest/index.html -->
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>ESM 6 module</title>
<base href="."/>
<!-- <script type="module" src="./my-lib.js"></script> -->
<script src="./my-lib.js"></script>
</head>
<body>
<h1>Hello world from ESM6!</h1>
<h2>Tip: Check your console</h2>
<!-- <script type="module"> -->
<script>
document.addEventListener("DOMContentLoaded", () => {
console.log("Hello World from ESM6!");
const ip = new MyEsm2.Interop();
const h = new MyEsm1.Hammer("green");
ip.doSomethingWithEsm1(h);
});
</script>
</body>
</html>
In that case I get the following error in the browser console:
Uncaught TypeError: esm1_lib_esm_Hammer_js__WEBPACK_IMPORTED_MODULE_0__ is undefined
doSomethingWithEsm1 index.ts:14
<anonymous> index.ts:27
<anonymous> my-lib.js:135
<anonymous> my-lib.js:138
webpackUniversalModuleDefinition universalModuleDefinition:9
<anonymous> universalModuleDefinition:10
index.ts:14:18
Hello World from ESM6! localhost:5001:21:17
Uncaught ReferenceError: MyEsm2 is not defined
<anonymous> http://localhost:5001/:22
EventListener.handleEvent* http://localhost:5001/:18
Anybody can help me understanding where I'm mistaking? The code is on github:
CodePudding user response:
At the end I've been able to figure out what was creating issues on the interoperability of the 2 ESM esm1 and esm2. It was node-fetch. I replaced it with cross-fetch and now it seems that everything is working as expected. For anybody facing with the same problem, I am using node:
(base)esm2$ node -v
v16.17.1
and below you have configuration (package.json, tsconfig.json and webpack.config.js) for both esm1 and esm2:
//esm1: package.json
{
"name": "emtest1",
"version": "1.0.0",
"description": "",
"type": "module",
"exports": {
".": {
"types": "./lib-esm/index-node.d.ts",
"import": "./lib-esm/index-node.js",
"require": "./_bundles/esm1.js"
}
},
"main": "./lib-esm/index-node.js",
"types": "./lib-esm/index-node.d.ts",
"files": [
"lib-esm/"
],
"scripts": {
"clean": "shx rm -rf _bundles lib-esm",
"build:dev": "npm run clean && tsc && webpack --config ./webpack.config.js --mode=development",
"build:prod": "npm run clean && tsc && webpack --config ./webpack.config.js --mode=production",
"webtest": "cp _bundles/* webtest/; node server.cjs",
"test:esm": "tsc -m es6 test.ts; node test.js"
},
"engines": {
"node": ">=16.0.0"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cross-fetch": "^3.1.5"
},
"devDependencies": {
"@tsconfig/node16": "^1.0.3",
"@types/node": "^18.7.23",
"node-static": "^0.7.11",
"shx": "^0.3.4",
"ts-loader": "^9.4.0",
"typescript": "^4.8.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
}
//esm1: tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node16",
"module": "es6",
"target": "es6",
"lib": ["es6", "dom"],
"outDir": "lib-esm",
"allowSyntheticDefaultImports": true,
"suppressImplicitAnyIndexErrors": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"declaration": true,
"declarationMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
],
"compileOnSave": false,
"buildOnSave": false
}
// esm1: webpack.config.js
import path from 'path';
import {fileURLToPath} from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PATHS = {
entryPoint4Browser: path.resolve(__dirname, 'src/index-node.ts'),
bundles: path.resolve(__dirname, '_bundles'),
}
var browserConfig = {
entry: {
'esm1': [PATHS.entryPoint4Browser],
'esm1.min': [PATHS.entryPoint4Browser]
},
target: 'web',
externals: {},
output: {
path: PATHS.bundles,
libraryTarget: 'umd',
library: 'esm1',
umdNamedDefine: true
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
extensionAlias: {
'.js': ['.ts', '.js'],
'.mjs': ['.mts', '.mjs']
}
},
devtool: 'source-map',
plugins: [
],
module: {
rules: [
{
test: /\.(ts|tsx)$/i,
use: 'ts-loader',
exclude: ["/node_modules/","/src/Box-node.ts"],
},
],
}
}
export default (env, argv) => {
return [browserConfig];
};
//esm2: package.json
{
"name": "emtest2",
"version": "1.0.0",
"description": "",
"type": "module",
"exports": {
".": {
"types": "./lib-esm/index.d.ts",
"import": "./lib-esm/index.js",
"require": "./_bundles/esm2.js"
}
},
"main": "./lib-esm/index.js",
"types": "./lib-esm/index.d.ts",
"files":[
"lib-esm/"
],
"scripts": {
"clean": "shx rm -rf _bundles lib-esm",
"build:dev": "npm run clean && tsc && webpack --config ./webpack.config.js --mode=development",
"build:prod": "npm run clean && tsc && webpack --config ./webpack.config.js --mode=production",
"webtest": "cp _bundles/* webtest/; node server.cjs",
"test:esm": "node lib-esm/index.js"
},
"engines" : {
"node" : ">=16.0.0"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",
"@types/node": "^18.7.23",
"node-static": "^0.7.11",
"shx": "^0.3.4",
"ts-loader": "^9.4.0",
"typescript": "^4.8.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},
"dependencies": {
"esm1": "file:../esm1"
}
}
//esm2: tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node16",
"module": "es6",
"target": "es6",
"lib": [ "es6", "dom" ],
"outDir": "lib-esm",
"allowSyntheticDefaultImports": true,
"suppressImplicitAnyIndexErrors": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"declaration": true,
"declarationMap": true
},
"include": [
"src/**/*",
],
"exclude": [
"node_modules",
"**/*.spec.ts"
],
"compileOnSave": false,
"buildOnSave": false
}
//esm2: webpack.config.js
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PATHS = {
entryPoint: path.resolve(__dirname, 'src/index.ts'),
bundles: path.resolve(__dirname, '_bundles'),
}
var config = {
entry: {
'esm2': [PATHS.entryPoint],
'esm2.min': [PATHS.entryPoint]
},
target: ['web'],
externals: {},
output: {
path: PATHS.bundles,
libraryTarget: 'umd',
library: 'esm2',
umdNamedDefine: true
},
resolve: {
symlinks: true,
extensions: ['.ts', '.tsx', '.js'],
extensionAlias: {
'.js': ['.ts', '.js'],
'.mjs': ['.mts', '.mjs']
},
},
devtool: 'source-map',
plugins: [],
module: {
rules: [{
test: /\.(ts|tsx)$/i,
use: 'ts-loader',
exclude: ["/node_modules/"],
},
],
},
}
export default config;
Hope that helps someone. Everything is in my github:
esm1: [https://github.com/fab77/esm1.git][1]
esm2: [https://github.com/fab77/esm2.git][2]