Pull to refresh

Пособие по webpack

Reading time 8 min
Views 347K
Original author: Shekhar Gulati


Давайте сначала разберемся, зачем нужен вебпак (webpack), и какие проблемы он пытается решить, а потом научимся работать с ним. Webpack позволяет избавиться от bower и gulp/grunt в приложении, и заменить их одним инструментом. Вместо bower'а для установки и управления клиентскими зависимостями, можно использовать стандартный Node Package Manager (npm) для установки и управления всеми фронтэнд-зависимостями. Вебпак также может выполнять большинство задач grunt/gulp'а.


Bower это пакетный менеджер для клиентской части. Его можно использовать для поиска, установки, удаления компонентов на JavaScript, HTML и CSS. GruntJS это JavaScript-утилита командной строки, помогающая разработчикам автоматизировать повторяющиеся задачи. Можно считать его JavaScript-альтернативой Make или Ant. Он занимается задачами вроде минификации, компиляции, юнит-тестирования, линтинга и пр.

Допустим, мы пишем простую страницу профиля пользователя в веб-приложении. Там используется jQuery и библиотеки underscore. Один из способов — включить оба файла в HTML:


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>User Profile</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" media="screen">
    <link rel="stylesheet" href="/css/style.css" media="screen">
  </head>
  <body>
    <div class="container">
      <div class="page-header">
        <h1 id="timeline"></h1>
      </div>
      <ul class="timeline">
      </ul>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
    <script src="js/profile.js"></script>
  </body>
</html>

Это простой HTML с Бутстрапом. Мы подключили jQuery и underscore с помощью тега script.


Давайте рассмотрим файл profile.js, который использует подключенные нами библиотеки. Наш код лежит внутри анонимного замыкания, которое хранит бизнес-логику приложения. Если не замыкать код в функцию, то переменные будут находиться в глобальном окружении, и это плохо.


(function(){
  var user = {
    name : "Shekhar Gulati",
    messages : [
      "hello",
      "bye",
      "good night"
    ]
  };

  $("#timeline").text(user.name+ " Timeline");

  _.each(user.messages, function(msg){
    var html = "<li><div class='timeline-heading'><h4 class='timeline-title'>"+msg+"</h4></div></li>";
    $(".timeline").append(html);
  });

}());

Код будет исполнен при вызове скрипта. Если открыть страницу в браузере, то профиль будет выглядеть так.



У этого кода две задачи:


  1. получить информацию о пользователе
  2. настроить таймлайн.

Известно, что смешивать понятия — плохая практика, так что нужно написать отдельные модули, отвечающие за определенные задачи. В файле profile.js мы использовали анонимное замыкание для хранения всего кода. В JavaScript есть способы делать модули получше. Два популярных способа это CommonJS и AMD.


  • Модуль CommonJS это грубо говоря кусок повторно используемого кода, который экспортирует определенные объекты, и они становятся доступными другим модулям с помощью require.
  • Asynchronous Module Definition (AMD) позволяет загружать модули асинхронно.

Если хотите узнать о модулях в JavaScript больше, то советую прочитать статью JavaScript Modules: A Beginner’s Guide.


А в этой статье мы будем писать модули на CommonJS. Давайте напишем модуль timeline с методами для установки хедера и таймлайна. В CommonJS можно импортировать зависимости с помощью функции require. Таймлайн зависит от jquery и underscore.


var $ = require('jquery');
var _ = require('underscore');

function timeline(user){
  this.setHeader = function(){
      $("#timeline").text(user.name+ " Timeline");
  }

  this.setTimeline = function(){
    _.each(user.messages, function(msg){
      var html = "<li><div class='timeline-heading'><h4 class='timeline-title'>"+msg+"</h4></div></li>";
      $(".timeline").append(html);
    });
  }
}

module.exports = timeline;

Этот код создает новый модуль timeline. Есть две функции: setHeader и setTimeline. Мы используем специальный объект module и добавляем ссылку на нее в module.exports. Таким образом мы сообщаем модульной системе CommonJS, что хотим позволить другим функциям использовать модуль.


