Webpack считается лучшим инструментом для сборки приложений на React и Redux. Полагаю, многие из тех, кто сегодня использует Angular 2 и другие фреймворки, не обходят вниманием и Webpack. И поскольку начинать работу с данным инструментом всегда непросто, я решил посвятить этой теме несколько публикаций в надежде облегчить старт другим разработчикам и заодно продемонстрировать некоторые особенности Webpack.



Когда я впервые увидел его файл конфигурации, он показался мне крайне странным и путанным. Но спустя некоторое время я понял, что всё дело в уникальном синтаксисе Webpack и несколько иной философии, которая может поначалу немного сбить с толку. Но, с другой стороны, именно эта новая философия и делает Webpack таким популярным.

Философия Webpack


Можно выделить 2 основных принципа философии Webpack:

  1. Что угодно может быть модулем. Модулями могут быть как JS-файлы, так и CSS-файлы, HTML-файлы или изображения. То есть можно использовать и require(“myJSfile.js”), и require(“myCSSfile.css”). Таким образом, вы можете разбивать любой артефакт на меньшие удобоуправляемые части, использовать их повторно и так далее.
  2. Загружайте только то, что вам нужно и когда вам нужно. Обычно сборщики модулей берут все модули и генерируют из них один большой файл bundle.js. Но во многих приложениях размер такого файла может достигать 10–15 MB – а это слишком много. Потому Webpack оснащен рядом функций, позволяющих делить код и генерировать множество bundle-файлов, а также асинхронно загружать необходимые части приложения тогда, когда это нужно.

А теперь давайте наконец перейдем к особенностям Webpack.

1. Development и production


Прежде всего нужно понять, что Webpack имеет множество функций, часть которых ориентирована на development, другая – на production, а третья – на то и на другое.



Пример Webpack-файлов для development и production

Большинство проектов используют так много функций, что у них, как правило, есть 2 больших файла конфигурации Webpack.

Для создания бандлов вам, скорее всего, потребуется писать скрипты в package.json, примерно так:

 “scripts”: {
  //npm run build to build production bundles
  “build”: “webpack --config webpack.config.prod.js”,
  //npm run dev to generate development bundles and run dev. server
  “dev”: “webpack-dev-server”
 }

2. webpack CLI и webpack-dev-server


Важно отметить что Webpack, как сборщик модулей, предоставляет 2 интерфейса:

  1. Инструмент webpack CLI – интерфейс по умолчанию (установлен как часть самого Webpack).
  2. Инструмент webpack-dev-server – сервер Node.js (нужно устанавливать отдельно).

Webpack CLI (подходит для production-сборок)

Этот инструмент берет опции через инструмент CLI, а также через файл конфигурации (по умолчанию – webpack.config.js) и передает их в Webpack для сборки.

И хотя вы можете начать изучение Webpack, используя CLI-инструмент, он по большей части пригодится вам только для последующей генерации production-сборок.

Пример использования:

OPTION 1: 
//Install it globally
npm install webpack --g
//Use it at the terminal 
$ webpack //<--Generates bundle using webpack.config.js

OPTION 2 :
//Install it locally & add it to package.json
npm install webpack --save
//Add it to package.json's script 
“scripts”: {
 “build”: “webpack --config webpack.config.prod.js -p”,
 ...
 }
//Use it by running the following:
"npm run build"

Webpack-dev-server (подходит для development-сборок)

Это Express Node.js сервер, который работает на порту 8080. Этот сервер вызывает Webpack изнутри, что дает дополнительные возможности вроде перезагрузки браузера (Live Reloading) и/или замены только что измененного модуля (Hot Module Replacement, или HMR).

Пример использования:

OPTION 1:
//Install it globally
npm install webpack-dev-server --save
//Use it at the terminal
$ webpack-dev-server --inline --hot
OPTION 2:
// Add it to package.json's script 

“scripts”: {
 “start”: “webpack-dev-server --inline --hot”,
 ...
 }
