Webpack для Single Page App

от автора


Привет!

Отгремели фанфары, прошел звон в ушах от истязаний «евангелистов», модников в сфере фронтенд разработки. Кто-то ушел на sprockets, кто-то пустился во все тяжкие и стал писать свои велосипеды или расширять функционал gulp или grunt. Но статей по поводу популярных инструментов автоматизации процесса сборки – стало существенно меньше и это факт! Пора бы заполнить освободившееся пространство чем-то существенно иным.

Уверен многие слышали о webpack. Кто-то подумал «он слишком много на себя берет» и не стал дочитывать даже вводную статью на оффициальном сайте проекта. Некоторые решили попробовать, но столкнувшись с небольшим количеством примеров настройки — отверг инструмент решив подождать пару лет. Но в целом, разговоров «вокруг» ходит масса. В этой статье — хочу развенчать некоторые сомнения. Может быть, кто-то заинтересуется и перейдет на «светлую сторону». Вообщем желающие под кат.

Честно сказать, активно работать с этим мощным инструментом, я начал в начале этого года. Сначала конечно же “wow” эффект. Который достаточно быстро сменился болью от жутко неудобной документации. Но пересилив этот этап – честно забыл о многих типичных проблемах, был поражен скоростью и удобством. Не буду утомлять, разберемся с…

Механика

Логика работы очень простая, но эффективная
Webpack принимает один или несколько файлов, так называемых (entry points) используя пути из конфигурационного файла, далее загружает его.

Сборщик – во время обработки, встречая знакомую ему функцию require() – оборачивает содержимое вызываемого файла в функцию. Которая возвращает его контекст.
При этом не нужно заботиться о «гонке загрузки». Все что вы потребуете, будет доставлено в нужной последовательности!

Важно отметить, что во время разработки, когда запущен webpack-dev-server – промежуточные файлы (chunks) попадают в оперативную память RAM. Браузер же, получает их по протоколу webpack:// прямо из контейнера.
Так же dev-server – поднимает простейший веб сервер на порт 8080, к которому можно достучаться по адресу localhost:8080

Этот подход управления содержимым, как Вы понимаете ускоряет время промежуточной сборки, на значимое количество секунд. Что в рамках рабочего дня – экономит уйму Вашего времени.

Что касается окончательной сборки, то webpack используя тот же конфиг выполняет лишь этап, когда файлы раскладываются по папкам в файловой системе, готовые работать на Ваше благо.

image
source: jlongster.com/Backend-Apps-with-Webpack—Part-III

Подготовка

Тут все достаточно тривиально. Все что нам нужно это node.js и npm. Далее просто следуйте простым командам:

$ mkdir app $ cd $_  $ npm init & npm i webpack webpack-dev-server --save-dev # важно поставить именно в dev dependencies 

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

  • Common JS
  • Stylus
  • Jade
  • Autoprefixer

Остальное поставим по необходимости.

Настройка

По умолчанию, webpack принимает конфигурационный файл выполненный в формате JSON. Находиться он должен в директории проекта и называться webpack.config.js

Для более удобной работы с запуском задач, а их будет две:

  • Сборка готового проекта
  • Режим разработки

Воспользуемся script секцией package.json файла, добавив следующие строчки:

"scripts": {   "dev": "NODE_ENV=development webpack-dev-server --progress",   "build": "NODE_ENV=production webpack --progress" } 

После этого, находясь в директории проекта, Вам будут доступны следующие команды:

$ npm run dev # режим разработки http://localhost:8080 $ npm run build # сборка готовых ассетов и файлов в ./dist 

Создадим файлы конфигурации терминальной магией:

$ touch webpack.config.js # корневой файл конфигурации обрабатываемый webpack $ mkdir config # директория с конфигурациями $ cd $_ # перейдем $ touch global.js # общие настройки инструментов, загрузчиков и плагинов $ mkdir env && cd $_ $ touch development.js # файл с 'тонкой' настройкой webpack для режима разработки $ touch production.js # аналогичный файл для конечной сборки 

Перейдем к настройке окружения, для этого откроем файл ./webpack.config.js и заполним следующим содержимым:

