Знакомство с Webpack 4

11

Загрузчики

Не все загрузчики еще переделаны под 4ю версию.

Сначала инициализируем проект через npm init -y.

Теперь создадим файл greeter.js и index.js. А также понадобится index.html

В первом создадим простой класс

class Greeter {
  greet(greeting, whom) {
    return `${greeting}, ${whom}!`;
  }
}

А в самом index.js запишем

const greeter = new Greeter();

const message = greeter.greet("Hello", "Webpack");

Вот и пригодится нам вебпак… для начала чтобы упаковать все скрипты в один файл.

Для начала его нужно установить:

npm i webpack webpack-cli -D

У 4го вебпака есть особенность, если не использовать файл конфигурации.
Вебпак по умолчанию ищет папку src, а в ней файл index.js и помещает всё в папку dist в файл main.js. По умолчанию вебпак работает в режиме production.

Также мы можем быстро синициализировать вебпак с помощью команды

webpack-cli init

Всего у вебпака 3 режима (mode):

  • development — Provides process.env.NODE_ENV with value development. Enables NamedChunksPlugin and NamedModulesPlugin.
  • production — Provides process.env.NODE_ENV with value production. Enables FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin and UglifyJsPlugin
  • none — Opts out of any default optimization options.

Useful link: modes

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

Для теста, для начала экспортируем класс

export class Greeter ...

А в index.js мы импортируем этот класс

import { Greeter } from "./greeter";

Чтобы запустить вебпак в режиме наблюдения — используется флаг --watch.

Как правило, в package.json создаются новые запии в свойстве scripts и это как правили запись для запуска в режиме разработки, сборка на продакшн и режим dev-serverа.

Добавление сторонней библиотеки.

Например установим jquery.

npm i jquery

Затем импортируем её туда, куда нам надо

import $ from "jquery";

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

Далее, нам нужно пересобрать проект.

Теперь мы можем спокойно использовать jquery как всегда.

Настройка Webpack

Для настройки вебпака, нам нужно создать файл webpack.config.js в корне проекта.

В этом файле нужно создать объект конфигурации.

Для начала нам нужно указать исходный файл. Вебпаку нужен какой то один стартовый файл. За это отвечает свойство entry. Здесь на нужно прописать путь к начальному файлу.

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

  • filename — название файла на выходе
  • path — путь до файла

Указывать нужно именно абсолютный путь до файла, поэтому нужно воспользоваться модулем path от nodejs.

В свойтве path указываем path.resolve(__dirname, название_папки). resolve принимает куски путей и объединяет их. __dirname путь к текущей директории в nodejs.

const path = require("path");

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'public');
    },
    mode: 'development',
    watch: true
}

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

webpack --config webpack.conf.js

Также в файле конфигурации мы можем сразу указать типа сборки (development, production ...). Это можно сделать через свойство mode: 'development'. Также отсюда можем указать и то, чтобы следил за файлами, с помощью свойства watch: true.

Аргумент для типа сборки который указан в package.json имеет больший «вес».

Также мы можем изменить путевой контекст для файла webpack.config.js. Для этого есть свойство context, в котором нужно указать абсолютный путь к папке с исходниками.

context: path.resolve(__dirname, "scr");

Также вебпак может создавать карты для наших файлов — sourcemaps. Для этого используется свойство devtool. Есть несколько режимов. Расмотрим пару из них:

  • eval — карты размещаются в исходном коде.
  • source-map — создается отдельный файл с картами.

Трансформация модулей

Загрузчики используются для трансформации модулей. Т.е. файлы прогоняются через загрузчики для трансформаций. Разные загрузчики отвечаютс за разную трансформацию.

Начнем с транспиляции кода.

Сначала нам нужно установить сам бабель — babel-core и его пресеты. Например пакет для транспиляции es6 в es5 называется babel-preset-env. Чтобы связать babel и webpack используется загрузчик babel-loader.

npm i babel-core babel-preset-env babel-loader -D

Далее перемещаемся в наш файл webpack.config.js для настройки загручика.

Для указания загручиков, создаем свойство module и передаем ему объект.
Первым свойство этого объекта мы передаем rules — в соответствии с какими правилами обрабатывать модули. Значением свойства rules является массив из объектов, где мы указываем что нам делать с файлами.
В этих объектах мы задаем несколько параметров. Один из них test в котором мы указываем с помощью регулярного выражения, какие файлы будут прогоняться через лоадер.

