Вивчіть основи системи модулів JavaScript і створіть власну бібліотеку

Останнім часом ми всі багато чуємо про “Модулі JavaScript”. Напевно, усі цікавляться, що з ними робити, і як вони взагалі відіграють життєво важливу роль у нашому повсякденному житті ...?

Отже, яка біса - це система модулів JS? ?

Оскільки розробка JavaScript стає все більш розповсюдженою, простори імен та залежності стають набагато складнішими в обробці. Для вирішення цієї проблеми були розроблені різні рішення у вигляді модульних систем.

Чому розуміння системи модулів JS важливо?

Дозвольте розповісти вам історію.

Розповідання історій настільки ж основне для людини, як і їжа. Більш того, насправді, оскільки, хоча їжа змушує нас жити, історії - те, що робить наше життя вартим життя - Річард Кірні

Чому я взагалі кажу про всі ці речі?

Отже, моя повсякденна робота полягає в розробці та архітекторських проектах, і я швидко зрозумів, що в проектах існує багато загальних функціональних можливостей. Я завжди закінчував копіюванням цих функцій у нові проекти знову і знову.

Проблема полягала в тому, що щоразу, коли змінювався один фрагмент коду, мені потрібно було вручну синхронізувати ці зміни у всіх своїх проектах. Щоб уникнути всіх цих нудних ручних завдань, я вирішив витягти загальні функціональні можливості і скласти з них пакет npm. Таким чином, інші члени команди зможуть повторно використовувати їх як залежності та просто оновлювати, коли буде випущено новий випуск.

Цей підхід мав деякі переваги:

  • Якщо в основній бібліотеці відбулися деякі зміни, тоді зміни потрібно було вносити лише в одному місці без рефакторингу коду всіх програм для одного і того ж.
  • Усі програми залишались синхронізованими. Щоразу, коли було внесено зміни, всім програмам просто потрібно було запустити команду “npm update”.

Отже, наступним кроком було видання бібліотеки. Правда? ?

Це була найскладніша частина, тому що в моїй голові стрибала купа речей, наприклад:

  1. Як зробити дерево струшуючим?
  2. На які системи модулів JS слід орієнтуватися (commonjs, amd, гармонія).
  3. Чи слід переписувати джерело?
  4. Чи слід зв’язувати джерело?
  5. Які файли слід опублікувати?

Під час створення бібліотеки у кожного з нас виникали такі запитання. Правда?

Я спробую зараз розглянути всі вищезазначені питання.

Різні типи модульних систем JS?

1. CommonJS

  • Реалізовано вузлом
  • Використовується на стороні сервера, коли у вас встановлені модулі
  • Відсутність завантаження модуля виконання / асинхронного модуля
  • імпортувати через " вимагати "
  • експортувати через “ module.exports
  • При імпорті ви повертаєте об'єкт
  • Не трясеться дерево, тому що при імпорті ви отримуєте предмет
  • Немає статичного аналізу, оскільки ви отримуєте об’єкт, тому пошук властивостей виконується під час виконання
  • Ви завжди отримуєте копію об’єкта, тому жодних змін у самому модулі немає
  • Погане управління циклічною залежністю
  • Простий синтаксис

2. AMD: Визначення асинхронного модуля

  • Реалізовано RequireJs
  • Використовується на стороні клієнта (браузер), коли потрібно динамічне завантаження модулів
  • Імпортувати через "вимагати"
  • Складний синтаксис

3. UMD: Визначення універсального модуля

  • Поєднання CommonJs + AMD (тобто Синтаксис CommonJs + асинхронне завантаження AMD)
  • Може використовуватися для обох середовищ AMD / CommonJs
  • UMD по суті створює спосіб використання будь-якого з них, одночасно підтримуючи визначення глобальної змінної. Як результат, модулі UMD здатні працювати як на клієнтському, так і на сервері .

4. Гармонія ECMAScript (ES6)

  • Використовується як для сервера / клієнта стороні
  • Час виконання / статичне завантаження підтримуваних модулів
  • Під час імпорту ви отримуєте значення прив'язок (фактичне значення)
  • Імпорт через „імпорт” та експорт через „експорт”
  • Статичний аналіз - Ви можете визначити імпорт та експорт під час компіляції (статично) - вам потрібно лише переглянути вихідний код, вам не потрібно його виконувати
  • Дерево можна похитнути, оскільки статичний аналіз підтримується ES6
  • Завжди отримуйте фактичне значення, тому в реальному часі змінюються самі модулі
  • Краще управління циклічною залежністю, ніж CommonJS

