Module Bundling
Agenda
1. Introduction
2. Webpack
3. jspm
Simon Bächler
Web developer at Feinheit
The Problem
• Web sites are turning into Web apps
• Code complexity grows as the site gets bigger
• Developers prefer discrete JS files/modules
• Deployment requires optimized code in just one
or a few HTTP calls
• No standardized import functionality (until now)
<!--[if lt IE 10]>
<script type="text/javascript" src="/static/libs/matchMedia/matchMedia.js"></script>
<script type="text/javascript" src="/static/libs/matchMedia/matchMedia.addListener.js"></script>
<script type="text/javascript" src="/static/libs/1579671/rAF.js"></script>
<![endif]-->
<!-- vendor scripts -->
<script type="text/javascript" src="/static/libs/handlebars/handlebars.min.js"></script>
<script type="text/javascript" src="/static/libs/underscore/underscore.js"></script>
<script type="text/javascript" src="/static/libs/backbone/backbone.js"></script>
<script type="text/javascript" src="/static/libs/backbone-pageable/lib/backbone-pageable.min.js"></script>
<script type="text/javascript" src="/static/libs/jquery-hoverIntent/jquery.hoverIntent.js"></script>
<script type="text/javascript" src="/static/libs/enquire/dist/enquire.min.js"></script>
<script type="text/javascript" src="/static/libs/iCheck/icheck.js"></script>
<script type="text/javascript" src="/static/libs/magnific-popup/dist/jquery.magnific-popup.min.js"></script>
<script type="text/javascript" src="/static/libs/jquery.transit/jquery.transit.js"></script>
<script type="text/javascript" src="/static/libs/jquery.scrollTo/jquery.scrollTo.min.js"></script>
<script type="text/javascript" src="/static/libs/socialcount/dist/socialcount.js"></script>
<script type="text/javascript" src="/static/libs/jQuery-Collapse/src/jquery.collapse.js"></script>
<script type="text/javascript" src="/static/libs/easydropdown/src/jquery.easydropdown.js"></script>
<script type="text/javascript" src="/static/libs/iosslider/_src/jquery.iosslider.min.js"></script>
<script type="text/javascript" src="/static/libs/fastclick/lib/fastclick.js"></script>
<script type="text/javascript" src="/static/libs/moment/min/moment.min.js"></script>
<script type="text/javascript" src="/static/libs/loglevel/dist/loglevel.min.js"></script>
<script type="text/javascript" src="/static/libs/raven-js/dist/raven.min.js"></script>
<!-- end vendor scripts -->
<!-- customized libraries -->
<script type="text/javascript" src="/static/webapp/js/backbone.treemodel.js"></script>
<script type="text/javascript" src="/static/webapp/js/backbone.subset.js"></script>
<script type="text/javascript" src="/static/webapp/js/jquery-ui-1.11.1.custom.min.js"></script>
<script type="text/javascript" src="/static/webapp/js/jquery.ui.datepicker-de-en.js"></script>
<script type="text/javascript" src="/static/webapp/js/moment-locales.js"></script>
<!-- app scripts -->
<script type="text/javascript" src="/static/webapp/js/raven.config.js"></script>
<script type="text/javascript" src="/static/webapp/js/global.js"></script>
<script type="text/javascript" src="/static/webapp/js/footer.js"></script>
<script type="text/javascript" src="/static/webapp/js/breadcrumbs.js"></script>
<script type="text/javascript" src="/static/webapp/js/filters/models.js"></script>
<script type="text/javascript" src="/static/webapp/js/filters/helpers/views.js"></script>
<script type="text/javascript" src="/static/webapp/js/filters/views/singleviews.js"></script>
<script type="text/javascript" src="/static/webapp/js/filters/views/listviews.js"></script>
<script type="text/javascript" src="/static/webapp/js/filters/collections.js"></script>
<script type="text/javascript" src="/static/webapp/js/filters/controller.js"></script>
<script type="text/javascript" src="/static/webapp/js/video.js"></script>
<script type="text/javascript" src="/static/webapp/js/slider.js"></script>
<script type="text/javascript" src="/static/webapp/js/forms.js"></script>
<script type="text/javascript" src="/static/webapp/js/analytics.js"></script>
<script type="text/javascript" src="/static/webapp/js/socialcount.js"></script>
<script type="text/javascript" src="/static/webapp/js/animations.js"></script>
<script type="text/javascript" src="/static/webapp/js/nav/models.js"></script>
<script type="text/javascript" src="/static/webapp/js/nav/views.js"></script>
<script type="text/javascript" src="/static/webapp/js/nav/controller.js"></script>
Javascript Modularization
2010 (Crockford)
var MYAPP = window.MYAPP || {}
MYAPP.Views = {
listView: new ListView(),
detailView: new DetailView()
};
Javascript Modularization
2010 (Crockford)
(function($, MYAPP) {
var app = new MYAPP.App();
app.init($('#container'));
})(jQuery, MYAPP);
True App Modularization
• Code is split up into different modules.
• Dependency graph
• The loading order is irrelevant.
• Code gets executed if all dependencies are
loaded.
• Dependencies can be mocked.
App delivery
• Requests are expensive.
• Deliver as few files as possible.
• Deliver the «above the fold» data first.
• Deliver only code that is needed by the client.
Smart Modularization
common-chunks.js
current-app.js
special-feature1.js special-feature2.js
Assets Versioning
• Adds its hash value to the file name
• Allows for setting long expiration headers
• Requires the HTML to update as well.
• Supported by most popular backends
/static/dist/main-ebf5ec7176585199eb0a.js
Modularization today
CommonJS
var $ = require('jquery');
module.exports = function () {};
Modularization today
AMD
define(['jquery'] , function ($) {
return function () {};
});
Modularization today
ECMA Script 2015
import $ from 'jquery';
function myExample(){};
export default myExample;
Java Script Module Bundlers
• RequireJS (AMD/CommonJS*)
• Browserify (CommonJS/AMD*/ES6*)
• Webpack (CommonJS/AMD/ES6*)
• JSPM / SystemJS (ES6/AMD/CommonJS)
Webpack supports all three
module systems
Configuration based
module.exports = {
context: path.join(__dirname, 'assets'),
entry: {
main: [ 'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./js/main'],
styles: ['./scss/app.scss', './scss/select.less'],
editor: './scss/editor.scss'
},
output: {
path: path.resolve('./assets/build/'),
publicPath: 'http://localhost:3000/assets/build/',
filename: '[name]-[hash].js'
},
devtool: 'source-map',
…
}
loaders: [
{
test: /.jsx?$/,
exclude: /node_modules/,
loaders: ['react-hot', 'babel']},
{
test: /.scss$/,
loader: ExtractTextPlugin.extract(
"css?sourceMap!postcss-loader!sass?" +
"outputStyle=expanded&sourceMap=true")))
},
{
test: /.less/,
loader: ExtractTextPlugin.extract(
"style-loader", "css-loader!less-loader?relative-urls")
},
{
test: /.(png|woff|svg|eot|ttf)$/,
loader: "url-loader?limit=20000"
}
]
Async loading
var films =
document.getElementById('film-gallery');
if(films !== null) {
require.ensure(['lodash'], function(require){
require('./components/FilmApp');
});
}
Hot Module Replacement
DEMO
CSS Live Reload
• No Live Reload with ExtractTextPlugin
• Source maps only with ExtractTextPlugin
(because of WebInspector)
npm is sufficient
npm is the default package manager
and task runner
Production configuration
• Import original configuration
• Overwrite config attributes
• Set NODE_ENV to 'production'
Documentation
jspm.io
jspm / SystemJS
• Package manager (jspm) and module loader
(SystemJS)
• For development: load modules synchronously
• For production: create a bundle
• Support for SPDY (via its own CDN) and possibly
HTTP2
jspm / SystemJS
• In active development
• Uses npm and Github as registries
• A CSS loading plugin is available
• Development != Production environment
DEMO
END OF PART II

