Cara membuat UI Component Library menggunakan React, Rollup dan Lerna

Date

21 Oct 2023

Final Source Code

Source Code


Kenapa Implement?

Ketika sedang mengerjakan berbagai macam project (misalkan ada 5 project) dengan desain Component yang sama, kalau dengan copas code component dari project A ke project C,B,D,E itu repot cuuy yakali copas code navbar (component) ke 5 project.

1 component berubah nanti project lain ikutan berubah? Kaga dong cuy

Pada tutorial kali ini gue menerapkan versioning dengan menggunakan lerna, sehingga jika project A melakukan update component versi ke 1.0.2 tetapi pada project B masih ingin menggunakan versi 1.0.1 itu tidak masalah. kecuali, pada project B menggunakan versi @latest

Nanti pake componentnya gimana cuy?

lu nanti tinggal install aja melalui package manager seperti yarn atau npm dan tinggal panggil aja deh component nya.

// kalo pake npm
npm install @mangkodir-ui/button@1.0.0

// kalo pake yarn
yarn add @mangkodir-ui/button@1.0.0
import Button from "@mangkodir-ui/button";

<Button>Sekuut</Button>

Diagram

membuat component library

Jadi, pada tutorial ini memungkinkan beberapa component dalam satu repo dengan version pada setiap component berbeda, tetapi dengan 1 node_modules yg sama dan dapat dengan mudah melakukan sharing code antar component tanpa harus berpindah repository (Monorepo). Rollup akan melakukan build component yang hasilnya merupakan module umd, hasil build tersebut akan di publish menggunakan lerna. Lerna akan melakukan push tag (untuk mempermudah jika ingin melakukan rollback) ke repo github dan publish component ke npmjs.com. ketika component sudah di publish di npmjs.com, component dapat di install dan digunakan.


Registration

Buat dulu akun lu di npm. https://www.npmjs.com/signup

Setelah login, bikin dulu organizationnya di npm. Klik tombol + pada Organizations.

membuat component library

Terus lu masukin deh tuh nama library lu, misalkan disini gue namanya @mangkodir-ui.

membuat component library

Setelah itu klik skip this for now aja.

membuat component library

Nanti hasilnya kek gini.

membuat component library


Generate Token

Setelah lu registrasi dan buat organization di npm, kita perlu bikin file .npmrc di device biar bisa publish package lu.

Sekarang, lu balik lagi ke dashboard website npmjs.com. dibagian sidebar kiri terdapat sebuah menu seperti dibawah ini

membuat component library

Pilih “Access Token” pada sidebar kiri

membuat component library

Pilih yg classic token.

membuat component library

Nanti hasilnya tuh kek gini

membuat component library

Jangan lupa untuk menyimpan token yang udah berhasil di generate, karena kita akan menggunakannya pada file .npmrc


Membuat file .npmrc

Setelah itu bikin file .npmrc di device lu.

Mac User

bikin file .npmrc di /Users/fadhelfalah

fadhelfalah : ini username di laptop gue.

Windows User

bikin file .npmrc di C:/users/fadhelpc-gaming/

fadhelpc-gaming : ini username di device windows gue.

Terus lu isi file nya seperti dibawah ini:

//registry.npmjs.org/:_authToken=npm_sebuahtoken
@mangkodir-ui:registry=https://registry.npmjs.org/
email=krazefollow@gmail.com
always-auth=true

Setup UI Component Library

Nah ini part yang akhirnya ditunggu, kita akan membuat UI Component Library dengan bundler Rollup dan versioningnya Lerna

Pertama ketik dulu perintah dibawah ini.

npm init

Setelah itu lu install dulu lerna secara global dengan ketik

npm install -g lerna

Nah baru deh lu generate file json lerna nya dengan cara.

npx lerna init --packages="packages/*"

ubahlah file lerna.json seperti dibawah ini.

{
  "npmClient": "npm",
  "version": "independent",
  "hoist": true,
  "stream": true,
  "private": true,
  "useWorkspaces": true,
  "bootstrap": {
    "npmClientArgs": ["--no-package-lock"]
  },
  "command": {
    "publish": {
      "allowBranch": ["master"]
    }
  },
  "useNx": false
}

Setelah itu kita setup si rollup buat nge bundling package kita.

Dibawah ini ada analogi nya si rollup itu ngapain.

membuat component library

Sekarang kita install library-library yg dibutuhin untuk rollup.

yarn add –D rollup react react-dom rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-terser @babel/core @babel/preset-env @babel/preset-react

Berikut ini merupakan code untuk package.json nya.

