Software Tech

Create a typescript utility library using Vite

In this post, we are going to look at how to create a typescript utility library using the Vite

Create a typescript utility library using Vite

In this post, we are going to look at how to create a typescript utility library using the Vite bundler.

Why?

There could be many reasons to build a library

To share the code inside your organisation
To contribute to the open source community by creating a NPM package
To extract a common piece of code in a monorepo so that multiple apps can the same logic inside the monorepo.

Technologies/Features used

The library we are going to build will have the following features. Again this is my set of tools feel free to change it based on your preference.

Feature
Technology used

Package manager
PNPM

Package bundler
Vite


Typescript

Basic linting
ES Lint

Code formatting
Prettier

Pre-commit hook validator
Husky

Linting only staged
lint-staged

Lint git commit subject
commitlint

Prerequisites

Tools

You will need the following properly installed on your computer.

Git
Node.js

PNPM install

If you have installed latest v16. or greater node version in your system, then enable the pnpm using the below cmd

corepack enable
corepack prepare pnpm@latest –activate

If you are using lower version of node in your local system then check this page for additional installation methods https://pnpm.io/installation

App creation

Let's create the app by running these commands.

–template vanilla-ts
cd
pnpm install && pnpm update

Run pnpm dev and check out the app.

Initialise git if you want and enforce the node version with some info in the README.md.

git init
npm pkg set engines.node=“>=18.16.1” // Use the same node version you installed
echo “#Typescript utility library” > README.md

Code formatter

I'm going with Prettier to format the code. Formatting helps us to keep our code uniform for every developer.

Installation

Let's install the plugin and set some defaults. Here I'm setting the single quote to be true, update it according to your preference.

pnpm add - prettier
echo ‘{n “singleQuote”: truen}' > .prettierrc.json
echo -e “.huskyn.vscoden.gitncoveragendistnnode_modulesnpublicnpackage.jsonnpnpm-lock.yaml” > .prettierignore

I also took some in including some of the packages path in the prettier ignore file that we are going to install and use later for convenience.

VS Code plugin

If you are using VS Code, then navigate to the Extensions and search for Prettier – Code formatter and install the extension.

Extension link: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

Let's update the workspace to use the prettier as the default formatter and automatically format the file on save.

Create the workspace settings json and update it with the following content.

.vscode && touch .vscode/settings.json
{
“editor.formatOnSave”: true,
“editor.defaultFormatter”: “esbenp.prettier-vscode”,
}

Now open the src/main.ts in your VSCode editor and save it. If the semicolons are automatically created then it confirms that our auto formatting is working fine.

Linting

Linter statically analyses your code to quickly find problems. ES Lint is the most preferred tool for linting the Javascript code.

EsLint

pnpm create @eslint/config

The eslint will ask you a set of questions to set up the linter as per your needs.
This is the configuration I've chosen for this project.

? How would you like to use ESLint? …
To check syntax only
❯ To check syntax and find problems
To check syntax, find problems, and enforce code style

? What type of modules does your project use? …
❯ JavaScript modules (import/export)
CommonJS (require/exports)
None of these

? Which framework does your project use? …
React
Vue.js
❯ None of these

Does your project use TypeScript? › No / Yes
– Yes

Where does your code run?
✔ Browser
Node

? What format do you want your config file to be in? …
❯ JavaScript
YAML
JSON

