В статье я хотел поделиться уже работающим в продакшене вариантом начала постепенной миграции «legacy» Angular JS проекта на все хорошее, что дал нам Angular 1.5 и связку ES6/TypeScript.
Итак дано: стандартный проект, разработка которого началась еще на бородатом Angular 1.2 (человеком, далеким от мира фронтенда), представленный в более или менее стандартном виде — отдельно по директориям сгруппированы модули с роутами, сервисы, директивы и невероятно жирные контроллеры, функционал из которых потихоньку выделяестся в отдельные директивы. Адский поток фич к реализации, полное отсутствие моделей, доступ к объектам и их модификации — как бог на душу положит.
Также в проекте уже присутствует более или менее налаженный и прописанный процесс сборки/минификации и деплоя всего этого добра при помощи gulp, CI и прочее.
Задача — не уйти в себя на поддержке проекта в таком виде, в каком он есть, начать писать хороший, поддерживаемый код, научиться чему-то новому.
Как раз подоспел Angular 1.5, представивший «компоненты» и после некоторого количества прочитанных мануалов по различным смежным темам (включая миграцию 1.3 -> 1.4, 1.4 -> 1.5, 1.x -> 2) в качестве программы на обозримое будущее были приняты такие пункты:
Теперь нужно определиться с обвязкой.
Браузеры на данном этапе развития не поддерживает ES6 imports, а значит чтобы использовать их (а мне хотелось больше «нейтива»), нужно собирать проект под браузер одним из «сборщиков». После некоторых изысканий выбор пал на webpack — его идеология отлично сочетается с идеологией компонент и позволяет прямо из кода компонента подключать необходимые шаблоны и стили.
Спустя пару месяцев он был обновлен со стабильной 1.x до 2 (beta). Версия 2 имеет несколько очень важных нововведений — в первую очередь это нативная поддержка ES6 Imports (в том числе и постепенная подгрузка частей кода по мере появления нужды клиента в этом коде).
Для вебпака нам нужно будет несколько «лоадеров» — это эдакие middleware, дающие вебпаку понять каким именно образом добавлять в сборку тот или иной файл. У меня этот набор относительно скромный:
Еще с вебпаком почти в комплекте поставляется webpack-dev-server, который позволяет очень сильно ускорить перекомпиляцию при изменениях — это локальный сервер, который раздает обычную статику из данной директории, а код, собираемый webpack-ом держит, пересобирает и раздает прямо из памяти.
Так же нам понадобится собственно компилятор TypeScript. Он спустя те же пару месяцев вместе с webpack был обновлен до беты 2.0, в основном из-за того, что 2.0 позволяет задавать базовый url для всех импортов, избавиться наконец от засилия относительных путей внутри файлов и заменить немного удручающее:
На вполне энтерпрайзное:
Еще нам вполне вероятно понадобится транспайлер (это как компилятор, только для компиляции ES6 в ES5) для того чтобы наш самый современный код нормально работал у самых обыкновенных пользователей сервиса. Самый популярный сейчас — это Babel JS. Конечно можно в роли траспайлера использовать непосредственно компилятор TypeScript, но он некоторые вещи делает хуже babel'a (async/await, например, typescript не транспайлит, насколько мне известно), поэтому я решил компилировать TypeScript в ES6 и потом с помощью Babel и пресета es2015-webpack (это специальный пресет для webpack 2, он не превращает ES6 Imports в CommonJS, т.к. webpack теперь умеет собирать ES6 Imports сам по себе).
Еще нам понадобится TypeScript Definition Manager (бывший tsd. Многие статьи рекомендуют ставить tsd, но tsd уже deprecated и сам по себе просит использовать вместо него проект typings).
Итак, давайте уже приступим.
В первую очередь установим все вышеописанное:
Вероятно typescript, webpack и typings придется установить еще и глобально для того, чтобы удобно работать.
Также нам нужно будет установить все нужные для нашей комфортной работы с typescript definition'ы:
А возможно и
И все такое, что там у вас еще используется.
Результатом выполнения этих команд будет созданный в текущей директории файл typings.json, который впоследствии восстановит все ваши typings'ы по вызову команды
Т.е. это эдакий аналог lock-файла или package.json для definition manager'а. Этот файл надо добавить в репозиторий. Также появится папка typings с собственно скачанными definition'ами для использования в typescript. (ее можно добавить в .gitignore и сделать вызов typings install частью сборки проекта)
Далее давайте уже начнем писать для всего этого конфиги.
./declarations.d.ts
Используется для того же самого для чего используются definition'ы из typings, но содержит те интерфейсы, которых не нашлось в репозиториях typings manager'а. У меня там например
Конечно с any — это я поленился, по идее там надо полностью описать интерфейс и тогда у вас появится корректный автокомплит по этим объектам в вашей IDE и, что самое главное, проверки корректности использования методов/свойств этих объектов на этапе компиляции. Это для меня todo, так сказать.
require здесь обязателен, иначе ваш код для вебпака просто не будет собираться.
./tsconfig.json
Это конфигурация компилятора typescript
Объявленный здесь массив files позволяет нам не писать в каждом файле ужасающий
Ну и как видно в этом конфиге отсутствует outfile и любые значащие файлы проекта. Просто потому что мы отдаем эти вопросы на откуп webpack.
./webpack.config.js
Собственно конфигурация webpack.
Итак…
Тут стоит описать еще несколько моментов. В нашей стандартной структуре директорий приложения, среди всех этих directives/controllers/modules мы создали новую — components (а также models, helpers, etc...), в которой собственно и будут жить компоненты. Базовым файлом в этой директории является файл components.ts, в него импортятся наши компоненты из поддиректорий. Этот файл мы и используем в качестве entry-point для webpack. Выглядит он как-то так:
./path/to/your/app/components/components.ts
Надо добавить что точек входа может быть (и должно быть) больше одной, иначе у вас просто с ростом проекта все начнет ужасно долго компилироваться. Выше в конфиге вебпака можно увидеть как устанавливается несколько entry-points. Ну и да, они не должны пересекаться по импортам. Если есть что-то общее (например модели) — то это общее так же нужно выделять в отдельный бандл и не забывать про оптимизацию, common chunks и возможность организовать ленивую загрузку скриптов.
Вполне понятно что тот же ParserHelper из этого примера — просто класс со статическими методами — может быть импортирован в ts-файл напрямую, без использования ангуляровского DI (что зачастую приятно), но здесь он регистрируется как фабрика для обеспечения обратной совместимости с legacy-частью приложения. Т.е. это один из уже переписанных на ts сервисов. А вот в AutoMarkupService мы уже хотим хранить какое-то состояние, или может быть нам просто нужен там стандартный ангуляровский DI. И потому для его регистрации в ангуляре используем нехитрый паттерн с getInstance:
./path/to/your/app/services/AutoMarkupService.ts
По-хорошему это все надо переделать на какой-то базовый класс или сразу на декоратор.
Теперь что касается самих компонентов:
В первую очередь нам понадобится совсем небольшой декоратор, который сильно облегчит нам работу:
./path/to/your/app/helpers/decorators.ts
А теперь внимательно смотрим на то, что можно сделать со всем тем, что мы уже понастраивали
./path/to/your/app/components/shared/static_info/staticInfo.component.ts
Заметьте что тут через require мы подключаем шаблон и стиль. Этот require — для webpack, после сборки вместо require в этом месте будут собственно итоговый css и html в текстовом виде. Ну или (в зависимости от настроек webpack) они будут где-то в других файлах, но к моменту вызова этой функции — уже точно будут загружены.
Так же важный момент насчет $onInit — пока вы транспайлите в es2015 он фактически не нужен. В es2015 еще нет классов и все это транспайлится в объект и к моменту вызова constructor все биндинги уже переданы. Но стоит только поменять пресет на es2016 или вовсе выкинуть Babel (для простоты отладки, например), как у вас все перестанет работать. $onInit — это в общем стандартный ангуляровский callback.
После всех подготовительных этапов осталось только в корневой директории (там, где у нас лежат все наши package.json, tsconfig.json, webpack.config.js и прочее) запустить
В директорию, указанную конфиге webpack по результатам работы соберется .js файл, который нужно наравне со всеми прочими включить в вашу .html-страницу (или добавить к вашей сборке специальную автоматику, которая будет этот файл собирать и минимизировать наравне со всеми прочими).
Команда
Запустит webpack в режиме watcher'а и будет пересобирать все при каждом изменении в ts или связанных с ними html и less.
Команда
Запустит webpack-dev-server, который будет отдавать обычную статику (в нашем случае это «legacy» часть приложения) с указанных в конфиге адресов, а часть, за которую теперь отвечает вебпак, держать в памяти и очень быстро перекомпилировать.
Ну вот. В общем итоговая (на сегодняшний день) конструкция выглядит как-то вот так, это результаты где-то наверное месяца весьма непоследовательно чтения различных посвященных этой теме статей, большая часть из которых несколько устарела (например как избиваться от reference path и относительных путей импортов в них не было написано) и десятков различных экспериментов (это не первая и не вторая итерация, все по большей части в свободное от работы — на которой надо пилить фичи — и личной жизни время, конечно же). Надеюсь кому-нибудь этот экскурс будет полезен. Также жду критики и предложений по улучшению всего, что я тут понагородил (ведь на самом деле я не совсем frontend developer и наверняка многое упустил из виду). Спасибо.
Итак дано: стандартный проект, разработка которого началась еще на бородатом Angular 1.2 (человеком, далеким от мира фронтенда), представленный в более или менее стандартном виде — отдельно по директориям сгруппированы модули с роутами, сервисы, директивы и невероятно жирные контроллеры, функционал из которых потихоньку выделяестся в отдельные директивы. Адский поток фич к реализации, полное отсутствие моделей, доступ к объектам и их модификации — как бог на душу положит.
Также в проекте уже присутствует более или менее налаженный и прописанный процесс сборки/минификации и деплоя всего этого добра при помощи gulp, CI и прочее.
Задача — не уйти в себя на поддержке проекта в таком виде, в каком он есть, начать писать хороший, поддерживаемый код, научиться чему-то новому.
Вводная
Как раз подоспел Angular 1.5, представивший «компоненты» и после некоторого количества прочитанных мануалов по различным смежным темам (включая миграцию 1.3 -> 1.4, 1.4 -> 1.5, 1.x -> 2) в качестве программы на обозримое будущее были приняты такие пункты:
- Старый функционал до поры до времени просто не трогаем
- Новый функционал сразу пишем в виде компонент (стили, шаблоны и тесты храним там же, где код конкретного компонента)
- Пишем совсем не стесняя себя в использовании фич из ES6/ES7
- Пишем на Typescript
- Старый функционал переделываем на новый лад по мере поступления по нему задач достаточно крупных для того, чтобы провести рефакторинг.
Теперь нужно определиться с обвязкой.
Браузеры на данном этапе развития не поддерживает ES6 imports, а значит чтобы использовать их (а мне хотелось больше «нейтива»), нужно собирать проект под браузер одним из «сборщиков». После некоторых изысканий выбор пал на webpack — его идеология отлично сочетается с идеологией компонент и позволяет прямо из кода компонента подключать необходимые шаблоны и стили.
Спустя пару месяцев он был обновлен со стабильной 1.x до 2 (beta). Версия 2 имеет несколько очень важных нововведений — в первую очередь это нативная поддержка ES6 Imports (в том числе и постепенная подгрузка частей кода по мере появления нужды клиента в этом коде).
Для вебпака нам нужно будет несколько «лоадеров» — это эдакие middleware, дающие вебпаку понять каким именно образом добавлять в сборку тот или иной файл. У меня этот набор относительно скромный:
- ts-loader, замененный впоследствии на awesome-typescript-loader (т.к. atl у удалось заставить поддерживать baseUrl из настроек typescript 2.0, ради которого, в основном, и был осуществлен переход на typescript 2.0)
- ng-annotate-loader, замененный в последствии на плагин для babel babel-plugin-angularjs-annotate по причине неподдержки первым ES6 imports — ультраполезная вещь, имплементация модуля ng-annotate, позволяющего изрядно визуально очистить код angular-комплектующих, отказавшись от minification-proof dependency injection и вместо
.factory('serviceId', ['depService', function(depService) { // ... }])
начать писать более или менее человечное:
.factory('serviceId', function(depService) { /*@ngInject*/ // ... })
- less-loader для компиляции less во время загрузки (в 'legacy' части проекта у нас основная масса стилей написана на less, и я не видел повода не продолжать использовать less, тем более у нас уже куча своих миксинов и переменных)
- style-loader — загрузка css, получающегося на выходе less
- raw-loader — в моем случае загрузка html прямо в js
Еще с вебпаком почти в комплекте поставляется webpack-dev-server, который позволяет очень сильно ускорить перекомпиляцию при изменениях — это локальный сервер, который раздает обычную статику из данной директории, а код, собираемый webpack-ом держит, пересобирает и раздает прямо из памяти.
Так же нам понадобится собственно компилятор TypeScript. Он спустя те же пару месяцев вместе с webpack был обновлен до беты 2.0, в основном из-за того, что 2.0 позволяет задавать базовый url для всех импортов, избавиться наконец от засилия относительных путей внутри файлов и заменить немного удручающее:
import IConversation from "../../../interfaces/IConversation"; import Conversation from "../../../models/Conversation"; import Interaction from "../../../models/Interaction"; import NotificationService from "../../../helpers/NotificationService";
На вполне энтерпрайзное:
import IConversation from "interfaces/IConversation"; import Conversation from "models/Conversation"; import Interaction from "models/Interaction"; import NotificationService from "helpers/NotificationService";
Еще нам вполне вероятно понадобится транспайлер (это как компилятор, только для компиляции ES6 в ES5) для того чтобы наш самый современный код нормально работал у самых обыкновенных пользователей сервиса. Самый популярный сейчас — это Babel JS. Конечно можно в роли траспайлера использовать непосредственно компилятор TypeScript, но он некоторые вещи делает хуже babel'a (async/await, например, typescript не транспайлит, насколько мне известно), поэтому я решил компилировать TypeScript в ES6 и потом с помощью Babel и пресета es2015-webpack (это специальный пресет для webpack 2, он не превращает ES6 Imports в CommonJS, т.к. webpack теперь умеет собирать ES6 Imports сам по себе).
Еще нам понадобится TypeScript Definition Manager (бывший tsd. Многие статьи рекомендуют ставить tsd, но tsd уже deprecated и сам по себе просит использовать вместо него проект typings).
Итак, давайте уже приступим.
Установка и настройка окружения
В первую очередь установим все вышеописанное:
npm install --save-dev webpack@2.1.0-beta.20 typescript@2 less-loader raw-loader style-loader typescript typings webpack webpack-dev-server babel-runtime babel-preset-es2015-webpack babel-polyfill babel-plugin-angularjs-annotate babel-loader babel-core awesome-typescript-loader
Вероятно typescript, webpack и typings придется установить еще и глобально для того, чтобы удобно работать.
Также нам нужно будет установить все нужные для нашей комфортной работы с typescript definition'ы:
typings install angular --source=dt --global --save
А возможно и
typings install jquery --source=dt --global --save
И все такое, что там у вас еще используется.
Результатом выполнения этих команд будет созданный в текущей директории файл typings.json, который впоследствии восстановит все ваши typings'ы по вызову команды
typings install
Т.е. это эдакий аналог lock-файла или package.json для definition manager'а. Этот файл надо добавить в репозиторий. Также появится папка typings с собственно скачанными definition'ами для использования в typescript. (ее можно добавить в .gitignore и сделать вызов typings install частью сборки проекта)
Далее давайте уже начнем писать для всего этого конфиги.
Конфигурации
./declarations.d.ts
Используется для того же самого для чего используются definition'ы из typings, но содержит те интерфейсы, которых не нашлось в репозиториях typings manager'а. У меня там например
declare function require(name: string): any; // used by webpack declare let antlr4: any; declare let rangy: any;
Конечно с any — это я поленился, по идее там надо полностью описать интерфейс и тогда у вас появится корректный автокомплит по этим объектам в вашей IDE и, что самое главное, проверки корректности использования методов/свойств этих объектов на этапе компиляции. Это для меня todo, так сказать.
require здесь обязателен, иначе ваш код для вебпака просто не будет собираться.
./tsconfig.json
Это конфигурация компилятора typescript
{ "compilerOptions": { "target": "ES6", "sourceMap": true, // for debug "experimentalDecorators": true, // decorators support, see ts reference "baseUrl": "./path/to/your/app" // url that will be 'root' for imports }, "files": [ "declarations.d.ts", // declarations file from previous point "typings/index.d.ts" // declarations, downloaded by definition manager ] }
Объявленный здесь массив files позволяет нам не писать в каждом файле ужасающий
/// <reference path="..." />
Ну и как видно в этом конфиге отсутствует outfile и любые значащие файлы проекта. Просто потому что мы отдаем эти вопросы на откуп webpack.
./webpack.config.js
Собственно конфигурация webpack.
'use strict'; var path = require('path'); var webpack = require('webpack'); var TsConfigPathsPlugin = require('awesome-typescript-loader').TsConfigPathsPlugin; // plugin to work with typescript base path. Skip it if you don't need this. var babelSettings = { plugins: [['angularjs-annotate', {'explicitOnly' : true}]], //explicitOnly here to disallow auto-annotating of each function. Skip it if you need automatioc anotation presets: ['es2015-webpack'] }; module.exports = { module: { loaders: [ { test: /\.tsx?$/, loader: 'babel-loader?' + JSON.stringify(babelSettings) + '!awesome-typescript-loader', }, {test: /\.html$/, loader: 'raw'}, {test: /\.less$/, loader: 'style!css?sourceMap!less?sourceMap'} ] }, entry: { components: './path/to/your/app/components/components.ts' // entry1: './path/to/your/app/components/entry1/entry1.component.ts' // entry1: './path/to/your/app/components/entry2/entry2.component.ts' // models: './path/to/your/app/models/models.bundle.ts' // ...whatever you want }, resolve: { extensions: ['.ts', '.js', '.html', '.css', '.less'], alias: { // lessWebApp: path.join(__dirname, '/path/to/your/app/less') - whatever you want to be used in your code }, plugins: [ new TsConfigPathsPlugin() ] }, devtool: 'source-map', output: { path: path.join(__dirname, 'path/to/your/build/js/bundles'), publicPath: '/js/bundles', filename: '[name].bundle.js' }, plugins: [ // new webpack.optimize.CommonsChunkPlugin({ name: 'common', filename: 'common.bundle.js' }) - use this to move out common chunks to one separate chunk ], devServer: { contentBase: path.join(__dirname, 'path/to/your/build/'), publicPath: '/js/bundles/' } };
Итак…
Перейдем непосредственно к
Тут стоит описать еще несколько моментов. В нашей стандартной структуре директорий приложения, среди всех этих directives/controllers/modules мы создали новую — components (а также models, helpers, etc...), в которой собственно и будут жить компоненты. Базовым файлом в этой директории является файл components.ts, в него импортятся наши компоненты из поддиректорий. Этот файл мы и используем в качестве entry-point для webpack. Выглядит он как-то так:
./path/to/your/app/components/components.ts
// component-based modules with their own routes import Module1 from "components/module1/module1"; import Module2 from "components/module2/module2"; // ts helpers and services import AnnotateHelper from "services/AutoMarkupService"; import ParserHelper from "helpers/ParserHelper"; // .... // not organized in modules components import AgentAvatarComponent from "components/agent/agent_avatar/agentAvatar.component"; import StaticInfoComponent from "components/shared/static_info/staticInfo.component"; // .... let componentModule = angular.module('api.components', [ Module1.name, Module2.name // .... ]); componentModule .component(AgentAvatarComponent.name, AgentAvatarComponent) .component(StaticInfoComponent.name, StaticInfoComponent) // .... .factory('ParserService', () => ParserHelper) // for static helpers // .... .factory('autoMarkupService', AutoMarkupService.getInstance); // for helpers that handles something inside export default componentModule;
Надо добавить что точек входа может быть (и должно быть) больше одной, иначе у вас просто с ростом проекта все начнет ужасно долго компилироваться. Выше в конфиге вебпака можно увидеть как устанавливается несколько entry-points. Ну и да, они не должны пересекаться по импортам. Если есть что-то общее (например модели) — то это общее так же нужно выделять в отдельный бандл и не забывать про оптимизацию, common chunks и возможность организовать ленивую загрузку скриптов.
Вполне понятно что тот же ParserHelper из этого примера — просто класс со статическими методами — может быть импортирован в ts-файл напрямую, без использования ангуляровского DI (что зачастую приятно), но здесь он регистрируется как фабрика для обеспечения обратной совместимости с legacy-частью приложения. Т.е. это один из уже переписанных на ts сервисов. А вот в AutoMarkupService мы уже хотим хранить какое-то состояние, или может быть нам просто нужен там стандартный ангуляровский DI. И потому для его регистрации в ангуляре используем нехитрый паттерн с getInstance:
./path/to/your/app/services/AutoMarkupService.ts
import IHttpService = angular.IHttpService; import IPromise = angular.IPromise; import Model from "models/Model"; export default class AutoMarkupService { private static instance: AutoMarkupService = null; public static getInstance($http, legacyUrlConfig) { /*@ngInject*/ if (!AutoMarkupService.instance) { AutoMarkupService.instance = new AutoMarkupService($http, legacyUrlConfig); } return AutoMarkupService.instance; } constructor(private $http: IHttpService, private legacyUrlConfig: {modelUrl: string}) { // do something } public doSomething(): IPromise<Model> { return this.$http.get(this.legacyUrlConfig.modelUrl); } }
По-хорошему это все надо переделать на какой-то базовый класс или сразу на декоратор.
Теперь что касается самих компонентов:
В первую очередь нам понадобится совсем небольшой декоратор, который сильно облегчит нам работу:
./path/to/your/app/helpers/decorators.ts
// .... export const Component = function(options: ng.IComponentOptions): Function { return (controller: Function) => { return angular.extend(options, {controller}); }; }; // ....
А теперь внимательно смотрим на то, что можно сделать со всем тем, что мы уже понастраивали
./path/to/your/app/components/shared/static_info/staticInfo.component.ts
import {Component} from "helpers/decorators"; require('./staticInfo.style.less'); @Component({ bindings: { message: "@" }, template: require('./staicInfo.template.html'), controllerAs: 'vm' }) export default class StaticInfoComponent { public message: string; /** * here you can put any angular DI and it will work */ constructor() { // this.message -> undefined } /** * function that will be called right after constructor(), * but in constructor() you will not have any bindings applied and here - will be */ $onInit() { // this.message -> already binded and working. } }
Заметьте что тут через require мы подключаем шаблон и стиль. Этот require — для webpack, после сборки вместо require в этом месте будут собственно итоговый css и html в текстовом виде. Ну или (в зависимости от настроек webpack) они будут где-то в других файлах, но к моменту вызова этой функции — уже точно будут загружены.
Так же важный момент насчет $onInit — пока вы транспайлите в es2015 он фактически не нужен. В es2015 еще нет классов и все это транспайлится в объект и к моменту вызова constructor все биндинги уже переданы. Но стоит только поменять пресет на es2016 или вовсе выкинуть Babel (для простоты отладки, например), как у вас все перестанет работать. $onInit — это в общем стандартный ангуляровский callback.
Как все это собрать и заставить работать
После всех подготовительных этапов осталось только в корневой директории (там, где у нас лежат все наши package.json, tsconfig.json, webpack.config.js и прочее) запустить
webpack
В директорию, указанную конфиге webpack по результатам работы соберется .js файл, который нужно наравне со всеми прочими включить в вашу .html-страницу (или добавить к вашей сборке специальную автоматику, которая будет этот файл собирать и минимизировать наравне со всеми прочими).
Команда
webpack -w
Запустит webpack в режиме watcher'а и будет пересобирать все при каждом изменении в ts или связанных с ними html и less.
Команда
webpack-dev-server -w
Запустит webpack-dev-server, который будет отдавать обычную статику (в нашем случае это «legacy» часть приложения) с указанных в конфиге адресов, а часть, за которую теперь отвечает вебпак, держать в памяти и очень быстро перекомпилировать.
Еще немного хинтов
- Запуск сборки вебпака можно легко добавить в ваш основной процесс сборки (например в какой-нибудь gulp build). У нас это выглядит как-то так:
./gulp/tasks/scripts.js
// .... // use webpack.config.js to build modules gulp.task('webpack', "executes build of ts/es6 part of application", function (cb) { if (shared.state.isSkipWebpack) { console.log('Skipping webpack task during watch. Please use internal webpack watch'); return cb(); } let config = require('../../webpack.config'); webpack(config, function (err, stats) { if (err) { console.log('webpack', err); } console.log('[webpack]', stats.toString({ chunks: false, errorDetails: true })); cb(); }); }); // ....
- Если вам кажется что вебпак работает медленно — самое время оптимизировать билд. CommonChunks плагин, разбиение проекта на много разных логических бандлов, тюнинг настроек кэша, использование dev-server'а в конце концов.
- Также, если ваш фронтенд неотчуждим от бэкенда и кажется что по этой причине использование webpack-dev-server'а невозможно, просто знайте, что одной из стандартных компонент webpack-dev-server'а является node-http-proxy и соответсвтенно в пару движений в сторону изменения конфига вы можете настроить прокси, который ваши запросы к нему будет перенаправлять… ну например на ваш staging сервер. Или еще куда-нибудь.
- Babel — очень мощный инструмент, который остался почти совсем за рамками данной статьи. Он включает в себя огромное количество плагинов, которые вы можете использовать на своем проекте.
- Для облегчения работы людям, не учавствующим в разработке фроненда, или для упрощения контейнеризации, или для еще чего, можно создать простые хелперы для запуска всех нужных приложений. Это позволит не ставить их глобально.
./runners/typings
#!/bin/sh "node/node" "node_modules/typings/dist/bin.js" "$@"
./runners/webpack
#!/bin/sh "node/node" "node_modules/webpack/bin/webpack.js" "$@"
etc. (да, кстати, нода у нас нашим сборщиком также ставится локально в директорию проекта, рядом с node_modules)
И запускать пакеты, установленные в node_modules, а не глобально. Это очень полезно если вы, например, собираете проект какой-то системой сборки, у вас там есть какой-то npm и вот чтобы не ставить глобально остальное, можно в этой системе сборки вызывать нужные команды таким вот образом:
./runners/typings install
- Пишите тесты и документацию.
- Курение убивет.
Конец
Ну вот. В общем итоговая (на сегодняшний день) конструкция выглядит как-то вот так, это результаты где-то наверное месяца весьма непоследовательно чтения различных посвященных этой теме статей, большая часть из которых несколько устарела (например как избиваться от reference path и относительных путей импортов в них не было написано) и десятков различных экспериментов (это не первая и не вторая итерация, все по большей части в свободное от работы — на которой надо пилить фичи — и личной жизни время, конечно же). Надеюсь кому-нибудь этот экскурс будет полезен. Также жду критики и предложений по улучшению всего, что я тут понагородил (ведь на самом деле я не совсем frontend developer и наверняка многое упустил из виду). Спасибо.