Javascript Bundling and modularization

  • 1.
  • 2.
  • 3.
  • 4.
    The Problem • Websites are turning into Web apps • Code complexity grows as the site gets bigger • Developers prefer discrete JS files/modules • Deployment requires optimized code in just one or a few HTTP calls • No standardized import functionality (until now)
  • 5.
    <!--[if lt IE10]> <script type="text/javascript" src="/static/libs/matchMedia/matchMedia.js"></script> <script type="text/javascript" src="/static/libs/matchMedia/matchMedia.addListener.js"></script> <script type="text/javascript" src="/static/libs/1579671/rAF.js"></script> <![endif]--> <!-- vendor scripts --> <script type="text/javascript" src="/static/libs/handlebars/handlebars.min.js"></script> <script type="text/javascript" src="/static/libs/underscore/underscore.js"></script> <script type="text/javascript" src="/static/libs/backbone/backbone.js"></script> <script type="text/javascript" src="/static/libs/backbone-pageable/lib/backbone-pageable.min.js"></script> <script type="text/javascript" src="/static/libs/jquery-hoverIntent/jquery.hoverIntent.js"></script> <script type="text/javascript" src="/static/libs/enquire/dist/enquire.min.js"></script> <script type="text/javascript" src="/static/libs/iCheck/icheck.js"></script> <script type="text/javascript" src="/static/libs/magnific-popup/dist/jquery.magnific-popup.min.js"></script> <script type="text/javascript" src="/static/libs/jquery.transit/jquery.transit.js"></script> <script type="text/javascript" src="/static/libs/jquery.scrollTo/jquery.scrollTo.min.js"></script> <script type="text/javascript" src="/static/libs/socialcount/dist/socialcount.js"></script> <script type="text/javascript" src="/static/libs/jQuery-Collapse/src/jquery.collapse.js"></script> <script type="text/javascript" src="/static/libs/easydropdown/src/jquery.easydropdown.js"></script> <script type="text/javascript" src="/static/libs/iosslider/_src/jquery.iosslider.min.js"></script> <script type="text/javascript" src="/static/libs/fastclick/lib/fastclick.js"></script> <script type="text/javascript" src="/static/libs/moment/min/moment.min.js"></script> <script type="text/javascript" src="/static/libs/loglevel/dist/loglevel.min.js"></script> <script type="text/javascript" src="/static/libs/raven-js/dist/raven.min.js"></script> <!-- end vendor scripts --> <!-- customized libraries --> <script type="text/javascript" src="/static/webapp/js/backbone.treemodel.js"></script> <script type="text/javascript" src="/static/webapp/js/backbone.subset.js"></script> <script type="text/javascript" src="/static/webapp/js/jquery-ui-1.11.1.custom.min.js"></script> <script type="text/javascript" src="/static/webapp/js/jquery.ui.datepicker-de-en.js"></script> <script type="text/javascript" src="/static/webapp/js/moment-locales.js"></script> <!-- app scripts --> <script type="text/javascript" src="/static/webapp/js/raven.config.js"></script> <script type="text/javascript" src="/static/webapp/js/global.js"></script> <script type="text/javascript" src="/static/webapp/js/footer.js"></script> <script type="text/javascript" src="/static/webapp/js/breadcrumbs.js"></script> <script type="text/javascript" src="/static/webapp/js/filters/models.js"></script> <script type="text/javascript" src="/static/webapp/js/filters/helpers/views.js"></script> <script type="text/javascript" src="/static/webapp/js/filters/views/singleviews.js"></script> <script type="text/javascript" src="/static/webapp/js/filters/views/listviews.js"></script> <script type="text/javascript" src="/static/webapp/js/filters/collections.js"></script> <script type="text/javascript" src="/static/webapp/js/filters/controller.js"></script> <script type="text/javascript" src="/static/webapp/js/video.js"></script> <script type="text/javascript" src="/static/webapp/js/slider.js"></script> <script type="text/javascript" src="/static/webapp/js/forms.js"></script> <script type="text/javascript" src="/static/webapp/js/analytics.js"></script> <script type="text/javascript" src="/static/webapp/js/socialcount.js"></script> <script type="text/javascript" src="/static/webapp/js/animations.js"></script> <script type="text/javascript" src="/static/webapp/js/nav/models.js"></script> <script type="text/javascript" src="/static/webapp/js/nav/views.js"></script> <script type="text/javascript" src="/static/webapp/js/nav/controller.js"></script>
  • 6.
    Javascript Modularization 2010 (Crockford) varMYAPP = window.MYAPP || {} MYAPP.Views = { listView: new ListView(), detailView: new DetailView() };
  • 7.
    Javascript Modularization 2010 (Crockford) (function($,MYAPP) { var app = new MYAPP.App(); app.init($('#container')); })(jQuery, MYAPP);
  • 8.
    True App Modularization •Code is split up into different modules. • Dependency graph • The loading order is irrelevant. • Code gets executed if all dependencies are loaded. • Dependencies can be mocked.
  • 9.
    App delivery • Requestsare expensive. • Deliver as few files as possible. • Deliver the «above the fold» data first. • Deliver only code that is needed by the client.
  • 10.
  • 11.
    Assets Versioning • Addsits hash value to the file name • Allows for setting long expiration headers • Requires the HTML to update as well. • Supported by most popular backends /static/dist/main-ebf5ec7176585199eb0a.js
  • 12.
    Modularization today CommonJS var $= require('jquery'); module.exports = function () {};
  • 13.
    Modularization today AMD define(['jquery'] ,function ($) { return function () {}; });
  • 14.
    Modularization today ECMA Script2015 import $ from 'jquery'; function myExample(){}; export default myExample;
  • 15.
    Java Script ModuleBundlers • RequireJS (AMD/CommonJS*) • Browserify (CommonJS/AMD*/ES6*) • Webpack (CommonJS/AMD/ES6*) • JSPM / SystemJS (ES6/AMD/CommonJS)
  • 18.
    Webpack supports allthree module systems
  • 19.
    Configuration based module.exports ={ context: path.join(__dirname, 'assets'), entry: { main: [ 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server', './js/main'], styles: ['./scss/app.scss', './scss/select.less'], editor: './scss/editor.scss' }, output: { path: path.resolve('./assets/build/'), publicPath: 'http://localhost:3000/assets/build/', filename: '[name]-[hash].js' }, devtool: 'source-map', … }
  • 20.
    loaders: [ { test: /.jsx?$/, exclude:/node_modules/, loaders: ['react-hot', 'babel']}, { test: /.scss$/, loader: ExtractTextPlugin.extract( "css?sourceMap!postcss-loader!sass?" + "outputStyle=expanded&sourceMap=true"))) }, { test: /.less/, loader: ExtractTextPlugin.extract( "style-loader", "css-loader!less-loader?relative-urls") }, { test: /.(png|woff|svg|eot|ttf)$/, loader: "url-loader?limit=20000" } ]
  • 21.
    Async loading var films= document.getElementById('film-gallery'); if(films !== null) { require.ensure(['lodash'], function(require){ require('./components/FilmApp'); }); }
  • 22.
  • 23.
    CSS Live Reload •No Live Reload with ExtractTextPlugin • Source maps only with ExtractTextPlugin (because of WebInspector)
  • 24.
    npm is sufficient npmis the default package manager and task runner
  • 25.
    Production configuration • Importoriginal configuration • Overwrite config attributes • Set NODE_ENV to 'production'
  • 26.
  • 27.
  • 28.
    jspm / SystemJS •Package manager (jspm) and module loader (SystemJS) • For development: load modules synchronously • For production: create a bundle • Support for SPDY (via its own CDN) and possibly HTTP2
  • 29.
    jspm / SystemJS •In active development • Uses npm and Github as registries • A CSS loading plugin is available • Development != Production environment
  • 30.
  • 31.

Editor's Notes

  • #3 This talk has two parts Part 2 is much shorter.
  • #4 This is an actual portrait of me, drawn by the very talented Tika.
  • #5 From Require.js
  • #6 Real Website example: 45 Scripts Server side Bundler No explicit dependencies in the scripts. Bundler does not value dependencies. Order of the scripts is important
  • #7 Global Object, provides a namespace. Everything goes into that object.
  • #8 Closure Does not pollute global namespace. Dependency on global objects. Unit testing is difficult
  • #9 Modules define their dependencies. Module loaders build a dependency graph and package the modules accordingly. Dependency Injection.
  • #10 Modularization competes with delivery over HTTP (1). Think about how you package your assets.
  • #11 The app should deliver several static files: A common-chunks.js file with all the code that is shared over multiple pages. So it can be cached by the browser. A Js file with code specific for the current app. Code for sections wich are rarely used or needs a big library should be split out into a separate module which is loaded asynchronously on-demand.
  • #12  e.g. Webpack exports a JSON with the asset targets and file names. Django has a library called django-webpack-loader that reads that JSON and adds the data into the HTML template. It also waits for Webpack to complete building if necessary.
  • #13 CommonJS: Introduced with Node.js. Designed for synchronous loading. (Browserify) Problem: It doesn’t work in the browser.
  • #14 Asynchronous Module Definition (AMD): Designed for asynchronous loading. Runs a callback when ready. A bit like $(function(){}); Require.js, Angular.js UMD: Universal Module Definition (for libraries)
  • #15 CommonJS: Designed for synchronous loading. (Browserify) Asynchronous Module Definition (AMD): Designed for asynchronous loading. Require.js, Angular.js UMD: Universal Module Definition (for libraries)
  • #16 Currently 3 popular bundlers. JSPM is very new. With plugins
  • #17 Survey in August 2015. Half the people are using a module bundler. Ashley Nolan http://ashleynolan.co.uk/blog/frontend-tooling-survey-2015-results
  • #18 By Tobias Koppers. This is a free-time project. MIT license
  • #19 Compared to Browserify it can split the code into multiple modules. Component structure: CSS and Image are also part of a component and can therefore be included. CSS load order is not guaranteed if CSS is embedded in the Javascript files. You can extract the CSS into its own file.
  • #20 The configuration is a Javascript object. Because it is a CommonJS module, configurations can be extended by other modules. E.g. a production config can just import the default configuration and overwrite the necessary properties.
  • #21 Assets are loaded using a loader. The test property is a regex that is applied to all the files in a directory. Loaders can be chained and passed attributes. The first loader tests for js and jsx files and uses babel to parse ES6 code. SCSS and LESS loaders can work in the same project. (3rd party library used less). URL loader copies the files if they are less than 20kB. Otherwise it embeds them. Copied files can be given a hash-name.
  • #22 Automatically creates separate bundles for parts of the app that are only needed on demand. Here the FilmApp is a large React application that is only loaded if an element with the ID 'film-gallery' is present in the DOM.
  • #23 Webpack Dev server stores files in memory.
  • #24 You have to chose between live reload and source maps. Web inspector only shows source maps for css if the source file is a CSS file.
  • #25 Bower can be used, but it is not necessary. No Gulp required. (Gulp can be used but makes the builds slower.) NPM is the default package manager (and task runner)
  • #26 Have a separate config file for production builds. Replace destination paths, add ugilfyJS NODE_ENV environment variable is used by React to remove debugging statements.
  • #28 jspm is a package manager for the SystemJS universal module loader, built on top of the dynamic ES6 module loader SystemJS is a Polyfill for the System specification
  • #29 There is no Javascript watch and compiling tasks in development.
  • #30 jspm Version 0.16.0 was released on 18. August. jspm Version 0.16.6 was released on 19. September. 7 Releases in one Month. SystemJS: 6 releases in the last two weeks. Sass loader plugin is still in alpha stage.
  • #32 Any questions so far?