Configuration

Prevent Duplication

Entry dependencies

The allows to share the modules between the chunks:

webpack.config.js

If we’re going to use multiple entry points on a single HTML page, is needed too, otherwise we could get into trouble described here.

webpack.config.js

And here’s the result of build:

As you can see there’s another file generated besides , and .

Although using multiple entry points per page is allowed in webpack, it should be avoided when possible in favor of an entry point with multiple imports: . This results in a better optimization and consistent execution order when using script tags.

The allows us to extract common dependencies into an existing entry chunk or an entirely new chunk. Let’s use this to de-duplicate the dependency from the previous example:

webpack.config.js

With the configuration option in place, we should now see the duplicate dependency removed from our and . The plugin should notice that we’ve separated out to a separate chunk and remove the dead weight from our main bundle. Let’s do an to see if it worked:

Here are some other useful plugins and loaders provided by the community for splitting code:

mini-css-extract-plugin: Useful for splitting CSS out from the main application.

Setup

The goals of development and production builds differ greatly. In development, we want strong source mapping and a localhost server with live reloading or hot module replacement. In production, our goals shift to a focus on minified bundles, lighter weight source maps, and optimized assets to improve load time. With this logical separation at hand, we typically recommend writing separate webpack configurations for each environment.

While we will separate the production and development specific bits out, note that we’ll still maintain a «common» configuration to keep things DRY. In order to merge these configurations together, we’ll use a utility called . With the «common» configuration in place, we won’t have to duplicate code within the environment-specific configurations.

Let’s start by installing and splitting out the bits we’ve already worked on in previous guides:

project

webpack.common.js

webpack.dev.js

webpack.prod.js

In , we now have setup our and configuration and we’ve included any plugins that are required for both environments. In , we’ve set to . Also, we’ve added the recommended for that environment (strong source mapping), as well as our simple configuration. Finally, in , is set to which loads , which was first introduced by the tree shaking guide.

Note the use of calls in the environment-specific configurations to include our common configuration in and . The tool offers a variety of advanced features for merging but for our use case we won’t need any of that.

Структура проекта

Общая структура проекта представлена ниже:

Под favicon выделена целая папка, так как в современном web обычным одним ico файлом не обойтись. Но для примера используется только этот один файл.

Спорным решением может показаться разделение картинок на две папки: и . Но здесь использовал идеологию расположения файлов из WordPress. На мой взгляд, кидать все изображения в одну папку — не очень хорошая идея.

