Today, in 2017, many evergreen browsers support ES6 Modules out of the box. Some browsers have it hidden behind a flag, including Node.js. But is it possible to support old and new environments with the same npm package? Yes! Editor’s Note: ES6 Modules are sometimes referred to as ES2015 Modules, or ESM, or _module_ scripts, or sometimes even by the extension _.mjs_ pronounced "Michael Jackson Scripts". We are all talking about the same thing so don't get confused if you hear different terms. I won’t go over the merits of using ES6 Modules or why TypeScript is awesome because there have been many blog posts describing these at length. Instead, I will focus on publishing a package to npm that is written in TypeScript but deployed as (ESM) and (CommonJS) so any consumer can use your package! .mjs .js Getting Started The first step is to setup your file so that TypeScript will use the latest and greatest JavaScript features like so: tsconfig.json { "compilerOptions": { "module": "es2015", "target": "ES2017", "rootDir": "src", "outDir": "dist", "sourceMap": false, "strict": true }} Obviously, we want modules to be because hey, its in the title of this article so we have to address it at some point! Let's target so we can use and keywords like a JS Ninja. You can name your and to whatever you want, but its a bit of a convention to use for output in JS Land. Source Maps are optional but I like to turn them off until I need them. Strict Mode is also optional but it's easier to start strict and get a little loosey goosey if you need too later. I highly recommend enabling it since it is disabled by default. es2015 es2017 async await rootDir outDir dist Wiring It Up Now we can discuss the file. Here's an example for a package called : package.json copee { "name": "copee", "version": "1.0.0", "description": "Copy text from browser to clipboard...natively!", "repository": "styfle/copee", "files": [ "dist" ], "main": "dist/copee", "types": "dist/copee.d.ts", "scripts": { "mjs": "tsc -d && mv dist/copee.js dist/copee.mjs", "cjs": "tsc -m commonjs", "build": "npm run mjs && npm run cjs" }, "devDependencies": { "typescript": "^2.5.3" }} The first four lines define the package , , , and GitHub which are self explainatory. name version description repository Next, we define which we simply define as a single folder, . These are the files that will be published to npm. files dist The entry point into your package is defined as and this is where the magic happens. Notice there is no file extension (such as ) as one might expect. This will allow Node to pick the file based on the way the consumer is importing your package--either legacy CJS or the new ESM. main .js Next is which is necessary for consumers who want to import via TypeScript. If you're writing your package in TypeScript, you should most definitely include for your fellow TS users to get type saftey! Seriously, it's just the right thing to do. types types Now comes the fun part: . These are your build steps which can be run via . The first build step uses the TypeScript Compiler ( ) to build our code using the file we defined earlier, plus a flag which emits our type definitions. Also note the command which moves (or rather renames) the output file from to . This is our ESM output. scripts npm run thenameofthescriptgoeshere mjs tsc tsconfig.json -d .d.ts mv .js .mjs Our next script, uses the TypeScript Compiler ( ) to build the same source code but emit the output as a CommonJS module. This is the module system for Node.js and is understood by , , etc. cjs tsc browserify webpack Lastly, we have which are your build tools. In this case, all we need is which includes the command used above. devDependencies typescript tsc Node Usage I’m going to show you how to write a consumer that imports the package above. If you already use Node regularly, jump to the next section for ESM usage. First, install the package: copee npm install --save copee Then, create a file with the following: index.js const { toClipboard } = require('copee'); console.log('CJS: We found a ', typeof toClipboard); The new program can be executed like so: node index.js Node ESM Usage I’m going to show you how to write a consumer that imports the package above. copee After installing , create a file. You must use the Michael Jackson Script extension (.mjs). copee index.mjs import { toClipboard } from 'copee';console.log('ESM: We found a ', typeof toClipboard); The new program can be executed like so: node --experimental-modules index.mjs Browser ESM Usage Node usage isn’t that spectacular because there have been modules since its inception, but the beauty of ESM is that the same code executing in a Node module will run unchanged in a browser! Yes, it’s true! Feast your eyes on this elegant code snippet below: <script type="module"> import { toClipboard } from 'https://cdn.jsdelivr.net/npm/copee/dist/copee.mjs'; $('#btn').on('click', () => { const win = toClipboard('Wow, "copee" works!'); if (win) { // it worked, check your clipboard! } });</script> We have a new script type for and we are using to automatically host our code on a CDN. This makes it easy to write a single import line and use the package in browsers all over the world! module jsDelivr copee Legacy Browsers What about legacy browsers, you say? Not everyone supports ESM? Well this can be solved by bundling as UMD with . After installing , add this to the section of your file. rollup rollup scripts package.json { "umd": "rollup -i dist/copee.mjs -o dist/copee.umd.js -f umd -n copee"} You can include both ESM and UMD builds on the same page without conflicts. See the snippet below: <script nomodule src="https://cdn.jsdelivr.net/npm/copee/dist/copee.umd.js"></script> <script type="module"> import { toClipboard } from 'https://cdn.jsdelivr.net/npm/copee/dist/copee.mjs';</script> By using the attribute, you are telling new browsers to ignore the UMD script. By using you are telling old browsers to ignore ESM. Now everyone wins! nomodule type=module You can see a working demo of this solution on the . Demo page Also, please checkout the for more details and of course, the working source code! GitHub repo Originally published at www.ceriously.com on October 16, 2017.
Share Your Thoughts