Webpack в Visual Studio для больших солюшенов

в 10:51, , рубрики: javascript, TypeScript, Visual Studio, webpack, Блог компании Mindbox

КПДВ У нас в солюшене 51 проект. В 10 из них используется TypeScript. Объем минимизированного JavaScript-кода ~1 MB. TypeScript-код одних проектов зависит от кода других проектов. Для многих React-компонентов используются глобальные переменные.

Все вместе это приводит к долгим часам отладки front-end кода. Чтобы упростить себе жизнь, мы внедрили Webpack. А по пути отловили грабли.

TL;DR

  1. Устанавливаем node 7 + npm
  2. Выполняем в консоли npm i -g webpack typescript
  3. Устанавливаем Webpack Task Runner
  4. Добавляем webpack.config.js
    в папку "основного" проекта
  5. Добавляем webpack.config.part.js
    в папку каждого зависимого проекта

Самые распространенные проблемы, которые возникают у нас при работе с TypeScript-кодом — «непоследовательное» наследование и обилие глобальных переменных. Они появляются при дефолтных настройках студии и большом размере солюшена.

Проблема — «непоследовательное» наследование

Проблема с наследованием возникает, когда базовый класс подключается после дочернего. Когда в нашем приложении падает ошибка Uncaught TypeError: Cannot read property 'prototype' of undefined, это скорее всего проблема с «непоследовательным» наследованием.

Приведу пример. На "старте" мы написали код ниже.

namespace Sandbox {
    export class Base {
        protected foo() {
            console.log('foo');
        }
    }

    export class Derived extends Base {
        public baz() {
            this.foo();
            console.log('baz');
        }
    }
}

Мы хотим, чтобы каждый класс лежал в отдельном файле, потому что считаем, что так удобнее. И код из листинга выше мы разделили на два файла: base.ts и derived.ts.

namespace Sandbox {
    export class Base {
        protected foo() {
            console.log('foo');
        }
    }
}

namespace Sandbox {
    export class Derived extends Base {
        public baz() {
            this.foo();
            console.log('baz');
        }
    }
}

Появился файл derived.ts, который неявно зависит от файла base.ts. Теперь важен порядок подключения этих двух файлов: если подключить сначала derived.js, а затем base.js, то получим сообщение об ошибке.

При настройках студии "по умолчанию" важен порядок подключения зависимых скриптов.

Проблема — глобальные переменные

Чтобы TypeScript-код из разных проектов использовал общий код, приходится объявлять глобальные переменные.

Например, мы написали полезную функцию makeSandwich в пространстве имен Utils в проекте Administration:

namespace Utils {
    ...
    export function makeSandwich() {
        ...
    }
}

Получаем следующий код в JavaScript:

var Utils;
(function (Utils) {
    function makeSandwich() {
        ...
    }
    Utils.makeSandwich = makeSandwich;
})(Utils || (Utils = {}));

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

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

При настройках студии "по умолчанию" создаются глобальные переменные.

Решение

И «непоследовательное» наследование и глобальные переменные — проблемы решения зависимостей. В мире JavaScript зависимости разруливают специальные инструменты: Webpack, Browserify, RequireJS, SystemJS и прочие. Выбрал Webpack, так как я с ним раньше работал.

В Visual Studio начиная с 13 версии появился Task Runner. Это такой инструмент, который привязывает задачу (task) к моменту жизненного цикла проекта: при открытии, на Clean, перед билдом или после билда. Расширяют Task Runner плагины для студии.

Чтобы встроить Webpack в билд студии, используйте Task Runner и плагин Webpack Task Runner. Работать с ними просто. Создайте файл webpack.config.js в папке с проектом и повесьте таск "Watch - Development" на открытие проекта. Подробнее в этой статье.

Каждый раз при запуске "Watch - Development" над файловой системой запускается watch — на каждое изменение в наблюдаемых файлах срабатывает билд. Без дополнительных настроек все прекрасно работает, если у вас один проект. Если же у вас два и более проектов, на каждый из них будет запущен watch. У нас 10 проектов с TypeScript. 10 вотчей на моем компьютере работают 3 минуты на билд скриптов. И это при изменении одного ts-файла. Нужно улучшить.

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

Я настроил наш билд таким образом, что при загрузке "основного" проекта будет запущена задача "Watch - Development" при помощи студийного Task Runner'а. Базовые настройки лежат в webpack.config.js: лоадеры, tsconfig, плагины. Внутри webpack.config.js также написан код, который ищет по всем папкам солюшена файлы webpack.config.part.js. Каждый файл webpack.config.part.js содержит настройки билда для конкретного проекта: entry, оверрайды настроек tsconfig и прочее. Обычно, там только entry. И "основной" и "зависимые" проекты содержат файл webpack.config.part.js. Таким образом, для билда скриптов во всех проектах используется только один watch.

Настройка билд-сервера банальна: запускаете webpack -p в папке "основного" проекта.

Еще один нюанс — помимо Webpack, студия все еще билдит наш TypeScript. В TypeScript с версии 2.0 в tsconfig.json можно запретить билд файлов директивой exclude с паттерном ./Scripts/*. Webpack проигнорирует эту настройку. Но в TypeScript 2.1 компилятор должен найти хотя бы один файл для билда, иначе упадет ошибка. Пришлось оставить один пустой файл для принесения в жертву студии.

Результаты

И наследование и использование глобальных переменных выродилось в директивы импорта:

import { Base } from '../../../../../BaseProject/Scripts/Base';
import * as Utils from '../../../../../Utils';

export class Derived extends Base {
    public baz() {
        this.foo();
        Utils.makeSandwich();
        console.log('baz');
    }
}

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

При использовании одного watch вместо нескольких, билд ускорился с 3 минут до нескольких секунд.

Продуктом компиляции станет один файл, содержащий все заимпортированные файлы. Причем, при запуске с production-настройками, файл будет минимизирован. Подробнее о работе Webpack можно узнать на сайте webpack.js.org.

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

Выводы

  • У вас один проект — все и так работает, не трожьте
  • У вас один проект, но хотите плюшки Webpack — вам сюда
  • У вас немного проектов (2-5) — вам все еще сюда
  • У вас более 5 проектов — попробуйте способ, описанный в статье

Ссылки

Автор: Mindbox

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js