'use strict';  var _ = require('lodash'); var _configs = {   global: require(__dirname + '/config/global'),   production: require(__dirname + '/config/env/production'),   development: require(__dirname + '/config/env/development') };  var _load = function(environment) {   // Проверяем окружение   if (!environment) throw 'Can\'t find local environment variable via process.env.NODE_ENV';   if (!_configs[environment]) throw 'Can\'t find environments see _config object';    // load config file by environment   return _configs && _.merge(     _configs[environment](__dirname),     _configs['global'](__dirname)   ); };  /**  * Export WebPack config  * @type {[type]}  */ module.exports = _load(process.env.NODE_ENV);  

Как вы заметили lodash, исправим его отсутствие выполнением следующей команды:

$ npm i lodash --save-dev 

Немного схитрив, мы сможем используя метод merge – библитеки lodash, ‘склеить’ нужную нам исходную конфигурацию, используя для этого 2 файла. В качестве аргумента функции принимая process.env.

./config/global.js

Файл содержит ненормативную лексику почти всю логику работы сборщика, следовательно к его содержимому нужно отнестись более ответственно:

'use strict'  var path        = require('path'); var webpack     = require('webpack'); var Manifest    = require('manifest-revision-webpack-plugin'); var TextPlugin  = require('extract-text-webpack-plugin'); var HtmlPlugin    = require('html-webpack-plugin');  module.exports = function(_path) {    //define local variables   var dependencies  = Object.keys(require(_path + '/package').dependencies);   var rootAssetPath = _path + 'app';    return {     // точки входа     entry: {       application: _path + '/app/app.js',       vendors: dependencies     },      // то, что получим на выходе     output: {       path: path.join(_path, 'dist'),       filename: path.join('assets', 'js', '[name].bundle.[chunkhash].js'),       chunkFilename: '[id].bundle.[chunkhash].js',       publicPath: '/'     },      // Небольшие настройки связанные с тем, где искать сторонние библиотеки     resolve: {       extensions: ['', '.js'],       modulesDirectories: ['node_modules'],       // Алиасы - очень важный инструмент для определения области видимости ex. require('_modules/test/index')       alias: {         _svg:         path.join(_path, 'app', 'assets', 'svg'),         _data:        path.join(_path, 'app', 'data'),         _fonts:       path.join(_path, 'app', 'assets', 'fonts'),         _modules:     path.join(_path, 'app', 'modules'),         _images:      path.join(_path, 'app', 'assets', 'images'),         _stylesheets: path.join(_path, 'app', 'assets', 'stylesheets'),         _templates:   path.join(_path, 'app', 'assets', 'templates')       }     },     // Настройка загрузчиков, они выполняют роль обработчика исходного файла в конечный     module: {       loaders: [         { test: /\.jade$/, loader: 'jade-loader' },         { test: /\.(css|ttf|eot|woff|woff2|png|ico|jpg|jpeg|gif|svg)$/i, loaders: ['file?context=' + rootAssetPath + '&name=assets/static/[ext]/[name].[hash].[ext]'] },         { test: /\.styl$/, loader: TextPlugin.extract('style-loader', 'css-loader!autoprefixer-loader?browsers=last 5 version!stylus-loader') }       ]     },      // загружаем плагины     plugins: [       new webpack.optimize.CommonsChunkPlugin('vendors', 'assets/js/vendors.[hash].js'),       new TextPlugin('assets/css/[name].[hash].css'),       new Manifest(path.join(_path + '/config', 'manifest.json'), {         rootAssetPath: rootAssetPath       }),       // Этот файл будет являться "корневым" index.html       new HtmlPlugin({         title: 'Test APP',         chunks: ['application', 'vendors'],         filename: 'index.html',         template: path.join(_path, 'app', 'index.html')       })     ]   } }; 

Аттеншен господа!
Появились новые зависимости – которые нужно доставить в проект следующей командой:

$ npm i path manifest-revision-webpack-plugin extract-text-webpack-plugin html-webpack-plugin --save-dev 

Разработка

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

'use strict';  /**  * Development config  */ module.exports = function(_path) {    return {     context: _path,     debug: true,     devtool: 'eval',     devServer: {       contentBase: './dist',       info: true,       hot: false,       inline: true     }   } }; 

Окончательная сборка

А вот настройка конечное сборки все еще в стадии «изменений». Хотя работает на 5+

'use strict';  /**  * Production config  */ module.exports = function(_path) {   return {     context: _path,     debug: false,     devtool: 'cheap-source-map',     output: {       publicPath: '/'     }   } } 

index.html

Этот шаблон, мы положим в папку ./app/index.html – именно он будет отдавать правильные пути до хешированной статики, после конечной сборки.

<!DOCTYPE html> <html{% if(o.htmlWebpackPlugin.files.manifest) { %} manifest="{%= o.htmlWebpackPlugin.files.manifest %}"{% } %}>   <head>     <meta charset="UTF-8">     <title>{%=o.htmlWebpackPlugin.options.title || 'Webpack App'%}</title>     {% for (var css in o.htmlWebpackPlugin.files.css) { %}       <link href="{%=o.htmlWebpackPlugin.files.css[css] %}" rel="stylesheet">     {% } %}   </head>   <body>     {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %}     <script src="{%=o.htmlWebpackPlugin.files.chunks[chunk].entry %}"></script>     {% } %}   </body> </html> 

В заключении

Хотелось бы поблагодарить разработчиков webpack за столь мощный инструмент.
Скорость его работы действительно поражает, даже на больших объемах исходных файлов.

Ах, да. Для того чтобы в своем проекте использовать например underscore достаточно установить его привычной npm командой

$ npm i underscore --save 

После выполнения, библиотека попадет в dependencies – следовательно webpack, поместит его содержимое в файл vendors.[hash].js при этом захеширует название файла полученное от md5 размера исходников + время последнего их изменения.

Для чего это надо, объяснять не буду.
На этом все, пробуйте, пишите комменты.
Спасибо.

Ссылки

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

ссылка на оригинал статьи http://habrahabr.ru/post/265801/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *