Легенда
Когда проект зародился, то нравился каждому. Белый лист бумаги и каждый смотрел на него с ожиданием и воображал какие перспективы откроются, какие проблемы решатся.
Вот на бумагу архитектор нанес первый блок. Сзади раздалась ругань. Это разработчики, спорили: Как лучше стартовать новый сервис и какой стартер выбрать.
У архитектора по спине пронесся холодок. Не успела сложиться архитектура даже для Proof Of Concept, не то что для Minimal Valuable Product, но уже возникли препятствия. Выбор стартера наложит пока не очевидные рамки.
Одно было ясно, сборщик будет использоваться. Архитектор подошел к Team Lead и попросил использовать WebPack и чистый проект без стартера, так как по прошлым проектам с ним в той или иной мере знакомы разработчикам.
Мотивация
Каждый кто в 2020 использовал браузер - пользовался результатами сборки с помощью WebPack.
Среди разработчиков некоторые добавляли обработчик для специальных файлов или плагин для нужд проекта или использовали уже готовую конфигурацию, например в create-react-app.
Задач много и помнить параметры конфигурации смысла нет. Структура и часто используемые настройки сами отложатся в голове.
Готовые плагины и loader's сильно облегчают работу, задача на 95% заключается в прочтении первой страницы документации, чтобы сконфигурировать под конкретный проект. Даже в таком случае ошибки в синтаксисе случаются. Мало кто сходу вспомнит devtool
или devtools
. Некоторые директивы относились к другой версии WebPack. Учет этого будет полезным положить на плечи TypeScript.
Пару лет назад мне не хватало подробного описания такой настройки, а на сайте самого WebPack только короткое описание: вот ссылка.
Особенности проекта в статье
В проекте для статьи нет цели написать всеобъемлющий мануал по настройке, будет базовый пример для backend и frontend.
Cервер будет отдавать статическую директорию с FE для нашего сайта. Сам же FE будет только выводить на страницу Hello World!
. Зависимостями для BE будет node
, для сборки webpack
.
GitHub: тут
Структура директорий c описанием
Для удобства демонстрации я буду использовать моно-репозиторий с server и webapp в одном проекте
~/projectfolder/ # Корень проекта -- инициализирован с помощью
yarn init
/apps # директория приложений
/server # директория backend -- инициализирована с помощью
yarn init
/src # исходный код сервера
файлы конфигурации (части относящиеся к BE)
/webapp # директория frontend -- инициализирована с помощью
yarn init
/src # исходный код браузерного приложения
файлы конфигурации (части относящиеся к FE)
/utils # расширенные утилиты
общие части конфигурации
Зависимости проекта
Общие в директории
~/project_folder
yarn add -D @types/node @types/webpack concurrently cross-env nodemon ts-loader ts-node typescript webpack webpack-cli
Для сервера в директории
/apps/server
нам не понадобится дополнительных зависимостей помимо тех что есть в общей директорииДля веб-приложения в директории
/apps/web_app
нам понадобитсяhtml-webpack-plugin
5 версии так как он предназначен для использования с WebPack 5 версии. На Момент написания этот пакет еще в beta доступе.
cd apps/web_app
yarn add -D html-webpack-plugin@5
Настройки TypeScript
Браузер, server, и компьютер разработчика или runner - это три среды с личными особенностями:
Для сервера главное, node с помощью которой будет выполняться итоговый скрипт сервера. Что доступно в зависимости от версии наглядно показывается по ссылке: https://node.green
Конкретная настройка сервера apps/server/tsconfig.json
не влияет на сборку, главное в конфигурации webpack указать правильный путь до файла для сборки сервера.
Для браузера, на конец 2020, лучше выбирать ES6 если нет задачи поддерживать Internet Explorer 11. Хороший сайт для проверки доступных функций: https://caniuse.com
Файл: apps/web_app/tsconfig.json
Компьютер разработчика или runner где будет собираться проект тоже накладывает ограничения, которые в большинстве ситуаций легко устранимы. Для запуска также понадобится конфигурация TS, она будет использоваться ts-node
который будет запускаться под капотом webpack.
tsconfig.json
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
}
Данный файл обязателен и частью для запуска самого webpack с конфигурацией написанной на typescript
Серверное приложение
Сервер для данной статьи предельно прост, раздачей файлов из одной папки. Код является копией статьи (ссылка) с сайта node, адаптированный под этот проект и с защитой от доступа к родительским папкам ..\..\secret
в запрошенных файлах.
apps/server/src/index.ts
import { resolve, normalize, join } from 'path'
import { createServer, RequestListener} from 'http'
import { readFile } from 'fs'
const webAppBasePath = '../web_app'; // Это путь до папки уже после build (в директории dist)
const handleWebApp: RequestListener = (req, res) => {
const resolvedBase = resolve(__dirname ,webAppBasePath);
const safeSuffix = normalize(req.url || '')
.replace(/^(\.\.[\/\\])+/, '');
const fileLocation = join(resolvedBase, safeSuffix);
readFile(fileLocation, function(err, data) {
if (err) {
res.writeHead(404, 'Not Found');
res.write('404: File Not Found!');
return res.end();
}
res.statusCode = 200;
res.write(data);
return res.end();
});
};
const httpServer = createServer(handleWebApp)
httpServer.listen("5000", () => {
console.info('Listen on 5000 port')
})
Frontend приложение
Web приложение также предельно простое. В document.body
монтируется простой <div id="root">Hello world!</div>
apps/web_app/src/index.ts
const rootNode = document.createElement('div')
rootNode.setAttribute('id', 'root')
rootNode.innerText = 'Hello World!'
document.body.appendChild(rootNode)
Настройка WebPack
Теперь нам осталось только настроить webpack.
Для удобства конфигурацию можно разбить на файлы. А так как мы используем TS, то мы получаем синтаксис import {serverConfig} from "./apps/server/webpack.part";
из-за этого основной файл становится предельно коротким.
webpack.config.ts
import {serverConfig} from "./apps/server/webpack.part";
import {webAppConfig} from "./apps/web_app/webpack.part";
import {commonConfig} from "./webpack.common";
export default [
/** server **/ {...commonConfig, ...serverConfig},
/** web_app **/ {...commonConfig, ...webAppConfig},
]
В нем мы только импортируем конфигурации и экспортируем их в виде массива попутно объединяя с общей частью.
Общая часть
Общая часть может содержать все что можно переиспользовать между различными конфигурациями. В нашем случае это поля mode
и resolve
. Обратите внимание, что у константы объявлена типизация const commonConfig: Configuration
, тип взят из import {Configuration} from "webpack";
.
webpack.common.ts
import {Configuration, RuleSetRule} from "webpack";
import {isDev} from "./apps/_utils";
export const tsRuleBase: RuleSetRule = {
test: /\.ts$/i,
loader: 'ts-loader',
}
export const commonConfig: Configuration = {
mode: isDev ? 'development' : 'production',
resolve: {
extensions: ['.tsx', '.ts', '.js', '.json'],
},
}
Также в этом файле лежит общая для проекта часть настройки правила для загрузки TS файлов const tsRuleBase: RuleSetRule
, тип взят из import {RuleSetRule} from "webpack";
.
isDev
это простая проверка isDev = process.env.NODE_ENV === 'development'
Конфигурация FE и BE
Тут уже все максимально похоже на простую настройку webpack, только с подсказками благодаря типизации import {Configuration, RuleSetRule, WebpackPluginInstance} from "webpack";
Обратите внимание на WatchIgnorePlugin
так как благодаря нему можно исключить какие-то файлы и директории и при изменениях в них не будет перекомпиляции.
apps/server/webpack.part.ts
import {Configuration, RuleSetRule, WatchIgnorePlugin, WebpackPluginInstance} from "webpack";
import {join} from "path";
import {tsRuleBase} from "../../webpack.common";
const serverPlugins: WebpackPluginInstance[] = [
new WatchIgnorePlugin({
paths: [join(__dirname, '..', 'apps', 'web_app')]
})
]
const tsRuleServer: RuleSetRule = {
...tsRuleBase,
options: {
configFile: join(__dirname, 'tsconfig.json')
}
}
export const serverConfig: Configuration = {
entry: join(__dirname, 'src', 'index.ts'),
output: {
path: join(__dirname, '..', '..', 'dist', 'server'),
filename: 'server.js'
},
target: 'node',
plugins: serverPlugins,
module: {
rules: [tsRuleServer]
}
}
apps/web_app/webpack.part.ts
import {Configuration, RuleSetRule, WatchIgnorePlugin, WebpackPluginInstance} from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import {join} from "path";
import {tsRuleBase} from "../../webpack.common";
const webAppPlugins: WebpackPluginInstance[] = [
new HtmlWebpackPlugin(),
new WatchIgnorePlugin({
paths: [join(__dirname, '..', 'apps', 'server')]
})
]
const tsRuleWebApp: RuleSetRule = {
...tsRuleBase,
options: {
configFile: join(__dirname, 'tsconfig.json')
}
}
export const webAppConfig: Configuration = {
entry: join(__dirname, 'src', 'index.ts'),
output: {
path: join(__dirname, '..', '..', 'dist', 'web_app'),
filename: 'bundle.js'
},
target: 'web',
plugins: webAppPlugins,
module: {
rules: [tsRuleWebApp]
}
}
Один из интересный моментов - это указание пути до файла конфигурации для ts-loader
, выглядит это так configFile: join(__dirname, 'tsconfig.json')
. Так как __dirname
в каждом случае различен. То в случае backend все компилируется в целевую версию EcmaScript esnext, а для frontend в es6.
Заключение
Весь код приведенный в статье публикуется под "UNLICENSE". Что также указано в репозитории Github: тут.
Использование в проектах конфигурации через TS - это конечно не бизнес фича. Но привносит комфорт в процесс настройки. На небольших проектах это не так заметно, но если вы например используете micro-frontend c помощью ModuleFederationPlugin
, то количество файлов конфигурации webpack растет с каждым микро-приложением и комфорт при настройке становится важен, тем более что время затраченное на именно TS тут минимальное.
PS. Хотелось бы узнать будет ли вам интересна настройка разработки через разворачивание в docker (для VSCode и JetBrains)
Sabubu
Ну и отношение к людям. Желаю вам, чтобы при попытке установки какой-нибудь программы вас тоже бы отправили в магазин за новым компьютером.
Писать код надо используя такие фичи, которые поддерживаются как минимум браузерами за последние 5 лет, а лучше за 10 (то есть, минимум ES5, желательно с shim'ами).
Кто-то не обновляет браузер (так как новые версии программ практически всегда едят больше памяти и медленнее работают), кто-то не может обновить браузер, так как у него Windows Xp, кто-то пользуется встроенным браузером на телефоне, который никогда не обновит производитель, кто-то привык к Internet Explorer и не хочет ничего менять.
Только плохой разработчик не понимает таких вещей. Таковы большинство сегодняшних фронтенд-специалистов, которые просто не способны верстать кроссплатформенно. Они лепят как попало, а потом (если заказчик потребует) начинают лепить костыли для поддержки требуемых браузеров. А надо не так. Надо просто знать, какие фичи с какого года поддерживаются, какие браузеры требуется поддерживать, и верстать исходя из этого с самого начала. А не пытаться костылями и shim'ами потом закрыть свой непрофессионализм.
Вообще, современные фротендщики вызывают только раздражение. Например, сколько можно верстать сайты, рассчитанные на гигантские экраны, где на экран помещается три с половиной поля? Вы думаете, у каждого пользователя дизайнерский 30-дюймовый монитор что ли стоит? Даже здесь, на Хабре, приходится ставить масштаб 90% для комфортного просмотра.
Какое неудачное название. Ведь если вы под каждую страницу делаете свой "фронтенд", то суммарный объем загружаемых файлов при обходе нескольких страниц получается больше, чем если бы был "монолитный" фронтенд. Правильнее было бы называть "мега-фронтенд" или даже "гига-фронтенд" (чувствую скоро дойдем и до этого).
shaltaev Автор
Строчкой ниже вашей цитаты
ES6, он же ES2015 — поддерживается браузерами очень даже хорошо (es6 support) и браузеры с его поддержкой выходят уже пятый год как.
Как раз даунгрейд до ES5 может вызывать для множества пользователей больше накладных расходов. Сделать для этих браузеров preload нужных polyfill будет корректнее, чем заставлять всех остальных грузить es5 версию.
Поддержка IE в современном мире требуется не всегда, но для него можно собрать и отдельный bundle.
Не совсем понял как frontend-developer связан с дизайном? Если бизнес принял решение делать сайт только на десктоп и тебе дали макеты только для него. То их ты и будешь верстать.
Если есть макеты для Responsive (Отзывчивой) вёрстки, то она и верстается.
funca
Кроме IE11 есть курьёзные частности. Например Angular до сих пор вынужден собираться в es2015 из-за того, что у zone.js уже много лет нет решения для работы с нативными промисами и async/await.
Carduelis
Посмотрите рекомендации от Google
TL DR: Делайте под современные браузеры.
xobotyi
> потому что у него Windows Xp
Смешно, ага. Чего бы не поддерживать платформу которую уже даже вендор 6 лет как не поддерживает. А чего бы не равняться на ie1 или netcat вообще? Компабилити ведь…
Если в ваших кейсах присутствует поддержка Win XP, то это проблемы сугубо ваши и ваших клиентов, весь мир шагнул дальше уже как 6 лет назад.