- How do I make the tree shakeable?
- What JS module systems should I target (
CommonJS
,AMD
,ES6
). - Should I transpile the source?
- Should I bundle the source?
- What files should I publish?
Let's try to address all the above questions now.
- Implemented by
Node.js
- Used for the server side when you have modules installed.
- No runtime/async module loading.
- Import via “require”.
- Export via “module.exports”.
- When you import you get back an object.
- No tree shaking, because when you import you get an object.
- No static analyzing, as you get an object, so property lookup is at runtime.
- You always get a copy of an object, so no live changes in the module itself.
- Poor cyclic dependency management.
- Simple syntax
// log.js
function log() {
console.log('Example of CJS module system');
}
// Expose log to other modules
module.exports = { log }
// index.js
var logModule = require('./log')
logModule.log()
= Implemented by RequireJs
.
- Used for the client side (browser) when you want dynamic loading of modules.
- Import via “require”.
- Complex syntax.
// log.js
define(['logModule'], function() {
// Export (expose) foo for other modules
return {
log: function() {
console.log('Example of AMD module system')
}
}
})
// index.js
require(['log'], function (logModule) {
logModule.log()
})
- Combination of
CommonJS
+AMD
(that is, syntax ofCommonJS
+ async loading ofAMD
). - Can be used for both
AMD
/CommonJS
environments UMD
essentially creates a way to use either of the two, while also supporting the global variable definition. As a result, modules are capable of working on both client and server.
// log.js
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define('exports'], factory)
} else if (typeof exports !== 'undefined') {
factory(exports)
} else {
var mod = {
exports: {}
}
factory(mod.exports)
global.log = mod.exports
}
})(this, function (exports) {
'use strict'
function log() {
console.log('Example of UMD module system')
}
// expose log to other modules
exports.log = log
})
- Used for both server/client side.
- Runtime/static loading of modules supported.
- When you import, you get back bindings value (actual value).
- Import via “import” and export via “export”.
- Static analyzing - you can determine imports and exports at compile time (statically) — you only have to look at the source code, you don’t have to execute it.
- Tree shakeable, because of static analyzing supported by
ES6
. - Always get an actual value so live changes in the module itself.
- Better cyclic dependency management than
CommonJS
.
// log.js
const log = () => {
console.log('Example of ES module system');
}
export default log
// index.js
import log from './log'
log()
This is all about different types of JS module systems and how they have evolved.
Tree shaking is a term commonly used in the context of JavaScript or dead-code elimination. It relies on the static structure of ES2015
module syntax, that is, import and export. The name and concept have been popularized by the ES2015
module bundler rollup.
Webpack and Rollup both support tree shaking, though we need to keep certain things in mind so that our code is tree shakeable.
- Can be tree shaken as we export as ES modules and only shake is included in the import:
// shakebake.js
const shake = () => console.log('shake')
const bake = () => console.log('bake')
export { shake, bake }
// index.js
import { shake } from './shakebake.js'
- Cannot be tree shaken as we have exported an object, and therefore both shake and bake are included in the import:
// shakebake.js
const shake = () => console.log('shake')
const bake = () => console.log('bake')
export default { shake, bake }
// index.js
import { shake } from './shakebake.js'
We should publish all the module variants, like UMD
and ES
, because we never know which browser/webpack version our consumers might use this library/package in.
// package.json
{
"name": "js-module-system",
"version": "0.0.1",
...
"main": "dist/index.js",
"module": "dist/index.es.js"
}
- The
main
field of thepackage.json
file is usually used to point to theUMD
version of the library/package. - The
module
field of thepackage.json
is used to point to the ES version of the library/package. Previously, many fields were used likejs:next
andjs:main
, but module is now standardized and is used by bundlers as a lookup for the ES version of the library/package.
Always try to publish the ES version of your library/package as well, because all the modern browsers now support ES modules. So you can transpile less, and ultimately you’ll end up shipping less code to your users. This will boost your application’s performance.
Each tool has it’s own benefits and serves different purpose based on your needs.
Webpack is a gret module bundler that is widely accepted and mostly used for building SPAs. It gives you all the features out of the box like code splitting, async loading of bundles, tree shaking, and so on. It uses the CommonJS
module system.
Rollup is also a module bundler similar to Webpack. However, the main advantage of rollup is that it follows new standardized formatting for code modules included in the ES6
revision, so you can use it to bundle the ES module variant of your library/package. It doesn't support async loading of bundles.
Babel is a transpiler for JavaScript best known for its ability to turn ES6
code into code that runs in your browser (or on your server) today. It just transpiles and doesn't bundle your code.
Use Rollup for libraries and Webpack for apps.
...
LICENSE
README.md
Changelog
- Metadata (
main
,module
,bin
) -package.json
- Control through the
files
property -package.json
In package.json
, the files
field is an array of file patterns that describes the entries to be included when your package is installed as a dependency. If you name a folder in the array, then it will also include the files inside that folder.
We will include the lib
and dist
folders in files
field in our case.
// package.json
{
...
"files": ["dist", "lib"]
}
Finally the library is ready to publish. Just type the npm run build
command in the terminal, and you can see the following output. Closely look at the dist
and lib
folders.
- Enable tree shaking.
- Target at least
ES6
andCommonJS
module systems. - Use Babel and bundlers for libraries.
- Use bundlers for core packages.
- Set the
module
field ofpackage.json
to point to the ES version of your module (it helps in tree shaking). - Publish the folders which have transpiled as well as bundled versions of you module.