// Use it by running 
$ npm start
Open browser at:
http://localhost:8080

Webpack и опции инструмента webpack-dev-server

Стоит отметить, что некоторые опции, такие как inline и hot, используются только для инструмента webpack-dev-server, в то время как, скажем, hide-modules подходят только для CLI.

Опции webpack-dev-server CLI и опции config

Стоит также отметить, что существует 2 способа передачи опций в webpack-dev-server:

  1. через объект devServer файла webpack.config.js;
  2. через опции CLI.

//Via CLI
webpack-dev-server --hot --inline
//Via webpack.config.js
devServer: {
 inline: true,
 hot:true
 }

Я обнаружил, что devServer config (hot:true и inline:true) иногда не работает. Поэтому я предпочитаю передавать опции как CLI-опции внутри package.json, вот так:

//package.json
{
scripts: 
   {“start”: “webpack-dev-server --hot --inline”}
}

Примечание: убедитесь, что не передаете hot:true и -hot вместе.

Опции hot и inline для webpack-dev-server

Опция inline добавляет Live Reloading для всей страницы. Опция hot включает Hot Module Reloading – горячую перезагрузку модуля, которая перезагружает только измененный компонент (а не всю страницу). Если передать обе опции, то при изменении источника webpack-dev-server запустит прежде всего HMR и, только если это не сработает, перезагрузит всю страницу.

//When the source changes, all 3 options generates new bundle but,
 
//1. doesn't reload the browser page
$ webpack-dev-server
//2. reloads the entire browser page
$ webpack-dev-server --inline
//3. reloads just the module(HMR), or the entire page if HMR fails
$ webpack-dev-server  --inline --hot

3. entry– ?строка, массив и объект


Entry передает в Webpack данные о том, где находится корневой модуль или точка входа. Это может быть строка, массив или объект – причем разные типы используются для разных целей.

Если у вас всего одна точка входа (как в большинстве приложений), вы можете выбрать любой формат, и результат будет тот же.


Разные типы entry с одинаковым результатом

entry? – ?массив

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

Например, если вам понадобится googleAnalytics.js в вашем HTML, можно сделать так, чтобы Webpack добавил этот файл в конец bundle.js:



entry ?– объект

Предположим, у вас настоящее многостраничное приложение, не SPA с мультипросмотром, а несколько HTML-файлов (index.html и profile.html). С помощью Webpack вы можете сразу сгенерировать множество бандлов, используя объект entry.

Файл конфигурации на примере ниже будет генерировать 2 JS-файла: indexEntry.js и profileEntry.js, которые можно использовать в index.html и profile.html соответственно.



Пример использования:

//profile.html
<script src=”dist/profileEntry.js”></script>
//index.html
<script src=”dist/indexEntry.js”></script>

Примечание: название файла происходит от ключей объекта entry

entry? – ?комбинация

Вы также можете использовать entry-массивы внутри entry-объекта. К примеру, следующий файл конфигурации сгенерирует 3 файла: index.js, profile.js и vendor.js, содержащий 3 vendor-файла.



4. output? – ?path и publicPath


output сообщает Webpack, где и как хранить результирующие файлы. У output есть 2 свойства, path и publicPath, поначалу это может немного смутить.

Свойство path сообщает Webpack, где хранить результат, тогда как свойство publicPath используется в нескольких плагинах Webpack для обновления URL внутри CSS- и HTML-файлов во время генерации production-сборок.


Использование свойства publicPath для development и production

Предположим, в вашем CSS-файле содержится URL для загрузки ./test.png на localhost. Но на production файл test.png может быть расположен на CDN, тогда как ваш сервер Node.js может работать на Heroku. Значит, работая на production, вам придется вручную обновлять URL во всех файлах, чтобы они указывали на CDN.

Но вместо этого можно применить свойство publicPath, а также целый ряд сопряженных плагинов, чтобы автоматически обновлять все URL при генерации production-сборок.


Пример publicPath production

