Открыть меню    

webpack

Преимущества webpack:

  1. Webpack поддерживает модули AMD прямо из коробки (иначе говоря берем AMD-модули (в стиле require) и начинаем использовать)
  2. webpack поддерживает и модули AMD (в стиле requireJS), и модули CommonJS (в стиде nodejs) одновременно.

2.1 Простая сборка - Простой конфиг

Устанавливаем глобально: npm i -g webpack

Создаем файл webpack.config.js. В этом файле в module.exports экспортируем объект конфига.

webpack.config.js:

jQuery or Javascript

module.exports = {
    entry: "./home",    // entry говорит о том,
                        //какой модуль собирать (в нашем случае это home.js)
    output: {
        filename: "build.js"
    } // output говорит о том, куда выводить
}

Запускаем сборку, в консоли для проекта:

webpack

Установим простейший сервер статики:

npm I –g node-static

Запускаем его в нужной директории:

static

По выданному адресу будет показано содержимое директории

2.2 Простая сборка - Внешний доступ к модулям

При webpack сборке глобальных переменных нет. Но что делать, если мы хотим, например, из какого-нибудь другого скрипта обратиться к функциональности нашего модуля. Например, мы хотим вызвать функцию не на момент загрузки, а потом.

  1. Во-первых сделаем exports (exports.welcome = welcome;) в файле home.js.
  2. В webpack.config добавим опцию library (library: "home"), exports модуля будет помещен в переменную home.

2.3 Простая сборка - Пересборка при изменениях

Опция watch: true в webpack.config следит за файлами и при их изменении перезапускает сборку. Например, EnvironmentPlugin будет передавать в итоговый код наши переменные.

2.4 Простая сборка - Source maps (Отладка)

