Пакуйте чемоданы. Грузите апельсины.

Владимир Кузнецов, Graph

Пакуйте чемоданы. Грузите апельсины.

Владимир Кузнецов, Graph

Frontend Conf 2015, Москва

План

Модульный код

IIFE (Immediately invoked function expression)

		var FooBarModule = (function () {
		  return {
		    foo: function () { /* some useful code here */ },
		    bar: function () { /* more code here */ } 
		  };
		}());
	

Плагины jQuery

		jQuery.fn.foobar = function (options) {
		  return this.each(function () {
		    // plug-in code here
		  });
		};
	

Порядок подключения фрагментов кода

Раньше делали так:

		<script src="jquery.js"></script>
		<script src="plugin1.js"></script>
		<script src="plugin2.js"></script>
		<script src="plugin3.js"></script>
		<script src="init.js"></script>
	

Потом стали делать так:

		<script src="bundle.js"></script>
	
		$ cat jquery.js \
		  plugin1.js plugin2.js plugin3.js \
		  init.js \
		    > bundle.js
	

Проблема: определять порядок автоматически

Конфигурация зависимостей

Конфигурация зависимостей

			jquery
			backbone
			├─ jquery
			└─ underscore
			item-list
			└─ backbone
			search-widget
			└─ autocomplete
			   └─ jquery
		
			jquery
			autocomplete
			search-widget
			underscore
			backbone
			item-list
		

Форматы модулей

CommonJS

wiki.commonjs.org/wiki/Modules

  1. Функция require
  2. Объект exports
  3. Объект module

Пример CommonJS-модуля

		/* dictionary.js */
		exports.hello = 'Hello World!';
	
		/* app.js */
		var dictionary = require('./dictionary.js');
		console.log(dictionary.hello);
	

Особенности CommonJS-модуля

Формат разрабатывался исключительно для выполнения на сервере или внутри приложения.

AMD (Asynchronous Module Definition)

github.com/amdjs/amdjs-api

  1. Функция define
  2. Поле define.amd — объект

Пример AMD-модуля

		define('dictionary', {
		  hello: 'Hello World!';
		});
	
		define('app', ['dictionary'], function (dictionary) {
		  console.log(dictionary.hello);
		});
	

AMD используется как обёртка для CommonJS

		define(['require', 'exports', 'module'],
		    function (require, exports, module) {
		  var foo = require('foo'),
		  exports.bar = function () {};
		});
	

Особенности AMD-модуля

UMD (Universal Module Definition)

github.com/umdjs/umd

Универсальная обёртка, которая в зависимости от окружения экспортирует модуль как CommonJS или как AMD.

Модули ECMAScript 2015

		/* dictionary.js */
		export const hello = 'Hello World!';
	
		/* app.js */
		import * as dictionary from './dictionary.js';
		console.log(dictionary.hello);
	

Модули ECMAScript 2015

Модуль экспортирует функции, переменные или константы, перед объявлением которых указано ключевое слово export.

		export var foo = "Hello World!"
		export function bar() { return "foobar"; };
		export default function () { return "default"; };
	

Модули ECMAScript 2015

		import theDefault from './foobar.js';
		import {foo, bar} from './foobar.js';
		import {foo as fooVariable} from './foobar.js';
		import * as foobar from './foobar.js';
	

Инструменты

Инструменты

Browserify

Browserify

		$ browserify main.js > bundle.js
	

Трансформации

CoffeeScript

		$ browserify -t coffeeify main.coffee > bundle.js
	

ECMAScript 2015 и JSX

		$ browserify -t babelify main.jsx > bundle.js
	

Шаблоны Handlebars

		/* main.js */
		var template = require('./view.hbs');
		var html = template({title: "Hello World!"});
	
		$ browserify -t browserify-handlebars main.js > bundle.js
	

Статические файлы

		/* main.js */
		var fs = require('fs');
		var text = fs.readFileSync(
		  __dirname + '/helloworld.txt', 'utf8'
		);
	

Статические файлы

		$ browserify -t brfs main.js > bundle.js
	
		/* bundle.js */
		var text = "Hello World!";
	

Использование вместе с «не модульным» кодом

		"browser": {
		  "bootstrap": "./assets/js/bootstrap.js"
		},
		"browserify-shim": {
		  "bootstrap": {"depends": ["jquery"]}
		}
	

Использование вместе с «не модульным» кодом

		/* main.js */
		require('bootstrap');
	

В упаковку автоматически будут добавлены все модули, которые были перечислены в конфигурации для «bootstrap».

Использование вместе с «не модульным» кодом

		"browserify-shim": {
		  "jquery": {"exports": "global:$"}
		}
	

Если грузим jQuery из CDN, то упаковывать её не нужно.

Ручное управление сборкой

Интерактивные экраны в студии London Live:

Общие модули мы вынесли в отдельную упаковку.

Упаковка с общими модулями

		$ browserify \
		  -r jquery -r underscore -r backbone \
		  > libs.js
	

Упаковка для каждого приложения

		$ LIBS="-x jquery -x underscore -x backbone"
		$ browserify $LIBS editor.js > editor.bundle.js
		$ browserify $LIBS viewer.js > viewer.bundle.js
	

Watchify

		$ watchify main.js -o bundle.js
	

Нужно явно указать результирующий файл.

Browserify

browserify.org

Webpack

Webpack

Загрузчики

Нужны для конвертирования любых статических ресурсов в JavaScript.

		var template = require('jade!./template.jade');
		require('style!css!less!./my-widget.less');
	

Загрузчики

Конфигурация загрузчиков через webpack.config.js:

		{ "module":
		  { "loaders": [
		    { "test": /\.jade$/, "loader": "jade-loader" }
		  ]}
		}
	

Плагины

Несколько упаковок

Некоторые загрузчики и плагины могут создавать дополнительные файлы.

Например, extract-text-webpack-plugin может сохранить стили для модулей в отдельный CSS-файл, а не вставлять их на страницу.

«Code splitting»

		if (!window.Intl) {
		  var waitForChunk = require('bundle!./intl.js);
		  waitForChunk(callback);
		} else {
		  callback();
		}
	

Webpack

webpack.github.io

Где хранить модули?!

npm

npm

Bower

Bower

npm vs. Bower

  npm Bower
Пакеты CommonJS AMD
Зависимости дерево список
Модули разных версий да нет

Заключение

Спасибо за внимание

Владимир Кузнецов
Twitter: @mista_k
Блог: noteskeeper.ru

Слайды презентации: goo.gl/p61RpF