-
Notifications
You must be signed in to change notification settings - Fork 8
Build process
- we want to write code in the latest greatest, but not everyone can haz it
- we want all the latest greatest, and for it to work with all the old things
- it has to be performant & lean, yet easy to debug
- try and make everyone happy especially ourselves, use all the tools!
- use D8 (debug shell for V8) with irhydra with locally built irhydra using dart -> awesome-deopt to easily find your deopts & quickly optimizable functions, don't be fooled by js
- make a bunch of small side-effect-free functions that can be easily scoped when you do your build
- be safe with export naming & sugar syntax for import/export, test your dist files
- add a lot of debugging functionality wrapped in conditionals that will be dropped out when you build, but kept for development versions
- export for lots of formats, the more the merrier, make the api painless for people to use
0. ✅ src
code is es6 supported by LTS node & last many versions of chrome
- this makes it extremely easy to export & run quickly
- easy understanding & control over exactly how your code will turn out when you export it
1. run the Makefile with shorthand combinations for what needs to be run (e.g., make
, or make copy test cov prepublish
)
- all npm scripts are run by default with Makefile
- all scripting logic is done in Makefile, e.g.
yarn run jest -- --coverage
- none of the npm scripts are in package.json, it looks like this ```js
"scripts": {
"buble": "buble",
"rollup": "rollup",
"jest": "jest",
"webpack": "webpack",
"gzip": "gzip-size",
}
2. copy with flow-remove-types (types are no longer in the src but the copying for easy importing is still helpful)
-
src/
->/
copying the files to the root encourages much easier modular imports -
src/
->dist/
-
test/
->test-dist
3. code coverage: run buble on the dist/
& test-dist/
with sourceMaps inlined (output into those same respective dirs)
4. nyc runs ava, which uses babel, which uses the inline sourceMaps from buble... this is an unfortunate & required convoluted sequence of steps because:
- nyc does not support for es6
- ava forces the use of babel
- unfortunately the tap reporter for ava doesn't play nicely with covert (which is old anyway)
- there are barely any code coverage libraries for js
- babel has issues (breaks) when running babel-transpiled-code that extends raw es6 classes
UPDATE - migrated to jest!
- much easier code coverage available with just
--coverage
- no need to transpile and deal with sourceMaps
- about 10x faster
- some inconsistent issues on CI that are known jest issues
- used jest codemods which worked very well this time
5. run rollup - exports
-
rollup: config: {}
entry inpackage.json
for some targets that need a little extra config - we already have a
dist
folder that is es3+, and we can use it for each target (vs transpiling all of the source code each time) - best practice here is an index/entry file that just conditionally requires & exports the right file for the environment
- rollup allows multiple targets each with their own format which I'll inline for convenience
-
amd
– Asynchronous Module Definition, used with module loaders like RequireJS -
cjs
– CommonJS, suitable for Node and Browserify/Webpack/FuseBox -
es
(default) – Keep the bundle as an ES module file -
iife
– A self-executing function, suitable for inclusion as a <script> tag. (If you want to - create a bundle for your application, you probably want to use this, because it leads to - smaller file sizes.) (preact builds with this) -
umd
– Universal Module Definition, works as amd, cjs and iife all in one ❗ (this is default dev export, used also for dev by inferno)
-
UPDATE
- additional exports specifically have been crafted with a custom rollup plugin, rollup-plugin-falafel do some things the rollup replace plugin cannot do,
- remove the not-needed unwrapModuleExports wrapper (for export default)
- replace constant variable names before rollup temporarily changes all code to es6 imports and then back to commonjs
- debugger exports added, which is a set of the best places for the
debugger
to be used, so just importing from (or aliasing to)/debugger
will enable that flow - .min (as mangled) exports alongside the more readable dist files for each format that are compressed but not mangled, shaken, not stirred.
- // @TODO need to document the upcoming documentation generating
- replace our environment variables to remove things for production
- export a development build that keeps these conditions, for easier debugging
- uglify-js3 is run with uglify-es
- optimize-js wraps some functions
- eslint uses babeleslint to check the code, prettier makes it pretty - then is forced back by eslint if the rules are overriden (since it does not respect some of them)
- codacy checks the lint rules in case any were missed by local tools
- travis does the same thing all over again so we are sure it didn't "just work for us"
- coveralls & badgesize gets the result & then you get badges & graphs
- record build data size-over-time
-
gzip-size of the build
gzip-size dist/index.js --raw >> build/size-over-time.txt
- record date
date +%Y:%M:%D:%H:%M:%S >> build/size-over-time.txt
- format
echo --- >> build/size-over-time.txt
- comment (should do a cli prompt)
-
gzip-size of the build
- experiment with other bundling setups
-
fuse-box has a much easier build process, reports gzip, much faster, can compile with buble, typescript, or babel without any extra plugins, but the size is just a little bigger than rollup so I can't use it for
main
export yet - but likely soon - at one point, creating a rollup bundle of es6 code, transpiling that with typescript (so helpers are not duplicated, though with the latest version they have a helper for that), then re-bundling & uglifying gave a little better size than buble, but it didn't stay that way for long :-/
- using webpack, even with the new webpack 3 scope hoisting was many many times bigger than everything else so it was just not an option, more for applications than libraries (using webpack is much better with webpack-chain)
-
NOTE these were not used, but are other bundlerishes
- gulp probably would work, but would need 100 plugins
- browserify, although the slowest, was the easiest to just with 1 line cli cmd, and is the most stable by far, so when I just wanted to zip an html + css + js file, it just worked no trouble no config, kudos there, but not really for optimizing size
- brunch is definitely not focused on this job, but similar to yeoman in the way that it can get you started with a skeleton, probably worth putting a getting started repo on it for chainable
- grunt using grunt nowadays, is like using underscore, jquery, and coffeescript all together nowadays - not for a good reason (which there are), but just because the code wasn't maintained
- pin.gy nice looking cli, but that's about the same as browserify without being an OG
-
broccoli.js it says node 0.10.x as
node --version
for "latest"
- cli
- rollup
- Makefile
- buble
- babel + ava + istanbul/nyc
- flow-remove-types
- typescript
- fuse-box
- uglify-js3
- optimize-js
- fuse-box
- falafel
some notes
- most important & standard ones are
main
,browser
- nobody should be using
js:next
,module
replaces it - typings is for typescript
{
"main:es6": "src/index.js",
"main:dev": "dists/dev/index.js",
"main:tsc": "dists/tsc/index.js",
"main:iife": "dists/iife/index.js",
"main:umd": "dists/umd/index.js",
"main:cjs": "dists/cjs/index.js",
"main:es": "dists/es/index.js",
"js:next": "dists/es/index.js",
"main": "dists/umd/index.js",
"module": "dists/umd/index.js",
"web": "dists/cjs/index.js",
"browser": "dists/cjs/index.js",
"alias": "dists/cjs/index.js",
"amd": "dists/amd/index.js",
"types": "typings/index.d.ts",
"typings": "typings/index.d.ts"
}
This is how replace/define plugin works:
Libraries (such as react, inferno, etc) (see react-readme, react-code, inferno-code) have conditionals which look like the following:
if (process.env.NODE_ENV === 'development') { /* do debugging */ }
After it has been run with a config similar to:
define({
'process.env.NODE_ENV': JSON.stringify('production'),
})
the library code will come out as
if ('production' === 'development') { /* do debugging */ }
when that code is run through uglify or babili with drop-dead-code (default: true
), it can do static-analysis, and will remove that block, since 'production'
is never 'development'
.
since this is a string replacement, it does not mean that a process
polyfill is required for the browser, so console.log(process.env)
will not exist unless it is auto-polyfilled because of that console.log
, or because you've explicitly added a polyfill that handles that.
the origin of define
is C define
to see more on deadcode elimination
- tree-shaking-versus-dead-code-elimination
- angular rollup
- angular webpack
- angular fuse? probably next
- doxdox creates some documentation from jsdocs
- docdown is used by lodash
- typescript/flow types can generated here too from jsdocs, or from the js
be careful - module.exports & exports.name work perfectly well, test your dist files
- dist file testing in chain-able
- https://github.com/infernojs/inferno/issues/928
- https://github.com/infernojs/inferno/issues/903
- https://github.com/infernojs/inferno/issues/686
- https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8005
- https://github.com/Microsoft/TypeScript/issues/5565#issuecomment-155171298