Debugger; сработает непосредственно в сборке, что неудобно. Есть решение – source maps. При помощи параметра devtool (значения которого позволяют строить source map по разному, http://webpack.github.io/docs/configuration.html#devtool) можно указать, что нужна source map. Теперь вы можете отслеживать изменения (debugger) для каждого файла (модуля).

devtool: "source-map" // Хороший вариант на production

2.5 Простая сборка - Окружение, NODE_ENV

В конфиге установлено watch: true. Однако в реальной жизни необходимо запустить webpack минимум 2 раза: для разработки (например, с watch: true) и для "боя" (просто сборка).

Традиционный способ переключаться между development и production это использовать Переменные окружения.

Плагин это такая штука, которая может подключаться на разных этапах компиляции и что-то делать.

В nodejs традиционно используется NODE_ENV. Информация о сборке может находиться и внутри самих скриптов. То есть наши модули могут содержат свои отладочные средства, которые включаются только в development и помогают нам в отладке. Как этого добится:

  1. Использовать плагин (понадобится плагин, для этого локально (для проекта) установим webpack: npm i webpack, так как мы собираемся импортировать модули из webpack)
  2. const webpack = require('webpack');
  3. В webpack есть достаточно много плагинов (http://webpack.github.io/docs/list-of-plugins.html). Нам потребуется EnvironmentPlugin. Передаем те ключи окружения, которые хочется сделать доступными клиенту.
  4. Чтобы передавать клиенту непосредственно переменную NODE_ENV, а не свойство process.env, понадобится другой плагин: DefinePlugin

jQuery or Javascript

const NODE_ENV = process.env.NODE_ENV || 'development';
  plugins: [
    new webpack.DefinePlugin({
        NODE_ENV: JSON.stringify(NODE_ENV)
        // JSON.stringify – превращает объекты в строку в формате JSON
    }),
  ]

// ==

if(NODE_ENV == 'development') { …….
Преобразуется в:
if(true) {...

2.6 Babel.js

Мы настроим загрузчик Babel для всех файлов JavaScript, чтобы транскомпилировать код на ES6 в код старой версии JavaScript, который смогут понять нынешние браузеры. http://frontender.info/packing-the-web-like-a-boss/#nazadvbudushtee – пример webpack с babel-loader (package.json)

Общепринятый способ использовать кроссбраузерно современный js это babel. http://babeljs.io/ - получает современный js и превращает его в более старый/кроссбраузерный.

Для использования babel в нашем приложении воспользуемся концепцией loader’ов, которую предоставляет webpack.

jQuery or Javascript

   module: {
        loaders: [{
            test: /\.js$/,
            loader: 'babel'
        }]
    }

Здесь говорится, что к файлам, которые оканчиваются на js нужно применять babel-loader. Loader это модуль. Устанавливаем npm i babel-loader.

Попадаем в репозитория модуля: npm repo babel-loader.

Как настраивать loaders: http://webpack.github.io/docs/configuration.html#module-loaders/. Свойство test на соответствие расширений, свойство include для проверки путей (префиксы путей), exlude те регулярные выражения или префиксы путей к которым loader не применим, свойство loader – наименование loader.

a. Resolving

Resolve (модули вообще, например, “./home”) и resolveLoader (для loader’ов) это пара настроек, которые описывают как webpack ищет модули.

b. Минификация

Для этого будем использовать плагин, который есть в webpack – UglifyJsPlugin.

jQuery or Javascript

if (NODE_ENV == 'production') {  // добавляем плагин для минификации
    module.exports.plugins.push(
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false,
                drop_console: true,
                unsafe: true
            }
        })
    );
}

3.1 Несколько точек входа

На текущей момент home.js собирается в build.js и все в одной директории (в реальности все по другому). Обычно есть директория, которая содержит файлы для разработки – frontend; и директория, в которой собирается проект – public.

Создадим еще 1 точку входа. Зачастую на сайте существует несколько разделов, например, поиск, магазин, о нас и т.д. Поэтому нам не нужно, чтобы 1 js файл содержал весь функционал сайта. То есть для каждого раздела будет загружаться свой необходимый функционал.

Работаем с config:

jQuery or Javascript

entry: {
    home: "./frontend/home",
    about: "./frontend/about",
},

output: {
    path: __dirname + '/public', //абсолютный путь к директории сборки
    filename: "[name].js", // шаблон вместо [name] при сборке будут подставлены home.js и about.js
    library:  "[name]", // аналогично: глобальные переменные home н about
    //publicPath (см. ниже)
},

Все модули находятся в директории frontend. __dirname Чтобы не дублировать используем параметр context:

jQuery or Javascript

    context: __dirname + '/frontend',

Соответственно поиск будет идти относительно нее.

jQuery or Javascript

    entry: {
        home: "./home",
        about: ". /about",

Если некий файл является точкой входа, то require(nodejs) его ниоткуда нельзя.

3.2 NoErrorsPlugin

При сборке, даже с ошибками webpack выдает файлы. NoErrorsPlugin – то есть сборки не будет, если будут ошибки.

3.3 CommonChunkPlugin

Выделим из наших точек входа общую часть. Ведь есть код, в нашем случае это welcome, который нужен на всех страницах сайта. Для этого воспользуемся плагином:

jQuery or Javascript

webpack.optimize.CommonsChunkPlugin()
new webpack.optimize.CommonsChunkPlugin({
    name: "common"
})

Где name - имя нового кусочка сборки.

D:\ggl\webpack\project>webpack
Hash: d3cfa8b8c434526d27d9
Version: webpack 1.12.14
Time: 3790ms
    Asset       Size  Chunks             Chunk Name
about.js  713 bytes       0  [emitted]  about
home.js  727 bytes       1  [emitted]  home
common.js    10.2 kB       2  [emitted]  common

Далее при сборке появится 3 файла about.js, home.js, common.js. common.js содержит те модули, которые используются во всех точках входа. Соответственно, для всех точек входа нужно подключить общий код:

HTML

    <script src="common.js"></script>
    <script src="about.js"></script>

3.4 Информация о сборке

Чтобы посмотреть подробнее какой модуль куда пошел можно запустить:

webpack --display-modules

Asset       Size  Chunks             Chu
about.js  713 bytes       0  [emitted]  abo
home.js  727 bytes       1  [emitted]  hom
common.js    10.2 kB       2  [emitted]  com
   [0] ./about.js 97 bytes {0} [built]
   [0] ./home.js 96 bytes {1} [built]
   [1] ./welcome.js 463 bytes {2} [built]

About.js пошел в 0 ({0}) фрагмент сборки. home.js в {1}.

еще детальнее:

webpack --display-modules -v

С помощью красивой графической утилиты:

webpack --json --profile >stats.json

этим мы создадим файл stats.json. Далее заходим на http://webpack.github.io/analyse/ и подставляем stats.json.

3.5 Настройки CommonsChunkPlugin

http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin

  • name – имя
  • filename - имя файла, в который запишется общий код. По умолчанию:
    filename: “[name].js”
  • minChunks – по умолчанию CommonsChunkPlugin смотрит все точки входа и выносит то что используется везде. Если 5 точек входа, а код используется в 6-ти, то CommonsChunkPlugin его не вынесет. Но, Мы можем указать, например, minChunks: 2 и CommonsChunkPlugin вынесет те модули, который используется хотя бы в 2-х точках сборки.
  • chunks – можем указать явно из каких модулей стоит выносить:

    jQuery or Javascript

            new webpack.optimize.CommonsChunkPlugin({
                    name: "common",
                    chunks: ['about', 'home']
                })
  • common – общий код из модулей 'about' и 'home'. Мы можем использовать (вызывать) CommonsChunkPlugin сколько угодно раз.

И т.д.

3.6 Общий код в commons.js

Зачастую в общий файл (common.js) нужно добавить какие-то свои вызовы: например, подключение и инициализацию библиотек, иниц-ю формы входа на сайт, или что-то еще, то есть код, которой нужен везде. Сделаем это:

Создадим одноименный (name: "common" в CommonsChunkPlugin) файл common.js, добавим в него свой код, добавим его как точку входа. В него попадет и ваш код и общий код.

Можно явно указать какие модули следует добавить в общую сборку:

jQuery or Javascript

common:  ["./common", ".welcome"]
// примечательно, что экспортирован будет только последний модуль (welcome)
// webpack включит в общую сборку и "./common" и  ".welcome"

3.7 Мульти-компиляция

Мульти-компиляция – мы рассматривали ситуацию, когда есть несколько точек входа, а все остальные параметры одинаковые. Но бывают ситуации, когда другие параметры разные, например, много сборок для различных языков, для браузеров. Для того чтобы делать несколько сборок, в которых параметры различаются, используют Мульти-компиляцию – module.exports экспортирует не 1 объект, а массив объектов:

module.exports = [{ }, { }, { }]

Инструкция try-catch

jQuery or Javascript

try{
    // код, который может привести к ошибке
} catch (error) {
    // действия при возникновении ошибки
}

jQuery or Javascript

try{
    someFunction();
} catch (error) {
    console.log(error.message);
}

Если в разделе try происходит ошибка, выполнение кода прекращается и возобновляется в блоке catch, в который передается объект с информацией об ошибке.

Объект error зависит от браузера, но в нем как минимум присутствует свойство message (сообщение об ошибке), а также: свойство name (тип ошибки); number – внутренний номер шибки; и т.д.

4.1 Продвинутый require – Динамический require

Запускаем в папке public: static

У нас есть приложение и оно хочет подключить модуль login не сразу, а лишь при необходимости (например, при нажатии на кнопку loginButton).

Есть два способа, чтобы выделить модуль и подключить его при необходимости:

1 способ – функция webpack: require.ensure

1 аргумент – массив с модулями, webpack сгенерирует из них отдельный фрагмент сборки, и при запуске приложения, если выполнение пройдет в callback require.ensure, модули подгрузятся динамически и в callback мы сможем гарантировано подключить необходимые модули:

jQuery or Javascript

document.getElementById('loginButton').onclick = function() {
  // ======== Способ 1 (require.ensure) ==
  require.ensure([‘./login’], function(require) {
    let login = require('./login');
    login();
  }, 'auth');
};

publicPath – указывает, как из интернета получить наши файлы (интернет путь к нашей сборке). Webpack обязан его знать, так как скрипты подгружаются динамически.

Чтобы научить webpack делать запросы (для chunk loading и HMR, “Hot Module Replacement” https://webpack.github.io/docs/hot-module-replacement.html, HMR – особенность, вводить модули во время выполнения) для webpack-dev-server требуется обеспечить полный URL посредством output.publicPath.

2 способ – AMD синтаксис

jQuery or Javascript

require([‘./login’],  function(login){
    login();
});

AMD синтаксис немного уступает require.ensure.

В нашем случае, если вы кликните по кнопке #loginButton подгрузится скрипт 1.auth.js. В require.ensure в [] можно не прописывать модули, так как webpack анализирует содержимое webpack и все require, которые находятся в callback, автоматически помещаются в [].

5.1 Внешние библиотеки – CDN и сборка: externals

Рассмотрим подключение на примере библиотеки lodash. В приложении мы можем просто использовать глобальную переменную, которую создает библиотека. Для lodash это подчеркивание _.

Например, функция _pluck – создает из массива новый массив, содержащий определенной свойство.

Javascript

let users = [
  {id: "abcd", name: "Vasya"},
  {id: "defa", name: "Petya"},
  {id: "1234", name: "Masha"}
];

console.log( pluck(users, 'name') );

Для того, чтобы работал следующий require:

Javascript

let _ = require(‘lodash’);

Несмотря на то что библиотека подключается посредством cdn. Мы должны в webpack.config прописать объект externals, в котором ключами служат названия модулей, в нашем случае это lodash, а значением глобальная переменная это подчеркивание _.

Javascript

    externals: {
        lodash: "_"
    }

Иначе при сборке будет ошибка.

Следовательно, когда мы let _ = require(‘lodash’); вместо lodash будет глобальная переменная _.

5.2 Внешние библиотеки – Локально: ProvidePlugin

Рассмотрим более частую ситуацию: когда библиотека не в CDN, а органично включена в проект. В частности, когда она ставится как:

npm install lodash

Далее можно let _ = require('lodash'); и библиотека подключится. Однако сборка будет большого размера, почти 0.5Мб.

Поэтому подключим только нужную функцию:

jQuery or Javascript

//let _ = require('lodash/compact');
let compact = require('lodash/compact');
let users = [
  {id: "abcd", name: "Vasya"},
  {id: "defa", name: "Petya"},
  {id: "1234", name: "Masha"},
  false
];
console.log( compact(users) );

А теперь укоротим наш код при помощи ProvidePlugin. Многие библиотеки экспортируют глобальные переменные, которые нужны почти везде (_, $, angular, etc). Если мы не хотим в начале каждого модуля прописывать let compact = require('lodash/compact'); то делаем следующее в webpack.config.js:

В plugins указываем объект new webpack.ProvidePlugin({ …. }), в котором ключами будут переменные, например, compact, а значением будут модули, например, 'lodash/compact'.

Если webpack видит переменную (compact), которая объявлена, но не используется, тогда ProvidePlugin подключает соответствующий модуль('lodash/compact'). Можно подключить и весь lodash:

jQuery or Javascript

  plugins: [
    new webpack.ProvidePlugin({
        compact: 'lodash/compact',
        _: 'lodash'
    })
  ]

Предпочтительнее использовать require, но тем не менее для некоторых глобальных библиотек, которые используются везде ProvidePlugin может быть хорошей опцией.

5.3 Внешние библиотеки – Оптимизация: noParse, include

При подключении сторонних библиотек у сборки могут возникнуть проблемы с производительностью.

При использовании angular и babel время сборки может составить:

Time: 3980ms

Информация о сборке

Команда

webpack --profile --display-modules

Покажет все модули сборки и сколько времени webpack потратил на каждый.

Более детальная информация о подключении:

webpack --profile --display-modules --display-reasons

Тормозы могут происходить из-за babel, так как библиотека angular не нуждается в обработке babel.

Исправим это:

jQuery or Javascript

loaders: [{
    test: /\.js$/,
    exclude: /\/node_modules\//,
    loader: ‘babel’
}]

Теперь все файлы за исключением (exclude) папки node_modules будут обрабатываться babel.

Директива noParse говорит о том, что библиотеку не надо анализировать (в ней нет require и т.д.) и это убыстрит время сборки. В качестве значения может быть массим из рег. Выражений, строка, рег. Выражение и т.д.

Например:

noParse: /angular\/angular.js/

5.4 Внешние библиотеки – Старые скрипты: пути и imports/exports

Иногда возникают ситуации, когда есть старый скрипт и нам нужно внести его в сборку.

Используем resolve

Сокращаем запись пути по alias к файлу vendor/old/dist/old.js:

В webpack.config.js:

jQuery or Javascript

  resolve: {
      root: __dirname + '/vendor',
      alias: {
          old: 'old/dist/old'
      }
  }

Использование:

var old = require('old');

В old.js лежит глобальный функция Work:

jQuery or Javascript

function Work() {
    setTimeout(function() {
        alert("work complete!");
    }, workSettings.delay);
}

Чтобы работать с такими скриптами у webpack есть: exports-loader и imports-loader

imports-loader – позволяет импортировать переменную внутрь файла.

exports-loader – позволяет экспортировать из файла одну или несколько переменных.

Синтаксис:

jQuery or Javascript

  module: {
    loaders: [{
      test: /old.js$/,
      loader: "imports?workSettings=>{delay:500}!exports?Work"
    }]
  },

Импортируем переменную workSettings

5.5 expose и script-loader

Expose-loader позволяет указать переменную и webpack сделает так, чтобы export этого модуля (./file.js) попал в глобальную переменную с таким названием (XMODULE):

require(“expose?XMODULE!./file.js”)

script-loader – позволяет подключить скрипт в глобальном контексте. Лучше не использовать.

6.1 Стили и файлы

Style-loader добавляет некоторый css в DOM через тег style.

Интеграция в нашу сборку шаблонов, стилей, картинок и других ресурсов. В нашем случае компонентный подход реализован для меню. Все что касается меню находится в папке menu. Задача webpack собрать main.js, подтянуть все что необходимо для меню, скопировать в папку public.

Импортируем css:

import './menu.css';

С точки зрения webpack все является модулями. для файла css webpack ищет Loader, при отсутствии оного webpack будет пытаться разобрать css как js-файл и возникнет ошибка.

jQuery or Javascript

{
    test: /\.css$/,
    loader: 'style!css!autoprefixer?browsers=last 2 versions'
}

Здесь подключаются 3 loader: style-loader, css-loader, autoprefixer-loader.

6.2 Стили и файлы - CSS и file-loader

css-loader играет важную в роль: в css могут быть и import и url, то есть css-loader все import и url заменят на require.

Для статически файлов используется file-loader:

jQuery or Javascript

{
    test: /\.(png|jpg|svg|ttf|eot|woff|woff2)$/,
    loader: 'file?name=[path][name].[ext]'
}

file-loader копирует статичные файлы в public (смотри конфиг) и возвращает путь к нему. Как и куда копируется определяется параметром name.

В node.js если пути не указываются, то подразумевается node_modules. Чтобы брать стили из модуля (то есть в папке node_modules, там где лежит модуль) можно подключить стили так: @import(“~flag-icon-css/css/flag-icon.css”); используется тильда.

.......................

По материалам курса Ильи Кантора webpack

Комментарии к статье

аватарка пользователя
2016-12-18
Alex

Пиши хотя бы источник, что взял курсы от Кантора Ильи.

аватарка пользователя
2016-12-18
dnz

alex, глаза разуй неуч

аватарка пользователя
2017-03-16
Алексей

Вопрос по пункту 6.2. Типичная ситуация: нужно в модуль подтянуть одну из картинок, находящуюся в папке проекта, путь из модуля ('../images/'+imgName+'.png'). Я установил file-loader, прописал лоадер в конфигурации как сказано выше, но почему-то выдает GET http://localhost:3000/images/10n.png 404 (Not Found) - путь к картинке отображается некорректно. Как правильно подключать?

Добавить комментарий