The config that you`ve selected requires the following dependencies:
@typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest

? Would you like to install them now? › No / Yes
– Yes

? Which package manager do you want to use? …
npm
yarn
❯ pnpm

Create the eslintignore file to let the eslint know which files to not format.

touch .eslintignore
echo -e “.huskyn.vscoden.gitncoveragendistnnode_modulesn*.config.tsn.eslintrc.cjsnpackage.jsonnpnpm-lock.yaml” > .eslintignore

Integrating Prettier with ESLint

Linters usually contain not only code quality rules, but also stylistic rules. Most stylistic rules are unnecessary when using Prettier, but worse – they might conflict with Prettier!

We are going to use Prettier for code formatting concerns, and linters for code-quality concerns.

Install the necessary plugins

pnpm add -D eslint-config-prettier eslint-plugin-prettier

Add the plugin:prettier/recommended as the last element in the extends property in eslintrc.js

module.exports = {
extends: […, plugin:prettier/recommended],
}

For more info on this: https://prettier.io/docs/en/integrating-with-linters.html

Let's create scripts for running the linter and prettier in the package.json file.

npm pkg set scripts.lint=“eslint .”
npm pkg set scripts.format=“prettier –write .”

Run the pnpm lint cmd to run the ESLint. If it throws, errors like below then our linter prettier integration is working as expected.

To fix it just run pnpm format cmd and run the pnpm lint –fix cmd again. Now the errors should be gone.

Pre-commit hook

Even if we added all these linter and formatter mechanisms to maintain the code quality, we can't expect all the developers to use the same editor and execute the lint and format command every time when they are pushing their code.

To automate that we need some kind of pre-commit hook validation. That's where husky and lint-staged plugins come in handy let's install and set them up.

Install the husky, commitlint and lint-staged NPM package and initialise it as shown below,

pnpm add -D @commitlint/cli @commitlint/config-conventional
echo -e “module.exports = { extends: [‘@commitlint/config-conventional'] };” > commitlint.config.cjs
pnpm add -D husky lint-staged
npx husky install
npx husky add .husky/pre-commit “pnpm lint-staged”
npx husky add .husky/commit-msg ‘npx –no — commitlint –edit ${1}'
npm pkg set scripts.prepare=“husky install”

Update the package.json file and include the following property. This will run the ESLint on all the script files and prettier on the other files.

“lint-staged”: {
“**/*.{js,ts,tsx}”: [
“eslint –fix”
],
“**/*”: “prettier –write –ignore-unknown”
},

Absolute path import support

We often around the files to make it more meaningful, when we are doing that, it would be great if we don't have to update the import statements. To do that,

Install the @types/node NPM package

pnpm add -D @types/node

Create the vite.config.ts file and update it like this,

touch vite.config.ts
import { defineConfig } from vite;
import { } from path;

// https://vitejs.dev/config/
export default defineConfig({
resolve: { alias: { src: resolve(src/) } },
});

The resolve property helps us to use absolute import paths instead of relative.

For ex:

import { add } from src/utils/arithmetic

App code cleanup:

Update the main.ts file with the following content,

export const add = (a: number, b: number) => a + b;
export const sub = (a: number, b: number) => a b;

Delete the sample files

rm -rf src/style.css src/counter.ts

Vite library mode

Vite by default build the assets in app mode with index.html as the entry file. But we want our app to expose our main.ts file as the entry file, so let's update the vite config to support that.

import { defineConfig } from vite;
import { resolve } from path;

// https://vitejs.dev/config/
export default defineConfig({
build: { lib: { entry: resolve(__dirname, src/main.ts), formats: [es] } },
resolve: { alias: { src: resolve(src/) } },
});

Local dev server update

We can make use of the local dev server to verify our utility library. To do that let's update the index.html file, do note that it will not be exposed to the outside world.


lang=“en”>

charset=“UTF-8” />
rel=“icon” type=“image/svg+xml” href=“/vite.svg” />
name= content=“width=device-width, initial-scale=1.0” />
Vite + TS


type=“module” src=“/src/main.ts”
type=“module”>
import { add, sub } from ./src/main;

const addOutput = document.getElementById(addOutput);
const subOutput = document.getElementById(subOutput);
const input1 = document.getElementById(input1);
const input2 = document.getElementById(input2);
const getNumber = (element) => parseInt(element?.);
const eventHandler = () => {
const firstVal = getNumber(input1);
const secondVal = getNumber(input2);
if (addOutput && subOutput) {
addOutput.innerText = add(firstVal, secondVal).toString();
subOutput.innerText = sub(firstVal, secondVal).toString();
}
};
eventHandler();
input1?.addEventListener(change, eventHandler);
input2?.addEventListener(change, eventHandler);

id=“input1” type=“number” value=“4” />
id=“input2” type=“number” value=“3” />
Sum of two numbers = id=“addOutput”
Diff of two numbers = id=“subOutput”

Run the dev server, you should be able to see the library methods in action.

Unit testing

We are going to use the most popular unit testing framework Jest for writing our unit tests.

Install the necessary packages

pnpm add -D jest @types/jest ts-jest ts-node

Package
Purpose

jest
Test runner

ts-jest
For running jest in typescript supported app.

ts-node
For creating jest config file in ts extension

@types/jest
Types library for Jest

Create the src/setupTests.ts file and update it with below content.

touch src/setupTests.ts
beforeAll(() => {
// Add your global beforeAll logics
});

beforeEach(() => {
// Add your globalbeforeEach logics
});

afterAll(() => {
// Add your global afterAll logics
});

afterEach(() => {
// Add your global afterEach logics
});

Create the jest.config.ts file and update it with below config.

touch jest.config.ts
export default {
preset: ts-jest,
testEnvironment: node,
moduleNameMapper: {
^src/(.*)$: ,
},
setupFilesAfterEnv: [],
modulePathIgnorePatterns: [./dist/],
coveragePathIgnorePatterns: [],
collectCoverageFrom: [./src/**/*.ts, !./src/**/*.test.ts],
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
};

Create the jest.ci-config.ts file and update it with below config.

touch jest.ci-config.ts
import config from ./jest.config;

export default {
config,
coverageThreshold: {},
};

Create a test file for main component and update it like below,

touch src/main.test.ts
import { add, sub } from ./main;

describe(Utility | Main, () => {
it(add – should add the given two numbers, () => {
expect(add(4, 2)).toEqual(6);
});

it(sub – should subtract the given two numbers, async () => {
expect(sub(4, 2)).toEqual(2);
});
});

Update the package scripts for running tests

npm pkg set scripts.test=“jest”
npm pkg set scripts.test:coverage=“jest –coverage”
npm pkg set scripts.test:ci=“jest –coverage –config='./jest.ci-config.ts'”

Run the tests

pnpm test

To run a particular test use the -t param.

pnpm test -t “add”

Sample repo

The code for this series is hosted in here.

Please take a look at the Github repo and let me know your feedback, queries in the comments section.

About Author

Vinodh Kumar

Leave a Reply

SOFAIO BLOG We would like to show you notifications for the latest news and updates.
Dismiss
Allow Notifications