Webpack

Андреев

Сергей

Что такое webpack

    - не таск ранер
    - модульный сборщик на js

Почему появился

Как было раньше

<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 tag

Как было раньше

$ cat jquery.js \
  plugin1.js plugin2.js plugin3.js \
  init.js \
    > bundle.js

cat

<script src="bundle.js"></script>

Как было раньше

var concat = require('gulp-concat');
 
gulp.task('scripts', function() {
  return gulp.src(['./lib/file3.js', './lib/file1.js', './lib/file2.js'])
    .pipe(concat('all.js'))
    .pipe(gulp.dest('./dist/'));
});

Таск ранер

<script src="bundle.js"></script>

Проблемы с другими сборщиками

- плохо расширяемые
    - плохо подходили для больших приложений

Надо: быть максимально модульным

Как работает

Все есть модули
   

любой файл модуль

любой модуль есть js
   

 процес сборки можно полностью переопределить

/***/ function(module, exports, __webpack_require__) {
	exports = module.exports = __webpack_require__(2)();
	exports.push([module.id, ".webpack {\n    color: red;\n}", ""]);
/***/ },
// app.js
var css = require("css!./file.css");


// file.css
.webpack {
    color: red;
}
$ webpack
Hash: 35409495c2b26d7d5468
Version: webpack 1.12.13
Time: 874ms
    Asset     Size  Chunks             Chunk Names
bundle.js  3.28 kB       0  [emitted]  app
   [0] ./app.js 36 bytes {0} [built]
    + 2 hidden modules

Что делает

    - разделение на чанки
  

  - асинхроная загрузка
    


    - оптимизация

- строит дерево зависимостей

Архитектура

    - модули
    - конфиг
    - cli
    - загрузчики
    - Плагины

CLI

$: npm i -g webpack
$: webpack <entry> <output>
$: webpack [--color] [--watch] [--config]

Конфиг

module.exports = {
    entry: {
        app: "./app.js"
    },
    output: {
        path: "dist",
        filename: "bundle.js"
    }
}

webpack.config.js

'use strict';

const ISDEV = process.env.NODE_ENV === 'development';

const webpack = require('webpack');

var path = require('path');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var autoprefixer = require('autoprefixer');

module.exports = {
    cache: true,
    context: path.join(__dirname, '/app'),
    entry: {
        app:'./main',
        vendors: './vendors',
        styles: 'less/main.less'

    },

    watch: ISDEV,
    watchOptions: {
        aggregateTimeout: 100
    },
    devtool: ISDEV ? "cheap-inline-module-source-map" : null,

    output: {
        path: path.join(__dirname,'/../www/assets/'), //path(__dirname,'../www/js')
        filename: '[name].min.js',
        publicPath: '/assets/'
    },

    resolveLoader: {
        modulesDirectories: ['node_modules'],
        moduleTemplates:    ['*-loader'],
        extensions:         ['', '.js']
    },
    resolve: {
        alias: {
            'jquery': 'jquery/dist/jquery.js',
            'jquery.ui': 'jquery-ui-1.10.4.custom.js',
            'jquery.chosen': 'chosen.jquery.min.js',
            'jquery.autosize': 'jquery.autosize.js',
            'jquery.ba-throttle-debounce': 'jquery.ba-throttle-debounce.min.js',
            'jquery.maskedinput': 'jquery.maskedinput.min.js',
            'jquery.tmpl': 'jquery.tmpl.js',
            'jquery.tokeninput': 'jquery.tokeninput.js',
            'jquery.select2': 'select2.min.js',
            'jquery.zebra-datepicker': 'zebra_datepicker.js',
            hyperd: 'hyperd.min.js',
            $: 'jquery'
        },
        root: path.join(__dirname, '/app'),
        extensions: ['', '.js'],
        modulesDirectories: ['libs','bower_components','node_modules'],
        fallback: path.join(__dirname, 'app/helpers')
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                include: /\/app/,
                exclude: /\/libs\/less/,
                loader: 'babel?optional[]=runtime'
            },
            {
                test: /^jquery\./,
                include: /\/libs/,
                loader: 'imports?$=jquery&jQuery=jquery'
            },
            {
                test: /\.hbs$/,
                include: /\/app/,
                loader: 'handlebars'
            },
            {
                test: /\.less$/,
                include: /\/app\/less/,
                loader: ExtractTextPlugin.extract('css?root=./../../&!postcss!less')
            },
            {
                test: /\.(jpe?g|png|gif)$/i,
                include: /\/images/,
                loaders: function () {
                    if (ISDEV) {
                        return [
                            'url?limit=500000&name=images/[path][name].[ext]'
                        ]
                    } else {
                        return [
                            'url?limit=5000&name=images/[path][name].[ext]',
                            'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false'
                        ]
                    }
                }()
            },
            {
                test   : /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
                include: /\/fonts/,
                loader : 'url?limit=5000&name=fonts/[name].[ext]',
            }
        ]
    },
    plugins: [
        new webpack.NoErrorsPlugin(),
        new webpack.DefinePlugin({
            isDev: JSON.stringify(ISDEV)
        }),
        new ExtractTextPlugin('app.min.css',{allChunks: true}),
        new webpack.ProvidePlugin({
            'windows.jQuery': 'jquery',
            'windows.$': 'jquery'
        }),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.min.js'),
    ],
    postcss: function () {

        return !ISDEV? [autoprefixer] : [];
    }
};