Pada bagian @mangkodir-ui bisa diganti dengan nama organization yg udah lu buat di npm.

{
  "name": "belajar-monorepo",
  "commands": {
    "publish": {
      "ignoreChanges": [
        "**/*.md",
        "*.txt",
        "**/example/**",
        "test/**",
        "**/test/**",
        "tests/**",
        "**/tests/**",
        "packages/**/package-lock.json"
      ]
    }
  },
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "npmClient": "yarn",
  "private": true,
  "scripts": {
    "build": "lerna exec -- rollup -c=../../rollup.config.js --bundleConfigAsCjs",
    "watch": "lerna exec --no-sort -- rollup -w -c=../../rollup.config.js --bundleConfigAsCjs",
    "single": "lerna exec --scope @mangkodir-ui/$packages -- rollup -c=../../rollup.config.js --bundleConfigAsCjs",
    "single:watch": "lerna exec --scope @mangkodir-ui/$packages -- rollup -w -c=../../rollup.config.js --bundleConfigAsCjs"
  },
  "keywords": [],
  "author": "",
  "license": "Gunadarma",
  "workspaces": ["packages/*"],
  "devDependencies": {
    "@babel/core": "^7.22.9",
    "@babel/preset-env": "^7.22.9",
    "@babel/preset-react": "^7.22.5",
    "lerna": "^5.5.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "rollup": "^3.26.3",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-terser": "^7.0.2",
    "styled-jsx": "^5.1.2"
  },
  "peerDependencies": {
    "react": "^18.2.0",
    "styled-jsx": "^5.1.2"
  }
}

Setelah selesai mengubah package.json, kita perlu melakukan konfigurasi rollup.

Konfigurasi rollup.config.js

Agar package dapat berjalan di browser lama dan mengecilkan hasil bundler, kita perlu melakukan konfigurasi seperti dibawah ini:

import babel from "rollup-plugin-babel";
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";

import path from "path";

const { LERNA_PACKAGE_NAME } = process.env;
const PACKAGE_ROOT_PATH = process.cwd();
const PKG_JSON = require(path.join(PACKAGE_ROOT_PATH, "package.json"));
const INPUT_FILE = path.join(
  PACKAGE_ROOT_PATH,
  `${PKG_JSONFile}/${PKG_JSONName}.js`
);
const OUTPUT_DIR = path.join(PACKAGE_ROOT_PATH, "dist");
const IS_BROWSER_BUNDLE = !!PKG_JSON.browser;
const formats = IS_BROWSER_BUNDLE ? ["umd"] : ["es", "cjs"];

export default formats.map((format) => ({
  plugins: [
    resolve(),
    commonjs({
      include: /node_modules/,
      namedExports: {
        "react-js": ["isValidElementType"],
        "react-is": ["typeOf", "isElement", "isValidElementType"],
      },
    }),
    babel({
      exclude: ["node_modules/**"],
      presets: [["@babel/preset-env", { modules: false }], "@babel/react"],
      plugins: ["styled-jsx/babel"],
    }),
  ],
  input: INPUT_FILE,
  external: ["react", "react-dom"],
  output: {
    ...(format === "umd"
      ? { file: path.join(OUTPUT_DIR, `${PKG_JSONName}.${format}.js`) }
      : {}),
    format,
    sourcemap: true,
    name: LERNA_PACKAGE_NAME,
    amd: {
      id: LERNA_PACKAGE_NAME,
    },
    ...(format !== "umd" ? { dir: OUTPUT_DIR } : {}),
  },
  ...(format !== "umd" ? { preserveModules: true } : {}),
}));

Disini terdapat 3 plugin yg digunakan pada rollup:

Pada konfigurasi rollup ini kita menggunakan modul UMD agar syntax nya bisa dijalankan di serverside atau clientside.

Untuk penjelasannya ada disini yak, gue ga terlalu dalem soal module ini.

Konfigurasi Component

Setiap package perlu di konfigurasi agar hasil build dapat digunakan. Disini nama file untuk component ialah button_test.js yang berada di packages/button_test/package.json.

berikut ini merupakan contoh structure folder untuk component button_test.

Notes: pada step ini abaikan folder dist, karena folder dist akan digenerate otomatis pada step cara build component.

membuat component library

sebagai contoh berikut ini merupakan contoh code component nya.

// packages/button_test/lib/button_test.js
import React from "react";
import css from "styled-jsx/css";