Теперь обновим profile.js, он должен использовать модуль timeline. Можно создать новый модуль, который будет загружать информацию о пользователе, но пока давайте ограничимся одним модулем.


var timeline = require('./timeline.js');
var user = {
  name : "Shekhar Gulati",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);

Если загрузить index.html в браузере, то отобразится пустая страница. В консоли (в developer tools) можно обнаружить ошибку:


profile.js:1 Uncaught ReferenceError: require is not defined

Проблема в том, что браузеры не понимают модульную систему вроде CommonJS. Нужно предоставить браузеру формат, который он ожидает.


Бандлеры модулей идут на помощь


Веб-браузеры не понимают эти хорошо описанные модули. Нужно или добавить весь JavaScript-код в один файл и импортировать его, или нужно добавить все файлы вручную на страницу с помощью тега script. Используем бандлер модулей (module bundler) для решения этой проблемы. Бандлер модулей комбинируют разные модули и их зависимости в один файл в правильном порядке. Он может парсить код, написанный с использованием разных модульных систем, и комбинировать в один формат, понятный браузеру. Два популярных бандлера модулей это:


  1. browserify: пакует npm-модули, чтобы потом использовать их в браузере. В случае с browserify приходится дополнительно подключать Grunt или Gulp для линтинга, запуска тестов и пр. Это значит, что нужно тратить время на работу с несколькими инструментами и интеграцией.
  2. webpack: система сборки, которая предоставляет не только бандлинг (компоновку) модулей, но и может выполнять задачи, которыми занимаются Gulp/Grunt. К тому же, вебпак не ограничивается JavsScript-файлами, он может работать с другой статикой вроде CSS, картинок, html-компонентов и др. Вебпак также поддерживает очень полезную фичу — code splitting (разбиение кода). Большое приложение можно разбить на куски, которые загружаются по мере необходимости.

Что такое вебпак?


Официальное определение звучит так:


webpack берет модули с зависимостями и генерирует статические ресурсы, которые представляют эти модули

Это определение теперь имеет смысл, когда понятна решаемая проблема. Вебпак берет набор ресурсов и трансформирует их в наборы (бандлы).



Трансформацией ресурсов занимаются загрузчики, которые являются сердцем вебпака.


Вебпак в действии


Для установки вебпака нужен node. Можно скачать node с официального сайта.


Теперь можно установить вебпак глобально:


$ npm install -g webpack

Создайте новый модуль командой npm init. Она создаст файл package.json. Установите зависимости с помощью npm.


$ npm install -S jquery
$ npm install -S underscore

В дополнение, нужно установить вебпак как зависимость.


$ npm install -S webpack

Замените index.html следующим кодом. Как видите, мы удалили все теги script для jquery и underscore. Также, вместо импорта js/profile.js импортируется dist/bundle.js.


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>User Profile</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" media="screen">
    <link rel="stylesheet" href="/css/style.css" media="screen" title="no title">

  </head>
  <body>

    <div class="container">
      <div class="page-header">
        <h1 id="timeline"></h1>
      </div>
      <ul class="timeline">
      </ul>

    </div>

    <script src="dist/bundle.js" charset="utf-8"></script>
  </body>
</html>

Давайте создадим бандл.


$ webpack js/profile.js dist/bundle.js

Hash: 6d83c7db8ae0939be3d0
Version: webpack 1.13.2
Time: 350ms
    Asset    Size  Chunks             Chunk Names
bundle.js  329 kB       0  [emitted]  main
   [0] ./js/profile.js 252 bytes {0} [built]
   [1] ./js/timeline.js 427 bytes {0} [built]
    + 2 hidden modules

Теперь страница работает нормально.


Можно сделать так, чтобы вебпак следил за изменениями и генерировал бандл автоматически. Для этого нужно запустить его с таким флагом:


$ webpack -w js/profile.js dist/bundle.js

Теперь вебпак не будет завершаться сам. При изменении файлов будет генерироваться новый бандл. Нужно лишь перезагрузить страницу в браузере. Давайте изменим profile.js:


