Functional Programming in JavaScript (II): Scaffolding a Modern JavaScript Library

Topics

  • Functional Programming in JavaScript
  • Building a Modern JavaScript Library with Vite
  • Unit Testing with Jest and Vitest

Resources

  • ๐Ÿ“– Check out the handbook
  • ๐Ÿ“œ Check out the slide
  • ๐Ÿ’ช Work in groups
  • ๐Ÿ”จ Collaborate with Live Share

Workshop

Initialize a project with Vite

Create a new project. [?]

1
2
3
4
npm create vite@latest
โœ” Project name: โ€ฆ collection-js
โœ” Select a framework: โ€บ vanilla
โœ” Select a variant: โ€บ vanilla-ts

Install dependencies.

1
2
cd collection-js
npm install

Open VS Code editor. [?]

1
code .

Initialize a Git repository

Write an initial commit message.

1
2
3
git init
git add .
git commit -m "Initial commit"

Create a repository from GitHub.

Push commits to remote.

1
2
git remote add origin git@github.com:<username>/collection-js.git
git push -u origin main

Tidy up the project

Remove sample files.

1
rm src/*

Write a new commit message.

1
2
git add .
git commit -m "Remove sample files"

Build with library mode

Create a vite.config.js file. [?]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { resolve } from 'path';
import { defineConfig } from 'vite';

export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'CollectionJS',
fileName: (format) => `collection-js.${format}.js`,
},
rollupOptions: {
external: [],
output: {
globals: {
},
},
},
},
});

Install dependencies.

1
npm install @types/node --save-dev

Create a index.ts file in src folder as an entry point.

1
2
3
4
5
6
7
const hello = () => {
console.log('Hello');
};

export {
hello,
};

Update package.json file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
// ...
"type": "module",
"files": [
"dist"
],
"main": "./dist/collection-js.umd.js",
"module": "./dist/collection-js.es.js",
"exports": {
".": {
"import": "./dist/collection-js.es.js",
"require": "./dist/collection-js.umd.js"
}
},
}

Run build command.

1
npm run build

Try with UMD module

Update index.html file.

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<!-- ... -->
<body>
<div id="app"></div>
<script src="/dist/collection-js.umd.js"></script>
<script>
window.CollectionJS.hello();
</script>
</body>
</html>

Start a server.

1
npm run dev

Try with ES module

Update index.html file.

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<!-- ... -->
<body>
<div id="app"></div>
<script type="module">
import { hello } from '/dist/collection-js.es.js';
hello();
</script>
</body>
</html>

Start a server.

1
npm run dev

Tidy up

Fix index.html file.

1
2
3
4
5
6
7
<!DOCTYPE html>
<html lang="en">
<!-- ... -->
<body>
<div id="app"></div>
</body>
</html>

Write a new commit message.

1
2
git add .
git commit -m "Add build config"

Initialize ESLint and EditorConfig

Create a .editorconfig file. [?]

1
2
3
4
root = true

[*]
indent_size = 2

Initialize ESLint and install dependencies. [?]

1
2
3
4
5
6
7
8
9
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. [?]

1
2
3
npm install eslint-config-airbnb-typescript \
eslint-plugin-import \
--save-dev

Update .eslintrc.cjs file. [?]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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.

1
2
3
4
5
6
{
"scripts": {
// ...
"lint": "eslint src"
}
}

Run lint command.

1
npm run lint

Write a new commit message.

1
2
git add .
git commit -m "Add eslint config"

Implement a โ€œmapโ€ function

Create a modules folder in src folder.

1
mkdir src/modules

Create a map.ts file in modules folder, and implement a map function.

1
2
3
4
5
6
7
8
9
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.

1
2
3
4
5
import map from './map';

export {
map,
};

Unit test with Vitest

Install dependencies. [?]

1
npm install vitest @vitest/coverage-c8 --save-dev

Update package.json file, add test and coverage commands to scripts field.

1
2
3
4
5
6
7
{
"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.

1
2
3
4
5
6
7
8
9
import { test, expect } from 'vitest';
import { map } from './index';

test('map should work', () => {
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.

1
2
# ...
coverage

Write a new commit message.

1
2
git add .
git commit -m "Implement map function"

Source Code