Далее в свойстве use мы указываем какой загрузчик нам необходимо использовать.
Также не забываем про свойство exclude чтобы исключить те папки (с помощью регулярок), содержимое которых мы не хотим прогонять через загрузчик — например node_modules.

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "public")
  },
  mode: "development",
  watch: true,
  devtool: "source-map",
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: "babel-loader"
      }
    ]
  }
};

НО! Пока мы не скажем какие пресеты и модули нужно использовать бабелю, он ничего делать не будет. А для указания этой инфы нам нужно создать файл .babelrc и указать это там. В этом файле используется json формат.

{
    "presets" : [
        "env"
    ]
}

Если не хотите создавать файл .babelrc, то можно указать пресеты сразу в файле конфигурации. Для этого нужно вместо строки в свойстве use указать объект. Где в свойстве loader мы укажем название загрузчика. А в свойстве option можем указать опции, опять же через объект, в котором укажем тоже самое, что и в файле .babelrc.

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: "babel-loader",
        options: {
          presets: ["env", "stage-0"]
        }
      }
    }
  ];
}

Если нам нужно указать несколько пресетов, то для этого сначала их нужно установить, а потом указать дополнительно в массиве presets.

npm i babel-preset-stage-0 -D

Например, пресеты stage-0. Весь функционал который добавляется в ES разбивается на стадии (0,1,2,3,4). И например мы хотим обрабатывать те фичи, который находятся на стадии stage-0. Это те фичи, которые еще на самом первом уровне принятия в стандарт EcmaScript.

Работа со стилями

Так как в вебпак всё является модулем. То мы можем импортировать наш css файл в наш главный js. Чтобы показать зависимости.

import "./styles.js";

По умолчанию js понимает javascript и json файлы. Для того, чтобы wp понимал что делать c css добавим для этого новый загрузчик. А в данном случае загрузчики.
Для начала установим их

npm i css-loader style-loader -D

Теперь в файле webpack.config.js добавляем еще одно правило. Сейчас для css.
Для начала укажем первый лоадер.

//...
rules: [
  {
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
      loader: "babel-loader",
      options: {
        presets: ["env", "stage-0"]
      }
    }
  },
  {
    test: /\.css$/,
    use: "css-loader"
  }
];

Теперь вебпак нормально импортирует css файлы из главного js файла.
Но пока стили не применяются. Укажем при импорте стилей идентификатор styles.

import styles from "./styles.css";

Теперь мы с помощью console.log можем посмотреть что загружает вебпак (загрузчик) в этот идентификатор при обработке.

Мы получаем массив,в котором указаны, массивы, функции. Первым элементом идет массив, который состоит из пути к стилям, второй из самих стилей.

Теперь мы можем вывести в файле index.html путем.

const style = document.createElement("style");
style.textContent = styles[0][1];
app.appendChild(style);

Именно для этого и используется загрузчик style-loader — чтобы вставлять стили в файл.
Для этого нужно добавить его также в массив с лоадерами для css файлов. Указываются в обратном порядке.

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

Это минимальные две вещи, которые были необходимы для загрузки стилей.

  • css loader — отвечает за импортирование модулей
  • style loader — отвечает за размещение стилей на странице

Что касается обработки sass файлов.
То сначала нужно прогнать через sass-loader -> css-loader -> style-loader.
Т.е. нам нужно просто добавить этап перегонки из scss в css.
Добавим еще один загручик. Для начала его нужно установить.

npm i sass-loader -D

Также для корректной работы нам понадобится модуль node-sass

npm i node-sass -g

А затем исправим немного запись в конфиге

{
    test: /\.scss$/,
    use: ['style-loader', 'css-loader', 'sass-loader']
}

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

Еще в css загрзчиках есть возможность включать css модули. Для этого в опциях загрузчика нужно указать modules: true. Благодаря css модулям, классам присваиваются значения, которые в рамках данного файла со стилями гарантированно будут уникальными.

{
    test: /\.scss$/,
    use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              sourceMap: true
            }
          },
          'sass-loader'
          ]
}

Работа с изображениями

В вебпак всё является модулем, поэтому мы можем импортировать картинку в наш js.
Как обычно, наш вебпак не знает изначально что делать с изображениями. Поэтому нам снова нужно установить загрузчик url-loader.

npm i url-loader -D

Теперь переходим в конфигурацию и добавляем запись для этого загрузчика в rules.

{
    test: /\.(jpe?g|png|gif|svg)$/,
    use: 'url-loader'
}

Этот модуль переведет изображение в base64.
Если мы, допустим, хотим установить лимит, после которого картинки не будут трансформироваться в base64, то нам нужно указать это в опциях загрузчика.
Но перед этим нам нужно загрузить еще один загрузчик : file-loader для того чтобы он мог совершать действия над файлами, например, копирование.

