Packaging JavaScript applications can be a bit overwhelming. The popular project uglifyjs does not support ES6, it is cumbersome to configure the allmighty Webpack, bundlers like Parcel and Microbundle still have bugs or do not compile to ESM bundles that work in a browser. It is hard to figure out the best way to bundle an application.
Here I give a small example, how we achieve the goal using the
- TypeScript compiler
- rollup.js bundler
- terser mangler
☕️ Prerequisites: Install Node.js
- Zero configuration bundling (esm, umd)
- Proper entry point hints for npm, unpkg and when used as module
- Runs with
<script type='module'>
in a web page
Go to a new directory say-hello/
npm init -y
Install dependencies
npm i -D typescript rollup terser
Replace package.json "scripts" (umd name sayHello is app specific)
"scripts": {
"clean": "rm -fr dist",
"build": "npm run clean && npm run lint && tsc --project tsconfig.build.json && npm run bundle:esm && npm run bundle:esm:min && npm run bundle:umd && npm run bundle:umd:min && npm run build:stats",
"build:stats": "(echo '\\033[35;3m' ; cd dist && ls -lh index*js index*gz | tail -n +2 | awk '{print $5,$9}')",
"bundle:esm": "rollup dist/index.js --file dist/index.mjs --format esm",
"bundle:esm:min": "terser --ecma 6 --compress --mangle --module -o dist/index.min.mjs -- dist/index.mjs && gzip -9 -c dist/index.min.mjs > dist/index.min.mjs.gz",
"bundle:umd": "rollup dist/index.js --file dist/index.umd.js --format umd --name sayHello",
"bundle:umd:min": "terser --ecma 6 --compress --mangle -o dist/index.umd.min.js -- dist/index.umd.js && gzip -9 -c dist/index.umd.min.js > dist/index.umd.min.js.gz",
},
Replace "main" in package.json
- "main": "index.js"
+ "main": "dist/index.js",
+ "module": "dist/index.min.mjs",
+ "unpkg": "dist/index.umd.min.js",
+ "types": "dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
Create TypeScript configuration tsconfig.json
{
"compilerOptions": {
"declaration": true,
"lib": ["es6", "dom", "dom.iterable"],
"module": "es6",
"moduleResolution": "node",
"removeComments": true,
"sourceMap": true,
"strict": true,
"target": "es6",
"outDir": "./dist"
},
"include": [
"src/**/*"
],
"exclude": [
"dist",
"node_modules"
]
}
Create test application src/index.ts
export function sayHello() {
return "Hi ya all!";
}
Create a file example/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Say Hello</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Daniel Dietrich">
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="app">
Loading...
</div>
<script type="module">
import { sayHello } from '../dist/index.mjs';
document.getElementById('app').innerHTML = sayHello();
</script>
</body>
</html>
npm run build
Loading our example web page example/index.html (might require to Disable Local File Restrictions in the browser)
The interesting part
<script type="module">
import { sayHello } from '../dist/index.mjs';
document.getElementById('app').innerHTML = sayHello();
</script>
When published to npmjs.com, we can use the unpkg.com CDN to include our module in a web page
<script type="module">
- import { sayHello } from '../dist/index.mjs';
+ import { sayHello } from 'https://unpkg.com/say-hello?module';
document.getElementById('app').innerHTML = sayHello();
</script>
[Written by @danieldietrich]