Управление зависимостями в Webpackперевод

Концепция модульности неотъемлемая часть большинства современных языков программирования. JavaScript, правда, не имел нормальной реализации этого подхода до тех пор, пока не появилась спецификация ECMAScript ES6.

В Node.js, одна из самых популярных на сегодняшний день платформ, менеджеры пакетов позволяют подгружать NPM модули в браузер, и компонетно-ориентированные библиотеки поддерживают модульность кода в JavaScript.

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

Webpack

Тем не менее, настройка Webpack и зависимостей может быть очень нервным и не всегда простым занятием, особенно для новичков.

В этой статье вы найдёте указания с примерами, как настраивать Webpack для разных сценариев, а также подводные камни относящиеся к сборке зависимостей.

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

Настройка алиасов и путей

Относительные пути напрямую не имеют отношения к зависимостям, но они используются когда мы определяем зависимости. Если структура файла проекта сложна, то может быть сложно определить точные пути до модулей. Одним из преимуществ Webpack является упрощение определения относительных путей в проекте.

Допустим у нас имеется следующая структура проекта:

- Project
    - node_modules
    - bower_modules
    - src
        - script
        - components	
            - Modal.js
            - Navigation.js
        - containers
            - Home.js
            - Admin.js

Мы можем ссылаться на зависимости посредством относительных путей к файлам, и, если мы хотим импортировать модуль в наш код, то выглядит это следующим образом:

Home.js

Import Modal from ‘../components/Modal’;
Import Navigation from ‘../components/Navigation’;

Modal.js

import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';

Каждый раз, когда мы хотим импортировать скрипт или модуль, мы должны знать путь до текущей папки и найти относительные путь до того, что надо импортировать. Можете представить как может все это усложниться, если у нас большой проект с многоуровневой структурой.

Но эту проблемы можно решить с помощью опции resolve.alias Webpack-а. Мы можем объявить так называемые алиасы папки или модуля с их расположением и нам не придется работать с относительными путями.

webpack.config.js

resolve: {
    alias: {
        'node_modules': path.join(__dirname, 'node_modules'),
        'bower_modules': path.join(__dirname, 'bower_modules'),
    }
}

In the Modal.js file, we can now import datepicker much simpler:

import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';

Разделение кода

У нас могут быть такие задачи, когда есть необходимость добавить скрипт в финальный модуль, или разделить модуль, или мы хотим загрузить отдельный модуль по необходимости. Настройка нашего проекта и Webpack для решения этой задачи может быть достаточно сложной.

В конфигурации Webpack, “entry” указывает где начальная точка сборки. Параметр может быть трёх типов: string, array и object. Если у нас одна точка входа, то можно использовать любой из этих типов, результат будет одним.

Если мы хотим работать с несколькими файлами и они не зависят друг от друга, entry должен быть типом Array. Например, можно добавить скрипт analytics.js в конец bundle.js:

webpack.config.js

module.exports = {
    // создаёт модуль из index.js и analytics.js
    entry: ['./src/script/index.jsx', './src/script/analytics.js'],
    output: {
        path: './build',
        filename: bundle.js '  
   }
};

Управление несколькими точками входа

Допустим у нас есть многостраничное приложение состоящее из нескольких HTML файлов, таких как index.html и admin.html. Мы можем создать два модуля описывая входную точку как объект. Пример, который генерирует два JavaScript модуля:

webpack.config.js

module.exports = {
   entry: {
       index: './src/script/index.jsx',
       admin: './src/script/admin.jsx'
   },
   output: {
       path: './build',
       filename: '[name].js' 
   }
};

index.html

admin.html

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

webpack.config.js

var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
   entry: {
       index: './src/script/index.jsx',
       admin: './src/script/admin.jsx'
   },
   output: {
       path: './build',
       filename: '[name].js' 
   },
   plugins: [commonsPlugin]
};

Главное не забыть добавить до всех остальных модулей.

Ленивая загрузка

Webpack может разделить статику на мелкие цепочки, что будет более гибким подходом, чем обычная конкатенация. Если у нас большое одностраничное приложение, обычная конкатенация в один модуль не самый лучший вариант, т.к. загрузка огромного файла может быть медленной, что определённо расстроит пользователей.

Одностраничные приложения должны подгружать только то, что нужно для отрисовки текущего состояния. Роутер на стороне клиента в архитектуре SPA отличное место для работы с разделённым кодом. Когда пользователь переходит по тому или иному пути, приложение подгружает только нужные зависимости.

Для этих целей можно использовать функции require.ensure или System.import, которые Webpack определяет статически.
В следующем примере у нас два React контейнера. Админка и dashboard.

admin.jsx

import React, {Component} from 'react';

export default class Admin extends Component {
   render() {
       return
Admin < /div>; } }

dashboard.jsx

import React, {Component} from 'react';
 
export default class Dashboard extends Component {
   render() {
       return
Dashboard < /div>; } }

Когда пользователь переходит по адресу /dashboard или /admin, загружаются только нужные скрипты. Далее пример с клиентским роутером и без.

index.jsx

if (window.location.pathname === '/dashboard') {
   require.ensure([], function() {
       require('./containers/dashboard').default;
   });
} else if (window.location.pathname === '/admin') {
   require.ensure([], function() {
       require('./containers/admin').default;
   });
}

