Комментарии 84
В целом, соглашусь с тезисом статьи, пространства имен – это хорошо и полезно. Но хочу немного уточнить некоторые фактические моменты:
В результате, в Java можно независимо создать большое количество бесконфликтных пакетов (модулей) без явного согласования их наименования независимыми группами разработчиков, а в JS для этого нужно явно использовать реестр npm
Npm использовать необязательно. Вы можете опубликовать пакет на Github/Gitlab и добавить себе его в зависимости
"my-package": "github:username/repo"
Полный список поддерживаемых форматов есть в документации
Еще можно поднять свой собственный репозиторий и публиковать пакеты в него. Если в компании куплена Artifactory Pro, то там это идет из коробки
То есть, если вы не публикуете пакет в публичный npm, а используете его приватно, можете называть его как угодно.
IDE способно без труда сформировать этот адрес. В PhpStorm это делается так [...] Тот же PhpStorm теряется, если применить подобный приём для JS-кода
Так это недостающая фича в PhpStorm, а не принципиальный косяк npm. Ничто не мешает генерировать ссылку типа такой: npm-module-name/path/to/file.js:123
.
В личной практике мне никогда это особо не мешало, ссылки на исходники коллегам скидываю на Github/Gitlab/Bitbuket и т.д.
Кстати, наличие классов в ES6 и отсутствие пространства имён в смысле логической группировки кода вероятно приведёт к появлению в больших ES6-проектах имён, аналогичных именам в Zend1 (Module_Path_To_Class).
Вот этот вывод непонятно как получился. Имена классов в JS-модулях локальные, придумывать уникальные имена в рамках всего проекта не надо.
Спасибо за развёрнутый коммент, коллега.
То есть, если вы не публикуете пакет в публичный npm, а используете его приватно, можете называть его как угодно.
Вот за то и речь. Называя как угодно свои приватные модули, мы уменьшаем себе возможность их использования в глобальных проектах. Допустим, вы сделали приватный модуль debug
для своего проекта, выложили на корпоративный репозиторий и используете его в своих проектах. В какой-то момент ваша корпорация поглощает другую корпорацию и вашему IT-отделу ставится задача адаптировать их замечательные наработки в ваши. А у них такой же модуль debug
используется. Вот и получаем вилы на ровном месте.
В java-мире, например, корпорация Oracle поглотила корпорацию Sun, а пакет com.oracle.json
замечательно сосуществуют рядом с пакетом com.sun.net.httpserver
.
А представьте, что вы бы захотели опубликовать свой приватный пакет debug
для открытого использования?
Ничто не мешает генерировать ссылку типа такой: npm-module-name/path/to/file.js:123.
Это всё равно привязка к тексту в файле, а не к логической структуре кода (константа, метод, класс, ...). Адресация поплывет при изменении кол-ва строк в файле.
Имена классов в JS-модулях локальные, придумывать уникальные имена в рамках всего проекта не надо.
Вполне возможно, что мой прогноз — "пальцем в небо".
Называя как угодно свои приватные модули, мы уменьшаем себе возможность их использования в глобальных проектах.
Значит я не так понял исходное утверждение. То есть плохо то, что npm-модулям любят давать короткие имена…
Ну во-первых, в npm завезли scopes, свои служебные пакеты вы можете называть @mycompany/package
, то есть ситуация как минимум не хуже composer.
Во-вторых, чем длиннее имя, тем сложнее его запомнить. Понятно, что org.apache.commons
помнят все, но вот какой-нибудь com.squareup.retrofit2
или io.reactivex.rxjava2
вряд ли, все равно будете гуглить сначала по названию, retrofit или rxjava соответственно, чтобы узнать полное имя.
Понятно, что org.apache.commons помнят все, но вот какой-нибудь com.squareup.retrofit2 или io.reactivex.rxjava2 вряд ли
Современные IDE довольно умны и подобная проблема в принципе не возникает. Достаточно приблизительно помнить «имя» необходимого пакета, а корректный импорт IDE берет на себя.
Хорошо, решение есть. Но посмотрите на ситуацию со стороны: сначала придумали длинные уникальные имена, а потом потребовались утилиты, чтобы их не писать. Логично!
То есть получается, что у любой библиотеки все равно есть имя собственное, а приписанное сначала com.mycompany особой роли не играет
Похоже, я ввёл вас в заблуждение, пытаясь продемонстрировать работу "пространства имён" на примере библиотек.
namespace
в PHP и package
в Java относятся к исходному коду, а не к библиотекам.
Попробую запутать вас ещё больше. Всем известные интернетовские домены — это пример применения namespace'ов на практике. Две разные библиотеки два разных домена с именем mail
одновременно существуют в одном проекте интернете именно потому, что они принадлежат двум разным пространствам имён:
- .ru — http://mail.ru
- .com — http://mail.com
Пространство имён — некоторое множество… созданное для логической группировки уникальных идентификаторов…
На самом деле можно в интернете проекте обойтись без доменов верхнего уровня и вообще без доменов namespace'ов и адресовать все хосты библиотеки "плоской" строкой. Это примерно то же самое, как хранить все файлы на компьютере в одном корневом каталоге. Да-да, файловая система — это тоже пример "логической группировки уникальных идентификаторов" (имён файлов) при помощи "некоторых множеств" (каталогов).
И если на компьютере хранить 5-10 файлов, то вполне резонно задаваться вопросом, а для чего мне вообще нужны каталоги? И получить вполне логичный ответ — для хранения 5-10 файлов каталоги не нужны.
В вашем проекте папки и файлы организованы так, в моём — по-другому, у кого-то — по третьему. Посмотрите содержимое файла autoload_namespaces.php
, фрагмент которого я привёл в разделе "Автозагрузка кода". В нём видно, как файловая система каждого модуля (а сторонний модуль в вашем проекте с вашими правилами для кого-то является собственным проектом со своими правилами) монтируется в единую логическую структуру кода в вашем проекте. Вот, например, исходники модулей находятся в таких подкаталогах внутри каталога ./vendor
вашего проекта:
- ./magento/zendframework1/library
- ./phpspec/prophecy/src
- ./phpmd/phpmd/src/main/php
Но вы не заморачиваетесь путями к исходникам, а просто пишете в своём коде:
$center = new \Prophecy\Call\CallCenter();
Поиск файла с исходниками автозагрузчик autoload.php
выполнит самостоятельно, на основании привязки пространства \Prophecy\*
к каталогу ./vendor/phpspec/prophecy/src
.
Если ваш проект собран при помощи PHP Composer, то ваш код не изменится, если Konstantin Kudryashov (автор модуля phpspec/prophecy
) захочет изменить структуру своего модуля и перенести исходники в своём модуле в каталог, например, ./phpspec/prophecy/src/main/php
. Он просто укажет в composer.json
своего модуля:
"autoload": {
"psr-0": {
"Prophecy\\": "src/main/php"
}
}
и при сборке или обновлении вашего проекта в следующий раз в карту пространства имен autoload_namespaces.php
запишется так:
'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/main/php'),
С появлением autoloader
'a PHP-разработчики перестали держать в голове файловую структуру своего проекта (и файловую структуру сторонних модулей, используемых в своём проекте) и стали держать логическую структуру кода (которая гораздо проще).
Пространство имён не нужно, если на компьютере в проекте хранится участвует 5-10 файлов разработчиков со своими модулями. Но если на компьютере в проекте хранится участвует 1000 файлов разработчиков со своими модулями, то пространство имён становится более актуальным.
Отсюда делаю вывод, что эта фича не является обязательной для достижения «максимальной сложности приложений».
Пространства имён не привязаны к языку. В Java нет жестких требований по расположению файлов — вы указываете их местоположение в CLASSPATH при запуске приложения.
И пространства имён не являются обязательной фичей для достижения "максимальной сложности приложений". Я пытался донести мысль, что пространство имён при прочих равных условиях (например, язык и квалификация разработчика) позволяют создавать более сложные приложения, чем без них. Вы можете писать на PHP без использования namespace, некоторые до сих пор так и делают. Но в таких пакетах, как Zend2, Symfony, Laravel — используются namespaces.
Кстати, в Yii и CodeIgniter, к моему удивлению, не используют пространства имён. В результате в Yii имена файлов выглядят так:
А в CodeIgniter так:
Обратите внимание на префиксы в названиях файлов — разрабы вынуждены обеспечивать уникальность имени каждого файла в пределах проекта (по сути, вручную, не используя средств языка, вводят пространства CDb
и DB_
). Да, для 1000 файлов они выкрутятся (какой ценой?), а для 300 млн.?
Если вдруг решите попрограммировать на PHP, не начинайте с Yii и CodeIgniter ;)
Да, троечка с namespace'ами. Вы реабилитировали Yii в моих глазах, спасибо :)
Да, для 1000 файлов они выкрутятся (какой ценой?), а для 300 млн.?
Это как раз для node.js не проблема. С большим количеством файлов их модульная система хорошо справляется. Настолько хорошо, что некоторые разработчики создают отдельные модули для самой тривиальной функциональности, порождая поводы для мемов.
В автобусном парке есть два икаруса и один лиаз с номерами Н123С, Р778Т и М543Н.
С другой стороны, автобусный парк обслуживает маршруты 711, 215 и 987.
Даже не смотря на то, что мы можем уверенно сказать, что икарус Н123С обслуживает маршрут 711 — это все равно две разные, не связные между собой, плоскости абстракций.
Сможет. Если прописать в ./vendor/composer/autoload_classmap.php
'Prophecy\\Call\\CallCenter' => $vendorDir . '/bla/bla/bla.php',
Ээээ… Вы серьёзно предлагаете что-то трогать в папочке вендор? Или это просто для примера? Если для примера, то надо было что-то такое приводить в пример:
{
"autoload": {
"classmap": ["./bla/bla"]
}
}
Через composer.json
оно, конечно, лучше, но я не стал плодить сущности в примере :) Тем более, что в конечном итоге оно всё равно окажется в ./vendor/composer/autoload_classmap.php
. А так ваше замечание совершенно справедливо, в папке ./vendor/composer
лучше не делать изменения ручками.
Тем более, что в конечном итоге оно всё равно окажется в ./vendor/composer/autoload_classmap.php
А вот и не факт. Ещё есть APCu и авторитативные карты классов.
А так ваше замечание совершенно справедливо, в папке ./vendor/composer лучше не делать изменения ручками.
Не то что «лучше»… А никогда и ни при каких обстоятельствах этого не делать. А если кто тронет — за это трогалку отчекрыживать.
Ну разве что только для отладки кода какого-нибудь можно трогать.
максимальная сложность приложений, которые можно создать на Java/PHP/C++/..., не может быть достигнута разработчиками с аналогичной квалификацией на JavaScript/Python/C/....
Абсолютно недоказанное утверждение. Никому отсутствие namespace не мешает говнокодить огромные приложения на javascript. В JS область видимости очень ограничена модулями. Два модуля с одним именем, ну не знаю, кому это может быть нужно.
Сколько пишу на JS никогда не сталкивался с тем, что мне нужны были namespacе.
А еще не рассказали про самое главное неудобство при работе с Javascript-модулями. Файлы из своего проекта можно импортировать только через относительные пути. Из-за этого получаются ужасные цепочки. Реальный код:
import UserService from '../../../../src/services/user/UserService.js';
Стандартной возможности написать импорт относительно корня проекта нет. Есть разного рода костыли (34 штуки!), но поскольку они нестандартные, то с ними регулярно что-то отваливается.
Введение нормального способа импортировать относительно корня проекта подарит нам возможность грамотнее структурировать модули, это точно.
// tsconfig.json
{
"compilerOptions": {
// ...
"baseUrl":"./path/to/src",
// ...
},
// ...
}
// actual file with import
import {UserService} from 'services/user/UserService';
// ...
Typescript же не перезапишет пути
При транспиляции он «развернет» их в относительные. Или я вас нe понял?
Вот issue на этот счет для typescript, и это не собираются чинить: github.com/Microsoft/TypeScript/issues/10866.
В нормальной системе это должно указываться в одном месте, чтобы все инструменты подхватывали автоматически. NODE_PATH мог бы быть таким стандартом, но его намеренно выпиливают из реализации ES-модулей в Node: nodejs.org/api/esm.html#esm_no_node_path
Файлы из своего проекта можно импортировать только через относительные пути.
Этот коммент замечательно иллюстрирует сказанное в пункте "Автозагрузка кода". Нам приходится отталкиваться от файловой структуры, если нам сложно отталкиваться от структуры кода.
Использование namespace’ов в PHP позволяет легко обнаруживать подобные конфликты на уровне IDE (для примера я сделал второй файл с дубликатом класса внутри):
Ваш пример не имеет особого смысла в Node, т.к. там декларации классов не попадают в глобальный скоуп. Т.е. можно в двух соседних файлах объявить класс с одинаковым названием и это не будет ошибкой.
Вы правильно сказали — "не попадают в глобальный скоуп". В JS вообще нет namespace'ов в смысле логической группировки элементов кода. Есть scope. Именно поэтому на JS'е сложно писать сложные проекты — сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены. А в PHP можно. И в Java можно. И в Typescript можно (если я правильно понял).
сложно уникально адресовать два класса с одинаковым названием
Вы можете привести пример из жизни (не сферический) где такое встречается и именно namespace'ы выручают?
Я не программирую на TypeScript, а в JS нет namespace'ов (поэтому они не выручат). Что касается PHP, то вот два класса с одинаковым именем Configuration
, конфликт которых "разводят" namespace'ы (оба класса, что характерно, находятся в файлах Configuration.php
):
Для того, чтобы создать объекты этих классов в современном PHP-приложении мне достаточно знать их полные имена, все остальное делает менеджер зависимостей (autoloading).
import { Configuration as dbalConfig } from 'doctrine/DBAL'
import { Configuration as ormConfig } from 'doctrine/ORM'
То есть там в принципе не стоит такой проблемы.
Что такое doctrine/DBAL
и doctrine/ORM
? Каталог, файл или некоторая строковая метка для поиска пути к исходникам по некоторому алгоритму?
Каталог, файл или некоторая строковая метка для поиска пути к исходникам по некоторому алгоритму?
А какая разница? Если при наборе from 'doctrine/...` вам ide так же выдаст список доступных импортов.
Стиль кода будет немного иной, понятно что в JS так исторически сложилось что модульность там прикручена сверху уже сложившейся платформы, но при написании кода это почти никогда мешает. Да, на локальные модули приходится relative path писать, но как вы написали есть 100500 способов это разрулить.
Я сейчас как раз в основном пишу на JS и Python, и я там ни разу не сталкивался с проблемами в стиле «мне нужно импортировать два класса с одинаковым названием и это не решается алиасами»
Вы по ходу дела плавно подменили первоначальную проблему "сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены" другой — "мне нужно импортировать два класса с одинаковым названием и это не решается алиасами" :)
Дело в том, что в PHP-проекте я могу написать:
$init = new \Doctrine\DBAL\Event\Listeners\MysqlSessionInit()
без того, чтобы использовать import
или require
. При этом я не должен знать о том, в каком каталоге находится файл, в котором находится исходный код. Именно потому, что в PHP/Java/TypeScript/… есть директива namespace
. А в JS/Python этой директивы нет, вы её никогда не использовали и не понимаете для чего она вообще может быть нужна. Если вам она не нужна, то это говорит только о том, что вы ещё не достигли своего предела сложности в JS/Python.
Вы по ходу дела плавно подменили первоначальную проблему «сложно уникально адресовать два класса с одинаковым названием, не привязываясь к файлам, в котором они определены» другой — «мне нужно импортировать два класса с одинаковым названием и это не решается алиасами»
А в чем разница? В моем примере выше никакой привязки к файлам нету.
При этом я не должен знать о том, в каком каталоге находится файл, в котором находится исходный код. Именно потому, что в PHP/Java/TypeScript/… есть директива namespace. А в JS/Python этой директивы нет, вы её никогда не использовали и не понимаете для чего она вообще может быть нужна. Если вам она не нужна, то это говорит только о том, что вы ещё не достигли своего предела сложности в JS/Python.
Так себе критерий оценки сложности проектов над которыми я работаю. Сложностью можно управлять по-разному, нe обязательно городить здоровенные монолиты с миллиардами классов, где без нэймспэйсов можно потеряться.
Если вам так нравится думать о там как вам namespace'ы позволяют писать крутой код — пожалуйста, но ваши аргументы для вывода статьи не убедительны.
А я статью писал не для того, чтобы кого-то в чём-то убедить. Я хотел увидеть, что серьёзных возражений против этого моего вывода не будет. Их и нет.
Я хотел увидеть, что серьёзных возражений против этого моего вывода не будет.
Вы сами можете придумать пример, который вас бы разубедил?
Что-то вы тут спорите, а самого главного никто не написал:
import { Configuration as dbalConfig } from 'doctrine/DBAL'
Вот этот вот код — это императивная конструкция. Она говорит "загрузи мне публичный класс из такой-то папочки". А в случае PHP:
use Doctrine\DBAL\Configuration;
Это декларативная конструкция. Которая говорит о том, что все упоминания класса "Configuration" в коде должны ссылаться на "Doctrine\DBAL\Configuration". При этом:
1) Этого класса вообще может не существовать в природе. Код может выглядеть так:
if (class_exists(Configuration::class)) {
// Класс существует!
}
2) Подгрузка его произойдёт только в момент непосредственного его вызова. Например, лишь при создании объекта. А так никаких подключений не делается.
3) Сам файл может располагаться где угодно (о чём, вы как раз и спорите), например в бинарном архиве phar (почти как jar в джаве). Или в кеше по какому-нибудь пути "/cache/2019_01_04/version1/1hjf8hfuhf34.php", который генерируется каким-нибудь оптимизатором или препроцессором.
Вот, теперь стало понятнее. Только остается вопрос: если есть столько разнообразных способов загрузить себе класс, то как это дебажить в случае неполадок?
Допустим, вы сделали небольшую правку, а приложение упало с сообщением, что не может найти какой-то класс, с вашими изменениями совсем не связанный. Раз привязки к файловой системе нет, где его искать непонятно.
Спрашиваю безо всякого троллинга, мне и правда интересно как такое решается.
Во-вторых, автолоадинг — это одна единственная функция в коде, которая принимает коллбек, срабатывающий тогда, когда класс не найден. Его задача — подключить файл. Достаточно там поставить точку останова чтобы понять почему не подключается файл.
Ну а когда кода много и не понятно что куда и откуда, то есть IDE. Она умеет индексировать и находить что угодно. А учитывая уникальность имён в системе (здравствуйте неймспейсы) — однозначно указывает на файл. Плюс ещё мощная система рефлексии, где одним методом можно получить физическое раположение файла в ФС по любому имени. По этому, физически сам файл с классом найти можно элементарно.
В-четвёртых, опять же стандарты, на этот раз PSR-1 и PSR-2, помимо кодстайла они содержат и другие вещи, например в PHP на уровне PSR-2 стандарта один класс должен соответствовать одному файлу. А это облегчает поиск файлов. Т.к. именование оных соответсвует имени класса и если какой-то класс не находится, а файл имеет точно такое же имя, то возможно что-то пошло не так не в именовании, а где-то на уровне кеша.
В-пятых, опять же IDE, учитывая количество всякое разное количество стандартов — она орёт благим матом, если где-то что-то написано без оных рекомендаций.
Короче, это даже не проблема, наткнуться на такую ошибку может только новичок. Можно посмотреть на тостер по сабжевому запросу и понять, что решение проблем элементарное: toster.ru/search?q=class+not+found
Понятно. Есть набор правил и ограничений, чтобы пользователи не стреляли себе в ноги, а классы – не терялись.
Но в вопросе import
против use
это ясности не добавляет.
import/export
в JS – это статические конструкции. Они читаются перед исполнением кода модуля, не могут быть вложены вif
или функцию, им нельзя динамически сгенерировать имя. Вполне себе декларативное описание, как иuse
- "module-name" в import с точки зрения спецификации языка – это просто индентификатор, который резолвится на усмотрение исполняющего окружения. В браузерах, например, предлагается использовать карты импортов, которые очень похожи на конфигурацию автолоадера
- Ленивой загрузки у JS-модулей нет,
import {SomeClass} from 'some-module'
перед исполнением проверит, что some-module содержит экспорт SomeClass. Для ленивой загрузки есть асинхронныйawait import('some-module')
, который загрузит модуль динамически.
Я считаю это плюсом, потому что в случае ошибки вы хотите знать об этом как можно раньше, а в критичных местах можно вставить динамический импорт, прекрасно осознавая риски.
Есть ли еще какие-то киллер-фичи автолоадера, которые я пропустил?
2. Верно, если не принимать во внимание п.1
3. Опять же — этот импорт императивен. Если вы подгружаете полифиллы в PHP, но не испольузете новые функции — они просто не будут подключаться и выполняться. В случае JS весь код подключается всегда, а чтобы избавиться от неиспользуемого — его костыляют через tree-shaking, который работает через жопу.
Помимо этого есть и другая особенность JS — это манки патчинг.
В таком коде:
// file example.js
import * from 'any';
// file example-2.js
import * from 'any';
import example from 'example';
Будет физически вызвано 4 подключения файлов. Где any просто дублируется в первом и втором файле, клонируя себя в локальный скоуп файлов.
В PHP это не так. В аналогичном примере на PHP не будет выполнено ни одного подключения. Т.е. ровно 0 файлов будет использовано (ну разве кроме самого первого используемого). А во время физического использования внешних зависимостей константность и глобальность имён гаратирует то, что никто не перезапишет и не переопределит класс/функцию, а значит и подключений никаких лишних не надо делать, т.к. нужные конструкции уже загружены в виртуальную машину, на них достаточно будет лишь сослаться.
Напишите «import * from 'undefined'» и получите ошибку.
Синтаксиса import * from
нет. Видимо здесь имелось в виду import * as something from 'undefined'
. Тем не менее все равно непонятно какая ошибка имеется в виду.
В PHP это не так. В аналогичном примере на PHP не будет выполнено ни одного подключения.
Что вы понимаете под словом "подключение"? Если в файле с классом есть какие-то сайд-эффекты (написать что-то в лог, наприимер), они вызовутся только в момент, когда вы сделаете new MyClass()
?
Синтаксиса import * from нет. Видимо здесь имелось в виду import * as something from 'undefined'. Тем не менее все равно непонятно какая ошибка имеется в виду.
Очевидно, ошибка отсутствия этого самого 'undefined'
Что вы понимаете под словом «подключение»?
Статическую линковку файлов.
Если в файле с классом есть какие-то сайд-эффекты (написать что-то в лог, наприимер), они вызовутся только в момент, когда вы сделаете new MyClass()?
В PHP с автолоадигом — да.
Очевидно, ошибка отсутствия этого самого 'undefined'
Имя модуля в импорте всегда должно быть статическим, написать import x from foo + 'bar' + baz
нельзя. Поэтому там undefined будет только в том случае, если вы действительно захотели импортировать модуль с таким именем.
Автолоадинг интересная штука, но на любителя. Мне нравится, когда потенциально невалидный код падает сразу после запуска, а не где-то в рантайме (возможно, в проде, если тесты ситуацию не отловили).
Автолоадинг интересная штука, но на любителя.
Давайте другими словами попробую объяснить: В современном PHP вообще не надо писать «импортов», они опциональны. Представьте себе JS без единого импорта? Язык сам знает откуда брать код и что подключать когда потребуется. Потому что все имена в ПО за счёт неймспейсов — уникальны, а любые связанные с этим ошибки вылавливаются на уровне статического анализа, т.к. у PHP он на порядки строже получается, как у TypeScript.
Мне нравится, когда потенциально невалидный код падает сразу после запуска, а не где-то в рантайме (возможно, в проде, если тесты ситуацию не отловили).
Эм… Тогда не надо писать на JS, потому что он тоже падает в рантайме, а не во время сборки, а import — это инструкция препроцессора, а не языка, в этом и отличия))) Да и как бы AMD никто не отменял, он тоже может только лишь во время исполнения сказать, что какого-то модуля не существует.
В Typescript пытались продвигать неймспейсы именно так как вы и описываете: http://www.typescriptlang.org/docs/handbook/namespaces.html
Сами исходники Typescript с помощью них сейчас и организованы, но в целом в сообществе такой способ популярности не имеет. Людям больше нравится писать явным образом импорты и экспорты.
import — это инструкция препроцессора, а не языка
Непонятно, почему вы так считаете. Синтаксис описан в спецификации языка, любой движок с поддержкой последней версии стандарта должен их понимать. То, что сейчас исходники прогоняют через babel – это временное решение для совместимости, в долгосрочной перспективе особой роли не играющее.
Тогда не надо писать на JS, потому что он тоже падает в рантайме
Проблемы есть, не спорю. Но строгое поведение импортов это шаг в сторону улучшения ситуации и должен приветствоваться а не наоборот.
Непонятно, почему вы так считаете. Синтаксис описан в спецификации языка, любой движок с поддержкой последней версии стандарта должен их понимать. То, что сейчас исходники прогоняют через babel – это временное решение для совместимости, в долгосрочной перспективе особой роли не играющее.
ECMA и JS — это всё же чуть-чуть разные штуки. А на уровне JS в браузерах как вы себе представляете этот import? А-ля AMD? Т.е. рекурсивно выгружаем весь node_modules в браузер клиенту для загрузки одного скриптика на пару строк? =)
От бабела с билдом всего в один файлик (образно, конечно, без учёта технологии чанков) никуда не деться, ни сейчас, ни через 10 лет.
Проблемы есть, не спорю. Но строгое поведение импортов это шаг в сторону улучшения ситуации и должен приветствоваться а не наоборот.
А мне вот кажется, что загрузка в память того, что реально используется на месте (причём загрузка один раз), вместо всего что может пригодиться в каждом отдельном модуле — на порядок лучше.
P.S. Именно по-этому фреймы на PHP весом в 100+ метров кода — памяти реально пару метров оперативы загребают. А на ноде «хелло ворлд», который меньше 10, а то и 20 метров кушает — написать довольно сложно. Ну я образно, конечно.
А на уровне JS в браузерах как вы себе представляете этот import
Так импорты уже поддерживаются в браузерах. Вот маленькое демо, например:
https://es-modules-demo.glitch.me
При локальной разработке задержки на загрузку модулей минимальные, поэтому можно грузить сразу из исходников, а не настраивать dev-server, а для продакшена можно и бандл собрать
А на ноде «хелло ворлд», который меньше 10, а то и 20 метров кушает — написать довольно сложно
Не думаю что причина в загрузке модулей. Память больше поедают инстансы классов, а не их конструкторы.
Конечно, всегда есть шансы, что при инициализации модуля запустится рекурсивный скрипт или биткоин-майнер, но мы же только адекватные сценарии рассматриваем?
Так импорты уже поддерживаются в браузерах. Вот маленькое демо, например:
Посмотрите на вкладку network. Такое в продакшене нельзя оставлять по очевидным причинам. О чём я и говорил.
При это предлагаю написать:
function example() {
import some from "any";
}
Т.е. ленивые (опциональные) зависимости написать невозможно. В отличии от другого языка (не будем показывать пальцем) =)
Не думаю что причина в загрузке модулей.
т.е. считаете что неиспользуемый import почти не кушает памяти?
Посмотрите на вкладку network. Такое в продакшене нельзя оставлять по очевидным причинам.
Тем не менее фича в языке есть, и для многих небольших проектов пригодится. А бандлеры всего лишь делают ahead-of-time оптимизацию, не нарушая семантики языка.
Вот такой код для ленивой загрузки сработает:
async function example() {
const some = await import("any");
}
А если сделать всю загрузку ленивой по умолчанию, то это плохо скажется на UX. Пользователи хотят чтобы кнопки сразу реагировали на их нажатие, а не ходили на сервер за новым кусочком кода для их обработки. Ленивая загрузка – полезная вещь, но должна использоваться точечно и с умом (по крайней мере, во фронтенде).
т.е. считаете что неиспользуемый import почти не кушает памяти?
Да, вот потребление памяти Gmail (самый прожорливый сервис, из мне известных)
Видно, что код находится далеко не на первом месте в списке потребителей. При этом на странице почти половина загруженного кода не используется:
Работать над выпиливанием ненужного кода, конечно, надо, но в потребление памяти вклад он вносит небольшой.
не нарушая семантики языка.
О да, конечно, 404 ошибка из промиза фетча, которую даже через трай кетч нормально не отловить в рантайме (потому что оператор импорта нельзя обернуть в трай/кетч). Против бабеловской ошибки в линкере в статическом связывании на уровне препроцессинга. Это совсем не нарушение семантики =)
Вот такой код для ленивой загрузки сработает:
Потому что это функция, а не оператор. Логично. Которая опять же зависит от путей к файлам на сервере.
А вообще, пока вы не попробуете сами автолоадинг в PHP — не поймёте насколько в JS это всё убого сделано. Но да, это лучше чем было в ES5, где commonjs полифиллами доставлялся.
P.S. А вообще реально попробуйте пописать что-нибудь на PHP. Если говорить о модульности и способах резолва зависимостей — это как небо и земля. Конкурировать тут с PHP может разве что только Java с classpath, но у неё тупо гибкости не хватает.
И если в js на уровне модуля можно определить какие вещи мы эспортируем и можно сделать приватные для модуля классы, то в пхп в любом месте можно использовать любой класс.
можно сделать приватные для модуля классы
Вот с этим согласен, это приятная плюшка, которую было бы неплохо иметь и в PHP. Но эта проблема решается на уровне conventions в проекте.
Вот только нормальной модульности в пхп как раз и нет.
Пока что… wiki.php.net/rfc/namespace-visibility
The implementation stalled after much progress and is currently blocked by namespaces existing only-during compile-time and not being available for run-time checks
Ссылок на, хотя бы, минимальную реализацию, найти не получилось.
Implementation
After the project is implemented, this section should contain
is currently blocked by...
Печаль :( Значит, будем выкручиваться через соглашения на уровне проектов. В Magento 2, походу, для этого в модуле оформляется отдельный фолдер ./Api/
, в который выкладываются интерфейсы, публичные с точки зрения модуля. Т.е., сторонние пользователи функционала, предоставляемого модулем, должны использовать только то, что находится в этом фолдере, а всё остальное — на свой страх и риск (разрабы модуля могут трогать остальной код модуля когда вздумается и сам себе злобный буратин, если завязался на что-то вне ./Api/
).
Раз привязки к файловой системе нет, где его искать непонятно.
Каждый composer compatible модуль привязывает (autoload) свой namespace к собственной файловой структуре в файле composer.json
(аналог package.json
в npm
). В результате composer
(менеджер зависимостей в PHP) при сборке проекта из отдельных модулей создаёт карту соответствия пространств имён фрагментам файловой системы. После чего вам достаточно подключить в вашем головном файле автозагрузчик, который и использует эту карту:
<?php
require_once __DIR__ . '/../vendor/autoload.php';
и вы можете создавать новые классы, основываясь на их именах:
$dbalCfg = new \Doctrine\DBAL\Configuration();
или
use Doctrine\DBAL\Configuration;
$dbalCfg = new Configuration();
или
use Doctrine\DBAL\Configuration as Config;
$dbalCfg = new Config();
Карта обновляется по мере подключения/отключения модулей
composer require vendor/module
и может быть использована также и IDE для поиска исходников по имени класса.
В спецификации веб-стандартов есть предложение использовать карту импортов. Эта карта будет описывать маппинги импортируемых имен на полные пути на файловой системе (или URL, если в браузере). Сможет ли эта штука дать вам поведение и гибкость, которую вы хотите?
Нет, не похоже. Проблемы с адресацией элементов кода в проекте это не решает.
Вы о копированнии ссылки на исходник в PhpStorm? На экспорт ES-модуля в теории можно сослаться. Вот код
// ./src/foo/bar/baz.js
export class MyClass {}
Идентификатор вида ./src/foo/bar/baz.js#MyClass
будет однозначно определять ваш класс. WebStorm/PhpStorm генерировать такие ссылки не может, но только потому что на это нет спроса в сообществе. Все и так справляются.
Главное, что эти экспорты поддерживаются автоматическим рефакторингом, то есть при переименовании MyClass, все места его использования обновятся автоматически. Для поддержки больших проектов это важнее копирования ссылок в чатик.
Я специально нигде ни в статье, ни в комментах не использовал use
, вы сами первый его написали (можете проверить по Ctrl+F) :) Но если после этого, кому-то что-то стало понятнее, то и хорошо.
Вы и про import ничего не писали =) Просто для JS разработчиков не совсем понятно чем отличается
use Some\Any as Alias;
// от
import {Any as Alias} from 'some/any';
и именование "Some\Any" от имени "SomeAny".
Если рассматривать с этой стороны, то думаю становится понятным почему JS разработчики не понимают смысла существования оных.
import { Configuration as dbalConfig } from 'doctrine/DBAL'
Вот этот вот код — это императивная конструкция.
Согласен, но при написании кода — это не представляет никаких трудностей. Как я выше написал — стиль кода будет другой. На ноде нет огромных фрэймворков с кучей классов, которые одинаково называются.
{
"compilerOptions": {
"allowJs": false,
"alwaysStrict": true,
"baseUrl": ".",
"downlevelIteration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"module": "esnext",
"moduleResolution": "node",
"noEmitHelpers": true,
"noEmitOnError": true,
"removeComments": false,
"resolveJsonModule": true,
"target": "esnext",
"lib": [
"dom",
"esnext"
],
"paths": {
"@crud": [
"./src/core/crud/index.ts"
],
"@crud/*": [
"./src/core/crud/*"
],
"@entities": [
"./src/core/entities/index.ts"
],
"@entities/*": [
"./src/entities/*",
"./src/core/entities/*"
],
"@frontend/*": [
"./src/frontend/*",
"./src/core/frontend/*"
],
"@ioc": [
"./src/core/ioc/index.ts"
],
"@ioc/*": [
"./src/core/ioc/*"
],
"@middlewares/*": [
"./src/middlewares/*",
"./src/core/middlewares/*"
],
"@subscribers": [
"./src/core/subscribers/index.ts"
],
"@subscribers/*": [
"./src/subscribers/*",
"./src/core/subscribers/*"
],
"@templates/*": [
"./src/templates/*",
"./src/core/templates/*"
],
"@utils/*": [
"./src/utils/*",
"./src/core/utils/*"
]
}
},
"exclude": [
"static",
"build",
"dist",
"node_modules"
]
}
Для интеграции с webpack есть github.com/dividab/tsconfig-paths-webpack-plugin, который позволяет использовать эти алиясы для всех лоадеров, использующих апи вебпака для ресолва путей.
Для ванильной ноды так или иначе приходится пользоваться вебпаком, ибо полноценные модули в ноду без хармони флагов пока не завезли. + у вебпака и прочих плюшек хватает.
Получается, что мы уже пишем не на ванильном JS, а на каком-то WebpackScript, что не очень хорошо, потому что переносимость кода страдает.
Но я имел в виду переносимость исходников. Заходите вы переехать с вебпака на что-то более новое и удобное – но не сможете, потому что у вас в коде есть завязки на конкретные фичи сборщика.
Если бы речь шла скажем о с++, где есть 2 основных сильно разнящихся компилятора (msvc и gcc, остальные более-менее совместимы с gcc), то стоит добиваться совместимости со всеми «сборщиками».
Но касательно node и браузеров в 2019 по сути остался единственный сборщик для новых проектов — webpack. Можно поспорить конечно насчет rollup, но имхо в повседневной разработке, где нет цели экономить на каждом байтике в результирующем бандле, вебпаку нет альтернативы, благодаря выстроенному комьюнити и числу готовых решений.
Gulp, Grunt — мертвы, посмотрите даты релизов последних версий.
А больше ничего достаточно популярного и не было.
Да и вообще вашу логику можно перефразировать как:
Вот пишите вы на языке А, а вдруг потом нужно будет переехать на более новый и удобный язык B — не сможете! Ой как не хорошо.
Для оценки этой роли достаточно попробовать разбить какой-нибудь npm-модуль на несколько модулей поменьше и перестроить свой проект на использование двух новых модулей вместо одного большого.
Такое в Python тоже можно провернуть, используя только `import`, но импортируя сторонние имена через свой промежуточный модуль.
А ещё в Python можно переименовать модуль:
import module as alias
Другое дело, что такие трюки приводят к нечитаемости кода − другой программист не сразу поймёт, с какими именно библиотеками вы работаете. Мне кажется, к PHP это тоже относится.
Кое-что о пространстве имён