var user = {
  name : "Shekhar Gulati!!!",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

Файл bundle.js, сгенерированный вебпаком, содержит много кода, относящегося к самому вебпаку, а ваш код там будет в измененном виде. Будет очень неудобно отлаживать приложение в браузере, в инструментах разработчика, например. Чтобы упростить себе жизнь, можно запустить вебпак с флагом devtools.


$ webpack -w --devtool source-map js/profile.js dist/bundle.js

Вебпак сгенерирует source map для файла bundle.js. Source map связывает минимизированный и собранный в один файл код с исходным, несобранным кодом. Для тестирования можно добавить строчку debugger в profile.js


var timeline = require('./timeline.js');
var user = {
  name : "Shekhar Gulati",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

debugger;
var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);

Перезагрузите страницу, и приложение остановится на этой строке.



Добавление CSS


В HTML выше видно, что мы загружаем /css/style.css. Вебпак умеет работать не только с JavaScript, но и с другой статикой, в том числе CSS. Удалите строку с /css/style.css из index.html. Мы будем подключать стили в profile.js таким образом:


require('../css/style.css');

var timeline = require('./timeline.js');
var user = {
  name : "Shekhar Gulati",
  messages : [
    "hello",
    "bye",
    "good night"
  ]
};

var timelineModule = new timeline(user);
timelineModule.setHeader(user);
timelineModule.setTimeline(user);

вебпак перезагрузит изменения, и мы увидим сообщение об ошибке в консоли:


ERROR in ./css/style.css
Module parse failed: /Users/shekhargulati/dev/52-technologies-in-2016/36-webpack/code/css/style.css Unexpected token (1:0)
You may need an appropriate loader to handle this file type.

Проблема в том, что вебпак не понимает CSS по умолчанию. Нужно установить пару загрузчиков для этого. Вот команда для установки необходимых загрузчиков:


$ npm install style-loader css-loader --save-dev

Вебпак использует загрузчики для трансформации текста в нужный формат. Теперь нужно обновить require:


require('style!css!../css/style.css');

Синтаксис style!css! означает, что сначала нужно применить трансформацию css для конвертации текста из style.css в CSS, а потом применить стиль к странице с помощью трансформации style.


Запустите вебпак снова.


$ webpack -w --devtool source-map js/profile.js dist/bundle.js

Конфигурация


Чтобы не указывать все опции в командной строке, можно создать конфигурационный файл webpack.config.js в корне приложения:


module.exports = {
  context: __dirname,
  devtool: "source-map",
  entry: "./js/profile.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  }
}

Теперь можно запускать вебпак простой командой webpack -w.


Когда мы добавили style!css! в profile.js, мы смешали продакшен-код с конфигурацией вебпака. Можно перенести эту опцию в файл конфигурации.


После изменений конфигурации нужно перезапускать вебпак.


var webpack = require('webpack');

module.exports = {
  context: __dirname,
  devtool: "source-map",
  entry: "./js/profile.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },
  module:{
    loaders: [
      {test : /\.css$/, loader: 'style!css!'}
    ]
  }
}

Самая интересная секция тут это декларация модулей. Тут мы указали, что если файл заканчивается на .css, то нужно применять трансформацию style!css!.


Горячая перезагрузка


Для горячей перезагрузки (hot reloading) нужен webpack-dev-server. Установите его так:


$ npm install -g webpack-dev-server

Теперь можно запускать сервер командой webpack-dev-server.


Мы запустим сервер по адресу http://localhost:8080/webpack-dev-server/ с конфигурацией из webpack.config.js.


Порт можно изменить опцией --port.


$ webpack-dev-server --port 10000

 http://localhost:10000/webpack-dev-server

Конфигурацию webpack-dev-server также можно указать в файле webpack.config.js, в секции devServer.


module.exports = {
  context: __dirname,
  devtool: "source-map",
  entry: "./js/profile.js",
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },
  module:{
    loaders: [
      {test : /\.css$/, loader: 'style!css!'}
    ]
  },
  devServer: {
    inline:true,
    port: 10000
  },
}



На сегодня все. Узнать больше о вебпаке можно из документации.

Tags:
Hubs:
+40
Comments 13
Comments Comments 13

Articles