Comments 52
О, вменяемый гайд по вебпаку, ура.
- Файл .babelrc
Я бы отметил, что сам по себе .babelrc поддерживает разные окружения. Удобно в dev использовать только то, что не поддерживает твой браузер, а в прод деплоить чистый ES5 для IE, Safari и прочих любителей клея, и без соурс-мапов.
Не люблю я *касты, занимают ужасно много времени по сравнению с написанной документацией. Сам тот факт, что все отсылают на этот скринкаст, говорит о том, что с официальной документацией дела из рук вон плохи.
Нет допущения, что не все воспринимают информацию с видео? Что документация, которую в любой момент можно открыть на любом разделе без поиска фрагмента в видео, и из которой можно скопировать кусок кода, чтобы попробовать, тупо функциональнее? Что некоторые люди быстрее читают, чем смотрят видео? Что не все страдают функциональной неграмотностью, зато могут страдать разными нарушениями восприятия речи на слух? Что документацию проще держать соответствующей текущему релизу, чем перезаписывать скринкаст каждые несколько месяцев?
Большинство проектов используют так много функций, что у них, как правило, есть 2 больших файла конфигурации Webpack.
Есть такой модуль: webpack-merge — он по уму склеивает разные конфиги, так что большие конфиги бьются на маленькие, а из основного можно подтягивать нужные чанки в зависимости от переменных окружения и делать разные сборки (dev, prod, test например)
entry – объект
если ендпоинтов несколько, можно массивом сделать не только entry но и сам конфиг webpack. То есть если в webpack.config.js сделать вот так:
module.exports = [mainConfig, helpersConfig, handlersConfig]; — где элементы массива — полноценные конфиги webpack- а, то он отработает по очереди, как если бы его три раза вызвали с разными конфигами.
как если бы его три раза вызвали с разными конфигами.
а почему «как если бы» если это так и есть
file-loader позволяет передать свой собственный publicPath через параметры, отличный от того, что в output. Таким образом можно все картинки в production-режиме закинуть на отдельный сервер.
Более того, в качестве publicPath можно даже функцию передать, чтобы динамически url генерить.
Отсюда совет — читайте исходники лоадеров, в них много всего интересного.
entry: {
'/modules/someModule': './src/modules/someModule')
}
На выходе получите бандл помещенный в /modules/someModule относительно output.path.
Как уже указывали выше, если работаешь с Webpack плотно, делая не совсем типичные вещи, придется читать исходиники, документация не полная.
Я полгода назад так и не смог этого сделать, чтоб нормально и без дубовых костылей работало.
Если у кого есть хороший опыт в этом вопросе поделитесь пожалуйста
import {B} from './B';
export class A {
toB(): B {
return new B();
}
}
И модуль B.ts:
import {A} from './A';
export class B extends A {
}
И главный модуль:
export * from './A';
export * from './B';
А потом мне надо собрать главный модуль как бандл. Сборка осуществляется через WebPack и ts-loader. Очевидно, в бандле объявление класса A должно идти раньше объявления класса B, иначе возникнет ошибка при попытке наследования. Но из-за циклической зависимости между модулями A и B подключение происходит в обратном порядке. А если подобных модулей добавить еще больше, порядок запуска кода предсказать становится очень сложно. Как решить эту проблему, не объединяя весь код в один модуль? Можно ли заставить WebPack подключать зацикленные модули в том порядке, который мне нужен?
Соответственно у вас в коде реальная жесть.
Либо уберите extends
Либо перенесите toB() в класс B
Вообще класс родитель не должен ничего знать о наследниках
Почему? Какому принципу ООП это противоречит?
Почему жесть? Я говорю же, что в Java и .NET это работает. Даже никаких warning'ов не всплывает. И даже в JavaScript без всяких бандлеров это отлично работает, если просто файлы в правильном порядке подключить. Либо это косяк WebPack, либо решение есть, но мы просто не знаем о нем. Никак не иначе.
Попробуйте указывать типы интерфейсами, а не классами.
Почему? Какому принципу ООП это противоречит?
инкапсуляция и KISS.
либо решение есть, но мы просто не знаем о нем. Никак не иначе.
Решение Вам сказали — родитель ничего не должен знать о наследниках. Все остальное — костыли на кривую архитектуру. Про обезьяну, банан и джунгли слышали? Это как раз следствие таких вот изысков.
class Object {
String toString();
}
class String extends Object {}
Теперь давайте дружно назовем разработчиков языков программирования Java, JS, и всего .NET-совместимого обезьянами.
Инкапсуляция и KISS — это не принципы, а концепции. И они здесь ни при чем. Учите матчасть.
Теперь давайте дружно назовем разработчиков языков программирования Java, JS, и всего .NET-совместимого обезьянами.
В том примере никто никого обезьяной не называл, там вообще то все вокруг банана крутилось. Но судя по вашей реплика этот пример Вам не знаком.
В вашем примере как раз и проявляется кривизна архитектуры. Потому как toString должен сериализовать объект в строку — СКАЛЯР. А потом строку делают объектом и вылезают костыли. Что характерно — свою задачу, на которой Вы столкнулись с этой засадой Вы почему то постеснялись озвучить, стыдливо прикрывшись разработчиками языков, которые видимо должны были вызвать благоговейный трепет и безусловную капитуляцию. И расскажите мне, пожалуйста, что именно я должен был увидеть на вики по приведенной Вами ссылке что дало бы мне просветление и осознание ограниченности моего кругозора?
All object-oriented programming languages provide mechanisms that help you implement the object-oriented model. They are encapsulation, inheritance, and polymorphism. Let’s take a look at these concepts.
Кстати, на сайте о java
Так что действительно учите-ка матчасть
Object и String в java это классы, а не интерфейсы.
Более того в примерах вашего кода тоже классы.
Что касается нарушения, то сериализация в строку произвольного класса в корневом это и есть general-purpose.
Дальше объяснять?
interface IA {
toB(): IB;
}
interface IB extends IA {}
class A implements IA {
toB(): IB {
return new B(); // здесь зависимость A от B
}
}
class B extends A implements IB {} // здесь зависимость B от A
Модули классов A и B все равно могут идти в неправильном порядке.
Конечно, можно убрать из A дефолтную реализацию метода toB. Но зачем? Чтобы бандлер это прожевал? Я лучше выберу другой бандлер, но ограничивать себя ради такой ерунды не стану.
Я не понял, вы считаете, что toString является плохим архитектурным решением, нарушающим ISP?
В этом ваша и проблема, что вы путаете принципы и конкретные реализации, которые могут от этих принципов отходить.
И, да, Object.toString в java это именно нарушение.
Что явилось причиной такого компромисса это не ко мне.
Вы же даже противоречия в подходе не видите.
Чёткий догмат ООП это SOLID, Java это идеальное ООП — значит бездумно лепим как там.
Теперь возвращаясь к вашему коду
Класс A может содержать метод toB(), но не как интерфейс полученный от родителя.
Строго говоря папа не обязан на генетическом уровне знать как из сына сделать дочку.
Это обязанности хирурга.
У которого общее с папой только то что они люди.
То есть возможен и будет правильным вариант
interface ICommon {}
interface IA extends ICommon {}
interface IB extends ICommon {
toA(): IA
}
Более точный рецепт вы сможете получить дав описание конкретной задачи
Есть иерархия классов представлений одной и той же модели — записи в блоге. Запись может отображаться в виде элемента общей ленты, в виде элемента ленты своих записей, в виде попапа на Google-карте, в виде содержимого диалога и никто не знает, какие еще новые представления появятся в будущем. Все представления выглядят по-разному, но у них есть много общего — общая часть вынесена в базовый абстрактный класс. В частности, общее у всех то, что если кликнуть по кнопке «Показать в диалоге», то открывается новый диалог с этой статьей. Здесь и возникает циклическая зависимость. Я реализовал в базовом классе обработку клика по кнопке с показом диалога. Это отлично работает, если явно указать порядок подключения файлов. TypeScript не показывает ошибок компиляции, даже предупреждений. Проблема возникает при попытке собрать это WebPack'ом. Приходится копировать эту логику во все подклассы. Не предлагайте решения с шиной событий или прочими хитрыми штуками: не хватало еще ради использования WebPack перезжать на другой фреймворк.
Тут проблема не столько в вебпаке, сколько в ущербной модульной системе, в которой можно выразить лишь динамические зависимости, но не статические.
Впрочем, я тут пилю свой велосипед, который имеет следующие особенности:
- Он просто работает. Не надо ничего конфигурировать, ставить плагины и тп. Всё, что нужно — следовать нескольким достаточно простым соглашениям по расположению кода и именованию идентификаторов.
- Не требует ручного импорта зависимостей — зависимости определяются по факту использования. Тут же выясняется приоритет зависимости, что позволяет даже циклические зависимости выстраивать в правильном порядке.
- Уже поддерживаются: JS и TS вперемешку; poscss+cssnext для CSS; сборка тестов в отдельный бандл; разные бандлы под разные окружения; сорсмапы с исходниками внутри.
- Есть дев сервер, собирающий бандлы по требованию и отслеживающий изменения файлов. то есть перезагружая страницу вы получаете гарантированно свежий бандл с учётом изменений.
- Намернно нет различия между продом и девом, что исключает редкие, но болезненные неприятные сюрпризы.
Например, ваш пример будет выглядеть как-то так:
my/aaa/aaa.ts
class $my_aaa {
toBBB() {
return new $my_bbb();
}
}
my/bbb/bbb.ts
class $my_bbb extends $my_aaa {
}
Чтобы сбилдить бадл достаточно набрать: npm start my
В директории ./my/-/
сгенерируются все необходимые файлы.
Был бы рад услышать конструктивную критику :-)
Я не совсем понял, как ваш сборщик работает. Он бандлит напрямую TypeScript, без промежуточной компиляции в CommonJS/AMD? Одна папка = один бандл? Есть документация какая-нибудь, кроме README.md? Есть поддержка SourceMaps? Я тоже свой велосипед использую, но честно-честно пытаюсь найти что-нибудь для себя подходящее, что все используют. Пока плохо получается :D
Он действует просто:
- Пробегается по файлам, регулярками вытягивает из них зависимости.
- Строит граф зависимостей между модулями (модуль — директория) и сериализует его, выстраивая правильную последовательность файлов.
- Пробегается трансляторами, формируя из шаблонов тайпскрипты, а из тайпскриптов яваскрипты.
- Собирает бандлы. Яваскрипты в один файл, тесты в другой, стили в третий, граф зависимостей в четвёртый.
Документацией займусь, когда логика устаканится. Сейчас прикручиваю, чтобы в рамках одного модуля между файлами трекались зависимости. Сорсмапы, конечно, поддерживаются.
Запилил, наконец, отслеживание зависимостей в рамках одного модуля. Теперь можно не раскидывать зависимые друг от друга файлы по разным директориям. Главное — давать им правильные имена:
./my/aaa.ts
./my/bbb.ts
Либо:
./my/my_aaa.ts
./my/my_bbb.ts
В этом случае, сначала в бандл войдёт модуль bbb
, а потом уже aaa
, так как aaa
от bbb
зависит с приоритетом -2, а bbb от aaa с приоритетом 0.
То есть наоборот, сначала aaa
, потом bbb
. :-)
давать им правильные имена:
Можете развернуть мысль подробнее? (интерес не праздный) Каковы правила именования?
Они должны матчиться на пространства имён. Например, в коде используется класс $my.view.card
(js/ts) или $my_view_card
(js/ts) или .my-view-card
(css) или [my-view-card]
(css) или --my-view-card
(css). Его определение должно лежать в модуле ./my/view/card
или ./my/view
или ./my
. Модуль грузится целиком (все стили, скрипты, шаблоны подключаются).
В рамках одного модуля зависимости определяются по префиксу имени. Например, в модуле ./my/view
имя файла может быть view_card.ts
или view-card.ts
или card.ts
или card_important.ts
какой-нибудь.
Ага, понял. Спасибо за ответ. А регулярки — постоянное решение? В сторону тайпскриптного компилятора не смотрели?(строить ast и искать по взрослому)
Пока да, они работают просто, надёжно, быстро и универсально (кроме TS, есть ещё и JS, и CSS и прочие языки). У тайпскриптового компилятора документации практически нет, так что у меня пока даже инкрементальную компиляцию прикрутить не удалось, из-за чего приходится ждать по несколько секунд перекомпиляции после изменения файла.
Ну и похвастаюсь — полный квест по установке сборщика, выкачивания исходников, пересборке сборщика, сборке проекта и открытия его в браузере можно пройти менее чем за 2 минуты, выполнив все 2 команды в консоли:
git clone https://github.com/nin-jin/pms.git ./pms && cd pms
npm start
У тайпскриптового компилятора документации практически нет, так что у меня пока даже инкрементальную компиляцию прикрутить не удалось
Это да. Я сейчас заморочился на метапрограммирование (конкретно на данном этапе — генерация клиента и сервера на основе swagger спеки, typescript естественно, то что предлагают разработчики стандарта — не выдерживает никакой критики (именно по typescript-у, за остальные молчу), но не только, там много всего. Так вот там похоже что похожая задача встанет, потому и присматриваюсь к Вашему проекту. Но хочется решить ее (если потребуется) так что бы правила не лезли в код и не диктовали, так как там похоже править надо будет не шаблоны а уже сгенеренный код (в частности смотреть какие теги использовались на клиенте и какие sql вызовы — на бэке)
Я похожую задачу решал чуть по другому. Было описание бизнес домена в простом виде типа:
$my_album $my_model
- альбом с фотографиями
title string
description string
person link $my_person
image link-list $my_image
$my_image $my_model
- мета информация о фотографии
linkSmall string
linkBig string
width integer
height integer
person link $my_person
album link-set $my_album
service link-set $my_service
По нему обновлялась схема базы данных и генерировались базовые классы, от которых можно было отнаследоваться и добавить поведения. В частности — разрешить доступ к определённым полям определённым типам пользователей для совершения определённых действий. Все модели через единый фасад по единой схеме были доступны по http и ws. При этом код получался изоморфным. Разница была лишь в том, что На клиенте модели работали с сервером, а на сервере — с субд. В принципе, можно и swagger описание генерировать, но куда лучше генерировать сразу либу на нужном языке, абстрагируя от конкретного протокола взаимодействия.
Да, у меня похожая задача. Но я swagger не генерю, он задан (ручками) — и на его основе генерится клиент и каркас сервера (используем плюсы статичной типизации при компиляции + валидатор в рантайме). Тут вроде как workflow утрясся, больших изменений не предполагаю. А вот с базой и вьюхами на клиенте пока думаю.
А Вы на базу через ORM лезли или ручками? (я пробую всю логику перенести на базу а в коде оставить только вызовы хранимок. Ну как пробую — делаю уже, а пробую из этого реализовать плюсы)
и генерировались базовые классы
То есть Вы по сути свою ORM генерили?
Проблема сваггера в том, что это довольно низкоуровневое описание заточенное под http. Высокоуровневую информацию из него вытаскивать достаточно проблематично.
Я использовал графовую субд — её для таких вещей использовать одно удовольствие. https://habrahabr.ru/post/267079/
10 особенностей Webpack