0w0

라이브러리 만드는 과정 기록

라이브러리 만드는 과정 기록

환경 준비

폴더, 파일 생성

md jslib
cd jslib
npm init
md src,test
ni .gitignore

.gitignore

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

TS

npm i -D typescript
node_module/.bin/tsc --init
// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

구현

// src/trim.ts
export default function (value: string) {
  return value.length < 1 ? 0 : value.trim();
}
// src/index.ts
import trim from './trim';

export default Object.freeze({ trim });

ESLint

npm i -D eslint
.\
ode_modules\\.bin\\eslint --init
Need to install the following packages:
  @eslint/create-config
Ok to proceed? (y) y

√ How would you like to use ESLint? · problems
√ 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
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 with npm? · No / Yes
Installing @typescript-eslint/eslint-plugin@latest, @typescript-eslint/parser@latest
// package.json

"scripts":{
  "eslint": "eslint --fix src/**/*.ts"
}
npm run eslint

Jest

npm i -D jest ts-jest @types/jest
// package.json
  "scripts": {
    "eslint": "eslint --fix src/**/*.ts",
    "test": "jest"
  },
  "jest": {
    "moduleFileExtensions": [
      "ts",
      "js"
    ],
    "transform": {
      "^.+\\.ts$": "ts-jest"
    },
    "globals": {
      "ts-jest": {
        "tsconfig": "tsconfig.json"
      }
    },
    "testMatch": [
      "**/test/**/*.test.ts"
    ]
  },
// test/trim.test.ts

import trim from '../src/trim';

describe('trim test', (): void => {
  test('no space', (): void => {
    const response = trim('test');
    expect(response).toBe('test');
  });

  test('left space', (): void => {
    const response = trim('              test');
    expect(response).toBe('test');
  });

  test('right space', (): void => {
    const response = trim('test                          ');
    expect(response).toBe('test');
  });

  test('both space', (): void => {
    const response = trim('              test                         ');
    expect(response).toBe('test');
  });
});
npm run test

# > js-lib@1.0.0 test
# > jest

# PASS  test/trim.test.ts
#  trim test
#    √ no space (2 ms)
#    √ left space
#    √ right space
#    √ both space

# Test Suites: 1 passed, 1 total
# Tests:       4 passed, 4 total
# Snapshots:   0 total
# Time:        4.55 s
# Ran all test suites.

rollup

npm i -D rollup rollup-plugin-terser @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-typescript tslib

ni rollup.config.js
// rollup.config.js
import { terser as pluginTerser } from 'rollup-plugin-terser';
import pluginTypescript from '@rollup/plugin-typescript';
import pluginCommonjs from '@rollup/plugin-commonjs';
import pluginNodeResolve from '@rollup/plugin-node-resolve';
import { babel as pluginBabel } from '@rollup/plugin-babel';
import * as path from 'path';

import pkg from './package.json';

const moduleName = pkg.name.replace(/^@.*\//, '');
const inputFileName = 'src/index.ts';

const banner = `
/**
 * @license
 * ${moduleName}.js v${pkg.version}
 * Released under the ${pkg.license} License.
 */
`;

export default [
  // 브라우저
  {
    input: inputFileName,
    output: [
      // uncompressed
      {
        name: moduleName,
        file: pkg.browser,
        format: 'iife',
        sourcemap: 'inline',
        banner,
      },
      // minified
      {
        name: moduleName,
        file: pkg.browser.replace('.js', '.min.js'),
        format: 'iife',
        sourcemap: 'inline',
        banner,
        plugins: [pluginTerser()],
      },
    ],
    plugins: [
      pluginTypescript(),
      pluginCommonjs({
        extensions: ['.js', '.ts'],
      }),
      pluginBabel({
        babelHelpers: 'bundled',
        configFile: path.resolve(__dirname, '.babelrc.js'),
      }),
      pluginNodeResolve({
        browser: true,
      }),
    ],
  },

  // ES Module
  {
    input: inputFileName,
    output: [
      {
        file: pkg.module,
        format: 'es',
        sourcemap: 'inline',
        banner,
        exports: 'named',
      },
    ],
    external: [
      ...Object.keys(pkg.dependencies || {}),
      ...Object.keys(pkg.devDependencies || {}),
    ],
    plugins: [
      pluginTypescript(),
      pluginCommonjs({
        extensions: ['.js', '.ts'],
      }),
      pluginBabel({
        babelHelpers: 'bundled',
        configFile: path.resolve(__dirname, '.babelrc.js'),
      }),
      pluginNodeResolve({
        browser: false,
      }),
    ],
  },

  // CommonJS
  {
    input: inputFileName,
    output: [
      {
        file: pkg.main,
        format: 'cjs',
        sourcemap: 'inline',
        banner,
        exports: 'default',
      },
    ],
    external: [
      ...Object.keys(pkg.dependencies || {}),
      ...Object.keys(pkg.devDependencies || {}),
    ],
    plugins: [
      pluginTypescript(),
      pluginCommonjs({
        extensions: ['.js', '.ts'],
      }),
      pluginBabel({
        babelHelpers: 'bundled',
        configFile: path.resolve(__dirname, '.babelrc.js'),
      }),
      pluginNodeResolve({
        browser: false,
      }),
    ],
  },
];
// package.json
  "main": "dist/lib.cjs.js", // Node.js
  "module": "dist/lib.es.js", // ES Module
  "browser": "dist/lib.js", // 브라우저
  "scripts": {
    "build": "rollup -c",
    "lint": "eslint --fix 'src/**/*.ts'",
    "test": "jest"
  },

Babel

npm i -D @babel/core @babel/preset-env

ni .babelrc.js
// .babelrc.js
module.exports = {
  presets: [['@babel/preset-env']],
};
npm run build

# > jslib@1.0.0 build
# > rollup -c

# src/index.ts → dist/trim-lib.js, dist/trim-lib.min.js...
# created dist/trim-lib.js, dist/trim-lib.min.js in 3.1s

# src/index.ts → dist/trim-lib.es.js...
# created dist/trim-lib.es.js in 2s

# src/index.ts → dist/trim-lib.cjs.js...
# created dist/trim-lib.cjs.js in 1.4s

실행해보기

폴더 벗어나서 폴더 만들기

md jslib-test
cd jslib-test
npm init -y
npm i ../jslib
ni test.js
const { trim } = require('jslib');

const value = trim('                 move?              ');

console.log(value); // "move?"

CI

  1. https://circleci.com/ 접속해서 해당 Git 저장소로 가입 & 로그인

  2. 프로젝트에서 해당하는 프로젝트 Set Up Project 선택

  3. 해당하는 사항 선택하며 넘어가기

  4. 저장소로 가서 Compare & pull request, Merge 하기

  5. 로컬 저장소 fetch 혹은 pull

  6. yml 수정하기

- sample: # This is the name of the workflow,
+ run-test: # This is the name of the workflow,
  1. push

  2. branch하나 만들어서 틀린 테스트 작성

// test/trim.test.ts
// ...

test('wrong test', (): void => {
  const response = trim('              test                         ');
  expect(response).toBe(' test');
});
  1. push

배포하기

  1. https://www.npmjs.com/ 접속해서 가입 & 로그인하기

  2. package.jsonname으로 배포되니 중복 확인

npm info jslib_trim
  1. npm login

  2. npm publish

  3. npmjs 확인