// Development: Both Server and the image are on localhost
.image { 
  background-image: url(‘./test.png’);
 }
// Production: Server is on Heroku but the image is on a CDN
.image { 
  background-image: url(‘https://someCDN/test.png’);
 }

5. Загрузчики и цепочки загрузчиков


Загрузчики – это дополнительные узловые модули, которые помогают загружать или импортировать файлы разных типов в совместимых с браузерами форматах – JS, CSS и т. д. Последующие загрузчики также позволяют импортировать такие файлы в JS, используя require или import в ES6.

Например, вы можете использовать babel-loader для конвертации JS-файла, написанного на ES6, в совместимый с браузером ES5:

module: {
 loaders: [{
  test: /\.js$/, 

Цепочки загрузчиков (работают справа налево)

Несколько загрузчиков для одного типа файлов можно объединить в цепочку. Формирование цепочек осуществляется справа налево, а загрузчики отделяются восклицательным знаком: «!».

Предположим, у нас есть CSS-файл myCssFile.css, и мы хотим выгрузить его содержимое в тег внутри HTML. Это можно сделать, используя 2 загрузчика: css-loader и style-loader.

module: {
 loaders: [{
  test: /\.css$/,
  loader: ‘style!css’ <--(short for style-loader!css-loader)
 }]

Вот как это работает:



  1. Webpack осуществляет поиск взаимосвязей CSS-файлов внутри модулей. Иными словами, Webpack проверяет, имеет ли JS-файл require(myCssFile.css). Если обнаруживается взаимосвязь, Webpack сначала передает этот файл в css-loader.
  2. css-loader загружает в JSON все CSS-файлы и их собственные взаимосвязи (т. е. import otherCSS). Затем Webpack передает результат в style-loader.
  3. style-loader берет JSON, добавляет его в стилевой тег ?– ? – и вставляет этот тег в файл index.html.

6. Настройка загрузчиков


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

В следующем примере url-loader настроен таким образом, чтобы использовать DataURL для изображений размером менее 1024 байт и URL для изображений размером более 1024 байт. Это можно осуществить, передав параметр limit одним из двух способов:



7. Файл .babelrc


babel-loader использует настройку presets, чтобы правильно конвертировать ES6 в ES5 и парсить React JSX в JS. Настройки можно передать через параметр query, как показано ниже:

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel',
      query: {
        presets: ['react', 'es2015']
      }
    }
  ]
}

Тем не менее, во многих проектах настройки babel могут стать чересчур большими, потому лучше всего держать их в файле конфигурации babel-loader, который называется .babelrc. Загрузчик babel-loader автоматически загрузит файл .babelrc, если таковой существует.

Это должно выглядеть примерно так:

//webpack.config.js 
module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel'
    }
  ]
}

//.bablerc
{
 “presets”: [“react”, “es2015”]
}

8. Плагины


Плагины – это дополнительные узловые модули, которые работают с результирующим бандлом.

К примеру, uglifyJSPlugin берет bundle.js, а затем минимизирует и обфусцирует его содержимое, чтобы уменьшить размер файла.

Аналогичным образом extract-text-webpack-plugin внутренне использует css-loader и style-loader, чтобы собрать все CSS-файлы в одном месте. Этот плагин извлекает результат во внешний файл styles.css и добавляет ссылку на этот файл в index.html.

//webpack.config.js
//Take all the .css files, combine their contents and it extract them to a single "styles.css"
var ETP = require("extract-text-webpack-plugin");

module: {
 loaders: [
  {test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
  ]
},
plugins: [
    new ExtractTextPlugin("styles.css") //Extract to styles.css file
  ]
}

Примечание: если вы хотите просто встроить CSS как элемент стиля в HTML, это можно сделать без плагина extract-text-webpack-plugin, а за счет CSS и загрузчиков стилей, как показано ниже:

