Пользователь
0,0
рейтинг
31 декабря 2015 в 13:42

Разработка → Webpack ProvidePlugin: как не писать простыню import/require в начале javascript модуля из песочницы

Если вы разрабатываете на современном javascript, то почти любой ваш модуль содержит простыню таких строк:

import React from 'react'
import $ from 'jquery'
...

Как оказалось, большинство этих строк можно не писать, доверив их генерацию автоматике. И помогает в этом новомодный webpack, в котором, как оказывается, полно приятных сюрпризов. Кроме всем известных require и import для любых файлов и уже описанного на хабре «hot module replacement», webpack может проанализировать ваш исходный код и автоматически включить нужные модули на основании используемых литералов. Под катом — краткое описание как работает эта магия.

За анализ ваших исходников и автоматическое создание import директив отвечает специальный плагин ProviderPlugin, который встроен в webpack и не требует установки. Чтобы магия сработала, необходимо указать плагин в конфигурационном файле wbpack и снабдить его списком идентификаторов и модулей. Как известно, webpack использует парсер esprima, и поэтому имеет весьмы точное представление о структуре вашего кода. Встретив в исходнике указанный индентификатор, webpack сгенерирует код загрузки указанного модуля так же, как это он это делает для import или require. Фрагмент конфигурационного файла:

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      'React':     'react',
...

При использовании плагина с конфигурацией из примера, webpack будет искать использование индентификатора React. Он проигнорирует такую строку:

const foo = "React";

и даже такую:

bar.React = true;

Зато встретив вот такую, сразу поймет что в этом модуле используется ReactJS и снабдит фрагмент bundle кодом загрузки соответствующего модуля:

React.createClass(…)

И конечно же никто не ограничивает вас только ReactJS. Используйте этот способ для популярных библиотек и часто использумых модулей вашего собственного проекта, чтобы сделать код лакониченее и проще:

new webpack.ProvidePlugin({
    '$':          'jquery',
    '_':          'lodash',
    'ReactDOM':   'react-dom',
    'cssModule':  'react-css-modules',
    'Promise':    'bluebird'
  })

P.S.


Если вы, как и я, хотите использовать ES6 import вместо старенького require, то делается это путем указания babel как loader'а для webpack. И не забывайте про .babelrc и presets — в последней версии babel разработчики подготовили сюрприз для новичков, без указания presets babel теперь не делает ничего:

module.exports = {
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
...
@AllRight88
карма
16,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • 0
    А у меня свой велосипед с автолоадером. Достаточно написать:
    var row = new $my_app_row
    Или, например:
    var row = $my.app.row.make()

    И из директории /my/app/row/ будут подключены все файлы (скрипты, стили, шаблоны и тп), что очень удобно и не требует никаких специальных конфигов.
    • +6
      Угу. Наелся я таких бесконфигурационных систем с БЭМом, и каким-то фреймворком созвучным с tonic, который по всему коду приложения разбрасывал артефакты сборки в виде папок -mix, вместо того чтобы все аккуратненько сложить в одну папочку build. А потом автор уволился оставив после себя кучу самописных скриптов на все случаи жизни: генерация спрайтов, автоимпорты стилуса, авторезолв зависимостей вместо нормальной модульной системы. Это было бы еще ничего, если зависимости были где-то четко зафиксированы. вместо этого была куча магии, самописная модульная система, которой не пользовался никто кроме автора. одним словом была та еще морока разобраться в этой куче, т.к. документации автор тоже после себя не оставил.

      Поэтому спасибо, но нет.
      • +5
        Ну а я наелся конфигурационных систем типа RequireJS, где по идентификатору модуля не понятно где его искать, потому как где-то в стороне лежит конфиг с маппингом пространств имён (и то в лучшем случае, а бывает и с маппингом конкретных идентификаторов) на файловую систему. Наелся сборщиков типа gulp, где отдельно собираются css, отдельно js и отдельно шаблоны, отдельно картинки, всё отдельно, при этом фиг скажешь, что вот этому вот стайлшиту требуется подключённый модернайзер, а когда нужно собрать из тех же исходников другой вариант приложения приходится всё это копипастить. Наелся сборщиков типа webpack, где подключение стилей и шаблонов происходит из js, даже если собственно js модулю не нужен. Впрочем, он ближе всех подошёл к идеалу.

        Я это к чему — то, что вы считаете «нормальной модульной системой» другие считают ущербной. И не по религиозным соображениям, как некоторые, а потому, что видят недостатки и знают как их можно устранить. Например, в PHP есть стандарт расположения файлов PSR-0, благодаря ему, по идентификатору сущности всегда понятно откуда её брать. Это позволяет не копипастить 100500 импортов в каждый файл — достаточно расположить код по стандарту и его можно спокойно использовать из любой части программы. Зависимость резолвится автоматически по факту использования. Особый маразм можно наблюдать в языке Go, где у вас программа с неиспользуемыми зависимостями просто не скомпилируется. Закомментировали блок, чтобы подебажить, ан-нет, получайте ошибку компиляции — идите и комментируйте импорты. А если компилятор/сборщик и так знает что используется модулем, то зачем дополнительно вручную указывать, что вы это «импортируете»? Это бесполезная бюрократия, а не «нормальная модульная система».

        А нормальная модульная система не должна зависеть от форматов файлов, потому как модуль — это не просто js или css файл. Модуль — это группа файлов (зачастую js+css+шаблон+картинки) и их имеет смысл подключать либо все, либо вообще не подключать, если модуль нигде по факту не используется.

        Можно подумать, остальные 8 мегабайт кода приложения были хорошо документированы. В моём коде хоть jsdoc-и были.
        • 0
          О каком PSR может идти речь, если до сих пор идут такие баталии как «не называйте файлы кэмелкейсом». И пока какого-нибудь вменяемого или хотя бы единого JSR не будет, автолоад лучше даже не начинать.
          В конфигурационных системах обычно есть хотя бы документация и, в конце концов, в этот конфиг можно заглянуть. В голову автора автолоадера не заглянешь, а если и заглянешь, то пожалеешь.
          • 0
            Пока не начнёшь он не появится. PSR-0 появился как стандартизация устоявшейся практики. Promises в яваскрипте появились как стандартизация устоявшейся практики. А если бы мы сидели и ждали стандарта, то никогда бы его не дождались.

            Я как бы и не спорил, что документация нужна и важна.
        • 0
          Наелся сборщиков типа webpack, где подключение стилей и шаблонов происходит из js, даже если собственно js модулю не нужен.

          это ведь логично, что в управляющем коде указано какие ресурсы следует подключить
          • 0
            Вы никогда не писали модули без единой строчки яваскрипта? Например, вынесенный partial template со своими стилями, или набор stylus примесей, зависящий от модернайзера, или описание компоненты на специальном декларативном языке.
            • 0
              Модули без единой строчки джаваскрипта это CSS и шаблоны. В CSS зависимости указываются конструкцией @import, в шаблонных движках, например jinja-подобных, зависимости инструкциями типа {% import 'blocks.html' %}. Приведённый вами пример компонента тоже имеет зависимость $mol_block, однако она нигде не указана явно. Видимо предполагается что при сборке её зависимость будет находиться в глобальной области и тогда всё нормально разрезолвится. Философия такая — писать полностью изолированные модули. До появления систем сборки тоже все писали в глобальной области и приятного было мало.
              • 0
                Модули без единой строчки джаваскрипта это CSS и шаблоны. В CSS зависимости указываются конструкцией import, в шаблонных движках, например jinja-подобных, зависимости инструкциями типа {% import 'blocks.html' %}.


                Как в css-модуле через import прописать зависимость от js-библиотеки modernizr? Вы привыкли к тому, что каждый язык замкнут внутри себя. Привыкли к тому, что иерархия скриптов, иерархия шаблонов, иерархия стилей и прочие иерархии — это отдельные, но очень похожие сущности. Привыкли к тому, что чтобы подключить какой-нибудь виджет, вам нужно подключать его стили, скрипты и шаблоны в трёх в разных местах в трёх различных нотациях. Либо, что чаще происходит, просто подключать все файлы по маске. А теперь представьте, что всё это можно не делать. Разве жизнь не заиграет красками в отсутствии рутины по регистрации каждого файла в каких либо списках?

                Приведённый вами пример компонента тоже имеет зависимость $mol_block, однако она нигде не указана явно.
                Вообще-то куда уж более явно — в месте использования. Именно для этого используется специальная нотация с символом доллара вначале — это глобальный идентификатор.

                Философия такая — писать полностью изолированные модули. До появления систем сборки тоже все писали в глобальной области и приятного было мало.
                Плохая философия. Изоляция модулей не решает проблему конфликтов имён. От слова вообще. Конфликты имён решаются исключительно пространствами имён. И у тех, кто использовал пространства имён, проблем не было ни раньше, ни сейчас. А вот дебагом изолированного кода заниматься «одно удовольствие».
                • +1
                  Как в css-модуле через import прописать зависимость от js-библиотеки modernizr?

                  Никак, а зачем? Что вы собрались делать с JS-модулем внутри CSS?

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

                  Как раз таки при помощи webpack все ресурсы модуля упаковываются в один файл и подключать потом нужно только его. Зависимости указываются во входной точке — в JS и это нормально.

                  Разве жизнь не заиграет красками в отсутствии рутины по регистрации каждого файла в каких либо списках?

                  Возможно. Но она также заиграет красками неявного поиска зависимостей и возможными ошибками связанными с этим. Откуда брать модуль A, если у меня два разных модуля B/A и C/A?

                  Вообще-то куда уж более явно — в месте использования. Именно для этого используется специальная нотация с символом доллара вначале — это глобальный идентификатор.


                  То есть по сути это транслируется в импорт другого модуля? Тогда это тоже самое, только синтаксис другой.

                  Плохая философия. Изоляция модулей не решает проблему конфликтов имён. От слова вообще. Конфликты имён решаются исключительно пространствами имён.

                  О каких именах речь? Модуль экспортирует только то что ему положено, код-пользователь может переименовать экспортируемый символ, если такой уже имеется.

                  А вот дебагом изолированного кода заниматься «одно удовольствие»

                  Мне сорсмапы помогают.
                  • 0
                    Никак, а зачем? Что вы собрались делать с JS-модулем внутри CSS?

                    modernizr.com/docs/#using-modernizr-with-css

                    Как раз таки при помощи webpack все ресурсы модуля упаковываются в один файл и подключать потом нужно только его. Зависимости указываются во входной точке — в JS и это нормально.
                    Архив или лапшекод с CSS в JS? А точка входа в виде JS — это кривизна.

                    Возможно. Но она также заиграет красками неявного поиска зависимостей и возможными ошибками связанными с этим. Откуда брать модуль A, если у меня два разных модуля B/A и C/A?
                    У них будут имена $B_A и $C_A и лежать они будут в ./B/A и ./C/A

                    То есть по сути это транслируется в импорт другого модуля? Тогда это тоже самое, только синтаксис другой.
                    Нет, это ни во что не транслируется — сборщик берёт граф зависимостей и сериализует его. И да, это то же самое, только без дублирования (использование — зависимость, import — зависимость).

                    О каких именах речь? Модуль экспортирует только то что ему положено, код-пользователь может переименовать экспортируемый символ, если такой уже имеется.
                    О глобальных именах. А разные имена одного символа в разных местах приложения — усложнение поддержки на ровном месте.

                    Мне сорсмапы помогают.
                    Оно-то тут при чём? Как вы через консоль сможете обратиться к нужному вам объекту? Никак. Вам необходимо искать нужный исходник, ставить там точку останова, довести исполнение приложения до этой точки и только тогда вы сможете получить доступ к изолированным переменным. А я просто вобью нужный путь в консоли, которая мне ещё и автодополнением будет помогать.
                    • 0
                      Если так сильно хочется указать зависимость на внешний модуль в CSS (для того чтобы webpack добавил его в свой граф зависимостей) то это можно сделать допилив css-loader. А с PostCSS так вообще открываются необозримые горизонты.

                      Архив или лапшекод с CSS в JS?
                      И SVG+PNG в base64 не забудьте. Какая разница во что превращается собранный и минифицированный код? Все всегда смотрят на сорцы.

                      Нет, это ни во что не транслируется — сборщик берёт граф зависимостей и сериализует его
                      Я и говорю, это указание на другую зависимость. Импорты это ведь тоже лишь вершины графа.

                      О глобальных именах
                      А зачем нужны глобальные имена?

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

                      Как вы через консоль сможете обратиться к нужному вам объекту?
                      Я дебажу точно также как вы написали, ставлю брейкпойнт в нужном месте кода, происходит остановка, где есть доступ ко всему окружению. ЧЯДНТ?

                      А ещё автономные модули удобнее тестировать, внешние модули можно замокать (полностью или частично).
                      • 0
                        Если так сильно хочется указать зависимость на внешний модуль в CSS (для того чтобы webpack добавил его в свой граф зависимостей) то это можно сделать допилив css-loader.
                        Да костылей-то везде можно напилить. Суть в том, что зависимость должна быть от модуля, а не конкретного файла. Какие там файлы из modernizr надо подклчюать, чтобы всё заработало? Какие-то скрипты? Может ещё стили какие-то? А меня вообще должно волновать внутреннее устройство стороннего модуля?

                        И SVG+PNG в base64 не забудьте.
                        Давайте уж совсем в маразм не впадать?

                        Какая разница во что превращается собранный и минифицированный код? Все всегда смотрят на сорцы.
                        Мы вроде про модули в исходниках говорили, а не про собранные пакеты модулей. Да даже и при сборке, минификация — пустая трата времени.

                        А зачем нужны глобальные имена?
                        А зачем вообще нужны идентификаторы?

                        Ага, как разработчик модуля назвал экспортируемый символ, так его и используй. А если 2 символа с одинаковыми именами — используй полный путь к модулю, как в джаве. Нет уж спасибо. Все современные IDE умеют работать с такими импортами и рефакторить соответствующий код.
                        А мозг программисту они тоже рефакторят? Простой пример: перенос кода из одного модуля в другой — IDE поправит имена зависимостей? Другой пример: вы редактируете 10 файлов, в половине из них какая-то сущность названа одним образом, в другой половине — другим. Сможете не запутаться где что писать, чтобы обратиться к ней? А в яве, да, впали в другую крайность.

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

                        А ещё автономные модули удобнее тестировать, внешние модули можно замокать (полностью или частично).
                        Это с изоляцией никак не связано. Это к инверсии зависимостей. Но и без инверсии зависимостей всё замечательно мокается.
  • +12
    Это очень, просто ужасно плохой пример и ужасная практика.

    Так делать категорически нельзя! Все подобные вещи живут в голове неделю, максимум две. Потом автор этих строк увольняется и уходит на следующую работу, а человек вместо него пытается понять, откуда у него определена переменная Drizzer (а это оказался моднейший новый фреймворк, про который ещё нет статьи на хабре)
    • –1
      Явное, безусловно, лучше неявного. Но так можно сказать про любые практики :). «Зачем нам использовать sass, если разработчик уволится, придет новый и ничего не поймет». Подразумевается, что перед началом работы новый разработчик онакомится с проктом — хотя бы webpack конфиг прочитает на предмет используемых модулей. А то ведь новичка и линтер удивит, и автобилд, и еще много всего «неизвестного ему».
      • 0
        Да, меня удивляет куча всякого полурабочего кода, появившегося за последние полтора года вокруг яваскрипта.

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

        Меня очень злило, когда я искал react-canvas для браузера, но ни в одном репозитории не было даже README в котором было бы написано, как в этом сезоне модно компилировать яваскрипт в пригодный для использования код.
    • 0
      Ну, уж $ и _ — так точно можно подключить. Гораздо хуже если будут вариации потом: в одном файле import * as $ from «jquery», в другом —
      import * as jquery from «jquery».
  • +7
    А все-таки явное лучше неявного. Да и IDE'шка с eslint'ом будет ругаться, мол, откуда это взялось, а если писать /* globals $, _, React */ так весь смысл пропадает.
    • 0
      Для eslint достаточно один раз указать наличие автозагружаемых модулей. Надеюсь, через некоторое время можно будет его дружить с конфигом webpack и это будет происходить автомагически.
      • +3
        Надеюсь, что нет. Сегодня eslint, завтра что-то еще, сегодня вебпак, завтра еще какое-нибудь дуо. Не надо еще и тулзы гвоздями прибивать друг к другу.
  • +9
    Как-то в неправильную сторону вы двигаетесь, я считаю. Ваш код делает вещи слишком неоднозначными. Читабельность заголовков улучшается ценой того, что сложность анализа кода через пару недель значительно вырастет.

    Тем более, многие IDE (от тех же JetBrains) умеют группировать импорты в одну строку.

    Мне кажется, импорты — чуть ли не единственное, что должно быть указано явно («explicitly»). Да, JS — гибкий язык, но не стоит этим злоупотреблять.

    Это чисто моё субьективное мнение.

    • +1
      Использовать или нет — безусловно, личный выбор каждой команды. В любом случае, согласитесь, что знать о такой возможности webpack довольно полезно. При встрече с таким подходом в каком-нибудь проекте на github будет сразу понятно, что там происходит.
  • +6
    Да ну нафиг. Модули для того и придумывались, чтобы каждый файл имел четкие зависимости. Генерацией импортов, если это уж такая невыносимая рутина, должен заниматься текстовый редактор.
    • +1
      +1. Боролись-боролись с глобалами и вот нате.
      • 0
        А вы с какой целью с ними боролись?
      • 0
        Боролись не с глобалами как таковыми, а с отсутствием/ущербностью альтернативы.
        • 0
          Глобальные переменные, помимо риска коллизий, это еще и неявные зависимости.
          • –1
            Идентификаторы модулей в импортах — не более чем уникальные ключи в глобальной переменной. Так что от коллизий имён вас это не спасёт. А спасают от этого пространства имён. И зависимости вполне себе явные в обоих случаях.
          • –1
            А какой может быть риск коллизий? Если у вас на проекте используется, например, реакт и лодаш, то довольно странно было бы иметь локальные переменные React и _, которые ссылались бы на что-нибудь другое кроме реакта и лодаша.
            Проблема глобальных переменных сильно преувеличена. Да, объявлять глобальные переменные внутри модулей плохая практика, потому что это усложняет рефакторинг, но вот иметь глобальные переменные, от которых зависит проект целиком, наоборот, только упрощает дело. Разумеется, это не относится к библиотекам и изолированным частям системы. Но если у вас есть 200 ангуляр контроллеров, единственная задача которых — это удовлетворение бизнес требований, и они никому не нужны вне текущего проекта то и ничего плохого в этом нет.
  • +10
    common.js

    import $ from '$';
    import JSON from 'JSON';
    import _ from '_';
    import Q from 'Q';
    import moment from 'moment';
    
    export {$, JSON, _, Q, moment};
    


    index.js

    import {$, _} from 'common';
    
    $('#app').hide();
    
  • +3
    ProvidePlugin лучше использовать для того, чтобы передовать в js конфигурационные параметры типа DEBUG
    • +1
      DefinePlugin.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.