const btnStyle = {
  "btn-blue": `
    background: linear-gradient(
        102deg,
        rgb(0, 196, 255) 0%,
        rgb(0, 153, 255) 100%
      );
      color: #fff;
      box-shadow: rgba(9, 172, 255, 0.518) 0.3981px 0.3981px 0.56299px -0.9375px,
        rgba(9, 172, 255, 0.49) 1.20725px 1.20725px 1.70731px -1.875px,
        rgba(9, 172, 255, 0.42) 3.19133px 3.19133px 4.51322px -2.8125px,
        rgba(9, 172, 255, 0.176) 10px 10px 14.1421px -3.75px;
    `,
};

export default function ButtonTest({ children, type = null, href = null, style = null }) {
  const { className = "", styles = "" } = css.resolve`
    ${btnStyle[type ? `btn-${type}` : "btn"] || ""}
  `;
  return (
    <>
      <button className={`mangkodir-btn ${className}`} {...{ href, style }}>
        {children}
      </button>
      <style jsx>
        {`
          .mangkodir-btn {
            height: 100%;
            opacity: 1;
            padding: 14px 16px;
            border: 0;
            border-radius: 12px;
            cursor: pointer;
          }
        `}
      </style>
      {styles}
    </>
  );
}

disini gue menggunakan styled-jsx untuk dapat melakukan styling css-in-js.

lalu config untuk package.json di packages/button_test/package.json

{
  "name": "@mangkodir-ui/button_test",
  "version": "1.0.0",
  "description": "> TODO: description",
  "author": "Fadhel <fadhelijlalfalah@gmail.com>",
  "homepage": "https://github.com/fadhelmurphy/mangkodir-ui-starter#readme",
  "license": "ISC",
  "srcFile": "lib",
  "srcName": "button_test",
  "main": "dist/button_test.umd.js",
  "files": [
    "dist/"
  ],
  "publishConfig": {
    "registry": "https://registry.npmjs.org",
    "access": "public"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/fadhelmurphy/mamatgarem-ui-starter.git"
  },
  "scripts": {
    "test": "echo \"Error: run tests from root\" && exit 1"
  },
  "browser": true,
  "bugs": {
    "url": "https://github.com/fadhelmurphy/mamatgarem-ui-starter/issues"
  }
}
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},


Cara Build Component

Mac User

packages=nama_package_lu yarn single

Contoh:

packages=button_test yarn single

Windows User

lerna exec —scope @mangkodir-ui/nama_package_lu — rollup -c=../../rollup.config.js —bundleConfigAsCjs

Contoh:

lerna exec --scope @mangkodir-ui/button_test -- rollup -c=../../rollup.config.js --bundleConfigAsCjs

Hasil setelah build

membuat component library
membuat component library

Setelah berhasil build akan men generate file js di dalam folder distribution (dist) seperti diatas.


Cobain component di local

Sebelum di publish, component nya bisa lu coba di local untuk memastikan apakah component lu ini bisa digunakan.

Misalkan saat ini lu lagi pake project react, lu bisa ubah package.json di project lu dengan cara sebagai berikut.

Pertama, pastiin lu tau lokasi folder project UI Component Library lu dan Contoh project (React.js).

import local

Contoh, disini mangkodir-ui (UI Library Component) dan contoh-project-nextjs (React.js) dengan posisi sejajar dalam satu folder yang sama.

consume package json

lalu pada contoh-project-nextjs/package.json kita tambahkan package seperti dibawah ini dan arahkan source folder nya.

Setelah itu install component nya.

npm install

setelah di install, component tersebut dapat digunakan.

import FadhelBtn from "@fdhl/button"
...

<FadhelBtn>
  Sebuah text
</FadhelBtn>

Hasilnya akan tampil seperti gambar dibawah ini.

consume package json

Untuk pengguna Nextjs 12 atau versi terbaru (Opsional)

Lu bisa langsung import component dari luar project tanpa harus mengubah package.json.

import FdhlButton from "../../mangkodir-ui/packages/button_test"
...

<FdhlButton>
  Sebuah text
</FdhlButton>

Publish & Versioning component

Untuk publish component, pastikan lu berada di branch master. Karena pada config lerna.json hanya memperbolehkan melakukan publish di master.

membuat component library

Setelah itu jalankan perintah ini di terminal dan pastikan berada root repository.

npx lerna publish

Jika berhasil akan memiliki tampilan seperti dibawah ini.

membuat component library
membuat component library

Lalu pada npm dan github nanti akan ada update dengan tampilan seperti

membuat component library
membuat component library

Kurang lebih tutorialnya kek gitu yaaa,
thank you udah baca ampe bawah