Отже, тепер ви знаєте все про різні типи модульних систем JS та про те, як вони еволюціонували.

Хоча модульна система ES Harmony підтримується всіма інструментами та сучасними браузерами, ми ніколи не знаємо, публікуючи бібліотеки, як наші споживачі можуть їх використовувати. Тому ми повинні завжди забезпечувати, щоб наші бібліотеки працювали в усіх середовищах.

Давайте поглибимось глибше та спроектуємо зразок бібліотеки, щоб відповісти на всі питання, пов’язані з виданням бібліотеки належним чином.

I’ve built a small UI library (you can find the source code on GitHub), and I’ll share all my experiences and explorations for transpiling, bundling, and publishing it.

Here we have a small UI library which has 3 components: Button, Card, and NavBar. Let’s transpile and publish it step by step.

Best practices before publishing ?

  1. Tree Shaking ?
  • Tree shaking is a term commonly used in the context of JavaScript for dead-code elimination. It relies on the static structure of ES2015 module syntax, that is, import and export. The name and concept have been popularized by the ES2015 module bundler rollup.
  • Webpack and Rollup both support Tree Shaking, meaning we need to keep certain things in mind so that our code is tree shakeable.

2. Publish all module variants

  • We should publish all the module variants, like UMD and ES, because we never know which browser/webpack versions our consumers might use this library/package in.
  • Even though all the bundlers like Webpack and Rollupunderstand ES modules, if our consumer is using Webpack 1.x, then it cannot understand the ES module.
// package.json
{"name": "js-module-system","version": "0.0.1",...
"main": "dist/index.js","module": "dist/index.es.js",
...}
  • The main field of the package.json file is usually used to point to the UMD version of the library/package.
  • You might be wondering — how can I release the ES version of my library/package? ?

    Поле m odule на p ackage.json використовується для вказівки на E- S версію бібліотеки / пакета. Раніше було використано багато полів, таких як j s:next та j s:main , але odule зараз m стандартизовано і використовується пакувальниками як підстановка для S vвибору бібліотеки / пакета.

Менш відомий факт: Webpack використовує resol.mainfields, щоб визначити, які поля package.jsonперевіряються. Порада щодо продуктивності: Завжди намагайтеся також опублікувати ESверсію вашої бібліотеки / пакета, оскільки всі сучасні браузери тепер підтримують ESмодулі. Таким чином, ви можете передати менше, і в кінцевому підсумку ви отримаєте менше коду для своїх користувачів. Це підвищить продуктивність вашої програми.

Отже, що далі? Транспіляція чи групування? Які інструменти нам слід використовувати?

Ah, here comes the trickiest part! Let’s dive in. ?

Webpack vs Rollup vs Babel?

These are all the tools we use in our day to day lives to ship our applications/libraries/packages. I cannot imagine modern web development without them — #blessed. Therefore, we cannot compare them, so that would be the wrong question to ask! ❌

Each tool has it’s own benefits and serves different purpose based on your needs.

Let’s look at each of these tools now:

Webpack

Webpack is a great module bundler ? that is widely accepted and mostly used for building SPAs. It gives you all the features out of the box like code splitting, async loading of bundles, tree shaking, and so on. It uses the CommonJS module system.

PS: Webpack-4.0.0 alpha is already out ?. Hopefully with the stable release it will become the universal bundler for all types of module systems.

RollupJS

Rollup is also a module bundler similar to Webpack. However, the main advantage of rollup is that it follows new standardized formatting for code modules included in the ES6 revision, so you can use it to bundle the ES module variant of your library/package. It doesn’t support async loading of bundles.

Babel

Babel is a transpiler for JavaScript best known for its ability to turn ES6 code into code that runs in your browser (or on your server) today. Remember that it just transpiles and doesn’t bundle your code.

My advice: use Rollup for libraries and Webpack for apps.

Transpile (Babel-ify) the source or Bundle it

Again there’s a story behind this one. ?

I spent most of my time trying to figure out the answer to this question when I was building this library. I started digging out my node_modules to lookup all the great libraries and check out their build systems.

After looking at the build output for different libraries/packages, I got a clear picture of what different strategies the authors of these libraries might have had in mind before publishing. Below are my observations.

As you can see in the above image, I’ve divided these libraries/packages into two groups based on their characteristics:

  1. UI Libraries (styled-components, material-ui)
  2. Core Packages (react, react-dom)

