├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── build ├── cjs │ └── index.js └── mjs │ └── index.mjs ├── hybrid ├── dist │ ├── hybrid.d.ts │ ├── hybrid.js │ ├── hybrid.js.map │ ├── hybrid.mjs │ ├── hybrid.mjs.map │ ├── say-hello.d.ts │ ├── say-hello.js │ ├── say-hello.js.map │ ├── say-hello.mjs │ └── say-hello.mjs.map ├── package-lock.json ├── package.json ├── src │ ├── hybrid.ts │ └── say-hello.ts └── tsconfig.json ├── package-lock.json ├── package.json └── src └── index.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{ts,tsx,js,jsx,json}] 12 | indent_size = 4 13 | curly_bracket_next_line = true 14 | spaces_around_operators = true 15 | spaces_around_brackets = outside 16 | indent_brace_style = K&R 17 | max_line_length = 120 18 | 19 | [{package.json,.travis.yml}] 20 | indent_size = 2 21 | 22 | # XML Project Files 23 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 24 | indent_size = 2 25 | 26 | # Markdown Files 27 | [*.md] 28 | trim_trailing_whitespace = false 29 | 30 | # Web Files 31 | [*.{htm,html,css,scss,less}] 32 | indent_size = 2 33 | insert_final_newline = true 34 | 35 | # Bash Files 36 | [*.sh] 37 | end_of_line = lf 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,linux,macos,windows 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear in the root of a volume 31 | .DocumentRevisions-V100 32 | .fseventsd 33 | .Spotlight-V100 34 | .TemporaryItems 35 | .Trashes 36 | .VolumeIcon.icns 37 | .com.apple.timemachine.donotpresent 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | ### Node ### 47 | # Logs 48 | logs 49 | *.log 50 | npm-debug.log* 51 | yarn-debug.log* 52 | yarn-error.log* 53 | 54 | # Runtime data 55 | pids 56 | *.pid 57 | *.seed 58 | *.pid.lock 59 | 60 | # Directory for instrumented libs generated by jscoverage/JSCover 61 | lib-cov 62 | 63 | # Coverage directory used by tools like istanbul 64 | coverage 65 | 66 | # nyc test coverage 67 | .nyc_output 68 | 69 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 70 | .grunt 71 | 72 | # Bower dependency directory (https://bower.io/) 73 | bower_components 74 | 75 | # node-waf configuration 76 | .lock-wscript 77 | 78 | # Compiled binary addons (http://nodejs.org/api/addons.html) 79 | build/Release 80 | 81 | # Dependency directories 82 | node_modules/ 83 | jspm_packages/ 84 | 85 | # Typescript v1 declaration files 86 | typings/ 87 | 88 | # Optional npm cache directory 89 | .npm 90 | 91 | # Optional eslint cache 92 | .eslintcache 93 | 94 | # Optional REPL history 95 | .node_repl_history 96 | 97 | # Output of 'npm pack' 98 | *.tgz 99 | 100 | # Yarn Integrity file 101 | .yarn-integrity 102 | 103 | # dotenv environment variables file 104 | .env 105 | 106 | ### Windows ### 107 | # Windows thumbnail cache files 108 | Thumbs.db 109 | ehthumbs.db 110 | ehthumbs_vista.db 111 | 112 | # Folder config file 113 | Desktop.ini 114 | 115 | # Recycle Bin used on file shares 116 | $RECYCLE.BIN/ 117 | 118 | # Windows Installer files 119 | *.cab 120 | *.msi 121 | *.msm 122 | *.msp 123 | 124 | # Windows shortcuts 125 | *.lnk 126 | 127 | 128 | # End of https://www.gitignore.io/api/node,linux,macos,windows 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Frédérique Mittelstaedt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hybrid ESM/CJS modules in TypeScript 2 | 3 | Generating and using hybrid node modules targeting both CommonJS/CJS and ECMAScript modules/ESM is difficult to do correct in a way that these modules are also usable from within node. 4 | 5 | This repo provides a working example of how to achieve this in TypeScript (based on a [previous example by @demurgos](https://github.com/demurgos/mjts)). 6 | 7 | ## Why? 8 | 9 | You need to always have support for "legacy" CommonJS modules, but to make the most out of modern tooling like `rollup`, you also want to publish ES modules, so these tools can work their magic and reduce bundle sizes. 10 | 11 | ## Gotchas 12 | 13 | First of all, in order to publicly export a CJS and ESM entrypoint, you need to supply `main` (CJS or hybrid entrypoint), `module` (ESM entry point), `browser` (CJS entry point), and `types` for correct TypeScript type export in your `package.json`. 14 | 15 | However, node does not accept standard `.js` files as ESM modules, but you need to either supply a loader script that handles module type detection (slow, potentially error prone) or change the filenames of all ES modules to `.mjs`, which node natively recognises as ESM modules. 16 | 17 | TypeScript unfortunately does not support renaming output files, so this needs to be done manually. 18 | 19 | Finally, importing the modules correctly requires different compiler arguments (see below). 20 | 21 | ## Structure 22 | 23 | The [`hybrid`](./hybrid) directory contains a hybrid CJS/ESM module ([`package.json`](./hybrid/package.json)) that just exports a single function. The function prints a string and returns a boolean indicating whether this function was executed in CJS or ESM. 24 | 25 | It gets imported by our main repo ([`package.json`](./package.json)), and the exported function gets used. This app just prints a few strings and finally whether the imported module was an ES or CJS module. 26 | 27 | ## Detecting CJS vs ESM 28 | 29 | In ESM, the `exports` variable does not exist, since exports are handled via the `export` keyword. Hence, whether or not this variable is undefined tells us if the executing context is ESM or CJS. 30 | 31 | ## Install 32 | 33 | Install the dependencies of the hybrid module, build it, then install the dependencies in the main repo. 34 | 35 | ```sh 36 | cd hybrid && npm install && npm run build && cd .. && npm install 37 | ``` 38 | 39 | ## Build config 40 | 41 | Any ES modules need to be renamed to `.mjs` or node.js will fail to recognise them as such and throw errors. 42 | 43 | ### Building hybrid apps 44 | 45 | From the main project [`package.json`](./package.json): 46 | 47 | ```json 48 | { 49 | "scripts": { 50 | "build:mjs": "tsc --rootDir src --outDir build/mjs --moduleResolution node --allowSyntheticDefaultImports --module esnext src/*.ts && mv build/mjs/index.js build/mjs/index.mjs", 51 | "build:cjs": "tsc --rootDir src --outDir build/cjs --moduleResolution node --esModuleInterop src/*.ts", 52 | } 53 | } 54 | ``` 55 | 56 | For regular applications, it's a good idea to split CJS and ESM output into different directories, because they will be used separately anyway. In both cases, you want `moduleResolution` set to `node`. 57 | 58 | To build an ES module app (that can also work with CJS modules), you need to enable the option `allowSyntheticDefaultImports` and set the `module` compiler option to `esnext`. 59 | 60 | To build a CommonJS module app (that can also work with ES modules), you need to enable the option `esModuleInterop`. 61 | 62 | ### Building hybrid packages 63 | 64 | From the hybrid package [`package.json`](./hybrid/package.json): 65 | 66 | ```json 67 | { 68 | "main": "./dist/hybrid", 69 | "types": "./dist/hybrid.d.ts", 70 | "browser": "./dist/hybrid.js", 71 | "module": "./dist/hybrid.mjs", 72 | "scripts": { 73 | "build:mjs": "tsc --allowSyntheticDefaultImports --module esnext && npm run move-esm-output && npm run process-source-maps", 74 | "move-esm-output": "mv dist/hybrid.js dist/hybrid.mjs && mv dist/say-hello.js dist/say-hello.mjs", 75 | "process-source-maps": "sed -e \"s/hybrid.js/hybrid.mjs/g\" ./dist/hybrid.js.map > ./dist/hybrid.mjs.map && sed -e \"s/say-hello.js/say-hello.mjs/g\" ./dist/say-hello.js.map > ./dist/say-hello.mjs.map", 76 | "build:cjs": "tsc --esModuleInterop", 77 | "build": "npm run build:mjs && npm run build:cjs" 78 | } 79 | ``` 80 | 81 | First of all, if no file extension is specified in the `main` field, the module loader can choose the desired from the available options at the path. If the module loader is configured to preferably load ES modules, then the `.mjs` file will be loaded if it exists, otherwise the `.js` at the spot will be loaded as a CJS module. 82 | 83 | A hybrid module will have a `.js` and a `.mjs` version of each file in the module. 84 | 85 | The build process for the ES module files is as follows: 86 | 87 | * Build the ES module with typescript: This generates `.js` code files and `.js.map` source map files. Note that you need to specify `--allowSyntheticDefaultImports` and `--module esnext` as compiler options. 88 | * Rename all generated `.js` files to `.mjs` (see the `move-esm-output` task above) 89 | * Parse all source map files and rename the target filename inside from the `.js` version to `.mjs`. Then output the replaced code at the same path ending in `.mjs.map` (see process-source-maps task above). 90 | 91 | The build process for CJS module files is simpler and **it should be run after the ES module code has been generated and post-processed** (i.e. **not in parallel**). You only need to run `tsc` with the additional compiler option `--esModuleInterop`. 92 | 93 | The final result will be, the following set of files for each individual source TypeScript source file / module (using the example file `/filename.ts`): 94 | 95 | * `/filename.d.ts`: The TypeScript definition file. 96 | * `/filename.js`: The CommonJS version of the module 97 | * `/filename.js.map`: the source map of the CommonJS module. 98 | * `/filename.mjs`: The ESM version of the module 99 | * `/filename.mjs.map`: the source map of the ES module. 100 | 101 | #### Minor gotcha 102 | 103 | Technically, in ES modules, you need to specify the full file path of an import (according to the spec). Within node, this is not necessary though, if an `.mjs` file exists at that location. On the browser side, this will not work, so you should post-process your output further to rewrite the paths to fully qualified paths including extension. Alternatively, you could reconfigure the module loader to search at the appropriate locations. 104 | 105 | The easiest option though is probably to just bundle up all the output using an ESM-aware bundler like *rollup*. 106 | 107 | ## Running hybrid apps 108 | 109 | From the main project [`package.json`](./package.json): 110 | 111 | ```json 112 | { 113 | "scripts": { 114 | "start:mjs": "npm run build:mjs && node --experimental-modules build/mjs/index.mjs", 115 | "start:cjs": "npm run build:cjs && node build/cjs/index.js" 116 | } 117 | } 118 | ``` 119 | 120 | To start node using CommonJS, you don't need to do anything and can just run `node .js`. 121 | 122 | To start node using/supporting ES modules, you need to (currently) supply the `--experimental-modules` option and then a path to the entrypoint `.mjs` file (`node --experimental-modules .mjs`). 123 | 124 | ## License 125 | 126 | [MIT License](./LICENSE.md) 127 | -------------------------------------------------------------------------------- /build/cjs/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | exports.__esModule = true; 6 | var hybrid_1 = require("hybrid"); 7 | var path_1 = __importDefault(require("path")); 8 | function run() { 9 | console.log("ESM is getting real"); 10 | console.log(path_1["default"].posix.join("i", "can", "use", "cjs", "modules")); 11 | console.log("And hybrid modules:"); 12 | console.log("This was generated in a " + (hybrid_1.sayHello() ? "ES" : "CJS") + " module."); 13 | } 14 | run(); 15 | -------------------------------------------------------------------------------- /build/mjs/index.mjs: -------------------------------------------------------------------------------- 1 | import { sayHello } from "hybrid"; 2 | import path from "path"; 3 | function run() { 4 | console.log("ESM is getting real"); 5 | console.log(path.posix.join("i", "can", "use", "cjs", "modules")); 6 | console.log("And hybrid modules:"); 7 | console.log("This was generated in a " + (sayHello() ? "ES" : "CJS") + " module."); 8 | } 9 | run(); 10 | -------------------------------------------------------------------------------- /hybrid/dist/hybrid.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./say-hello"; 2 | -------------------------------------------------------------------------------- /hybrid/dist/hybrid.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | exports.__esModule = true; 6 | __export(require("./say-hello")); 7 | //# sourceMappingURL=hybrid.js.map -------------------------------------------------------------------------------- /hybrid/dist/hybrid.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"hybrid.js","sourceRoot":"","sources":["../src/hybrid.ts"],"names":[],"mappings":";;;;;AACA,iCAA4B"} -------------------------------------------------------------------------------- /hybrid/dist/hybrid.mjs: -------------------------------------------------------------------------------- 1 | export * from "./say-hello"; 2 | //# sourceMappingURL=hybrid.js.map -------------------------------------------------------------------------------- /hybrid/dist/hybrid.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"hybrid.mjs","sourceRoot":"","sources":["../src/hybrid.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC"} -------------------------------------------------------------------------------- /hybrid/dist/say-hello.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints "Hello, World!" 3 | * @returns whether this function was executed in a CommonJS context or in an ES module 4 | */ 5 | export declare function sayHello(): boolean; 6 | -------------------------------------------------------------------------------- /hybrid/dist/say-hello.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | /** 4 | * Prints "Hello, World!" 5 | * @returns whether this function was executed in a CommonJS context or in an ES module 6 | */ 7 | function sayHello() { 8 | console.log("Hello, World!"); 9 | // The exports variable will be undefined in an ES module, but not in CommonJS 10 | // So, we can use it to discriminate between CommonJS and ES modules 11 | return typeof exports === "undefined"; 12 | } 13 | exports.sayHello = sayHello; 14 | //# sourceMappingURL=say-hello.js.map -------------------------------------------------------------------------------- /hybrid/dist/say-hello.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"say-hello.js","sourceRoot":"","sources":["../src/say-hello.ts"],"names":[],"mappings":";;AACA;;;GAGG;AACH;IACI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAE7B,8EAA8E;IAC9E,oEAAoE;IACpE,OAAO,OAAO,OAAO,KAAK,WAAW,CAAC;AAC1C,CAAC;AAND,4BAMC"} -------------------------------------------------------------------------------- /hybrid/dist/say-hello.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints "Hello, World!" 3 | * @returns whether this function was executed in a CommonJS context or in an ES module 4 | */ 5 | export function sayHello() { 6 | console.log("Hello, World!"); 7 | // The exports variable will be undefined in an ES module, but not in CommonJS 8 | // So, we can use it to discriminate between CommonJS and ES modules 9 | return typeof exports === "undefined"; 10 | } 11 | //# sourceMappingURL=say-hello.js.map -------------------------------------------------------------------------------- /hybrid/dist/say-hello.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"say-hello.mjs","sourceRoot":"","sources":["../src/say-hello.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,MAAM;IACF,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAE7B,8EAA8E;IAC9E,oEAAoE;IACpE,OAAO,OAAO,OAAO,KAAK,WAAW,CAAC;AAC1C,CAAC"} -------------------------------------------------------------------------------- /hybrid/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hybrid", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.3.2", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.2.tgz", 10 | "integrity": "sha512-9NfEUDp3tgRhmoxzTpTo+lq+KIVFxZahuRX0LHF/9IzKHaWuoWsIrrJ61zw5cnnlGINX8lqJzXYfQTOICS5Q+A==", 11 | "dev": true 12 | }, 13 | "typescript": { 14 | "version": "2.9.1", 15 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.1.tgz", 16 | "integrity": "sha512-h6pM2f/GDchCFlldnriOhs1QHuwbnmj6/v7499eMHqPeW4V2G0elua2eIc2nu8v2NdHV0Gm+tzX83Hr6nUFjQA==", 17 | "dev": true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hybrid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hybrid", 3 | "description": "Hybrid package", 4 | "version": "1.0.0", 5 | "author": "Frédérique Mittelstaedt (https://www.gfm.io)", 6 | "license": "MIT", 7 | "private": true, 8 | "main": "./dist/hybrid", 9 | "types": "./dist/hybrid.d.ts", 10 | "browser": "./dist/hybrid.js", 11 | "module": "./dist/hybrid.mjs", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/gfmio/typescript-esm-cjs-hybrid-example" 15 | }, 16 | "keywords": [ 17 | "typescript", 18 | "esm", 19 | "hybrid", 20 | "module", 21 | "example" 22 | ], 23 | "scripts": { 24 | "build:mjs": "tsc --allowSyntheticDefaultImports --module esnext && npm run move-esm-output && npm run process-source-maps", 25 | "move-esm-output": "mv dist/hybrid.js dist/hybrid.mjs && mv dist/say-hello.js dist/say-hello.mjs", 26 | "process-source-maps": "sed -e \"s/hybrid.js/hybrid.mjs/g\" ./dist/hybrid.js.map > ./dist/hybrid.mjs.map && sed -e \"s/say-hello.js/say-hello.mjs/g\" ./dist/say-hello.js.map > ./dist/say-hello.mjs.map", 27 | "build:cjs": "tsc --esModuleInterop", 28 | "build": "npm run build:mjs && npm run build:cjs" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^10.3.2", 32 | "typescript": "^2.9.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hybrid/src/hybrid.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from "./say-hello"; 3 | -------------------------------------------------------------------------------- /hybrid/src/say-hello.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Prints "Hello, World!" 4 | * @returns whether this function was executed in a CommonJS context or in an ES module 5 | */ 6 | export function sayHello(): boolean { 7 | console.log("Hello, World!"); 8 | 9 | // The exports variable will be undefined in an ES module, but not in CommonJS 10 | // So, we can use it to discriminate between CommonJS and ES modules 11 | return typeof exports === "undefined"; 12 | } 13 | -------------------------------------------------------------------------------- /hybrid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "moduleResolution": "node", 5 | "sourceMap": true, 6 | "declaration": true 7 | }, 8 | "files": [ 9 | "./src/hybrid.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-esm-cjs-hybrid-example", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.3.2", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.2.tgz", 10 | "integrity": "sha512-9NfEUDp3tgRhmoxzTpTo+lq+KIVFxZahuRX0LHF/9IzKHaWuoWsIrrJ61zw5cnnlGINX8lqJzXYfQTOICS5Q+A==", 11 | "dev": true 12 | }, 13 | "hybrid": { 14 | "version": "file:hybrid", 15 | "dev": true, 16 | "dependencies": { 17 | "@types/node": { 18 | "version": "10.3.2", 19 | "bundled": true 20 | }, 21 | "typescript": { 22 | "version": "2.9.1", 23 | "bundled": true 24 | } 25 | } 26 | }, 27 | "typescript": { 28 | "version": "2.9.1", 29 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.1.tgz", 30 | "integrity": "sha512-h6pM2f/GDchCFlldnriOhs1QHuwbnmj6/v7499eMHqPeW4V2G0elua2eIc2nu8v2NdHV0Gm+tzX83Hr6nUFjQA==", 31 | "dev": true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-esm-cjs-hybrid-example", 3 | "version": "1.0.0", 4 | "author": "Frédérique Mittelstaedt (https://www.gfm.io)", 5 | "license": "MIT", 6 | "private": true, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/gfmio/typescript-esm-cjs-hybrid-example" 10 | }, 11 | "keywords": [ 12 | "typescript", 13 | "esm", 14 | "hybrid", 15 | "module", 16 | "example" 17 | ], 18 | "scripts": { 19 | "build:mjs": "tsc --rootDir src --outDir build/mjs --moduleResolution node --allowSyntheticDefaultImports --module esnext src/*.ts && mv build/mjs/index.js build/mjs/index.mjs", 20 | "build:cjs": "tsc --rootDir src --outDir build/cjs --moduleResolution node --esModuleInterop src/*.ts", 21 | "start:mjs": "npm run build:mjs && node --experimental-modules build/mjs/index.mjs", 22 | "start:cjs": "npm run build:cjs && node build/cjs/index.js" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^10.3.2", 26 | "hybrid": "file://./hybrid", 27 | "typescript": "^2.9.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { sayHello }from "hybrid"; 3 | import path from "path"; 4 | 5 | function run(): void { 6 | console.log("ESM is getting real"); 7 | console.log(path.posix.join("i", "can", "use", "cjs", "modules")); 8 | console.log("And hybrid modules:"); 9 | console.log(`This was generated in a ${sayHello() ? "ES" : "CJS"} module.`); 10 | } 11 | 12 | run(); 13 | --------------------------------------------------------------------------------