Открыть меню    

Webpack 3

Обновленная версия статьи (под Webpack 3):

знания по JavaScript - вопросы

Установка webpack 2 как devDependencies:

npm i -D webpack

Узнать ВСЕ версии webpack:

npm view webpack versions

С последней:

npm view webpack versions --json

Установить конкретную версию:

npm i -D webpack@2.2.0

Создаем bundle через консоль:

webpack ./src/app.js ./dist/app.bundle.js -p --watch

Что (./src/app.js) пропускаем через webpack; куда (./dist/app.bundle.js).

Используйте флаг -p для production, чтобы минифицировать код.
Используйте флаг --watch, чтобы отслеживать изменения в файлах.

HTML Webpack Plugin

html-webpack-plugin динамически создает html-файл, плюс вставляет в него сгенерированный bundle.js.

Установка:

npm i html-webpack-plugin --save-dev

Первоисточник: HTML Webpack Plugin

Чтобы кастомизировать шаблон: Writing Your Own Templates

Пример использования с кастомизацией шаблона:

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/app.js',
    output: {
        path: path.resolve(__dirname,"./dist"),   // папка, в том числе, для HtmlWebpackPlugin
        filename: 'app.bundle.js'
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: 'Project demo',          // кастомизируем шаблон
            // minify: { collapseWhitespace: true },
            template: 'src/index.html'      // каркас для выходного 'шаблона'
        })
    ]
}

Loader

Loader - это функции принимающие в качестве параметра путь (например, к img) и вставляют результат в финальный bundle. Например, если нам нужно вставить css мы используем css-loader и т.д. Как уже упоминалось выше: в то время как loader оперируют с преобразованиями на отдельных файлах, плагины оперируют с большими кусками кода.

CSS, style, SASS:

css-loader

npm i css-loader --save-dev

    module: {
        rules: [{
                test: /\.css$/,
                use: 'css-loader'
            }
        ]
    },

css-loader включает css-код в выходной bundle.js. То есть без style-loader ничего не произойдет (стили не будут применены): css-loader просто вставит ваш css код в js-файл.

style-loader

style-loader - добавляет CSS в DOM, посредством инъекции тега style.

npm install style-loader --save-dev

    module: {
        rules: [{
                test: /\.css$/,
                loaders: 'style-loader!css-loader' // OR:  use: ['style-loader', 'css-loader']
            }
        ]
    },

sass-loader

sass-loader

    module: {
        rules: [{
                test: /\.scss$/,
                //loaders: 'style-loader!css-loader' // OR:  use: ['style-loader', 'css-loader']
                use: ['style-loader', 'css-loader', 'sass-loader']
            }
        ]
    },

Создаем отдельный css-файл (extract-text-webpack-plugin)

Для этого нам понадобится extract-text-webpack-plugin , который извлекает текст из bundle, или bundles, в отдельный файл.

# for webpack 3
npm install --save-dev extract-text-webpack-plugin
# for webpack 2
npm install --save-dev extract-text-webpack-plugin@2.1.2
# for webpack 1
npm install --save-dev extract-text-webpack-plugin@1.0.1

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: './src/app.js',
    output: {
        // папка, в том числе, для HtmlWebpackPlugin
        path: path.resolve(__dirname,"./dist"),
        filename: 'app.bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    //resolve-url-loader may be chained before sass-loader if necessary
                    use: ['css-loader', 'sass-loader'],
                    // генерируем отдельный файл:
                    publicPath: '/dist'
                })
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            // кастомизируем шаблон
            title: 'Project demo',
            // minify: { collapseWhitespace: true },
            // каркас для выходного 'шаблона'
            template: 'src/index.html'
        }),
        new ExtractTextPlugin('style.css')
    ]
}

Webpack Dev Server

webpack-dev-server

npm i webpack-dev-server -D

//Далее это (в package.json):
"scripts": {
    "dev": "webpack -d --watch"
},

// заменим на:
"scripts": {
    "dev": "webpack-dev-server"
},
// отличия см. ниже

Webpack Dev Server: больше опций

Документация к настройкам Webpack Dev Server: Webpack Dev Server

Чем отличпется команда "dev": "webpack-dev-server" от "dev": "webpack -d":
первая команда работает с файлами из памяти, второя читает непосредственно из папки (например, у нас это папка dist).

Кастомизируем Webpack Dev Server в файл webpack.config.js:

module.exports = {
    entry: './src/app.js',
    output: {
        //...
    },
    module: {
        //...
    },
    devServer: {
        contentBase: path.join(__dirname, "dist"),
        compress: true,
        port: 9001,
        stats: 'errors-only',
        open: true
    },
    plugins: [
        //...
    ]
}

