{"id":16867958,"url":"https://github.com/jens-ox/cll","last_synced_at":"2025-12-30T20:59:59.961Z","repository":{"id":149897734,"uuid":"621738237","full_name":"jens-ox/cll","owner":"jens-ox","description":"React Component Library Library","archived":false,"fork":false,"pushed_at":"2023-04-13T10:04:56.000Z","size":2056,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-24T21:43:18.526Z","etag":null,"topics":["component-library","nextjs","react","tutorial"],"latest_commit_sha":null,"homepage":"https://cll-web.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jens-ox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-31T09:26:34.000Z","updated_at":"2023-04-27T10:25:39.000Z","dependencies_parsed_at":"2023-06-01T01:00:23.328Z","dependency_job_id":null,"html_url":"https://github.com/jens-ox/cll","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-ox%2Fcll","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-ox%2Fcll/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-ox%2Fcll/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jens-ox%2Fcll/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jens-ox","download_url":"https://codeload.github.com/jens-ox/cll/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244284210,"owners_count":20428325,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["component-library","nextjs","react","tutorial"],"created_at":"2024-10-13T14:56:18.512Z","updated_at":"2025-12-30T20:59:59.916Z","avatar_url":"https://github.com/jens-ox.png","language":"TypeScript","readme":"# Component Library Library\n\nThe goal of this repo is to showcase different setups that allow you to share React components between repositories.\n\n```mermaid\ngraph LR;\n    1(bare);\n    2(bare-ts);\n    3(bare-ts-tooling);\n    4(css);\n    5(tailwind);\n    6(mui);\n    7(icons);\n    1--\u003e2;\n    2--\u003e3;\n    3--\u003e4;\n    4--\u003e5;\n    3--\u003e6;\n    3--\u003e7;\n```\n\n## Motivation\n\nIn most corporate landscapes you have multiple UIs that live in different repositories. In order to avoid duplication and improve consistency, common components such as buttons and dialogs should be shared across those repositories.\n\n## Available library setups\n\nCurrently the following setups are available - each one builds up on the previous one.\n\n- [**bare**](#level-1-barebones) - absolute bare minimum required to share a React component, doesn't even use JSX.\n- [**bare-ts**](#level-2-barebones-typescript) - simplest TypeScript setup possible.\n- [**bare-ts-tooling**](#level-3-bare-bones-typescript-with-tooling) - same as `bare-ts`, but with ESLint, tsup and a GitHub Action.\n- [**css**](#level-4-css) - adds global and component styling to `bare-ts-tooling`.\n- [**tailwind**](#level-5-tailwind) - adds [TailwindCSS](https://tailwindcss.com/) to `css`.\n- [**mui**](#level-6-material-ui) - based on `bare-ts-tooling`, custom Material UI theme and custom component.\n- [**icons**](#level-7-icon-library) - based on `bare-ts-tooling`, icon library that directly converts SVG files.\n\n## Setup\n\nIn order to not over-complicate things unnecessarily, each sharing setup will have one library (that houses the components to be shared) and one simple Next.js app (that consumes the shared components).\n\nWe will also set up all libraries as [ES Modules](https://nodejs.org/api/esm.html#introduction). All libraries (with exception of the bare-bones example) will be written in [TypeScript](https://www.typescriptlang.org/). We are going to use [`pnpm`](https://pnpm.io/) as a package manager (but everything will work with `npm` or `yarn` just fine).\n\nTo run a specific library locally,\n\n1. clone the repo,\n2. `pnpm install` at the root,\n3. `pnpm build` in the library of your choice,\n4. `pnpm dev` in its consuming application.\n\n## Core Concepts\n\nAll libraries are intended to be used in React applications that have some kind of proper build step. That means that you will not be able to do old-school shenanigans like sourcing it directly via a `script` tag in some hand-written HTML.\n\nThis makes our lives as library authors way more comfortable:\n\n- We do not minify our library. The application that consumes the library does.\n- We do not bundle dependencies (with exceptions, as always). The application's bundling step resolves dependencies transitively.\n- We do not polyfill for random old browsers. The application's bundling step does so if necessary.\n\n## [Level 1: Barebones](./lib/bare/)\n\n\u003e **Note**\n\u003e\n\u003e Summary: Absolute bare minimum necessary to share a React component.\n\nWhile I would not recommend doing this, you can use React completely without a build step. Doing so results in the absolute most minimal setup possible.\n\n**Create a `package.json`**\n\nFirst, create a `package.json` file:\n\n```json\n{\n  \"name\": \"@ccl/lib-bare\",\n  \"main\": \"./index.js\",\n  \"type\": \"module\"\n}\n```\n\n*   `name` ([Reference](https://nodejs.org/api/packages.html#name)): This is the name of your library that users will use to import it. Here, one would import a component like this: `import { Button } from '@ccl/lib-bare`.\n*   `main` ([Reference](https://nodejs.org/api/packages.html#main)): All things exported from the file referenced here will be available to import. Here, `index.js` needs to contain `export const Button = ...` so that we can do `import { Button } from '@ccl/lib-bare`.\n*   `type` ([Reference](https://nodejs.org/api/packages.html#type)): This tells the importing application what type of JavaScript module to expect. We want to build our libraries as ES Modules, so we set it to `\"module\"`.\n\n**Add `react`**\n\nAs we're not going to have a build step for this library, properly setting up React as a dependency doesn't really matter and will be covered in a later setup. For now, we'll just do `pnpm add react`.\n\n**Create a component**\n\nNormally, you would create a React component like this:\n\n```jsx\nexport const Button = () =\u003e (\n  \u003cbutton style={{ backgroundColor: 'steelblue' }}\u003e\n    {children}\n  \u003c/button\u003e\n)\n```\n\nFor this to work we would need a build step - JSX syntax is not vanilla JavaScript. Instead, our component will look like this:\n\n```js\nimport { createElement } from 'react'\n\nexport const Button = ({ children }) =\u003e createElement('button', {\n  style: { backgroundColor: 'steelblue' }\n}, children)\n```\n\nFor more information on the `createElement` method feel free to head over to the [React docs](https://react.dev/reference/react/createElement).\n\n**Publish it**\n\nThis repo uses a PNPM workspace setup, so we don't need to publish the packages here. Outside of a monorepo, you would use [npm publish](https://docs.npmjs.com/cli/commands/npm-publish) or some wrapper around it (like [np](https://github.com/sindresorhus/np)) for this.\n\n**Consume it**\n\nWithin an app, you can now use the button component by importing it like this:\n\n```js\nimport { Button } from 'our-library'\n```\n\n![Button inside our demo app](./.github/screenshots/lib-bare.png)\n\n\u003cfigcaption\u003eOur ugly button within our demo app!\u003c/figcaption\u003e\n\n## [Level 2: Barebones TypeScript](./lib/bare-ts/)\n\n\u003e **Note**\n\u003e\n\u003e Summary: Bare minimum required for sharing a React component written in TypeScript.\n\nNobody wants to write React without JSX. As we're going to need a build step anyway and there's no sane reason to build something without TypeScript nowadays, we're going to go directly to JSX + TypeScript.\n\nStarting from the Level 1 code, we\n\n- move the `index.js` to `src/index.tsx` (to better separate code and build artefact later), and\n- add `typescript` and React's types to our `devDependencies` (see the [Appendix](#dependency-types) for an overview over the different dependency types): `pnpm add -D typescript @types/react`.\n\nOnce we set up a build step, the built library will be exposed in `dist/index.js` together with a declaration file at `dist/index.d.ts`, so we update the `package.json` accordingly:\n\n```json\n{\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\"\n}\n```\n\n**Setting up TypeScript**\n\nIn order to compile TypeScript + JSX to vanilla JavaScript, we will add a `build` script to our `package.json` that runs the TypeScript compiler (we will use fancier tooling like `esbuild` in the future):\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"tsc\"\n  }\n}\n```\n\nBy default, the TypeScript compiler doesn't really know what to do with your stuff, so you need to create a `tsconfig.json`:\n\n```json\n{\n  \"include\": [\"src\"],\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"moduleResolution\": \"nodenext\",\n    \"outDir\": \"dist\",\n    \"jsx\": \"react-jsx\"\n  }\n}\n```\n\n- `include` ([Reference](https://www.typescriptlang.org/tsconfig#include)): This tells TypeScript which files to look at.\n- `compilerOptions.target` ([Reference](https://www.typescriptlang.org/tsconfig#target)): This tells TypeScript which version of ECMAScript (aka which JavaScript standard) to compile to. When developing apps, this should be set to something sensible like `ES6`. For libraries like here, we want the consuming application to have full control over its own bundling and polyfilling, so we use the most up-to-date standard, which is exposed as `ESNext`.\n- `compilerOptions.moduleResolution` ([Reference](https://www.typescriptlang.org/tsconfig#moduleResolution)): There's three choices here, `classic`, `node` and `nodenext` (aka `node16`). You probably never want to use `classic` in modern projects. `node` references Node's CommonJS resolution algorithm. Since we want to emit an ES Module, we will use `nodenext`, Node's ES Modules resolution algorithm.\n- `compilerOptions.outDir` ([Reference](https://www.typescriptlang.org/tsconfig#outDir)): This tells TypeScript where to put the compiled files. We want them in `dist` (don't forget to add that directory to your `.gitignore`!)  \n- `compilerOptions.jsx` ([Reference](https://www.typescriptlang.org/tsconfig#jsx)): This informs the TypeScript compiler that we will use JSX syntax. TypeScript can convert that either to `createElement` calls through `react` (which we used in Level 1) or newer `_jsx` calls available since React 17 through `react-jsx` (see [this blog post](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#whats-different-in-the-new-transform)). We want the modern stuff and will use `react-jsx`.\n\nThe library can now be published and consumed analogous to the library from Level 1. However, VSCode won't be happy with us, as we did not expose any declaration file (the file telling TypeScript which vanilla JS thing has which type, see [reference](https://www.typescriptlang.org/docs/handbook/2/type-declarations.html)). In order to do that, we extend our `tsconfig.json`:\n\n```json\n{\n  \"compilerOptions\": {\n    \"declaration\": true\n  }\n}\n```\n\nOnce we run `pnpm build` for our library once more (and publish it if necessary), VSCode understands the type of our imported button:\n\n![VSCode understands the button's type](./.github/screenshots/vscode-screenshot1.png)\n\n## Level 3: Bare-bones TypeScript with tooling\n\n\u003e **Note**\n\u003e\n\u003e Summary: TypeScript, ESLint + Prettier, tsup, GitHub Actions\n\nThis level extends [`bare-ts`](#level-2-barebones-typescript) by adding a proper ESLint setup, tsup for faster builds, and a simple GitHub Action.\n\n**ESLint**\n\nWe will now extend Level 2 by setting up linting using [ESLint](https://eslint.org/):\n\n```sh\npnpm add -D eslint eslint-config-standard-with-typescript eslint-plugin-prettier eslint-config-prettier eslint-config-standard eslint-plugin-react\n```\n\n- `eslint`: the binaries doing the actual linting.\n- `eslint-config-standard-with-typescript`: an opinionated set of rules to follow the [JavaScript Standard Style](https://standardjs.com/), including TypeScript support.\n- `eslint-plugin-react`: react-specific ESLint rules.\n- `eslint-plugin-prettier`: let's us run [Prettier](https://prettier.io/) as part of ESLint. Prettier takes care of things like line lengths etc.\n- `eslint-config-prettier`: in order to avoid clashes between ESLint end Prettier, this config overwrites all ESLint rules that would clash with prettier.\n\nNow, we can set up an `.eslintrc.js`:\n\n```js\nmodule.exports = {\n  root: true,\n  ignorePatterns: ['dist/**/*'],\n  extends: [\n    'standard-with-typescript',\n    'plugin:react/recommended',\n    'plugin:react/jsx-runtime',\n    'plugin:prettier/recommended'\n  ],\n  plugins: ['react', 'prettier'],\n  rules: {\n    'react/prop-types': 'off',\n    'import/order': 'error',\n    'no-use-before-define': 'off',\n    '@typescript-eslint/no-use-before-define': 'error',\n    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],\n    'prettier/prettier': [\n      'error',\n      {\n        tabWidth: 2,\n        printWidth: 120,\n        singleQuote: true,\n        trailingComma: 'none',\n        semi: false\n      }\n    ]\n  },\n  settings: {\n    react: {\n      version: 'detect'\n    }\n  },\n  parserOptions: {\n    project: 'tsconfig.json'\n  }\n}\n```\n\nA couple of notes on the config:\n\n- We don't want to lint the build artifacts, so we add `dist` to the `ignorePatterns`.\n- In addition to `react/recommended`, we also include `react/jsx-runtime`, as we're using the [new JSX runtime as of React 17](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html).\n- `react/prop-types` is turned off as the props are typed through TypeScript.\n- `no-use-before-define`: we want to use the TypeScript version, so we turn off the JavaScript one.\n\n\u003e **Note**\n\u003e\n\u003e Yes, configuring ESLint is currently quite uncomfortable. There will be a [new config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) soon, but as of now it's still experimental and many libraries (like `@typescript-eslint`) don't support it, yet.\n\n**tsup**\n\nCompiling our 5 LOC, 1-component component library currently takes 2.4s on my machine. Once a library gets bigger, the compile time can grow significantly. Over the last years, a lot of fantastic Rust- and Go-based tooling has been developed. We're going to use [tsup](https://tsup.egoist.dev/), which internally uses [esbuild](https://esbuild.github.io/):\n\n```sh\npnpm add -D tsup\n```\n\nWe'll use the following `tsup.config.ts` config file:\n\n\n```ts\nimport { defineConfig } from 'tsup'\n\nexport default defineConfig({\n  entry: ['src/index.tsx'],\n  dts: true,\n  target: 'esnext',\n  format: 'esm',\n  sourcemap: true,\n  minify: false\n})\n```\n\nUpdate the `package.json` accordingly:\n\n```json\n{\n  \"scripts\": {\n    \"build\": \"tsup\"\n  }\n}\n```\n\nRunning `pnpm build` now takes 1.4s on my machine, and only 4ms of those are spent on actually compiling the library.\n\n**Watch mode**\n\nTo avoid having to re-build the library everytime you change something, you can use the watch mode provided by `tsup`. Simply add a script to your `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"tsup --watch\"\n  }\n}\n```\n\nRunning `pnpm dev` will now re-build the library on file changes.\n\n**GitHub Action**\n\n\u003e **Note**\n\u003e\n\u003e As this repository is a monorepo, all actions will be in `.github/workflows`. If you copy one library from here, don't forget to also copy the respective workflow!\n\nWe'll add a simple GitHub Actions job that lints the library and builds it:\n\n\n```yml\nname: bare-ts-tooling\non: [push]\njobs:\n  Simple-Gate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: pnpm/action-setup@v2\n        with:\n          version: 8.1.0\n      - uses: actions/setup-node@v3\n        with:\n          node-version: \"18\"\n          cache: \"pnpm\"\n      - run: pnpm install\n      - name: Linting\n        run: pnpm lint\n      - name: Build\n        run: pnpm build\n```\n\nWhile there's lots of stuff that could be added in terms of tooling (size checks, auto-publish on tagging,...) this should suffice for now.\n\n## Level 4: CSS\n\n\u003e **Note**\n\u003e\n\u003e Summary: TypeScript, ESLint + Prettier, tsup, GitHub Actions, vanilla CSS\n\nAs we're now using `tsup` for building our library, adding CSS becomes very comfortable, as `tsup` supports this natively (through `esbuild`).\n\n**Rearranging**\n\nBut first, we're going to do some quality-of-life improvements by preparing a separation of components:\n\n- add a new file, `src/button/index.tsx` and move the button component there.\n- replace `src/index.tsx` with just `export * from './button/index.tsx`.\n\nWe're using `nodenext` for our module resolution in `tsconfig.json`, which requires us to use file extensions for our imports. But TypeScript doesn't like this by default - we have to set `allowImportingTsExtensions` in our `tsconfig` first do to that, which also requires us to set `noEmit`.\n\n\u003e **Note**\n\u003e\n\u003e You could also set your module resolution to `node` and do `export * from './button` instead. We we'd use `tsc` to compile our library, we would have to do `export * from './button/index.js` and therefore reference a non-existent file. [There's a long explanation by the TS team as to why this is a sensible decision](https://github.com/microsoft/TypeScript/issues/49083#issuecomment-1435399267). We're using `tsup` to build our library, so setting `noEmit` is not a problem.\n\n**Adding CSS**\n\nCreate a `src/button/styles.css` that includes something like this:\n\n```css\n.button {\n  background: steelblue;\n  color: white;\n  border: none;\n}\n```\n\nWe can then import those styles into our component by doing `import './styles.css'`:\n\n```tsx\nimport { type PropsWithChildren } from 'react'\nimport './styles.css'\n\nexport const Button: React.FC\u003cPropsWithChildren\u003e = ({ children }) =\u003e \u003cbutton className=\"button\"\u003e{children}\u003c/button\u003e\n```\n\nIf we now run `pnpm build`, you will see an `index.css` in your `dist` folder.\n\nIf you want, you can additionally add a global stylesheet by creating `src/styles.css` and referencing it in `src/index.tsx`:\n\n\n```tsx\nimport './styles.css'\n\nexport * from './button/index.tsx'\n```\n\n\u003e **Note**\n\u003e\n\u003e To avoid clashes between the library and application CSS, I would recommend to not style tags directly and use CSS classes instead - ideally prefixing them in some way, e.g. `.acme-ui-button`.\n\n**Using the generated CSS**\n\nWhen consuming your library, you also need to import the generated stylesheet from `dist/index.css`. In Next.js, you would typically do this in `_app.tsx`.\n\n```tsx\nimport '@/styles/globals.css'\nimport 'our-library/dist/index.css'\n\n/* ... */\n```\n\n\u003e **Note**\n\u003e\n\u003e The order of CSS imports matters. What the \"correct\" order is depends a bit on your setup - usually, your application will do a CSS reset, therefore importing your library's CSS after your application's CSS might be sensible.\n\nTo make importing the styles a bit nicer, we can replace the `main` field with an [`exports` field](https://nodejs.org/api/packages.html#exports) to our library's `package.json` file:\n\n\n```json\n{\n  \"exports\": {\n    \".\": \"./dist/index.js\",\n    \"./styles\": \"./dist/index.css\"\n  },\n}\n```\n\nAfterwards, we can import the CSS like this:\n\n```tsx\nimport '@/styles/globals.css'\nimport 'our-library/styles'\n\n/* ... */\n```\n\n## Level 5: Tailwind\n\n\u003e **Note**\n\u003e\n\u003e Summary: TypeScript, ESLint + Prettier, tsup, GitHub Actions, Tailwind\n\nBefore we start, our goals here are:\n\n- allow Tailwind classes within JSX (`className=\"bg-indigo-400\"`)\n- allow Tailwind classes via PostCSS syntax in a global stylesheet (`@apply bg-indigo-400` in `./src/styles.css`)\n- allow Tailwind classes via PostCSS syntax in per-component stylesheets (e.g. `./src/button/styles.css`)\n- expose the library's Tailwind config as a preset for downstream applications (as applications should match the theme of the component library)\n\n**Setting up Tailwind**\n\nWe do the usual commands to set up Tailwind:\n\n- `pnpm add -D postcss tailwindcss autoprefixer`\n- `npx tailwindcss init`\n\nThis creates a `postcss.config.js` and a `tailwind.config.js`. As our library is an ES module, we'll have to rename the PostCSS config to `postcss.config.cjs` (that's likely a [bug in tsup](https://github.com/egoist/tsup/issues/874)).\n\nAs we want to expose custom theme values later, we will split the `tailwind.config.js` into two parts:\n\n- `tailwind.base.ts` includes everything we want to share with downstream applications, like custom colors.\n- `tailwind.config.ts` is the Tailwind config used by our library. It extends the base config with configuration specific to our library, like `content`.\n\nOur `tailwind.base.ts` looks like this (yes, [Tailwind now supports ES Module config files](https://tailwindcss.com/blog/tailwindcss-v3-3)):\n\n```js\nimport { type Config } from 'tailwindcss'\n\nconst config: Config = {\n  content: [],\n  theme: {\n    extend: {\n      colors: {\n        fancy: 'steelblue'\n      }\n    }\n  }\n}\n\nexport default config\n```\n\nOur `tailwind.config.ts` looks like this:\n\n```js\nimport { type Config } from 'tailwindcss'\nimport base from './src/tailwind.base.ts'\n\nconst config: Config = {\n  presets: [base],\n  content: ['./src/**/*.tsx'],\n  corePlugins: {\n    preflight: false\n  }\n}\n\nexport default config\n```\n\nAs we want to expose the base config, we'll put it in `src/tailwind.base.ts` and export it in `src/index.ts`:\n\n```ts\n// src/index.ts\n\nimport './styles.css'\n\nexport * from './button/index.tsx'\nexport { default as tailwindConfig } from './tailwind.base.ts'\n```\n\nDownstream applications will be able to import the config like this:\n\n```js\nimport { tailwindConfig } from 'our-library'\n```\n\nThe rest of the downstream setup is identical to Level 4 - we import the styles by doing `import 'our-library/styles'` in `_app.tsx`.\n\n**Writing styles**\n\nNow that everything is cabled together, `esbuild` should correctly invoke PostCSS and extract all styles. In order to test that, we try out all possible variations:\n\n- Inline classes\n\n```tsx\n// src/button/index.tsx\n\nimport { type PropsWithChildren } from 'react'\nimport './styles.css'\n\nexport const Button: React.FC\u003cPropsWithChildren\u003e = ({ children }) =\u003e (\n  \u003cbutton className=\"button bg-indigo-400/50 hover:bg-indigo-400/60 text-indigo-900 font-medium transition hover:shadow\"\u003e\n    {children}\n  \u003c/button\u003e\n)\n```\n\n- In the component's stylesheet\n\n```css\n/* src/button/style.css */\n\n.button {\n  @apply rounded px-2 py-1;\n}\n```\n\n- In the root stylesheet\n\n```css\n/* src/style.css */\n@tailwind components;\n@tailwind utilities;\n\nbutton {\n  @apply font-sans;\n}\n```\n\n\u003e **Note**\n\u003e\n\u003e The first two lines in `src/style.css` are required, otherwise PostCSS doesn't know what to do with inline Tailwind classes.\n\u003e\n\u003e You might notice that `@tailwind base;` is missing - this is intentional, as we don't want any reset styles in our library's CSS. Otherwise, including the library CSS file after an application's CSS will reset the application CSS.\n\nIf everything works correctly, running `pnpm build` should yield a `dist/index.css` that contains classes from `src/style.css` (including the inline classes from `src/button/index.tsx`) and `src/button/styles.css`.\n\n![Custom Tailwind button](./.github/screenshots/tailwind.jpeg)\n\nNote the steelblue text at the left bottom, using `text-fancy` :relaxed:\n\n## Level 6: Material UI\n\n\u003e **Note**\n\u003e\n\u003e Summary: TypeScript, ESLint + Prettier, tsup, Material UI\n\nCompared to Tailwind, sharing Material UI (MUI) components is relatively straight-forward. \n\nWe're going to start with `bare-ts-tooling`, reusing the `tsup` setup.\n\n**Setting up Material UI**\n\nWe will make the assumption that all applications using our MUI components will also use MUI. Therefore, we will set up MUI as a peer dependency of our library. The only difference between declaring it as a peer dependency instead of a dependency is, that the downstream application will be forced to use a compatible version of MUI. Imagine having MUI v4 in the application and MUI v5 in the library - that will cause two competing versions of MUI to be in the final application bundle.\n\n```sh\npnpm add -D @mui/material --save-peer\n```\n\nFor our application, we're going to use [`next-ts` example](https://github.com/mui/material-ui/tree/master/examples/material-next-ts) provided by MUI.\n\n**Theming**\n\nMUI provides a plethora of components - it should be obvious that wrapping every single one of them does not make any sense and is certainly not the intention of the MUI authors.\n\nInstead, MUI provides a [theming](https://mui.com/material-ui/customization/theming/) solution - by wrapping your application in a theme, you can customize all design aspects of your application. Think of it as a configuration object shared across all MUI components you use.\n\nWe will create a simple theme in `src/theme.ts`:\n\n```ts\nimport { createTheme } from '@mui/material'\n\nexport const theme = createTheme({\n  palette: {\n    primary: {\n      main: '#ffe4e1'\n    },\n    secondary: {\n      main: '#edf2ff'\n    }\n  }\n})\n```\n\nAdditionally, we will replace the button in `src/index.tsx` with `src/Button.tsx` (see code) and adapt `src/index.tsx` accordingly:\n\n```ts\nexport * from './theme.ts'\nexport * from './Button.tsx'\n```\n\nAnd that's it! `pnpm build` should correctly generate the contents of the `dist` folder.\n\n**Consuming**\n\nWe can use the library theme in our applications like this:\n\n```tsx\nimport { theme } from 'our-library';\n\n// Create a theme instance.\nexport const appTheme = createTheme(theme, {\n  palette: {\n    error: {\n      main: red.A400,\n    },\n  },\n});\n```\n\nAll that's left to do is to use some component, like the fancy button in our case, and consume the theme where necessary:\n\n![MUI](./.github/screenshots/mui.jpeg)\n\n## Level 7: Icon Library\n\n\u003e **Note**\n\u003e\n\u003e Summary: TypeScript, tsup, svgr\n\nIn this level, we want to export our existing SVG icons as an icon library. We will be using [`bare-ts-tooling`](#level-3-bare-bones-typescript-with-tooling) as a base layer, but can remove everything related to ESLint, as we are only going to deal with SVG source files.\n\nThere are different approaches to bundling icons as a library - for example, you can manually create a bundle directly from SVG files by writing a custom parser, or you could directly bundle the SVG files and embed them properly at runtime.\n\nWe want to expose clean ES Modules that are nicely tree-shakable, so we are going to convert the SVG icons to React components.\n\n### Converting SVG to TSX\n\nAs will all other libraries, we want to expose an ES Module with TypeScript declarations alongside it. If we would be creating a vanilla JS library, we could directly convert the SVGs into JS without any JS-TS transpiling.\n\nWe are going to use [`svgr`](https://react-svgr.com/) to convert SVG to TSX:\n\n1. Install the SVGR cli: `pnpm add -D @svgr/cli`\n2. Place all your SVGs in one folder, e.g. `src/icons`.\n3. Create a script in your `package.json` that uses `svgr` to convert the icons to TSX:\n    ```json\n    {\n      \"scripts\": {\n        \"svgr\": \"svgr --icon --ref --typescript --out-dir tsx src/icons\"\n      }\n    }\n    ```\n    (`--icon` is needed to that `svgr` keeps viewboxes, and `--ref` adds `forwardRef` statements)\n4. Adjust the `tsup.config.ts` to ingest the `svgr` output:\n    ```ts\n    import { defineConfig } from 'tsup'\n\n    export default defineConfig({\n      entry: ['tsx/index.ts'],\n      target: 'esnext',\n      format: 'esm',\n      dts: true,\n      sourcemap: true,\n      minify: false\n    })\n    ```\n5. In `tsconfig.json`, change `moduleResolution` to `node`, as `svgr` doesn't use file extensions in the generated TSX.\n6. Add `tsx` (the `svgr` output directory) to your `.gitignore`.\n7. Add a `clean` script to your `package.json`:\n    ```json\n    {\n      \"scripts\": {\n        \"clean\": \"rm -rf tsx \u0026\u0026 rm -rf dist\"\n      }\n    }\n    ```\n8. Cable all scripts together into a `build` script:\n    ```json\n    {\n      \"scripts\": {\n        \"build\": \"pnpm clean \u0026\u0026 pnpm svgr \u0026\u0026 tsup\"\n      }\n    }\n    ```\n\nRunning `pnpm build` should now\n\n- remove old build files,\n- generate TSX based on the SVG files in `src/icons`, and\n- convert the generated TSX into a nice bundle.\n\nThat's it!\n\n## Appendix\n\n### Dependency Types\n\nThere are three types of dependencies - normal dependencies, development dependencies and peer dependencies. Often it doesn't make that much of a difference what you put where. Also, there are quite a few differences in dependency management between developing an application and developing a library. Here's my mental model:\n\n|                    | Application | Library |\n| ------------------ | ------------- | ------------- |\n| `dependencies`     | Dependencies that are referenced within code that will be included in the bundle (e.g. component libraries, `react-query`) | =, with the exceptions (see below) |\n| `devDependencies`  | Dependencies needed to build the bundle (e.g. types, build tooling) | =, plus dependencies you want to be bundled in your library bundle (ideally none) |\n| `peerDependencies` | None | Dependencies without which your library is useless within the application context (usually this is only `react`). Make sure to make the version requirement in the peer dependencies as loose as possible to maximize compatibility. |\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjens-ox%2Fcll","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjens-ox%2Fcll","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjens-ox%2Fcll/lists"}