module: {
 loaders: [{
  test: /\.css$/,
  loader: ‘style!css’ <--(short for style-loader!css-loader)
 }]

9. Загрузчики и плагины


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

В свою очередь, плагины работают на уровне бандла или фрагмента по окончании генерации бандла. А некоторые плагины вроде commonsChunksPlugins пошли еще дальше и изменили способ создания самих бандлов.

10. Разрешение файловых расширений


Многие файлы конфигурации Webpack имеют свойство разрешения расширений с пустой строкой, как показано ниже. Пустая строка нужна, чтобы способствовать импорту без расширений вроде require(“./myJSFile”) или импортировать myJSFile из ./myJSFile без файловых расширений.

{
 resolve: {
   extensions: [‘’, ‘.js’, ‘.jsx’]
 }
}

Вот и всё!
Поделиться с друзьями
-->

Комментарии (51)


  1. k12th
    05.09.2016 16:33
    +3

    О, вменяемый гайд по вебпаку, ура.


    1. Файл .babelrc

    Я бы отметил, что сам по себе .babelrc поддерживает разные окружения. Удобно в dev использовать только то, что не поддерживает твой браузер, а в прод деплоить чистый ES5 для IE, Safari и прочих любителей клея, и без соурс-мапов.


    1. aktmtm
      05.09.2016 18:58
      +7

      Есть хороший скринкаст от Ильи Кантора: http://learn.javascript.ru/screencast/webpack на Русском.


      1. gibson_dev
        05.09.2016 19:47

        Подтверждаю, процентов 90-95 всех вопросов как рукой снимет.


      1. k12th
        06.09.2016 00:05
        +4

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


        1. sosnovskyas
          06.09.2016 17:35

          Нет допущения что скринкаст может быть очень удачным? Я лично присоединяюсь ко всем кто советует. Множество практических примерров. Переход от простого к сложному. Разбор множество деталей и неочевидностей. Помоему достаточно аргументов.


          1. k12th
            06.09.2016 17:47

            Нет допущения, что не все воспринимают информацию с видео? Что документация, которую в любой момент можно открыть на любом разделе без поиска фрагмента в видео, и из которой можно скопировать кусок кода, чтобы попробовать, тупо функциональнее? Что некоторые люди быстрее читают, чем смотрят видео? Что не все страдают функциональной неграмотностью, зато могут страдать разными нарушениями восприятия речи на слух? Что документацию проще держать соответствующей текущему релизу, чем перезаписывать скринкаст каждые несколько месяцев?


  1. gearbox
    05.09.2016 16:44
    +1

    Большинство проектов используют так много функций, что у них, как правило, есть 2 больших файла конфигурации Webpack.

    Есть такой модуль: webpack-merge — он по уму склеивает разные конфиги, так что большие конфиги бьются на маленькие, а из основного можно подтягивать нужные чанки в зависимости от переменных окружения и делать разные сборки (dev, prod, test например)


    1. gearbox
      05.09.2016 16:49

      entry ?– объект

      если ендпоинтов несколько, можно массивом сделать не только entry но и сам конфиг webpack. То есть если в webpack.config.js сделать вот так:
      module.exports = [mainConfig, helpersConfig, handlersConfig]; — где элементы массива — полноценные конфиги webpack- а, то он отработает по очереди, как если бы его три раза вызвали с разными конфигами.


      1. serf
        05.09.2016 17:42

        как если бы его три раза вызвали с разными конфигами.

        а почему «как если бы» если это так и есть


        1. gearbox
          05.09.2016 18:19

          Вызывая три раза с разными конфигами, вы не расшарите между ними контекст, вызывая три конфига в одном массиве — все три конфига захватывают один контекст (конфиг webpack — это просто js модуль, мы же помним?)


      1. serf
        05.09.2016 17:54

        Я бы лучше постарался все впихнуть в один конфиг, с несколькими конфигами не всегда будет удобно работать.


  1. AMar4enko
    05.09.2016 16:45

    К пункту 4:
    file-loader позволяет передать свой собственный publicPath через параметры, отличный от того, что в output. Таким образом можно все картинки в production-режиме закинуть на отдельный сервер.
    Более того, в качестве publicPath можно даже функцию передать, чтобы динамически url генерить.
    Отсюда совет — читайте исходники лоадеров, в них много всего интересного.


  1. serf
    05.09.2016 17:50

    Новички не знают, но указывая entry таким образом (путь как имя entry) можно иметь разные output для разных entry, иногда бывает очень полезно:

    entry: {
        '/modules/someModule': './src/modules/someModule')
    }
    


    На выходе получите бандл помещенный в /modules/someModule относительно output.path.

    Как уже указывали выше, если работаешь с Webpack плотно, делая не совсем типичные вещи, придется читать исходиники, документация не полная.


    1. serf
      05.09.2016 17:52

      Еще «resolve.alias» очень полезная вещь.


  1. asci
    05.09.2016 18:13
    +1

    Есть, кстати, отличный туториал по вебпаку на русском https://learn.javascript.ru/screencast/webpack


  1. seDruid
    06.09.2016 07:27

    А как обстоят дела с обработкой изображений в webpack? Например нужно собрать все мелкие картинки(svg,png,jpg...) и склеить их в спрайт (один или несколько в зависимости от размера) и при этом корректно подменить в в css? Это возможно?

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

    Если у кого есть хороший опыт в этом вопросе поделитесь пожалуйста


  1. enepomnyaschih
    06.09.2016 22:19

    Может быть, вы сможете ответить на мой вопрос. Есть три модуля TypeScript. Модуль A.ts:

    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 подключать зацикленные модули в том порядке, который мне нужен?


    1. BoryaMogila
      07.09.2016 19:32

      Насколько я знаю то webpack при зборке вынесет class A и B в верх бандла, а потом их будет использовать. Но какая то странная зависимость. Так делать не стоит.


      1. enepomnyaschih
        07.09.2016 20:34

        Нет, он прогонит код B.ts и бандл упадет с ошибкой еще на этапе загрузки. А если в главном модуле поменять их местами, все будет работать. Никакая это не странная зависимость, такое встречается сплошь и рядом. Особенно в TypeScript, где целый модуль надо импортировать лишь для того, чтобы указать имя стороннего класса в семантике какого-нибудь метода. Это не только проблема WebPack: RequireJS тоже этим страдает. В то время как Java и .NET подобной проблемой не страдали никогда. Обидно, что при всем своем развитии в последние годы средства работы с JavaScript все еще такие неудобные.


        1. Fortop
          08.09.2016 08:32
          +1

          Вообще класс родитель не должен ничего знать о наследниках

          Соответственно у вас в коде реальная жесть.
          Либо уберите extends
          Либо перенесите toB() в класс B


          1. enepomnyaschih
            08.09.2016 08:46

            Вообще класс родитель не должен ничего знать о наследниках

            Почему? Какому принципу ООП это противоречит?

            Почему жесть? Я говорю же, что в Java и .NET это работает. Даже никаких warning'ов не всплывает. И даже в JavaScript без всяких бандлеров это отлично работает, если просто файлы в правильном порядке подключить. Либо это косяк WebPack, либо решение есть, но мы просто не знаем о нем. Никак не иначе.


            1. k12th
              08.09.2016 10:33

              Попробуйте указывать типы интерфейсами, а не классами.


              1. enepomnyaschih
                08.09.2016 13:37

                Возможно, это и решение, если мне не нужна дефолтная реализация. А она иногда нужна =)


              1. enepomnyaschih
                08.09.2016 13:41

                Хотя даже при наследовании интерфейсов будет циклическая зависимость между модулями, где классы объявлены.


            1. gearbox
              08.09.2016 12:23

              Почему? Какому принципу ООП это противоречит?

              инкапсуляция и KISS.


              либо решение есть, но мы просто не знаем о нем. Никак не иначе.

              Решение Вам сказали — родитель ничего не должен знать о наследниках. Все остальное — костыли на кривую архитектуру. Про обезьяну, банан и джунгли слышали? Это как раз следствие таких вот изысков.


              1. enepomnyaschih
                08.09.2016 12:28
                -1

                Хотите реальный пример?

                class Object {
                 String toString();
                }
                class String extends Object {}
                


                Теперь давайте дружно назовем разработчиков языков программирования Java, JS, и всего .NET-совместимого обезьянами.

                Инкапсуляция и KISS — это не принципы, а концепции. И они здесь ни при чем. Учите матчасть.


                1. gearbox
                  08.09.2016 16:26

                  Теперь давайте дружно назовем разработчиков языков программирования Java, JS, и всего .NET-совместимого обезьянами.

                  В том примере никто никого обезьяной не называл, там вообще то все вокруг банана крутилось. Но судя по вашей реплика этот пример Вам не знаком.


                  В вашем примере как раз и проявляется кривизна архитектуры. Потому как toString должен сериализовать объект в строку — СКАЛЯР. А потом строку делают объектом и вылезают костыли. Что характерно — свою задачу, на которой Вы столкнулись с этой засадой Вы почему то постеснялись озвучить, стыдливо прикрывшись разработчиками языков, которые видимо должны были вызвать благоговейный трепет и безусловную капитуляцию. И расскажите мне, пожалуйста, что именно я должен был увидеть на вики по приведенной Вами ссылке что дало бы мне просветление и осознание ограниченности моего кругозора?


                  1. enepomnyaschih
                    08.09.2016 19:02

                    Я не собираюсь с вами препираться. Если не знаете ответ на поставленный вопрос о WebPack, я с вами разговаривать не буду. А вы можете продолжать есть этот кактус, оправдываясь, что если он чего-то не поддерживает, значит «архитектура кривая».


                1. Fortop
                  08.09.2016 19:45

                  The OOP Principles

                  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

                  Так что действительно учите-ка матчасть


                  1. enepomnyaschih
                    08.09.2016 19:50

                    Под принципами ООП в первую очередь понимают SOLID. Где здесь нарушена инкапсуляция?


                    1. Fortop
                      08.09.2016 19:54

                      Если рассматривать со стороны SOLID, то приведеный пример нарушает I


                      1. enepomnyaschih
                        08.09.2016 19:58

                        Как интерфейс, содержащий один метод, может нарушить ISP? Будь их два, можно было бы поспорить.


                        1. Fortop
                          08.09.2016 20:03

                          Интерфейс не может содержать реализацию.

                          Object и String в java это классы, а не интерфейсы.
                          Более того в примерах вашего кода тоже классы.

                          Что касается нарушения, то сериализация в строку произвольного класса в корневом это и есть general-purpose.

                          Дальше объяснять?


                          1. enepomnyaschih
                            08.09.2016 20:22

                            Я умею отличать класс от интерфейса. Проблема остается в обоих случаях:

                            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?


                            1. Fortop
                              09.09.2016 12:41

                              Начну с конца.

                              В этом ваша и проблема, что вы путаете принципы и конкретные реализации, которые могут от этих принципов отходить.

                              И, да, Object.toString в java это именно нарушение.
                              Что явилось причиной такого компромисса это не ко мне.

                              Вы же даже противоречия в подходе не видите.
                              Чёткий догмат ООП это SOLID, Java это идеальное ООП — значит бездумно лепим как там.

                              Теперь возвращаясь к вашему коду
                              Класс A может содержать метод toB(), но не как интерфейс полученный от родителя.

                              Строго говоря папа не обязан на генетическом уровне знать как из сына сделать дочку.
                              Это обязанности хирурга.
                              У которого общее с папой только то что они люди.

                              То есть возможен и будет правильным вариант
                              interface ICommon {}
                              interface IA extends ICommon {}
                              interface IB extends ICommon {
                              toA(): IA
                              }

                              Более точный рецепт вы сможете получить дав описание конкретной задачи


                              1. enepomnyaschih
                                09.09.2016 13:34

                                Ок, если в Java/JS/.NET это нарушение ISP, так и быть, приведу свой пример.

                                Есть иерархия классов представлений одной и той же модели — записи в блоге. Запись может отображаться в виде элемента общей ленты, в виде элемента ленты своих записей, в виде попапа на Google-карте, в виде содержимого диалога и никто не знает, какие еще новые представления появятся в будущем. Все представления выглядят по-разному, но у них есть много общего — общая часть вынесена в базовый абстрактный класс. В частности, общее у всех то, что если кликнуть по кнопке «Показать в диалоге», то открывается новый диалог с этой статьей. Здесь и возникает циклическая зависимость. Я реализовал в базовом классе обработку клика по кнопке с показом диалога. Это отлично работает, если явно указать порядок подключения файлов. TypeScript не показывает ошибок компиляции, даже предупреждений. Проблема возникает при попытке собрать это WebPack'ом. Приходится копировать эту логику во все подклассы. Не предлагайте решения с шиной событий или прочими хитрыми штуками: не хватало еще ради использования WebPack перезжать на другой фреймворк.


    1. vintage
      10.09.2016 16:17

      Тут проблема не столько в вебпаке, сколько в ущербной модульной системе, в которой можно выразить лишь динамические зависимости, но не статические.


      1. vintage
        10.09.2016 16:48
        -1

        Впрочем, я тут пилю свой велосипед, который имеет следующие особенности:


        1. Он просто работает. Не надо ничего конфигурировать, ставить плагины и тп. Всё, что нужно — следовать нескольким достаточно простым соглашениям по расположению кода и именованию идентификаторов.
        2. Не требует ручного импорта зависимостей — зависимости определяются по факту использования. Тут же выясняется приоритет зависимости, что позволяет даже циклические зависимости выстраивать в правильном порядке.
        3. Уже поддерживаются: JS и TS вперемешку; poscss+cssnext для CSS; сборка тестов в отдельный бандл; разные бандлы под разные окружения; сорсмапы с исходниками внутри.
        4. Есть дев сервер, собирающий бандлы по требованию и отслеживающий изменения файлов. то есть перезагружая страницу вы получаете гарантированно свежий бандл с учётом изменений.
        5. Намернно нет различия между продом и девом, что исключает редкие, но болезненные неприятные сюрпризы.

        Например, ваш пример будет выглядеть как-то так:


        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/-/ сгенерируются все необходимые файлы.


        Был бы рад услышать конструктивную критику :-)


        1. enepomnyaschih
          10.09.2016 19:29

          Да, к WebPack я прикопался лишь потому, что он совмещает CommonJS с клиентом. На самом деле, как мне кажется, проблема в самом CommonJS. Но и AMD не лучше, а других общепринятых форматов объявления JS-модулей я не знаю.

          Я не совсем понял, как ваш сборщик работает. Он бандлит напрямую TypeScript, без промежуточной компиляции в CommonJS/AMD? Одна папка = один бандл? Есть документация какая-нибудь, кроме README.md? Есть поддержка SourceMaps? Я тоже свой велосипед использую, но честно-честно пытаюсь найти что-нибудь для себя подходящее, что все используют. Пока плохо получается :D


          1. vintage
            10.09.2016 20:13

            Он действует просто:


            1. Пробегается по файлам, регулярками вытягивает из них зависимости.
            2. Строит граф зависимостей между модулями (модуль — директория) и сериализует его, выстраивая правильную последовательность файлов.
            3. Пробегается трансляторами, формируя из шаблонов тайпскрипты, а из тайпскриптов яваскрипты.
            4. Собирает бандлы. Яваскрипты в один файл, тесты в другой, стили в третий, граф зависимостей в четвёртый.

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


            1. vintage
              11.09.2016 12:26

              Запилил, наконец, отслеживание зависимостей в рамках одного модуля. Теперь можно не раскидывать зависимые друг от друга файлы по разным директориям. Главное — давать им правильные имена:


              ./my/aaa.ts
              ./my/bbb.ts

              Либо:


              ./my/my_aaa.ts
              ./my/my_bbb.ts

              В этом случае, сначала в бандл войдёт модуль bbb, а потом уже aaa, так как aaa от bbb зависит с приоритетом -2, а bbb от aaa с приоритетом 0.


              1. vintage
                11.09.2016 12:36

                То есть наоборот, сначала aaa, потом bbb. :-)


              1. gearbox
                11.09.2016 15:00

                давать им правильные имена:

                Можете развернуть мысль подробнее? (интерес не праздный) Каковы правила именования?


                1. vintage
                  11.09.2016 16:02

                  Они должны матчиться на пространства имён. Например, в коде используется класс $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 какой-нибудь.


                  1. gearbox
                    11.09.2016 18:03

                    Ага, понял. Спасибо за ответ. А регулярки — постоянное решение? В сторону тайпскриптного компилятора не смотрели?(строить ast и искать по взрослому)


                    1. vintage
                      11.09.2016 19:07

                      Пока да, они работают просто, надёжно, быстро и универсально (кроме TS, есть ещё и JS, и CSS и прочие языки). У тайпскриптового компилятора документации практически нет, так что у меня пока даже инкрементальную компиляцию прикрутить не удалось, из-за чего приходится ждать по несколько секунд перекомпиляции после изменения файла.


                      Ну и похвастаюсь — полный квест по установке сборщика, выкачивания исходников, пересборке сборщика, сборке проекта и открытия его в браузере можно пройти менее чем за 2 минуты, выполнив все 2 команды в консоли:


                      git clone https://github.com/nin-jin/pms.git ./pms && cd pms
                      npm start


                      1. gearbox
                        11.09.2016 20:27

                        У тайпскриптового компилятора документации практически нет, так что у меня пока даже инкрементальную компиляцию прикрутить не удалось

                        Это да. Я сейчас заморочился на метапрограммирование (конкретно на данном этапе — генерация клиента и сервера на основе swagger спеки, typescript естественно, то что предлагают разработчики стандарта — не выдерживает никакой критики (именно по typescript-у, за остальные молчу), но не только, там много всего. Так вот там похоже что похожая задача встанет, потому и присматриваюсь к Вашему проекту. Но хочется решить ее (если потребуется) так что бы правила не лезли в код и не диктовали, так как там похоже править надо будет не шаблоны а уже сгенеренный код (в частности смотреть какие теги использовались на клиенте и какие sql вызовы — на бэке)


                        1. vintage
                          11.09.2016 21:02

                          Я похожую задачу решал чуть по другому. Было описание бизнес домена в простом виде типа:


                          $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 описание генерировать, но куда лучше генерировать сразу либу на нужном языке, абстрагируя от конкретного протокола взаимодействия.


                          1. gearbox
                            12.09.2016 09:00

                            Да, у меня похожая задача. Но я swagger не генерю, он задан (ручками) — и на его основе генерится клиент и каркас сервера (используем плюсы статичной типизации при компиляции + валидатор в рантайме). Тут вроде как workflow утрясся, больших изменений не предполагаю. А вот с базой и вьюхами на клиенте пока думаю.


                            А Вы на базу через ORM лезли или ручками? (я пробую всю логику перенести на базу а в коде оставить только вызовы хранимок. Ну как пробую — делаю уже, а пробую из этого реализовать плюсы)


                            и генерировались базовые классы

                            То есть Вы по сути свою ORM генерили?


                            1. vintage
                              12.09.2016 09:40

                              Проблема сваггера в том, что это довольно низкоуровневое описание заточенное под http. Высокоуровневую информацию из него вытаскивать достаточно проблематично.


                              Я использовал графовую субд — её для таких вещей использовать одно удовольствие. https://habrahabr.ru/post/267079/


                              1. gearbox
                                12.09.2016 10:03

                                Ну насчет бд — свой постгрес я пока не готов выбросить, а вот мысль строить все вокруг данных мне понравилась. И я ее думаю. ) Спасибо за диалог!