Bundling js-ipfs and js-ipfs-api for the Browser #429
Description
JS IPFS is a large collection of modules that aim to implement IPFS in Node.js and the browser. As such the distributions of these modules has a specific set of constraints.
Our current setup is not bad, and does generate bundles usable in Node.js and the browser, but there are some pain points that need work.
Current Pain Points
- Bundles are quite large
- Lots of dependencies are duplicated, for example
readable-stream
is included 12 times in the currentjs-ipfs
browser bundle. - Developers have to know very domain specific configurations to
be able to use browserify or webpack. - We break browserify and webpack compat without knowing about it
until we get a bug report.
Optimization Goals
- Bundle Size
- Ease of use for developers embedding the library (i.e. Orbit)
- Ease of use for contributors
Module Formats
There are two different module formats for JavaScript modules in main use today.
- CommonJS
var dep = require('dependency')
- Only native format in Node.js at the moment.
- ES2015 Modules
import dep from 'dependency'
- You can read about it in detail in this article: http://www.2ality.com/2014/09/es6-modules-final.html
The current code base uses CommonJS.
Available Tooling
The tooling landscape is quite large today, with things developing and changing quite rapidly. The for us currently relevant tooling is listed below.
Module Bundlers
A module bundler can take in many JavaScript files and generate a bundle, which is usable in the browser.
- [Webpack](CommonJS, ES2015)
- [jspm](CommonJS, ES2015)
- [Closure Compiler](CommonJS, ES2015)
- [Rollup](CommonJS, ES2015)
- Browserify
- [Babel](CommonJS, ES2015)
ES2015 Transpilers
Transpilers can transform code written with ES2015 features and output code that is usable in ES5 (and lower) environments.
A good comparision of the differences in size and runtime can be found in The cost of transpiling ES2015 in 2016.
Proposal
Given the set of constraints mentioned above, the following is a list of steps I suggest to improve and solve our current pain points.
1. Improve build artifacts
Similar to what PouchDB does, the end result for Node.js and the browser should be a single file.
If there are differences between Node.js and browser, modules use two different entry points
src/index.js
- Original source for node.jssrc/index-browser.js
- Original source the browser
For the builds we target the same places as currently
dist/index.js
ES5 code for the browserlib/index.js
- ES5 code for node.js
but lib/index.js
will be a single file, fully transformed rather than still many files such that treeshaking and processing of things like webpack loaders already happend and this is runnable through in node.js directly.
To make tooling aware of what is avaliable, the following should fields should be in package.json
"main": "./lib/index.js",
"jsnext:main": "./src/index.js",
"browser": {
"./lib/index.js": "./dist/index.js"
},
"jspm": {
"main": "dist/index.js"
}
Benefits
- Fully compatabile out of the box, with default configuaration with
- browserify
- webpack
- jspm
- rollup
Drawbacks
- Transpiled code in
lib/index.js
is a bit harder to read as it
is now a single large file.
2. Test webpack & browserify in CI
- Build with the default configurations for browserify and webpack.
- Run the full test suite against these versions.
Benefits
- We can be sure that our builds are usable by other developers.
Drawbacks
- CI run time increases.
3. Move to ES2015 Modules
- Using tools like cjs-to-es6 this is pretty straight forward for our own modules.
- For dependencies that do not yet publish a build which uses ES2015
- Enable tree shaking in our webpack build.
Benefits
- Smaller module size, due to the availability of statically analyzable dependencies and so allowing us to use tree shaking
Drawbacks
- Not runnable in Node.js directly anymore until they integrate ES2015 modules or you use something like
babel-register
.
4. Carefully audit the dependency tree
- Look at all of them
- Migrate where needed and large enough benefits are clear to ES2105 modules
- Major culprits that we know about
- all shims for Node.js functionality in the browser
- forge
- web-crypto -> browserify-crypto
- readable-stream and all users of it
Benefits
- Only include what we absolutly need
- Improves tree shaking if we can use dependencies that use ES2015 modules.
Drawbacks
- Takes time
Resources
Blog Posts
- https://nolanlawson.com/2016/08/15/the-cost-of-small-modules/
- https://pouchdb.com/2016/01/13/pouchdb-5.2.0-a-better-build-system-with-rollup.html
- https://pouchdb.com/2016/06/06/introducing-pouchdb-custom-builds.html
- https://github.com/samccone/The-cost-of-transpiling-es2015-in-2016
- http://www.2ality.com/2015/12/webpack-tree-shaking.html