# Part II: Scaffolding a Modern JavaScript Library
# Initialize a project with Vite
Create a new project. [?] (opens new window)
npm create vite@latest
✔ Project name: … collection-js
✔ Select a framework: › vanilla
✔ Select a variant: › vanilla-ts
Install dependencies.
cd collection-js
npm install
Open VS Code editor. [?] (opens new window)
code .
# Initialize a Git repository
Write an initial commit message.
git init
git add .
git commit -m "Initial commit"
Create a repository from GitHub (opens new window).
Push commits to remote.
git remote add origin git@github.com:<username>/collection-js.git
git push -u origin main
# Tidy up the project
Remove sample files.
rm src/*
Write a new commit message.
git add .
git commit -m "Remove sample files"
# Build with library mode
Create a vite.config.js
file. [?] (opens new window)
import { resolve } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'CollectionJS',
fileName: 'collection-js',
},
rollupOptions: {
external: [],
output: {
globals: {
},
},
},
},
});
Install dependencies.
npm i @types/node --save-dev
Create a index.ts
file in src
folder as an entry point.
const hello = () => {
console.log('Hello');
};
export {
hello,
};
Update package.json
file.
{
// ...
"type": "module",
"files": [
"dist"
],
"main": "./dist/collection-js.umd.cjs",
"module": "./dist/collection-js.js",
"exports": {
".": {
"import": "./dist/collection-js.js",
"require": "./dist/collection-js.umd.cjs"
}
},
}
Run build
command.
npm run build
# Try with UMD module
Update index.html
file.
<!DOCTYPE html>
<html lang="en">
<!-- ... -->
<body>
<div id="app"></div>
<script src="/dist/collection-js.umd.cjs"></script>
<script>
window.CollectionJS.hello();
</script>
</body>
</html>
Start a server.
npm run dev
# Try with ES module
Update index.html
file.
<!DOCTYPE html>
<html lang="en">
<!-- ... -->
<body>
<div id="app"></div>
<script type="module" src="/dist/collection-js"></script>
<script type="module">
import { hello } from '/dist/collection-js';
hello();
</script>
</body>
</html>
Start a server.
npm run dev
# Tidy up
Fix index.html
file.
<!DOCTYPE html>
<html lang="en">
<!-- ... -->
<body>
<div id="app"></div>
<script type="module" src="/dist/collection-js"></script>
</body>
</html>
Write a new commit message.
git add .
git commit -m "Add build config"
# Initialize ESLint and EditorConfig
Create a .editorconfig
file. [?] (opens new window)
root = true
[*]
indent_size = 2
Initialize ESLint and install dependencies. [?] (opens new window)
npm init @eslint/config
✔ How would you like to use ESLint? · syntax
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser, node
✔ What format do you want your config file to be in? · JavaScript
✔ Would you like to install them now? · No / Yes
✔ Which package manager do you want to use? · npm
Install Airbnb ESLint config and install dependencies. [?] (opens new window)
npm install eslint-config-airbnb-typescript \
eslint-plugin-import \
--save-dev
Update .eslintrc.cjs
file. [?] (opens new window)
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
overrides: [
{
files: [
'src/**/*.ts',
'src/**/*.tsx',
],
extends: [
'airbnb-typescript/base',
'plugin:import/recommended',
],
parserOptions: {
project: [
'./tsconfig.json',
],
},
},
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: [
'@typescript-eslint'
],
rules: {
},
};
Update package.json
file, add a lint
command to scripts field.
{
"scripts": {
// ...
"lint": "eslint src"
}
}
Run lint
command.
npm run lint
Write a new commit message.
git add .
git commit -m "Add eslint config"
# Implement a "map" function
Create a modules
folder in src
folder.
mkdir src/modules
Create a map.ts
file in modules
folder, and implement a map
function.
const map = (items: Array<any>, callable: Function) => {
const res = [];
for (let i = 0; i < items.length; i++) {
res[i] = callable(items[i]);
}
return res;
};
export default map;
Create an index.ts
file in modules
folder, then import and export the module.
import map from './map';
export {
map,
};
# Unit test with Vitest
Install dependencies. [? (opens new window)]
npm i vitest @vitest/coverage-c8 --save-dev
Update package.json
file, add test
and coverage
commands to scripts field.
{
"scripts": {
// ...
"test": "vitest",
"coverage": "vitest run --coverage"
}
}
Create a map.test.ts
file in modules
folder, and create a test case for the map
function.
import { test, expect } from 'vitest';
import { map } from './index';
test('map', () => {
const actual = map([1, 2, 3, 4, 5], (v: number) => v * 2);
const expected = [2, 4, 6, 8, 10];
expect(actual).toStrictEqual(expected);
});
Run test
command.
npm run test
> collection-ts@0.0.0 test
> vitest
DEV v0.22.1 /Users/memochou/Projects/collection-js
✓ src/modules/map.test.ts (1)
✓ src/index.test.ts (11)
Test Files 2 passed (2)
Tests 12 passed (12)
Start at 23:01:17
Duration 950ms (transform 541ms, setup 0ms, collect 140ms, tests 18ms)
Run coverage
command.
npm run coverage
> collection-ts@0.0.0 coverage
> vitest run --coverage
RUN v0.22.1 /Users/memochou/Projects/collection-js
Coverage enabled with c8
✓ src/modules/map.test.ts (1)
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 23:03:08
Duration 1.35s (transform 447ms, setup 0ms, collect 50ms, tests 3ms)
% Coverage report from c8
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
map.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Update .gitignore
file.
# ...
coverage
Write a new commit message.
git add .
git commit -m "Implement map function"