Марк Эриксон «Мой опыт модернизации пакетов до ESM»

ссылка на оригинал



Наши мнения можете послушать в подкасте, а ниже вольный пересказ (статья больно интересная).



TL;DR Марк мейнтейнер redux, react-redux, redux-thunk, reselect и @reduxjs/toolkit. В конце 2022 он попытался перетащить эти пакеты на ESM но к августу 2023 так и не смог закончить работу. Потому что всё очень плохо.



Итак, на дворе 2022 и Марк садится за работу. Пора бы уже перетащить пакеты на ESM! В этот момент он осознаёт, что нет ни одного руководства, как сделать универсальный пакет, который будет работать везде. Как вы можете догадаться, он не хочет идти по пути Синдре Сорхуса и выпускать Pure ESM, нет, он хочет оставить совместимость для старых приложений и поддержать вообще всё, что можно. Включая Webpack 4. Похвальное желание.



Прочитав 175 статей он приходит к выводу, что нужно:



- добавить
"type": "module"
в
package.json
чтобы показать, что это ESM

- прописать exports с энтри-поинтами для разных окружений

- кажется нужно поставить расширение
.mjs
, но это уродливо, так что нафиг



Дело нехитрое, Марк собирает RTK с новым
package.json
, всё выглядит неплохо, он кидает PR и БУМ, всё сломалось. Да, рантайм работает как надо, но вот
Jest
отвалился.
Jest
не переварил `
export default
` в ESM и как бы Марк не пытался обойти проблему — ничего не вышло. Что же, пришлось перейти на
Vitest
. Прощай,
Jest
.



Альфа собрана, опубликована и... БУМ! Тайпскрипт с
moduleResolution
node16
и
nodenext
не может вытащить типы. А
Node.js
не может работать
.js
CommonJS если пакет переключен впакеты на ESM но к а CommonJS файлы в таких пакетах должны иметь расширение
.cjs
.



- Если мы написали
"type": "module"
то используем
.js
для ESM и
.cjs
для CommonJS

- Если мы не написали
"type": "module"
то используем
.mjs
для ESM и
.js
для CommonJS



Всё по спеке! Да, бандлерам зачастую тут по барабану, они проглотят и так, но Node.js требовательна. Открытым остаётся вопрос нужно ли делать несколько
d.ts
-файлов. Есть мнение, что нужно.



Марк собирается с духом и делает ещё один заход. Чтобы было проще, он откладывает в сторону RTK и берёт в руки
redux-thunk
. 20 строк кода, что может пойти не так? Выкидывает
Babel
с
Rollup
, берет в руки
ESBuild
и tsup. Пара часов игры с конфигом и вот на выходе два файла:
.mjs
и
.cjs




Тут он узнаёт, что Webpack 4 всё ещё популярнее Webpack 5. А Webpack 4 не поддерживает поле exports и ему не нравится .mjs в поле main. А так же Webpack 4 не понимает optional chaining. Придётся для него сделать артефакт-фоллбек: ESM в синтаксисе ES2017, с расширением .js. И пусть этот артефакт лежит в поле main.



Кажется победа близка, альфы выпущены, отзывы хорошие. Всё работает!



В начале мая выходит
Next 13.4
. Да-да, тот самый, с React Server Components. Те самые RSC в которых не работает код использующий контекст. React.createContext() и useLayoutEffect — они отсутствовали на сервере и взрывали код. Команда Next предложила использовать отдельный export react-server в который положить код оптимизированный для серверных компонент (рекомендация с «use client» появилась чуть позже).



Тут Марк сгорел. Он потратил месяцы на стабилизацию сборки и вот такая засада — нужно пилить новое решение чтобы не ломались RSC. И никакой нормальной документации нет. Просто всё стало сложнее, для мейнтейнеров пакетов.



Ну и вывод в целом такой, что переход с CJS на ESM это кошмар и конца ему не видно. А тут ещё и RSC. Удивительно, что наша экосистема хоть как-то работает. А Redux Toolkit 2.0 выйдет, точно выйдет.