Для работы с проектом использую Visual Studio Code, которым очень доволен. Особенно мне нравится, что командная строка встроена в программу и вызывается через Ctrl + `.

Сделаем болванку Node.js проекта. Для этого создадим папку нашего проекта с вышеописанной структурой и перейдем в неё в командной строке, где вызовем команду для создания файла .

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

Установим три общих пакета, которые нам потребуются в любом случае: , (работу с командной строкой в webpack вынесли в отдельный пакет) и (для запуска локального сервера, чтобы в браузере сразу отображались сохраненные изменения проекта).

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

Writing Your Own Templates

Let’s say for example you wanted to put a webpack bundle into the head of your
HTML as well as the body. Your template might look like this:

<!DOCTYPE html><html><head><metahttp-equiv="Content-type"content="text/html; charset=utf-8"/><title>My App</title><scriptsrc="{%=o.htmlWebpackPlugin.files.chunks.head.entry%}"></script></head><body><scriptsrc="{%=o.htmlWebpackPlugin.files.chunks.main.entry%}"></script></body></html>

To use this template, configure the plugin like this:

{  entry'index.js',  output{    path'dist',    filename'index_bundle.js'},  pluginsnewHtmlWebpackPlugin({      template'src/assets/my_template.html'})}

Alternatively, if you already have your template’s content in a String, you
can pass it to the plugin using the option:

pluginsnewHtmlWebpackPlugin({    templateContent templateContentString})

The option can also be a function to use another template language like jade:

pluginsnewHtmlWebpackPlugin({templateContentfunction(templateParams,webpackCompiler){}})

Note the plugin will throw an error if you specify both and
.

The variable in the template is the data that is passed in when the
template is rendered. This variable has the following attributes:

  • : data specific to this plugin

    • "htmlWebpackPlugin"{"files"{"css""main.css","js""assets/head_bundle.js","assets/main_bundle.js","chunks"{"head"{"entry""assets/head_bundle.js","css""main.css"},"main"{"entry""assets/main_bundle.js","css"},}}}

      If you’ve set a publicPath in your webpack config this will be reflected
      correctly in this assets hash.

    • : the options hash that was passed to
      the plugin. In addition to the options actually used by this plugin,
      you can use this hash to pass arbitrary data through to your template.

  • : the webpack configuration that was used for this compilation. This
    can be used, for example, to get the ().

Тонкая настройка

Мы можем тонко настраивать webpack для любого режима (продакшн или девелопмент). Для этого воспользуемся возможностью возвращать вместо объекта конфигурации функцию:

webpack.config.js

JavaScript

module.exports = (env={}, argv={}) => {
  return {
    module: {},
    plugins: []
  }
};

1
2
3
4
5
6
7
8

module.exports=(env={},argv={})=>{

  return{

    module{},

    plugins

  }

};

 

В параметре env передаются переменные окружения, а в переменной – параметры командной строки (например, опция ). Благодаря этому мы можем подключать/отключать нужные плагины и лоадеры, например, вот так:

webpack.config.js

JavaScript

plugins: .filter(plugin => !!plugin);

1
2
3
4
5
6
7

plugins

  argv.mode===»development»

    ?newHtmlWebpackPlugin()

    null

.filter(plugin=>!!plugin);

 

Неплохо, правда?

NPM-скрипты

– это уже довольно длинная команду, которую не хочется набирать много раз. Давайте сразу перенесем ее в , а то она ведь может и разрастись.

Просто добавьте секцию scripts, если ее еще нет, и пару новых скриптов:

package.json

JavaScript

«scripts»: {        
«build»: «webpack —mode production»,        
«build:dev»: «webpack —mode development»
}

1
2
3
4
5
6

«scripts»{        

«build»»webpack —mode production»,        

«build:dev»»webpack —mode development»

}
 

Теперь можно вызывать эти команды гораздо проще:

Shell

yarn build 
(npm run build)

1
2
3
4

yarn build 

(npm run build)

 

и

Shell

yarn build:dev
(npm run build:dev)

1
2
3
4

yarn builddev

(npm run builddev)

 

Specify the Mode

Many libraries will key off the variable to determine what should be included in the library. For example, when is not set to some libraries may add additional logging and testing to make debugging easier. However, with set to they might drop or add significant portions of code to optimize how things run for your actual users. Since webpack v4, specifying automatically configures for you:

webpack.prod.js

If you’re using a library like , you should actually see a significant drop in bundle size after adding . Also, note that any of our local code can key off of this as well, so the following check would be valid:

src/index.js

output

JsonpTemplatePlugin

Chunks are wrapped into JSONP-calls. A loading algorithm is included in entry chunks. It loads chunks by adding a tag.

are the output options.

is the JSONP function.

is used as path for loading the chunks.

is the filename under that chunks are expected.

NodeTemplatePlugin

Chunks are wrapped into Node.js modules exporting the bundled modules. The entry chunks loads chunks by requiring them.

are the output options.

is the filename under that chunks are expected.

Chunks are loaded by . Else it’s similar to .

are the output options.

SourceMapDevToolPlugin

Decorates the templates by generating a SourceMap for each chunk.

the filename template of the SourceMap. , , , and are replaced. If this argument is missing, the SourceMap will be inlined as DataUrl.

HotModuleReplacementPlugin

Add support for hot module replacement. Decorates the templates to add runtime code. Adds API.

the filename for hot update chunks.

the filename for the hot update manifest.

JSON function name for the hot update.

Сборка HTML страниц

Перейдем к самому веселому: к сборке HTML страниц, где у меня возникли самые большие трудности.

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

В качестве шаблонизатора HTML будем использовать шаблонизатор по умолчанию lodash. Вот так будет выглядеть типичная HTML страница до сборки:

Вначале в переменной прописываем все наши переменные страницы, которые хотим использовать на этой странице. Потом встраиваем шаблоны шапки и футера через .

Важное уточнение. В статьях про сборку HTML страниц через обычно подключают встраиваемые шаблоны просто через команду:. Но при этом в этих встраиваемых шаблонах синтаксис lodash работать не будет (я так и не понял, почему так происходит)

И данные из переменной туда не передадутся. Поэтому принудительно говорим webpack, что мы встраиваем именно шаблон, который надо обработать как lodash шаблон

Но при этом в этих встраиваемых шаблонах синтаксис lodash работать не будет (я так и не понял, почему так происходит). И данные из переменной туда не передадутся. Поэтому принудительно говорим webpack, что мы встраиваем именно шаблон, который надо обработать как lodash шаблон.

Теперь мы можем использовать полноценные lodash синтаксис в встраиваемых шаблонах. В коде файла ниже через печатаем заголовок статьи.

В пакете html-webpack-plugin генерировать несколько HTML страниц:

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

Для этого в файле внесем следующие изменения:

Функция будет осуществлять поиск всех HTML страниц

Обратите внимание, что в коде функции есть настройка , которая говорит Webpack, что не нужно встраивать ссылки на js и css файл в HTML код самостоятельно: мы сделаем всё сами вручную в шаблонах и

Также нужно отметить, что встраиваемые шаблоны обрабатываются плагином (содержимое файла просто загрузить как текст), а не , как чаще всего предлагают. И также, как в случае с CSS, не использую пакеты или .

И остается последний необязательный момент для работы с HTML. JavaScript файл и CSS файл у нас будут минимифицроваться. А вот HTML файлы хочу, наоборот, сделать красивыми и не минифицировать. Поэтому после сборки всех HTML файлов хочется пройтись по ним каким-то beautify плагином. И тут меня ждала подстава: не нашел способа как это сделать в Webpack. Проблема в том, что обработать файлы нужно после того, как будут вставлены встраиваемые шаблоны.

Нашел пакет html-cli, который может это сделать независимо от Webpack. Но у него 38 установок в месяц. То есть это означает два варианта: либо никому не нужно приводить к красивому внешнему виду HTML файлы, либо есть другое популярное решение, о котором я не знаю. А ради только одной этой функции Gulp прикручивать не хочется.

Устанавливаем этот плагин:

И в файле прописываем еще два скрипта, которые после работы Webpack будут приводить к красивому внешнему виду HTML файлы с установкой табуляции в два пробела.

Update 2018.04.11 Обратите внимание на то, что в команду я добавил еще , который очищает папку перед сборкой. Поэтому для итоговой сборки рекомендую использовать не команду *npm run build, а команду npm run build-and-beautify

Поэтому для итоговой сборки рекомендую использовать не команду *npm run build, а команду npm run build-and-beautify.

Последний штрих

Мы почти закончили. Мы настроили Webpack для сборки нашего файла main.js и вывода его в bundle.js в наш каталог /public.

Теперь мы можем использовать ES-модули в JavaScript. Помните, как функция click вызывалась до того, как она появилась в браузере? Теперь мы можем использовать синтаксис export и import, чтобы экспортировать функцию из game.js и вызывать ее в main.js, чтобы полностью избежать этой проблемы:

main.js

// В начале main.js
import click from './game'

Наконец, нам нужно внести небольшое изменение в наш HTML-файл. Прежде чем мы узнали о Webpack, index.html загрузил два отдельных файла JavaScript. Теперь весь код в этих файлах будет упакован в bundle.js — так что мы можем просто указать тег скрипта на bundle.js.

Идем дальше и заменим ваши теги script ссылкой на bundle.js:

  <!-- <script src="../src/game.js" defer></script>
  <script src="../src/main.js" defer></script> -->
  <script src="bundle.js" defer></script>

Теперь запустите open public/index.html.

Ваша программа выглядит и функционирует точно так же, как и раньше? Супер! Вы все сделали правильно.

Загляните в свой DevTools и перейдите на вкладку «Sources». Щелкнуть на bundle.js и увидите свой прекрасно упакованный JavaScript.

Добавление тестовых сценариев в сборку

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

Затем создайте новый каталог и новый файл , содержащий следующий фрагмент:

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

Однако, если выполнить , будет ошибка «Код JavaScript недопустим», потому что он содержит ключевое слово , которое Node в данный момент не поддерживает. Но что если обойти это ограничение, используя Webpack для управления зависимостями тестового кода?

Прим.: это можно легко исправить, просто используя вместо в тестовых файлах. Но тестовый код также будет проходить через процесс сборки, если вы тестируете JavaScript-код типа Flow, который использует аннотации, или веб-приложения Vue.js, которые используют файлы . Все они должны быть преобразованы в обычный JavaScript.

Список тестовых инструкций:

  1. Webpack строит граф зависимостей, начинающийся с тестовых файлов, а не с файлов приложения.
  2. Webpack создаёт файл JavaScript, содержащий весь тестовый код и его зависимости без ключевого слова .
  3. Выполняются тесты, запуская Mocha для этого JavaScript-файла.

Всё это будет выглядеть следующим образом:

Как можно увидеть, будут происходить две отдельные сборки. Одна из которых содержит код приложения в качестве точки входа и папку в качестве выходной директории, а другая — тестовые файлы в качестве точки входа и папку в качестве выходного каталога. Итак, давайте обновим конфигурационный файл, чтобы Webpack поддерживал вторую сборку:

Давайте разберёмся, что этот код делает. В строке 4 есть оператор , который  выполняется, если системная переменная имеет непустое значение. Так что, если вы выполните , то вам придётся вводить оператор , но это не потребуется, если просто выполнить .

Внутри оператора происходит выбор, какой JS-файл является точкой входа. Вместо уже установленного выходного каталога будет папка . А вместо точки входа — массив файлов, соответствующих глобальному выражению . Другими словами, это все файлы, которые находятся в каталоге и имеют путь, заканчивающийся на .

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

Наконец, в разделе вашего добавьте следующую строку:

Это означает, что когда вы выполняете , создаётся каталог (каталог сборки тестирования) с помощью Webpack, затем запускается Mocha для этой сборки, и, наконец, код удаляет каталог , поскольку он больше не используется (Commit 7).

Bundle Analysis

Once you start splitting your code, it can be useful to analyze the output to check where modules have ended up. The official analyze tool is a good place to start. There are some other community-supported options out there as well:

  • webpack-chart: Interactive pie chart for webpack stats.
  • webpack-visualizer: Visualize and analyze your bundles to see which modules are taking up space and which might be duplicates.
  • webpack-bundle-analyzer: A plugin and CLI utility that represents bundle content as a convenient interactive zoomable treemap.
  • webpack bundle optimize helper: This tool will analyze your bundle and give you actionable suggestions on what to improve to reduce your bundle size.
  • bundle-stats: Generate a bundle report(bundle size, assets, modules) and compare the results between different builds.

Без конфигурации

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

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

index.js

JavaScript

const hello = subject => console.log(`Hello, ${subject}!`);
hello(‘Dolly’);

1
2
3
4

consthello=subject=>console.log(`Hello,${subject}!`);

hello(‘Dolly’);

 

Примечание: важно соблюдать предложенную мной структуру файлов (). Почему, станет понятно очень скоро

Теперь создадим в нашей папке комфортную среду для разработки. Начнем с файла . Чтобы создать его, воспользуйтесь командой ().

Примечание: здесь и далее я буду использовать пакетный менеджер yarn, но вы с таким же успехом можете применять npm. На всякий случай буду в скобочках писать альтернативную команду.

Теперь установим сам webpack и инструмент webpack-cli для работы с ним из терминала.

Shell

yarn add webpack webpack-cli —dev
(npm i -D webpack webpack-cli)

1
2
3
4

yarn add webpack webpack-cli—dev

(npmi-Dwebpack webpack-cli)

 

А теперь просто запустим webpack!

Shell

yarn webpack
(npx webpack)

1
2
3
4

yarn webpack

(npx webpack)

 

В консоли появилось предупреждение (терпение, мы к нему очень скоро вернемся), но тем не менее работа выполнена. У нас появилась новая папка , а в ней файл , в котором вы найдете минифицированную версию вашего .

Что произошло?

Webpack взял исходник, который вы ему предоставили, и «собрал» его в один файл. Пока ничего впечатляющего: один исходник – один файл результата, но это лишь начало, поверьте.

Как webpack узнал, где искать ваш исходник? Очень просто, он по умолчанию начинает с файла . Это конвенция, которая позволяет нам не говорить лишних слов, а просто сразу начинать работать. Точно так же по умолчанию webpack кладет получившуюся сборку в .

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

hello.js

JavaScript

export default (subject) => console.log(`Hello, ${subject}!`);

1
2
3

export default(subject)=>console.log(`Hello,${subject}!`);

 

index.js

JavaScript

import hello from ‘./hello.js’;
hello(‘Dolly’);

1
2
3
4

import hello from’./hello.js’;

hello(‘Dolly’);

 

Запустите webpack (), и вы получите тот же самый результат!

Таким образом, главный талант webpack – собирать отдельные модули в один большой бандл. Но это не единственное, что он может.

Описание решения

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

В разделе Конфиги с решением вы найдете полный код.

Указываем две точки входа , и сделать динамическим имя для исхзодящих файлов

  // взято из webpack.base.conf.js
  // Константа PATHS есть в спойлере в этой статье
  entry: {
    back: PATHS.src_back,
    front: PATHS.src_front,
  },
  output: {
    filename: `js/.js?v=`,
    path: PATHS.dist,
    publicPath: '/'
  },

Вычленяем из код для чанков и

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

Параметр — Это регулярное выражение, которому должен соответствовать буть к файлу

optimization: {
  splitChunks: {
    cacheGroups: {
      common_vendors: {
        test: /node_modules(vue|vue-router|vuex|axios|@riophaevue-treeselect)/,
        name: 'common_vendors', // имя чанка
        chunks: 'initial',
        enforce: true,
      },
      buefy: {
        test: /node_modules(buefy)/,
        name: 'buefy',
        chunks: 'initial',
        enforce: true,
      },
    }
  }
},

Добавить обработку двух шаблонов

Ключевой момент — настройка . Тут мы определяем, какие чанки подгружаем в шаблон.

и мы определили на предыдущем этапе, а чанки и формируются какбы автоматически по имени точки входа, указанной в параметре

  // взято из webpack.dev.conf.js и webpack.build.conf.js
  // baseWebpackConfig (PATHS) есть в спойлере в этой статье
  new HtmlWebpackPlugin({
    template: `${baseWebpackConfig.externals.paths.src_back}/index.html`,
    filename: './index.html',
    title: "mode_development", // Нужно для костыльного условия в шаблоне
    inject: false,
    chunks: baseWebpackConfig.externals.paths.back_chunks, // 
  }),
  
  // Обработка шаблона точки входа "front"
  new HtmlWebpackPlugin({
    template: `${baseWebpackConfig.externals.paths.src_front}/index.html`,
    filename: './pp.html',
    title: "mode_development",
    inject: false,
    chunks: baseWebpackConfig.externals.paths.front_chunks, // 
  }),

Инъекция файлов в HTML (pug/шмаг) шаблон

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

В принципе, это можно сделать и вручную, но давайте не будем так делать:)

  <!-- Так не делаем -->
  <script src="/js/common_vendors.js"></script>
  <script src="/js/buefy.js"></script>
  <script src="/js/back.js"></script>

В шаблоне мы можем вызвать конструкцию для подключения JS и CSS:

  <!-- Подгружаем CSS Файлы -->
  <% for (var css in htmlWebpackPlugin.files.css) { %>
  <link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css %>">
  <% } %>

  <div id="app">
    ...
  </div>
  
  <!-- Подгружаем JS Файлы -->
  <% for (var js in htmlWebpackPlugin.files.js) { %>
  <script src="<%= htmlWebpackPlugin.files.js %>"></script>
  <% } %>

Dynamic Imports

Two similar techniques are supported by webpack when it comes to dynamic code splitting. The first and recommended approach is to use the that conforms to the ECMAScript proposal for dynamic imports. The legacy, webpack-specific approach is to use . Let’s try using the first of these two approaches…

Before we start, let’s remove the extra and from our configuration in the above example as they won’t be needed for this next demonstration:

webpack.config.js

We’ll also update our project to remove the now unused files:

project

Now, instead of statically importing , we’ll use dynamic importing to separate a chunk:

src/index.js

The reason we need is that since webpack 4, when importing a CommonJS module, the import will no longer resolve to the value of , it will instead create an artificial namespace object for the CommonJS module. For more information on the reason behind this, read webpack 4: import() and CommonJs.

Let’s run webpack to see separated out to a separate bundle:

As returns a promise, it can be used with functions. Here’s how it would simplify the code:

src/index.js

Extra Files

To add any extra files for usage in your template, simply add them to . It can either be a single file, or an array of files.

pluginsnewHtmlWebpackPlugin({    extraFiles'cat.png',    template'extraFiles.html'})

The file will then be available in the template, under the object .
The name of the object will be the filename without the extension, so watch out for collisions.

If the template looks like this:

<!DOCTYPE html><html><body><imgsrc="{%=o.htmlWebpackPlugin.files.extraFiles.cat%}"alt="kewt kitten"/></body></html>

It will result in the following output-html:

<!DOCTYPE html><html><body><imgsrc="82ad978dbbb32d586fa123b28e03fc37.png"alt="kewt kitten"/></body></html>
Добавить комментарий

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

Adblock
detector