npm i file-loader -D

Теперь запишем в наш конфиг

{
    test: /\.(jpe?g|png|gif|svg)$/,
    use: {
        loader: 'url-loader',
        options: {
            limit: 8000
        }
    }
}

Добавим код для создания изображения.

import logo from './webpck.png';

const img = document.createElement('img');
img.src = logo;
img.className = styles.logo;

Мы увидим, что вебпак создал файл и положил в папку public. Он положил её именно в эту папку, потому что она прописана в свойстве output.

Чтобы указать другую папку для изображений, то добавим в опции загрузчика следующую запись outputPath: 'img/'. Тем самым перезаписав выходной путь только для изображений.
Также в опциях мы можем указать шаблон для названий изображений. Также можно для обработки изображений использовать загрузчик, который будет сжимать картинки, но это нужно уже смотреть в документации.

{
    test: /\.(jpe?g|png|gif|svg)$/,
    use: {
        loader: 'url-loader',
        options: {
            limit: 8000,
            outputPath: 'img/',
            name: '[name].[ext]'
        }
    }
}

Плагины

Примерная схема работы
file -> loader -> plugin -> bundle

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

Начнем с сохранения стилей в отдельный файл. Для начала скачаем и установим сам плагин:

npm i extract-text-webpack-plugin -D //deprecated
npm install --save-dev mini-css-extract-plugin

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

//const ExtractTextPlugin = require('extract-text-webpack-plugin'); //deprecated
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

В старой версии в массив передаем новый экземпляр плагина, конструктору которого передаем в качестве аргумента название файла со стилями.

В новой передаем объект, где указываем шаблон для название целового файла, и шаблон для названий чанков.

/* DEPRECATED
plugins: [
    new ExtractTextPlugin('styles.css')
]
*/
plugins: [
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ]

Также этот плагин необходимо настроить, а именно указать для специальный загрузчик. Раньше для этого, там где у нас стоял загрузчик для scss файлов, мы в свойстве use перезапишем значение. Fallback означает, действие, которое произойдет, если что-то пойдет не так.

Теперь мы указываем наш плагин со свойством loader, а затем загрузчики, которые идут перед ним.

/* DEPRECATED
{
    test: /\.scss$/,
    use: ExtractTextPlugin.extract({
        use: 'css-loader',
        fallback: 'style-loader'
    })
}
*/
{
    test: /\.scss$/,
    use: [
        MiniCssExtractPlugin.loader,
        {
            loader: "css-loader",
            options: {
                modules: true,
                sourceMap: true,
                importLoader: 2
            }
        },
        "sass-loader"
    ]
}

Единственное что, для 4й версии вебпака, данный плагин устарел. Для 4й версии теперь используется MiniCssExtractPlugin.

Разделение кода (code splitting)

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

Например, у нас есть две страницы: с авторизацией и с плеером.
На первой используется файл login.js, а на второй player.js.
Логичнее разделить для каждой страницы скрипты, нежели использовать один бандл для всех.

Для начала, нам нужно будет изменить точку вхождения. Вместо строки нужно указать объект, в качестве свойств которого указываются названия бандлов, а их значения — пути к файлам. И сразу надо указать в свойстве output->filename, значение с использованием шаблона [name], где name это название, которое возьмется из свойства entry.

entry: {
  main: './index.js',
  player: './player.js'
},
output: {
  filename: '[name].js',
  path.path.resolve(__dirname, 'public')
}

Посмотрим пример использования разделения на примере фреймворка react. Для этого установим

npm i react react-dom

Для реакта понадобиться дополнительный загрузчик.

npm i babel-preset-react -D

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

resolve: {
  extensions: ['.js', '.json', '.jsx', '*']
}

А также добавить в настройке модуля babel дополнительный пресет и подправить регулярку в test.

{
    test: /\.jsx?$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: ['env', 'stage-0', 'react']
        }
    }
}

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

entry: {
    main: './index.js',
    player: './player.js',
    vendor: ['react', 'react-dom']
}

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

const webpack = require('webpack');

Теперь нам понадобится его метод SplitChunksPlugin. А точнее по умолчанию всё работает как надо… Подробнее можно почитать здесь

С чем мы можем столкнуться. Допустим загрузили мы свои файлы на сервак. Браузер скачал и закэшировал наши файлы. Мы у себя что то обновили, затем залили всё на сервер. А браузер по умолчанию закачивать их не будет, так как название остается прежним. Вебпак позволяет решить и эту проблему также. Для этого мы добавим кусочек в паттерн [chunkhash] названия выходного файла.