index.jsx

ReactDOM.render(
{props.children}
}>
           
            {
               require.ensure([], function (require) {
                   cb(null, require('./containers/dashboard').default)
               }, "dashboard")}}
           />
            {
               require.ensure([], function (require) {
                   cb(null, require('./containers/admin').default)
               }, "admin")}}
           />
       
   
   , document.getElementById('content')
);

Разделение стилей

В Webpack загрузчики, такие как style-loader и css-loader, производят предобработку стилей и добавляют их в JavaScript модуль. Но в некоторых случаях, они могут привести к появлению неоформленного контента (Flash of unstyled content, FOUC).

Этого можно избежать с помощью ExtractTextWebpackPlugin, который создаёт отдельные CSS-пакеты, вместо того, чтобы запихивать всё в единый JavaScript модуль.

webpack.config.js

var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
   module: {
       loaders: [{
           test: /\.css/,
           loader: ExtractTextPlugin.extract('style', 'css’)'
       }],
   },
   plugins: [
       new ExtractTextPlugin('[name].[chunkhash].css')
   ]
}

Работа со сторонними библиотеками и плагинами

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

ProvidePlugin

Большинство сторонних плагинов основываются на глобальных зависимостях. В случае jQuery, плагины ожидают наличие переменной $ или jQuery.

Можно использовать плагин ProvidePlugin, который добавит var $ = require("jquery") в случае наличия глобального $ идентификатора.

webpack.config.js

webpack.ProvidePlugin({
   ‘$’: ‘jquery’,
})

Imports-loader

Некоторые jQuery плагины ожидают $ в глобальном пространстве или в объекте window. В этом случае можно использовать imports-loader, который внедряет глобальные переменные в модули. 

example.js

$(‘div.content’).pluginFunc();

Далее, мы можем внедрить переменную $ в модуль, настроив imports-loader:

require("imports?$=jquery!./example.js");

Это добавит var $ = require("jquery"); в example.js.

Еще один способ:

webpack.config.js

module: {
   loaders: [{
       test: /jquery-plugin/,
       loader: 'imports?jQuery=jquery,$=jquery,this=>window'
   }]
}

С помощью => (не имеет отношения к стрелочным функциям ES6), мы можем устанавливать произвольные переменные. Последняя часть переопределяет глобальную переменную, чтобы она указывала на объект window. Это то же самое, если обернуть весь скрипт обернуть в конструкцию (function () { ... }).call(window); и вызывать ф-ции через window.

Также можно подключать библиотеки через CommmonJS или AMD:

// CommonJS
var $ = require("jquery");  

// AMD
define([‘jquery’], function($) {  
// jquery доступен
});

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

В следующем примере у нас jQuery плагин, который использует оба формата (CommonJS и AMD) и зависит от jQuery.

jquery-plugin.js

(function(factory) {
   if (typeof define === 'function' && define.amd) {
       // AMD формат
       define(['jquery'], factory);
   } else if (typeof exports === 'object') {
       // CommonJS формат
       module.exports = factory(require('jquery'));
   } else {
       // используются глобальные переменные
   }
});

webpack.config.js

module: {
   loaders: [{
       test: /jquery-plugin/,
       loader: "imports?define=>false,exports=>false"
   }]
}

Мы можем выбрать формат загрузки, который хотим применить к той или иной библиотеки. Если выставить define в false, Webpack не будет пытаться использовать AMD формат, а если определим exports в false, Webpack не будет использовать CommonJS формат.

Expose-loader

Если нам нужно добавить модуль в глобальную область видимости, можно использовать expose-loader. Это может быть полезно в случае, когда внешние скрипты, которые не являются частью конфигурации Webpack.

webpack.config.js

module: {
   loaders: [
       test: require.resolve('jquery'),
       loader: 'expose-loader?jQuery!expose-loader?$'
   ]
}

Благодаря такой конфигурации jQuery доступен в глобальной обасти видимости.

window.$
window.jQuery

Configuring External Dependencies

Если мы хотим включить некие удалённые скрипты, нужно указать это ы настройках.

В настройках это указывается в параметре “externals”. Например, можно подключить библиотеку из CDN через отдельный тэг



Комментарии

добавить
Комментариев пока нет. Будете первым?
Чтобы комментировать, нужно авторизоваться

Советуем почитать


Почему стоит изучать Ruby on Rails
Администратор 0

Почему стоит изучать Ruby on Rails читать далее

Вы начинающий программист? Или просто думаете какой бы язык изучить? Очень рекомендуем вам обратить внимание на Ruby on Rails. Не смотря на обилие языков программирования и доступных фреймворков, Ruby on Rails очень популярен среди web-разработчиков. Всё благодаря функционалу и скорости разработки.

0 28.01.2018 17:12:42

Федеральная система
Сергей 0

Федеральная система "Город" читать далее

В прошлый раз описал процесс работы с платёжной системой Cyberplat, теперь хочу поделиться опытом работы с ФСГ (Федеральная система город).

Разработано сие чудо ЦФТ. Старались делать все по ГОСТ, поэтому произвести интеграцию не так просто, как хотелось бы (рассматриваем PHP).

0 11.07.2016 17:40:15