Webpack на практике: с нуля до создания автотестов
Перевод статьи Webpack: From 0 to automated testing
Язык JavaScript повсеместно используется для создания больших веб-сервисов. Для таких проектов приходится импортировать много стороннего кода (Lodash, React, Angular и др.). Из-за этого код усложняется, и в нём гораздо чаще возникают ошибки. Чем больше в вашем коде будет зависимостей, тем большей головной болью станет подключение тегов <script>
в правильном порядке.
Webpack создаёт граф зависимостей для JavaScript, CSS и прочих, выдавая однофайловые сборки кода так, чтобы вы могли импортировать все необходимые ресурсы JavaScript всего одним тегом <script>
.
Это руководство поможет создать сборку для небольшого веб-приложения, а затем научит вас использовать Webpack для настройки автоматических тестов.
Создание приложения
В качестве тестового приложения мы сделаем карту для ленивцев, которая поможет найти магазины в Кембридже, где продают травяной чай из гибискуса. Потому что каждый ленивец в кембриджском заповеднике Fresh Pond знает, что чай из гибискуса — лучший чай, чтобы умерить свой горячий темперамент!
Прим.: на самом деле в заповеднике Fresh Pond не обитают ленивцы, но они правда любят вкусные цветы гибискуса после долгого дня, проведённого на деревьях.
Создайте каталог с именем webpack-mocha-tutorial
, в него добавьте другой каталог app/src
и запустите пакетный менеджер npm init
или yarn init
. Исходный код приложения находится здесь. Также в статье будут ссылки на коммиты, чтобы прослеживать изменения кода по ходу чтения.
Основная структура приложения будет выглядеть так:
- У вас есть файл
app/src/distance.js
, экспортирующий функцию, которая запускает формулу вычисления расстояния (на самом деле нужно использовать ортодому) и функцию, которая сообщает, какая точка из массива точек ближе всего находится.
// Функция distance() принимает 2 значения, представленных // числами x и y, и возвращает расстояние между ними // // [TODO] Используйте ортодому function distance(p2, p1) { let yDist = p2.y - p1.y; let xDist = p2.x - p1.x; return Math.sqrt(Math.pow(yDist, 2) + Math.pow(xDist, 2)); } // sortByDistance принимает ваше местоположение и массив точек // и возвращает отсортированный массив точек function sortByDistance(myPt, points) { return points.sort( (pt1, pt2) => distance(pt1, myPt) - distance(pt2, myPt)); }
- Также у вас есть файл
app/src/page.js
, который использует код изdistance.js
, чтобы вывести ближайший магазин из списка, а затем отобразить его на странице.
let stores = [ {name: "Cambridge Naturals", x: -71.1189, y: 42.3895}, {name: "Sarah's Market", x: -71.1311, y: 42.3823}, {name: "Whole Foods Fresh Pond", x: -71.1420, y: 42.3904}, ]; let here = {name: "You are here", x: -71.1470, y: 42.3834}; let nearest = sortByDistance(here, stores)[0]; document.getElementById("nearest-store").innerHTML = nearest.name;
- И, наконец, у вас есть страница
index.html
.
<!DOCTYPE html> <html> <head> <title>Ближайший магазин чая из гибискуса</title> </head> <body> <p>Nearest store is <span id="nearest-store"></span></p> <script src="app/src/distance.js"></script> <script src="app/src/page.js"></script> </body> </html>
Общая структура каталогов такова:
Таким образом, файл distance.js
определяет функции расстояния, затем файл page.js
запускает их, помещая результат функции sortByDistance()
в дерево документов (DOM). Но если посмотреть на зависимость между файлами, то видно, что файл page.js
зависит от файла distance.js
, а не наоборот (Commit 2).
Добавляем Webpack
Для работы с Webpack необходимо его установить с помощью npm или yarn:
$ yarn add --dev webpack webpack-cli
Теперь у вас подключён Webpack и доступна его командная строка. Но прежде, чем можно будет запустить сборку, файл page.js
должен импортировать код из distance.js
. А distance.js
экспортирует свои функции с помощью строки:
module.exports = {distance, sortByDistance};
И чтобы page.js
мог использовать экспортированную функцию sortByDistance()
, добавляем строку:
import {sortByDistance} from "./distance";
Отлично, теперь все зависимости JavaScript связаны. Используем Webpack для создания приложения. Выполним следующую команду:
$ npx webpack app/src/page.js
Сейчас вы должны увидеть новый файл dist/main.js
, который содержит весь ваш код из page.js
и distance.js
. Далее получаем index.html
с импортом dist/main.js
вместо всех скриптов из app/src
, изменив код страницы следующим образом:
<!DOCTYPE html> <html> <head> <title>Ближайший магазин чая из гибискуса</title> </head> <body> <p>Ближайший магазин: <span id="nearest-store"></span></p> <script src="dist/main.js"></script> </body> </html>
Теперь можете открыть страницу в браузере, код по-прежнему должен работать. Поскольку файл main.js
содержит весь код из distance.js
и page.js
, можно импортировать всё из одного файла.
Frontend developer (Vue)
Sportmaster Lab, Липецк, до 130 000 ₽
tproger.ru Вакансии на tproger.ru
Это работает так: с помощью команды $ npx webpack app/src/page.js
вы указываете, что отправной точкой (в терминологии Webpack — точкой входа в ваш код JavaScript) является page.js
. Webpack читает файл page.js
и видит в нём строку import {sortByDistance} from ./distance
. Теперь он знает, что distance.js
является зависимостью к page.js
. И из всех зависимостей в вашем коде Webpack строит граф и использует его для построения пакетного JavaScript-файла dist/main.js
(Commit 3).
Webpack строит граф зависимостей из точки входа, page.js
Между прочим, это также работает, когда ваш код импортирует сторонние зависимости в каталог node_modules
Прим. перев.: все модули, используемые в npm, по умолчанию подключаются из директории node_modules
.
Давайте попробуем выполнить некоторые манипуляции с графом зависимостей с помощью jQuery вместо document.getElementById()
. Сначала установим jQuery:
$ yarn add --dev jquery
Затем обновим page.js
, чтобы включить в него jQuery:
import {sortByDistance} from "./distance"; import $ from "jQuery"; let stores = [ {name: "Cambridge Naturals", x: -71.1189, y: 42.3895}, {name: "Sarah's Market", x: -71.1311, y: 42.3823}, {name: "Whole Foods Fresh Pond", x: -71.1420, y: 42.3904}, ]; let here = {name: "You are here", x: -71.1470, y: 42.3834}; let nearest = sortByDistance(here, stores)[0]; $("#nearest-store").html(nearest.name);
Теперь граф зависимостей выглядит так:
Ваш новый граф зависимостей, где page.js включает jQuery
И если выполнить $ npx webpack app/src/page.js
и перезагрузить index.html
(несмотря на то, что размер файла dist/main.js
намного больше из-за кода jQuery) приложение по-прежнему работает.
Прежде чем продолжить, перейдите в файл package.json
и добавьте эти три строчки:
"scripts": { "build": "webpack app/src/page.js" }
Теперь, чтобы запустить сборку пакета, можно просто выполнить $ yarn build
вместо $ npx webpack app/src/page.js
. Если команда для сборки изменится, будет удобнее просто обновить её в файле package.json
с помощью новой команды сборки и вы по-прежнему можете выполнять сборку с помощью $ yarn build
, вместо того чтобы привыкать к запуску новой команды (Commit 4).
Настройка Webpack с помощью файла webpack.config.js
То, что можно сделать с помощью команды $ npx webpack app/src/page.js
, является стандартным режимом работы Webpack. Если выполнить $ webpack [entry-file.js]
, то он создаст граф зависимостей из входного файла и выдаст пакетный файл dist/main.js
. Но задать расположение точек входа и выхода программы можно, настроив файл конфигурации. Поместите следующий код в файл с именем webpack.config.js
:
module.exports = { entry: __dirname + "/app/src/page.js", output: { path: __dirname + "/dist/", } }
Теперь можно выполнить $ npx webpack
или сделать ту же сборку, что и раньше, без указания точки входа в программу в аргументах командной строки, т. к. теперь всё это есть в webpack.config.js
. Также это значит, что нужно обновить скрипт файла package.json
следующим образом:
"build": "webpack",
Если в файле конфигурации изменить путь вывода на что-то вроде __dirname + "/somewhere_else"
, то при повторном выполнении команды $ yarn build
пакетный файл будет помещён в somewhere_else/main.js
(Commit 5).
Файл конфигурации предназначен не только для настройки расположения входных и выходных файлов. Также можно настроить что именно Webpack делает, когда встречает файлы разных типов, используя специальные загрузчики, которые на самом деле являются JavaScript-программами, преобразующими ваш код. Например, в файле конфигурации может быть правило, определяющее, что, если Webpack встречает файл TypeScript в своём графе зависимостей, этот файл отправляется в загрузчик, который преобразует его из TypeScript в обычный JavaScript.
Загрузчик, который будет использоваться далее, — это Babel. Если вы не использовали его раньше, Babel — это инструмент, который берёт код JS, использующий современные функции, и преобразует его в эквивалент, совместимый со старыми версиями JavaScript. Это позволяет вашему приложению работать в старых браузерах или в браузерах, которые ещё не поддерживают некоторые новые функции JavaScript. В конце концов, некоторые ленивцы не обновляли свои браузеры с 2009 года. Некоторая часть написанного кода не будет работать в браузере 2009 года:
return points.sort((pt1, pt2) => distance(pt1, myPt) — distance(pt2, myPt));
Здесь используется стрелочная функция и старые браузеры её не воспринимают. Поэтому используем загрузчик и отправим эту функцию в прошлое. Для начала выполним следующее:
$ yarn add --dev babel-core babel-loader@7.1.5 babel-preset-env
Затем в файле webpack.config.js
добавим следующий код в module.exports
:
module: { rules: [ { test: /.js$/, exclude: ["/node_modules/"], use: [ { loader: "babel-loader", options: { presets: ["env"], }, }, ], }, ], },
Этот код добавит новое правило в ваш Webpack. Если в дереве зависимостей Webpack встречает файл, который заканчивается на .js
(например, distance.js
), и этот файл отсутствует в папке node_modules
(например, jQuery), то к этому файлу применяется данное правило.
Любой файл, который соответствует этому правилу, проходит через все загрузчики в блоке use
(в данном случае только через загрузчик babel). Таким образом, файлы distance.js
и page.js
пройдут через загрузчик, что приведёт к удалению стрелочной функции в distance.js
, а затем Webpack продолжит свой путь сборки. Тем временем, как только Webpack встречает jQuery, он просто загружает этот код как он есть без загрузчиков, поскольку jQuery находится в каталоге node_modules
.
Теперь, если выполнить $ yarn build
и посмотреть исходный код в dist/main.js
, фрагмент, соответствующий вашей функции сортировки, использует ключевое слово function
, а не стрелочную функцию (Commit 6).
Подсвеченный код на верхнем изображении — это функция sortByDistance()
из dist/main.js
до использования загрузчика, а подсвеченный код внизу — та же функция после добавления загрузчика. Обратите внимание на то, как выше мы используем стрелочную функцию, а ниже присутствует уже знакомое браузеру 2009 года ключевое слово function
.
Теперь приложение готово к работе на устаревших браузерах. Чтобы этот код был поддерживаемым, следует написать несколько тестов для него.
Добавление тестовых сценариев в сборку
Добавим несколько тестовых сценариев в файл distance.js
. Для этого будем использовать Mocha, пакетный инструмент для написания тестов, и Chai в качестве нашей библиотеки установок. Выполните следующую команду:
$ yarn add --dev mocha chai
Затем создайте новый каталог app/test
и новый файл app/test/distance.test.js
, содержащий следующий фрагмент:
import {expect} from "chai"; import {distance, sortByDistance} from "../src/distance"; describe("distance", function() { it("calculates distance with the good ol' Pythagorean Theorem", function() { let origin = {x: 0.0, y: 0.0}; let point = {x: 3.0, y: 4.0}; expect(distance(point, origin)).to.equal(5); }); }); describe("sortByDistance", function() { it("sortsByDistance", function() { let places = [ {name: "Far away", x: 100, y: 50}, {name: "Nearby", x: 20, y: 10}, ]; let origin = {name: "Origin", x: 0, y: 0}; let sorted = sortByDistance(origin, places); expect(sorted[0].name).to.equal("Nearby"); expect(sorted[1].name).to.equal("Far away"); }); });
Теперь у вас есть тестовые сценарии для функций distance()
и sortByDistance()
, устанавливающие, что distance()
вычисляет формулу расстояния, а sortByDistance()
сортирует массивы координат с помощью формулы расстояния, используя наборы тестов Mocha и установки Chai. Довольно стандартная тестовая настройка.
Однако, если выполнить $ mocha app/test/distance.test.js
, будет ошибка «Код JavaScript недопустим», потому что он содержит ключевое слово import
, которое Node в данный момент не поддерживает. Но что если обойти это ограничение, используя Webpack для управления зависимостями тестового кода?
Прим.: это можно легко исправить, просто используя require
вместо import
в тестовых файлах. Но тестовый код также будет проходить через процесс сборки, если вы тестируете JavaScript-код типа Flow, который использует аннотации, или веб-приложения Vue.js, которые используют файлы .vue
. Все они должны быть преобразованы в обычный JavaScript.
Список тестовых инструкций:
- Webpack строит граф зависимостей, начинающийся с тестовых файлов, а не с файлов приложения.
- Webpack создаёт файл JavaScript, содержащий весь тестовый код и его зависимости без ключевого слова
import
. - Выполняются тесты, запуская Mocha для этого JavaScript-файла.
Всё это будет выглядеть следующим образом:
Как можно увидеть, будут происходить две отдельные сборки. Одна из которых содержит код приложения в качестве точки входа и папку dist
в качестве выходной директории, а другая — тестовые файлы в качестве точки входа и папку test-dist
в качестве выходного каталога. Итак, давайте обновим конфигурационный файл, чтобы Webpack поддерживал вторую сборку:
let glob = require("glob"); let entry = __dirname + "/app/src/page.js"; let outputPath = __dirname + "/dist/"; if (process.env.TESTBUILD) { entry = glob.sync(__dirname + "/app/test/**/*.test.js"); outputPath = __dirname + "/test-dist/"; } module.exports = { entry: entry, output: { path: outputPath, }, // остальная часть config-файла остаётся прежней
Давайте разберёмся, что этот код делает. В строке 4 есть оператор if
, который выполняется, если системная переменная TESTBUILD
имеет непустое значение. Так что, если вы выполните $ TESTBUILD=true webpack
, то вам придётся вводить оператор if
, но это не потребуется, если просто выполнить $ npx webpack
.
Внутри оператора if
происходит выбор, какой JS-файл является точкой входа. Вместо уже установленного выходного каталога dist
будет папка test-dist
. А вместо точки входа app/src/path.js
— массив файлов, соответствующих глобальному выражению app/test/**/*.test.js
. Другими словами, это все файлы, которые находятся в каталоге app/test
и имеют путь, заканчивающийся на .test.js
.
Новая точка входа и выходной путь передаются в объект module.exports
, затем запускается Webpack для создания тестовой сборки. Конфигурация Webpack представляет собой обычный код JavaScript, поэтому можно использовать стандартную библиотеку Node и операторы if
для её настройки. Если выполнить $ TESTBUILD=true npx webpack
, то можно увидеть каталог test-dist
. А если запустить $ npx mocha test-dist/main.js
, то можно увидеть, как выполняются ваши тесты.
Наконец, в разделе scripts
вашего package.json
добавьте следующую строку:
"test": "TESTBUILD=true webpack && mocha test-dist/main.js && rm -rf test-dist"
Это означает, что когда вы выполняете $ yarn test
, создаётся каталог test-dist
(каталог сборки тестирования) с помощью Webpack, затем запускается Mocha для этой сборки, и, наконец, код $ rm -rf test-dist
удаляет каталог test-dist
, поскольку он больше не используется (Commit 7).
Маппинг исходных файлов тестового кода
Тестовая сборка готова, но есть один нюанс, касающийся тестирования кода. Если запустить Mocha для файла test-dist/main.js
и один из этих тестов не пройдёт, как это будет выглядеть? Давайте сделаем тест формулы расстояния в app/test/distance.test.js
ошибочным:
describe("distance", function() { it("calculates distance with the good ol' Pythagorean Theorem", function() { let origin = {x: 0.0, y: 0.0}; let point = {x: 3.0, y: 4.0}; expect(distance(point, origin)).to.equal(2071); }); });
Выполните $ yarn test
и вот что получится:
Тест провален, но нельзя узнать, в какой строке исходного кода находится ошибка. Если таких тестов для веб-приложения у вас много, то поиски строки с ошибкой затянутся надолго.
Код с ошибкой находится в строке 8 в файле app/test/distance.test.js
, но Mocha запускается для файла test-dist/main.js
, поэтому, с точки зрения Mocha, ошибка находится в строке 116. К счастью, Webpack поддерживает source maps, которые покажут, какая строка кода соответствует ошибке. Source maps (или карты кода) — это файлы исходного кода, которые показывают точное соответствие элементов готового рабочего кода проекта и вашего кода разработки. Выполняется своего рода проход декодером по пакетному файлу main.js
, чтобы получить исходные строки кода, которые соответствуют связному коду. Давайте обновим оператор if
в файле webpack.config.js
:
let entry = __dirname + "/app/src/path.js"; let outputPath = __dirname + "/dist/"; let devtool = ""; if (process.env.TESTBUILD) { entry = glob.sync(__dirname + "/app/test/**/*.test.js"); outputPath = __dirname + "/test-dist/"; devtool = "source-map"; }
Затем в объект module.exports
добавим строку:
devtool: devtool,
Теперь в тестировочных сборках каталог test-dist
будет содержать файл типа source maps. Выполните $ npx webpack TESTBUILD=true
и каталог test-dist
будет содержать файл main.js.map
в дополнение к пакету main.js
.
Чтобы Mocha мог использовать эту source map при запуске тестов, необходимо установить ещё один пакет:
$ yarn add --dev source-map-support
Теперь, чтобы его использовать, нужно обновить скрипт Mocha в разделе scripts.test
файла package.json
:
TESTBUILD=true webpack && mocha test-dist/main.js --require source-map-support/register && rm -rf test-dist
Флаг --require source-map-support/register
требует пакет source-map-support
, это означает, что Mocha будет использовать source map, если она доступна. Теперь, если вы выполните $ yarn test
и получите ошибку, вы увидите, в какой строке она находится, и сможете исправить код (Commit 8).
Итак, теперь у вас есть пример настройки обычных и тестовых сборок с использованием маппинга. Существует также множество других способов, которыми вы можете воспользоваться в своих сборках, например, объединение нескольких загрузчиков JavaScript для обработки вашего кода как на конвейере или же запуск Webpack как отладочного сервера, чтобы моментально видеть, как изменения в коде влияют на окончательную сборку Webpack. Продолжайте пробовать различные пакеты для компоновки файла webpack.config.js
!
Для более точной настройки Webpack вам может быть полезен следующий материал:
Webpack 4: практические рекомендации по настройкеtproger.ru
Хинт для программистов: если зарегистрируетесь на соревнования Huawei Honor Cup, бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.
Перейти к регистрации
- 7 views
- 0 Comment