tangrams / tangram

WebGL map rendering engine for creative cartography
https://tangram.city
MIT License
2.22k stars 290 forks source link

Bundle as ES6 code by default, with ES5 transpiled bundle as fallback #677

Closed bcamper closed 6 years ago

bcamper commented 6 years ago

This PR is a breaking change, affecting the syntax for loading Tangram via <script> tags, and is intended for a v0.16.0 release.

ES6/ES2015+ and ES5 bundles

This PR changes the Tangram build process to produce two bundles for distribution:

Browsers have evolved since the early days of Tangram to natively support a much greater range of JS syntax and features. By skipping the transpiling process, we can produce a bundle that is both smaller (no extra transpiled code or unnecessary polyfills) and faster (less code for the browser to load over the network and parse). The minified ES6 bundle on this branch is 560k (157k gzipped), vs. 652k (180k gzipped) for the ES5 build; that's about 15% less code that users have to download and run.

We can use the module/nomodule pattern to automatically load the correct bundle for the browser, using this syntax:

<script type="module" src="dist/tangram.debug.js"></script> <!-- modern browsers load the ES6 bundle -->
<script nomodule src="dist/tangram.es5.debug.js"></script> <!-- older browsers (e.g. IE11) load the ES5 bundle -->

Browsers that support ES modules (type="module" syntax) will load the ES6 script and ignore the ES5 one. Those that don't will ignore the ES6 script, and load the ES5 one, marked with nomodule. Read more about this pattern here:

https://developers.google.com/web/fundamentals/primers/modules#browser https://philipwalton.com/articles/deploying-es2015-code-in-production-today/ https://jakearchibald.com/2017/es-modules-in-browsers/#nomodule-for-backwards-compatibility

Additionally, because scripts with the module type automatically load in "deferred" mode, users must also include the defer keyword for any scripts that depend on Tangram (so that they won't run until Tangram is finished loading). For example, if your app code is in index.js, load it like this (anywhere after the Tangram <script> tag):

<script defer src="index.js"></script>

This is also a breaking change, however, using defer to load scripts is considered a best practice.

Worker code

As a side effect of moving to ES6 code, the method in which Tangram's web workers are instantiated has also changed. Scripts loaded with type="module" always execute in strict mode. Tangram previously used a custom method of creating its web worker code (in build/quine.js), which used arguments.callee.toString() to access its own source code. However, the callee property isn't allowed in strict mode.

Instead, workers are now created using the Browserify webworkify plugin, which uses Browserify's API to resolve dependencies and generate worker code at run-time (while remaining strict-mode compliant). This has the advantage of removing bespoke code that was also brittle (and had been known to sometimes conflict with other build systems and bundlers). The downside is that worker threads instantiated with webworkify do not work with source maps, which can make debugging worker code more difficult; however, this is somewhat mitigated by the removal of transpiled/polyfilled code from the main bundle, which was one of the main barriers to readability when debugging. (Note that an earlier branch experimenting with a Webpack-based build did have run-time generated workers with proper source maps; Webpack can be reconsidered in the future, but was outside the scope of this PR).

Babel 7

This branch also upgrades to Babel 7, and the preset-env plugin that is now the best practice when using Babel. Together, these provide a succinct method of configuring our two builds (see babel.config.js). The two most notable features here are the esmodules: true syntax, to target browsers that support ES modules (for the ES6 build), and useBuiltIns: usage, which provides a new automatic polyfilling mode, which selectively adds polyfills to the build based on detecting which features are used in code (for the ES5 build). Automatic polyfilling simplifies Tangram, replacing handwritten code for importing a limited set of polyfills.

Terser

Finally, this branch switches from uglify for minification, to terser, an uglify fork that adds support for ES6 code. (Revisiting minification tooling may also make sense in the future, but for now, terser provides a drop-in replacement that is compatible with the rest of the new build pipeline.)