If you’re a good observer ? you might have figured out the difference between these two groups.

UI Libraries have a dist folder that has the bundled and minified version for ES and UMD/CJSmodule systems as a target. There is a lib folder that has the transpiled version of the library.

Core Packages havejust one folder which has the bundled and minified version for CJS or UMD module system as a target.

But why is there a difference in build output of UI libraries and Core Packages? ?

UI Libraries

Imagine if we just publish the bundled version of our library and host it on CDN. Our consumer will use it directly in at/> tag. Now if my consumer wants to use just the <;Button/> component, they have to load the entire library. Also, in a browser, there is no bundler which will take care of tree shaking, and we’ll end up shipping the whole library code to our consumer. We don’t want this.

import {Button} from "//unpkg.com/uilibrary/index.js";

Now if we simply transpile the src into lib and host the lib on a CDN, our consumers can actually get whatever they want without any overhead. “Ship less, load faster”. ✅

import {Button} from "//unpkg.com/uilibrary/lib/button.js";

Core Packages

Core packages are never utilized via the t/> tag, as they need to be part of main application. So we can safely release the bundled version (UMD, ES) for these kinds of packages and leave the build system up to the consumers.

For example, they can use the UMD variant but no tree shaking, or they can use the ES variant if the bundler is capable of identifying and getting the benefits of tree shaking.

// CJS requireconst Button = require("uilibrary/button");
// ES importimport {Button} from "uilibrary";

But…what about our question: should we transpile (Babelify) the source or bundle it? ?

For the UI Library, we need to transpile the source with Babel with the es module system as a target, and place it in lib. We can even host the lib on a CDN.

We should bundle and minifythe source using rollup for cjs/umd module system and es module system as a target. Modify the package.json to point to the proper target systems.

// package.json
{"name": "js-module-system","version": "0.0.1",...
"main": "dist/index.js", // for umd/cjs builds"module": "dist/index.es.js", // for es build
...}

For core packages, we don’t need the lib version.

We just need to bundle and minifythe source using rollup for cjs/umd module system and es module system as a target. Modify the package.json to point to the proper target systems, same as above.

Tip: We can host the dist folder on the CDN as well, for the consumers who are willing to download the whole library/package via t/> tag.

How should we build this?

We should have different scripts for each target system in package.json . You can find the rollup config in the GitHub repo.

// package.json
{..."scripts": {"clean": "rimraf dist","build": "run-s clean && run-p build:es build:cjs build:lib:es","build:es": "NODE_ENV=es rollup -c","build:cjs": "NODE_ENV=cjs rollup -c","build:lib:es": "BABEL_ENV=es babel src -d lib"}...}

What should we publish?

  • License
  • README
  • Changelog
  • Metadata(main , module, bin) — package.json
  • Control through package.jsonfiles property

In package.json , the "files" field is an array of file patterns that describes the entries to be included when your package is installed as a dependency. If you name a folder in the array, then it will also include the files inside that folder.

We will include the lib and dist folders in "files" field in our case.

// package.json
{..."files": ["dist", "lib"]...}

Finally the library is ready to publish. Just type the npm run build command in the terminal, and you can see the following output. Closely look at the dist and lib folders. ?

Original text


Wrap up

Wow! Where does the time go? That was a wild ride, but I sincerely hope it gave you a better understanding of the JavaScript Module system and how you can create your own library and publish it.

Just make sure you take care of the following things:

  1. Make it Tree Shakeable. ?
  2. Target at least ES Harmony and CJS module systems. ?
  3. Use Babel and Bundlers for libraries. ?
  4. Use Bundlers for Core packages. ?
  5. Set the module field of package.json to point to the ES version of your module (PS: It helps in tree shaking). ?
  6. Publish the folders which have transpiled as well as bundled versions of you module. ?

Trending this week ?

  1. Webpack-V4 alpha released. ?
  2. ParcelJs: Blazing fast, zero configuration web application bundler. ?
  3. Turbo: 5x faster than Yarn & NPM, and runs natively in-browser ?

Thanks to Juho Vepsäläinen and Lakshya Ranganath for their reviews & feedback, Sean T. Larkin and Tobias Koppers for sharing the insights of webpack at ReactiveConf, Addy Osmani for sharing workings of different JS module Systems in “Writing Modular JavaScript With AMD, CommonJS & ES Harmony”.

P.S. If you like this, make sure to recommend (by clap? ) , follow me on twitter, and share this with your friends!?