Home > OS >  2 ESM interoperability issue with Node Typescript Webpack
2 ESM interoperability issue with Node Typescript Webpack

Time:10-05

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]
  • Related