Как установить react и babel

Устанавливаем Babel:

npm i -D babel babel-preset-react babel-preset-es2015

Далее в вашем проекте создайте файл .babelrc. Файл .babelrc используем для конфигурации babel. Содержимое .babelrc:

{
    "presets": [
        "es2015",
        "react"
    ]
}

Для webpack необходимо настроить соответствующий loader (setup):

npm install --save-dev babel-loader babel-core

module: {
  rules: [
    {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
    }
  ]
}

Устанавливаем react (Installing React):

npm i -D react react-dom

Далее вы можете для примера создать небольшой пример на React Hello World with ES6 and JSX, и не забудьте расположить где-нибуть в html файле div элемент с id равным root.

Несколько шаблонов в webpack и RimRaf

Допустим при каждой сборке нам потребовалось очищать папку dist. Для этого вопользуемся rimraf

npm install rimraf --save-dev

Для этого в package.json добавим:

    "scripts": {
        "dev": "webpack-dev-server",
        "prod": "npm run clean && webpack -p",
        "clean": "rimraf ./dist/*"
    },

Зададим несколько шаблонов:

//...
plugins: [
    new HtmlWebpackPlugin({
        // кастомизируем шаблон
        title: 'Project demo',
        // minify:
        // { collapseWhitespace: true },
        // каркас для выходного 'шаблона'
        template: 'src/index.html',
        // меняем output (см. выше) на свой, то есть определяем место, куда будем генерировать шаблон
        // filename: './../index.html'
        hash: true
    }),
    new HtmlWebpackPlugin({
        title: 'Contact page',
        template: 'src/contact.html',
        // автоматически сохранится в папку dist (так как она указана в output, см. выше)
        filename: 'contact.html',
        hash: true
    }),
//...

Но отсюда возникает закономерный вопрос: как указать разные bundle (js) для разных страниц? Если вы хотите несколько выходящих (output) bundles, вам потребуется определить несколько entry + не забудьте поменять в otput значение у filename на [name].bundle.js:

//...
entry: {
    app: './src/app.js',
    contact: './src/contact.js'
},
output: {
    // папка, в том числе, для HtmlWebpackPlugin
    path: path.resolve(__dirname,"./dist"),
    filename: '[name].bundle.js'
},
//...

Далее, чтобы исключить лишние chunks ('куски') js кода, нужно воспользовать опциями excludeChunks (исключаем) или chunks:

//...
plugins: [
    new HtmlWebpackPlugin({
        // кастомизируем шаблон
        title: 'Project demo',
        // minify:
        // { collapseWhitespace: true },
        // каркас для выходного 'шаблона'
        template: 'src/index.html',
        // меняем output (см. выше) на свой, то есть определяем место, куда будем генерировать шаблон
        // filename: './../index.html'
        hash: true,
        excludeChunks: ['contact']
    }),
    new HtmlWebpackPlugin({
        title: 'Contact page',
        template: 'src/contact.html',
        // автоматически сохранится в папку dist (так как она указана в output, см. выше)
        filename: 'contact.html',
        hash: true,
        chunks: ['contact']
    }),
//...

Также на stack: создаем конфиг для множества entry points / templates: Webpack конфиг для множества entry points / templates

Как использовать pug (jade) шаблоны в Webpack

Подробнее почитать о pug: pug

Также для 3 Webpack нам потребуется установить HTML Loader

npm i -D html-loader

npm i -D pug pug-html-loader

    module: {
        rules: [
            // pug
            {
                test: /\.pug$/,
                use: ["html-loader", "pug-html-loader"]
            }
    //...
    plugins: [
        new HtmlWebpackPlugin({
            title: 'Project demo',
            template: 'src/index.pug'
        }),

Hot Module Replacement - CSS

При помощи Hot Module Replacement можно обновлять CSS без перезагрузки страницы.

Настройки приведены здесь: webpack.config.js

// подключим webpack
var webpack = require('webpack');

module: {
    rules: [
        {
            test: /\.scss$/,
            // для 'Hot Module Replacement' закомментируем:
            /*
            use: ExtractTextPlugin.extract({
                fallback: 'style-loader',
                //resolve-url-loader may be chained before sass-loader if necessary
                use: ['css-loader', 'sass-loader'],
                // генерируем отдельный файл:
                publicPath: '/dist'
            })
            */
            // для Hot Module Replacement:
            use: ['style-loader', 'css-loader', 'sass-loader']
        },

    //...

plugins: [
    // Hot Module Replacement:
    new ExtractTextPlugin({
        filename: 'style.css',
        // для Hot Module Replacement отключим плагин ExtractTextPlugin
        disable: true,
        allChunks: true
    }),


    // Hot Module Replacement:
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()

    //...

Обратите внимание: в консоли при каждом изменении в scss файле будет отображаться: [HMR] App is up to date.

Production vs Development Environment

Обратите внимание, что в предыдущем разделе мы отключили ExtractTextPlugin для HMR, но для Production он понадобится. Для этого в package.json добавим для prod NODE_ENV=production:

    "scripts": {
        "dev": "webpack-dev-server",
        "prod": "set NODE_ENV=production&&npm run clean && webpack -p",
        "clean": "rimraf ./dist/*"
    },
// webpack.config.js
//...
var isProd = process.env.NODE_ENV === 'production';
var cssDev = ['style-loader', 'css-loader', 'sass-loader'];
var cssProd = ExtractTextPlugin.extract({
                fallback: 'style-loader',
                //resolve-url-loader may be chained before sass-loader if necessary
                use: ['css-loader', 'sass-loader'],
                // генерируем отдельный файл:
                publicPath: '/dist'
            });
var cssConfig = isProd ? cssProd : cssDev;

//...
module: {
    rules: [
        {
            test: /\.scss$/,
            use: cssConfig
        },

//...

new ExtractTextPlugin({
    filename: 'style.css',
    // в зависимости от режима (dev/prod)
    disable: !isProd,
    allChunks: true
}),

Как загружать изображения с webpack 2

     file-loader

Для этого нам понадобятся два loaders: image-webpack-loader (оптимизация) и file-loader - сканирует все файлы и пытается загрузить их в папку dist (при prod, или туда, куда вы укажите). file-loader понадобится для того, чтобы webpack мог работать с картинками как с модулями, то есть с помощью file-loader мы сможем загружать картинки в javascript и работать с ними).

npm install --save-dev file-loader

// webpack.config.js
module: {
rules: [
//...
{
    test: /\.(gif|png|jpe?g|svg)$/i,
    // ?name=[path][name].[ext] - сохранили оригинальное имя и путь в папке dist: dist\src\images\pugjs.png
    //use: ["file-loader?name=[path][name].[ext]"]

    // ?name=[name].[ext]&outputPath=images/ - оригинальное имя сохранит в папке images: dist\images\pugjs.png
    use: ["file-loader?name=[name].[ext]&outputPath=images/"]
}

/* app.scss */
body {
    color: red;
    background: whitesmoke  url('./images/pugjs.png') center no-repeat;
}
<!-- contact.html -->

<!-- не сработает -->
<img src="./images/pugjs.png" alt="" />


<!-- сработает -->
<img src=<%= require("./images/pugjs.png") %>  />

Внутри html, а также css, можно также указать publicPath для картинок.

module: {
    rules: [
            use: [
                "file-loader?name=[name].[ext]&outputPath=images/&publicPath=images/",
                "image-webpack-loader" // напомню, лоадеры работают с конца
            ]
        }

При этом на выходе мы получим:

<!-- contact.html -->
<img src=images/images/pugjs.png  />
/* style.css*/
body{color:red;background:#f5f5f5 url(images/images/pugjs.png) 50% no-repeat}

Как итог: outputPath - это место, куда будут копироваться картинке при сборке; publicPath - это путь в исходниках, заданный принудительно.

     image-webpack-loader

Чтобы отптимизировать изображения прежде чем залить их в папку dist опцианально можно использовать image-webpack-loader (напомню, лоадеры работают с конца в массиве).

npm install image-webpack-loader --save-dev

Bootstrap и webpack 2

Мы будем использовать bootstrap-loader

npm install bootstrap-loader -D

При установке возникнут предупреждения с целью установить:

npm i -D resolve-url-loader url-loader

Далее вы можете выбрать, какую версию bootstrap вам нужно поставить: installation

Установим, например, 3-й bootstrap:

npm install --save-dev bootstrap-sass

Чтобы настроить bootstrap в корне проекта создайте файл .bootstraprc и скопируйте в него данный конфиг - именно в этом файле определяется какие скрипты/стили компонентов следует подключать в проект, а какие нет.

Далее создадим файл webpack.bootstrap.config.js (также в корне проекта), в котором проверяется режим работы на основе process.env.BOOTSTRAPRC_LOCATION, и скопируем в файл webpack.bootstrap.config.js данный контент.

Подключаем шрифты

Также потребуется подключить loader для шрифтов, которые использует bootstrap: Icon fonts

module: {
  loaders: [
    { test: /\.(woff2?|svg)$/, loader: 'url-loader?limit=10000' },
    { test: /\.(ttf|eot)$/, loader: 'file-loader' },
  ],
},

Добавим папку font для шрифтов:

{ test: /\.(woff2?|svg)$/, loader: 'url-loader?limit=10000$name=fonts/[name].[ext]' },
{ test: /\.(ttf|eot)$/, loader: 'file-loader?name=fonts/[name].[ext]' },

Подключаем jQuery

Подключим jQuery

npm i -D imports-loader jquery

module: {
  loaders: [
    // Bootstrap 3
    { test:/bootstrap-sass[\/\\]assets[\/\\]javascripts[\/\\]/, loader: 'imports-loader?jQuery=jquery' },
  ],
},

Переопределяем переменный bootstrap

В файле .bootstraprc можно раскомментировать (и задать свой путь) для bootstrapCustomizations: ./src/bootstrap/customizations.scss, где мы сможем переопределить любое значение достпупное в файле _variables.scss.

/* ./src/bootstrap/customizations.scss */
$brand-success: green !default;
/* app.scss */
@import "./bootstrap/customizations.scss";

body {
    h1 {
        color: $brand-success;
    }
}

ProvidePlugin

ProvidePlugin - автоматически загружает модули, вместо того чтобы import или require их повсюду.

new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery'
})

Миграция (разница между версиями)

p.s. Изменений между 2-й и 3-й версией совсем немного, так что вы без труда сможете перейти с одной на другую.

Для того чтобы определить, как изменилось поведение того или иного loader/plugin и других частей webpack при переходе с одной версии на другую, можно воспользоваться руководством: migrating

Исходники

package.json:

{
  "name": "webpack-101",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server",
    "prod": "set NODE_ENV=production&&npm run clean && webpack -p",
    "clean": "rimraf ./dist/*"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel": "^6.23.0",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "bootstrap-loader": "^2.2.0",
    "bootstrap-sass": "^3.3.7",
    "css-loader": "^0.28.7",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.5",
    "html-loader": "^0.5.1",
    "html-webpack-plugin": "^2.30.1",
    "image-webpack-loader": "^3.4.2",
    "imports-loader": "^0.7.1",
    "jquery": "^3.2.1",
    "node-sass": "^4.6.1",
    "pug": "^2.0.0-rc.4",
    "pug-html-loader": "^1.1.5",
    "react": "^16.1.1",
    "react-dom": "^16.1.1",
    "resolve-url-loader": "^2.2.0",
    "rimraf": "^2.6.2",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.19.0",
    "url-loader": "^0.6.2",
    "webpack": "^3.8.1",
    "webpack-dev-server": "^2.9.4"
  }
}

webpack.config.json:

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
var webpack = require('webpack');
// for bootstrap
var bootstrapEntryPoints = require("./webpack.bootstrap.config");

var isProd = process.env.NODE_ENV === 'production';
var cssDev = ['style-loader', 'css-loader', 'sass-loader'];
var cssProd = ExtractTextPlugin.extract({
                fallback: 'style-loader',
                //resolve-url-loader may be chained before sass-loader if necessary
                use: ['css-loader', 'sass-loader'],
                // генерируем отдельный файл:
                publicPath: '/dist'
            });

var cssConfig = isProd ? cssProd : cssDev;

// for bootstrap
var bootstrapConfig = isProd ? bootstrapEntryPoints.prod : bootstrapEntryPoints.dev;


module.exports = {
    entry: {
        app: './src/app.js',
        contact: './src/contact.js',
        // for bootstrap
        bootstrap: bootstrapConfig
    },
    output: {
        // папка, в том числе, для HtmlWebpackPlugin
        path: path.resolve(__dirname,"./dist"),
        filename: '[name].bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.scss$/,
                // для 'Hot Module Replacement' закомментируем
                /*
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    //resolve-url-loader may be chained before sass-loader if necessary
                    use: ['css-loader', 'sass-loader'],
                    // генерируем отдельный файл:
                    publicPath: '/dist'
                })
                */

                // для Hot Module Replacement
                // use: ['style-loader', 'css-loader', 'sass-loader']

                // в зависимости от режима (dev/prod)
                use: cssConfig
            },
            // настраиваем babel
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: "babel-loader"
            },
            // pug
            {
                test: /\.pug$/,
                use: ["html-loader", "pug-html-loader"]
            },
            {
                test: /\.(gif|png|jpe?g|svg)$/i,
                // ?name=[path][name].[ext] - сохранили оригинальное имя и путь в папке dist: dist\src\images\pugjs.png
                //use: ["file-loader?name=[path][name].[ext]"]

                // ?name=[name].[ext]&outputPath=images/ - оригинальное имя сохранит в папке images: dist\images\pugjs.png
                use: [
                    //"file-loader?name=images/[name].[ext]",
                    "file-loader?name=[name].[ext]&outputPath=images/", //&publicPath=images/
                    "image-webpack-loader" // напомню, лоадеры работают с конца
                ]
            },
            // for bootstrap:
            // https://github.com/shakacode/bootstrap-loader#icon-fonts
            { test: /\.(woff2?|svg)$/, loader: 'url-loader?limit=10000$name=fonts/[name].[ext]' },
            { test: /\.(ttf|eot)$/, loader: 'file-loader?name=fonts/[name].[ext]' },
            { test:/bootstrap-sass[\/\\]assets[\/\\]javascripts[\/\\]/, loader: 'imports-loader?jQuery=jquery' },
        ]
    },
    devServer: {
        contentBase: path.join(__dirname, "dist"),
        compress: true,
        port: 9001,
        stats: 'errors-only',
        open: true,
        // Hot Module Replacement:
        hot: true

    },
    plugins: [
        new HtmlWebpackPlugin({
            // кастомизируем шаблон
            title: 'Project demo',
            //  minify:
            //  { collapseWhitespace: true },
            //  каркас для выходного 'шаблона'
            //  template: 'src/index.html',

            template: 'src/index.pug',

            // меняем output (см. выше) на свой, то есть определяем место, куда будем генерировать шаблон
            // filename: './../index.html'
            hash: true,
            excludeChunks: ['contact']
        }),
        new HtmlWebpackPlugin({
            title: 'Contact page',
            template: 'src/contact.html',
            // автоматически сохранится в папку dist (так как она указана в output, см. выше)
            filename: 'contact.html',
            hash: true,
            chunks: ['bootstrap', 'contact']
        }),
        //new ExtractTextPlugin('style.css'),

        // Hot Module Replacement:
        new ExtractTextPlugin({
            filename: '/css/[name].css',
            // для Hot Module Replacement отключим плагин ExtractTextPlugin
            //disable: true,

            // в зависимости от режима (dev/prod)
            disable: !isProd,
            allChunks: true
        }),

        // Hot Module Replacement:
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin()
    ]
}

Полезные ресурсы

Старая версия статьи: webpack 2

     plugins

знания по JavaScript - вопросы

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

Например, commons-chunk-plugin другой корневой плагин, который может быть использован, чтобы создать отдельный модуль с общим кодом из нескольких точек входа.

Генерируем страницу из pug-файла при помощи HtmlWebpackPlugin:

    // plugins
    plugins: [ // кастомизирует процесс сборки webpack
        new HtmlWebpackPlugin({
            filename: 'index.html',
            chunks: ['index'],
            template: PATHS.source + '/pages/index/index.pug'
        }),
        new HtmlWebpackPlugin({
            filename: 'blog.html',
            chunks: ['blog'],
            template: PATHS.source + '/pages/blog/blog.pug'
        }),
    ],

     loader

loader loaders говорят webpack , что нужно делать, когда он встречает imports для разных типов файлов.

Вы можете объединить loader's вместе в серию трансформаций.

Хороший пример того как это работает - импортировать Sass из нашего JavaScript.

    // loader
    module: {
        rules: [
            {
                test: /\.pug$/,
                loader: 'pug-loader',
                options: {
                    pretty: true
                }
            }
        ]
    },

Обрабатываем Pug в скрипте - 'на лету' при помощи pug-loader (pug-loader):

// ./test.pug
div=title
//blog/blog.js
import tmpl from './test.pug';

let el = document.querySelector('.test')

let data = {
    title: 'хохохохоох'
};

el.innerHTML = tmpl(data);

     webpack-dev-server:

    //webpack-dev-server - стартуем:
    "scripts": {
        "start": "webpack-dev-server --env development",
    },

webpack-dev-server редактируется посредством объеста devServer, который является свойством module (module.exports)

     Production & Development

Разделение конфига на development и production.

Разделяем задачи посредством параметра env (--env development, где development это значение для env):

    "scripts": {
        "start": "webpack-dev-server --env development",
        "build": "webpack --env production",
    },
// only development
const developmentConfig =  {
    devServer: {
        // в консоли увидем только ошибки
        stats: 'errors-only',
        port: 9000
    }
};

module.exports = function (env) {
    // для production будет один объект, а для development другой
    if (env === 'production') {
        return common;
    };
    if (env === 'development') {
        return Object.assign(
            {},
            common,
            developmentConfig
        );
    };
};

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

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