初めに
npmパッケージ開発は簡単にできるよ!と聞いたので試してみました。
今回はBackとFrontでcamelケース(camelCaes)
やsnakeケース(snake_case)
で統一されていない際、発狂するものが現れないよう値やjson Keyの命名ルールを変換するパッケージを開発し、公開してみます。
こんな感じです。
変換例1)
testStr ⇔ test_str
変換例2)
{
testStr: "testStr",
testObj: { testKeyA: "testKeyA", testKeyB: "testKeyB" },
testArr: [
{ testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" },
{ testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" },
],
}
⇔
{
test_str: "testStr",
test_obj: { test_key_a: "testKeyA", test_key_b: "testKeyB" },
test_arr: [
{ test_arr_key_a: "testKeyA", test_arr_key_b: "testKeyB" },
{ test_arr_key_a: "testKeyA", test_arr_key_b: "testKeyB" },
],
}
そして作成したのが、こちらです。(バグがあったらごめんなさい)
開発環境
os: Windows10
node:18.17.0
開発
準備をする
- npmアカウント作成
以下URLにて、npmアカウントを作成します。
-
コマンドにてLogin
アカウントを作成したら、コマンドプロントにて以下コマンドを実行します。npm login
ログイン状態の確認は以下コマンドにてできます。
npm whoami
Typescriptの設定をする
-
プロジェクトフォルダを作成し、npm初期化を行う
開発準備をします。
以後出てくるconvert-variable-naming-rules
は公開するパッケージ名となります。
使用されていないパッケージ名を調べる際は、以下で検索してみましょう。
未使用だったら404となります。早い者勝ちです。使いましょう。
https://www.npmjs.com/package/<使用したいパッケージ名>$ mkdir convert-variable-naming-rules $ cd ./convert-variable-naming-rules
$ npm init package name: (convert-variable-naming-rules) version: (1.0.0) description: Convert camel case to snake case. And vice versa entry point: (index.js) test command: git repository: keywords: author: Tomoki Horiuchi license: (ISC) MIT
npm init
の選択肢は以下項目以外は初期値でpackage.json
が作成します。-
description
:Convert camel case to snake case. And vice versa- 説明分です。
-
author
:Tomoki Horiuchi- 作者です。私の事です。
-
license
:MIT- このパッケージのライセンスです。MITについては↓
wiki:MIT_License
- このパッケージのライセンスです。MITについては↓
package.json
が生成されました。package.json{ "name": "convert-variable-naming-rules", "version": "1.0.0", "description": "Convert camel case to snake case. And vice versa", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Tomoki Horiuchi", "license": "MIT", }
後ほど
typescript
の設定をしますが、先に必要な項目をpackage.json
に追加してしまいます。package.json{ "name": "convert-variable-naming-rules", "version": "1.0.0", "description": "Convert camel case to snake case. And vice versa", "main": "dist/index.js", // 追記: 最初に呼ばれるスクリプトファイルの指定 "types": "dist/index.d.ts", // 追記: 型定義ファイルの指定 "type": "commonjs", // 追記: これは必要なのか…?後に記載 "scripts": { "build": "tsc", // 追記: Buildコマンド "test": "jest" // 追記: Testコマンド }, "author": "Tomoki Horiuchi", "license": "MIT", }
※
"type": "commonjs", // 追記: これは必要なのか…?後に記載
について
"type": "commonjs"
なしでBuild⇒Publishにてパッケージ公開をした際、他プロジェクトにて使用した際、以下のエラーが出力されました。Error [ERR_REQUIRE_ESM]: require() of ES Module C:\01_repository\0_tech_campus\npm\test\node_modules\test-horiuchi\dist\index.js from C:\01_repository\0_tech_campus\npm\test\index.ts not supported. index.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules. Instead rename index.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in C:\01_repository\0_tech_campus\npm\test\node_modules\test-horiuchi\package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).
どうやらモジュールの仕様として、互換性がない旨のエラーの様です。
詳しい方がいたので載せておきます。
CommonJSとES Modulesについてまとめる -
-
typescript
の設定を行う
今回はtypescriptで実装するため、typescript
のインストールと、tsconfig.json
の設定をしていきます。npm install typescript npx tsc --init
tsconfig.json{ "compilerOptions": { "target": "es2016", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "declaration": true, // 追記: 型定義ファイルの出力 "sourceMap": true, // 追記: ソースマップの出力 "outDir": "./dist", // 追記: 出力先を指定 }, "include": [ "src/**/*.ts" // 追記: コンパイル対象を指定 ] }
-
必要なファイルやフォルダを作成
mkdir src touch .npmignore
.npmignore
にはパッケージ公開の対象外を定義できます。
今回はコンパイル済みのdist
の中身をライブラリ本体とするので、とりあえずこんなこところです。.vscode /node_modules /src /test tsconfig.json
開発する
今回は前述していたように、Strgin値
とjsonのKey
をCamelCase
⇔SnakeCase
に変換するパッケージを開発してみます。
特に説明することもないので、簡単にコードだけ貼っておきます。
mkdir src
touch ./src/index.ts
touch ./src/ToCamelCase.ts
touch ./src/ToSnakeCase.ts
export * from "./ToSnakeCase";
export * from "./ToCamelCase";
export class ToCamelCase {
/**
* camelCase word to snake_case word
*
* example:
* camel_case => camelCase
* @param snakeStr
* @returns snake case word
*/
convertSnakeWordToCamel = (snakeStr: string) => {
return snakeStr.replace(/_([a-z])/g, (_, group) => {
return group.toUpperCase();
});
};
/**
* object`s camelCase key to snake_case
*
* example:
* {
* test_str: 'testStr',
* test_obj: { test_key_a: 'testKeyA', test_key_b: 'testKeyB' },
* test_arr: [
* { test_arr_key_a: 'testKeyA', test_arr_key_b: 'testKeyB' },
* { test_arr_key_a: 'testKeyA', test_arr_key_b: 'testKeyB' }
* ]
* }
* =>
* {
* testStr: "testStr",
* testObj: { testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" },
* testArr: [
* { testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" },
* { testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" }
* ],
* }
* @param snakeObj
* @returns camel case key object
*/
convertSnakeObjKeyToCamel: any = (snakeObj: any) => {
if (typeof snakeObj !== "object" || snakeObj === null) {
return snakeObj;
}
if (Array.isArray(snakeObj)) {
return snakeObj.map((item: object) =>
this.convertSnakeObjKeyToCamel(item)
);
}
const camelObj: any = {};
for (const key in snakeObj) {
if (snakeObj.hasOwnProperty(key)) {
const snakeKey = this.convertSnakeWordToCamel(key);
camelObj[snakeKey] = this.convertSnakeObjKeyToCamel(snakeObj[key]);
}
}
return camelObj;
};
}
export class ToSnakeCase {
/**
* camelCase word to snake_case word
*
* example:
* camelCase => camel_case
* @param camelStr
* @returns snake_case word
*/
convertCamelWordToSnake = (camelStr: string) => {
return camelStr.replace(/([A-Z])/g, "_$1").toLowerCase();
};
/**
* object`s camelCase key to snake_case
*
* example:
* {
* testStr: "testStr",
* testObj: { testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" },
* testArr: [
* { testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" },
* { testArrKeyA: "testKeyA", testArrKeyB: "testKeyB" }
* ],
* }
* =>
* {
* test_str: 'testStr',
* test_obj: { test_key_a: 'testKeyA', test_key_b: 'testKeyB' },
* test_arr: [
* { test_arr_key_a: 'testKeyA', test_arr_key_b: 'testKeyB' },
* { test_arr_key_a: 'testKeyA', test_arr_key_b: 'testKeyB' }
* ]
* }
* @param camelObj
* @returns snake_case object
*/
convertCamelObjKeyToSnake: any = (camelObj: any) => {
if (typeof camelObj !== "object" || camelObj === null) {
return camelObj;
}
if (Array.isArray(camelObj)) {
return camelObj.map((item: object) =>
this.convertCamelObjKeyToSnake(item)
);
}
const snakeObj: any = {};
for (const key in camelObj) {
if (camelObj.hasOwnProperty(key)) {
const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
snakeObj[snakeKey] = this.convertCamelObjKeyToSnake(camelObj[key]);
}
}
return snakeObj;
};
}
一応テストもする
一応パッケージ公開するものなのでテストもしておきましょう。
せっかく作ったので供養させてください。読み飛ばして大丈夫です。
こちらも特に説明することもないので、簡単にコードだけ貼っておきます。
npm i -D jest ts-jest @types/jest
npx ts-jest config:init
mkdir test
touch ./test/const.ts
touch ./test/ToCamelCase.test.ts
touch ./test/ToSnakeCase.test.ts
export * from "./ToSnakeCase";
export * from "./ToCamelCase";
import { ToCamelCase } from "../src/ToCamelCase";
import { camelStr, snakeStr, camelObj, snakeObj } from "./const";
test("test convertSnakeWordToCamel", () => {
const value = new ToCamelCase().convertSnakeWordToCamel(snakeStr);
expect(value).toBe(camelStr);
});
test("test convertSnakeObjKeyToCamel", () => {
const value = new ToCamelCase().convertSnakeObjKeyToCamel(snakeObj);
expect(value).toEqual(camelObj);
});
import { ToSnakeCase } from "../src/ToSnakeCase";
import { camelStr, snakeStr, camelObj, snakeObj } from "./const";
test("test convertCamelWordToSnake", () => {
const value = new ToSnakeCase().convertCamelWordToSnake(camelStr);
expect(value).toBe(snakeStr);
});
test("test convertCamelObjKeyToSnake", () => {
const value = new ToSnakeCase().convertCamelObjKeyToSnake(camelObj);
expect(value).toEqual(snakeObj);
});
テスト結果
PASS test/ToCamelCase.test.ts
PASS test/ToSnakeCase.test.ts
Test Suites: 2 passed, 2 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 3.514 s
Ran all test suites.
✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
パッケージを公開する
いよいよ自分のパッケージが世に放たれます。
以下コマンドでパッケージの公開ができます。
--access public
はつけていないとエラーになる報告がありましたが、私はつけないでも大丈夫でした。
でもここに記述するものには一応つけておきます。保険です。
npm publish --access public
ちなみに、2回目以降に公開する際、package.json
のversion
を更新してあげる必要があります。
更新してあげないとこんなエラーが出て拗ねます。
npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/convert-variable-naming-rules - You cannot publish over the previously published versions: 1.0.36.
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy, or
npm ERR! 403 on a server you do not have access to.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\...\_logs\2023-12-17T06_37_04_446Z-debug-0.log
失敗した!って時の非公開コマンドはこちらです。
npm unpublish <パッケージ名>
パッケージの動作確認する
早速公開したパッケージの動作確認してみましょう。
こちらに関してはすぐです。
新しいプロジェクトを作成し、今回開発したパッケージを呼び出すだけです。
mkdir plugin-test
cd ./plugin-test
npm init
npm i -D ts-node typescript
npm i convert-variable-naming-rules
touch index.ts
import { ToSnakeCase } from "convert-variable-naming-rules";
const toSnakeCase = new ToSnakeCase();
console.log(toSnakeCase.convertCamelWordToSnake("camelCase"));
実行します。
npx ts-node index.ts
実行結果
camel_case
OK!
最後に
普段開発する手順に加え、
npmアカウント作成&ログイン+αをするだけでnpmパッケージの公開ができてしまいました。
今回は対象にしていま線が、グローバルインストール用のパッケージも簡単に作れるそうです。
是非皆さんも開発し、便利に楽しく開発で切る環境を構築していきましょう。
個人的には、ネタパッケージが増えることを渇望しております。
記事の内容に間違いがありましたらそっと連絡ください