output: {
    filename: '[name][chunkhash].js',
    path: path.resolve(__dirname, 'public')
},

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

npm install clean-webpack-plugin --save-dev

Теперь нам нужно зареквайрить его в файле конфигурации

const CleanWebpackPlugin = require('clean-webpack-plugin');

И добавить в plugins еще один экземпляр, но на сей раз класса чистильщика.

plugins: [
    new CleanWebpackPlugin(['public']),
    new MiniCssExtractPlugin({
        filename: "styles.css",
        chunkFilename: "[id].css"
    })
],

Также вебпак позволяет нам генерировать сам html-файл куда потом сам вставляет скрипты и стили. Нам опять понадобится дополнительный плагин. На этот раз — это html-webpack-plugin.

npm i html-webpack-plugin -D

Теперь нужно добавить его в наш файл конфигурации.

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

И добавим экземпляр этого класса в наше свойство plugins. И конструктор этого класса принимает объект в качестве аргумента. Мы можем передать следующие свойства. Например при использовании реакта, мы можем попросить чтобы плагин не генерил весь файл сам, а можно указать шаблон, по которому нужно генерить. Для этого укажем свойство template в которое передадим расположение шаблона.

new HtmlWebpackPlugin({
    template: './index.html',
    title: 'Intro to Webpack'
}),

Webpack Dev Server

WDS не записывает изменения файлов на диске, он хранит их в памяти.

Для начала нужно его установить

npm i webpack-dev-server -D

Чтобы посмотреть что хранится у сервера в памяти, мы можем набрать после хоста сервера в браузере: /webpack-dev-server.

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

devServer: {
  port: 8080
}

Также, можем дополнительно прописать в файле package.json сценарий для запуска.

"start": "webpack-dev-server --mode development --open"

Флаг open для того чтобы браузер автоматически открылся, и флаг mode чтобы указать в каком режиме делать сборку.

Мы можем указать девсерверу из какой папки ему выдавать файлы. По умолчанию он их выдает из корневой директории. Для того чтобы поменять это воспользуемся свойством contentPath в свойстве devSever. В качестве значения нужно указать абсолюный путь.

contentPath: path.resolve(__dirname, 'public');

Мы можем указать в конфиге в свойстве output указать свойство publicPath. Это важное свойство при загрузке по требованию, или загрузке внешних ресурсов, таких как картинки, файлы. Этот параметр указывает общедоступный URL-адрес выходного каталога, когда он ссылается в браузере.

Пока смотрим, что у нас есть в нашем конфиге на текущий момент.

const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    context: path.resolve(__dirname, 'src'),
    entry: {
        main: './index.js',
        player: './player.js',
        vendor: ['react', 'react-dom']
    },
    output: {
        filename: '[name][chunkhash].js',
        path: path.resolve(__dirname, 'public'),
        publicPath: '/'
    },
    devServer: {
        port: 8080,
        contentPath: path.resolve(__dirname, 'public')
    },
    mode: 'development',
    watch: true,
    devtool: 'source-map',
    module: {
        rules: [{
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['env', 'stage-0', 'react']
                    }
                }
            },
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: "css-loader",
                        options: {
                            modules: true,
                            sourceMap: true,
                            importLoader: 2
                        }
                    },
                    "sass-loader"
                ]
            },
            {
                test: /\.(jpe?g|png|gif|svg)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 8000,
                        name: '[name].[ext]',
                        outputPath: 'img/'
                    }
                }
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['public']),
        new HtmlWebpackPlugin({
            template: './index.html',
            title: 'Intro to Webpack'
        }),
        new MiniCssExtractPlugin({
            // Options similar to the same options in webpackOptions.output
            // both options are optional
            // filename: "[name].css",
            filename: "[name].[chunkhash].css",
            chunkFilename: "[id].css"
        })
    ],
    resolve: {
        extensions: ['.js', '.json', '.jsx', '*']
    }
}

Когда у нас файлы еще не сгенерены, или генерация допустим html будет серверм, но чтобы вебпак знал где искать файлы, относительно текущей страницы, то нам нужно прописать в настройках вебсервера еще свойство publicPath. Т.е. физически на диске этих файлов не будет, но сам сервер в памяти будет обращаться к этим файлам именно по этому пути.
Например:

devServer: {
    port: 8080,
    contentPath: path.resolve(__dirname, 'public'),
    publicPath: '/assets/'
}

В общем, его можно указать как в девсервере, так и в output.

Часть информации бралась из видео одного автора codedojo

You might also like More from author

Leave A Reply

Your email address will not be published.