if (!ISDEV) {
    module.exports.plugins.push(
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                // don't show unreachable variables etc
                warnings:     false,
                drop_console: true,
                unsafe:       true
            }
        })
    );
}

webpack.config.js

Загрузчики

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

loaders: [
            {
                test: /\.js$/,
                include: /\/app/,
                loader: 'babel?optional[]=runtime'
            },
            {
                test: /^jquery\./,
                include: /\/libs/,
                loader: 'imports?$=jquery&jQuery=jquery'
            },
            {
                test: /\.hbs$/,
                include: /\/app/,
                loader: 'handlebars'
            },
            {
                test: /\.less$/,
                include: /\/app\/less/,
                loader: ExtractTextPlugin.extract('css?root=./../../&!postcss!less')
            },
            {
                test: /\.(jpe?g|png|gif)$/i,
                include: /\/images/,
                loaders: 'url?limit=500000&name=images/[path][name].[ext]'
            },
            {
                test   : /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
                include: /\/fonts/,
                loader : 'url?limit=5000&name=fonts/[name].[ext]',
            }
        ]

Плагины

  - имеют доступ ко всем модулям (до и после трансформации);
  - могут добавлять в упаковку свои модули (например, «runtime»);
  - имеют доступ ко всем ресурсам, создаваемым после упаковки;
  - применяются для изменения конфигурации сборки, оптимизации, добавления в модули каких-то объектов, горячего обновления ресурсов.

Что в итоге ?

Кейсы

npm run ***

"scripts": {
    "watch": "webpack -d --progress --colors --watch",
    "dev": "env NODE_ENV=development webpack --progress --colors",
    "build": "webpack --progress --colors",
    "analyze": "webpack --progress --profile --json > stats.json",
    "size": "cat stats.json | analyze-bundle-size"
},
// webpack.config.js
const ISDEV = process.env.NODE_ENV === 'development';

module.exports = {
  watch: ISDEV,
  devtool: ISDEV ? "cheap-inline-module-source-map" : null,
}
if (!ISDEV) {
    module.exports.plugins.push(
        new webpack.optimize.UglifyJsPlugin()
    );
}

require.ensure

/static imports
import _ from 'lodash'

// dynamic imports
require.ensure([], function(require) {
  let contacts = require('./contacts')
})
require.ensure([], function(require) {
  require.include("./file");
  require("./file2");
});

Hot Module Replacement

Код, который узнает о факте обновления, должен написать разработчик.

для react уже есть

Извлечение стилей из упаковки

var ExtractTextPlugin = require('extract-text-webpack-plugin');
...
{
    test: /\.less$/,
    include: /\/app\/less/,
    loader: ExtractTextPlugin.extract('css?root=./../../&!postcss!less')
},

Плагины для оптимизации упаковки

    DedupePlugin
CommonsChunkPlugin
    UglifyJsPlugin

Анализ сборки

Минусы и плюсы

Время сборки

product build -  42 564 ms
dev build - 21 262 ms
dev watch js - 400 - 2 500 ms
dev watch less - 2 000 - 5 000 ms

проблема со старым кодом

jQuery плагины

Вопросы

Webpack

By Sergey Andreev

Webpack

  • 1,526
Loading comments...

More from Sergey Andreev