🧚🏼♂️
はじめてのnpmパッケージを作って公開(コンポーネントライブラリ)
はじめに
前々から一度はやってみたかったnpmパッケージを作って公開するをやってみました。
TL;DR
- npmパッケージを作って公開する手順を知りたかったので、調べてわかったことを書きました。(実際に簡単なものを作って公開した手順をひととおり書きました)
- 作ったパッケージは、
react
,typescript
,rollup
,storybook
,emotion
を使ったコンポーネントライブラリのセットアップといった内容です(簡単なTextコンポーネント作っただけです) - npmのパッケージを公開してみたい、簡単なコンポーネントライブラリ作ってみたい、という人の参考になるかもです。
作ったもの
やったこと
以下、パッケージ作成からnpm公開までにやったことです。
- npmパッケージを公開するプロジェクトの作成
- 必要なパッケージ導入する
- 実装する(コンポーネントライブラリの実装)
- ビルドする
- npmパッケージ公開する
- 別プロジェクトで公開したパッケージを使ってみる
プロジェクトの作成
react-component-lib-sample
の部分などは、適宜変更してください。
$ mkdir react-component-lib-sample
$ cd react-component-lib-sample
$ yarn init
$ mkdir src
$ touch .gitignore
.gitignore
.DS_Store
*.log
node_modules
# Dist and query are both build output folders
dist*/
# But don't ignore the RTK Query source
lib
es
.yalc
yalc.lock
.idea/
.vscode/
temp/
.tmp-projections
build/
.rts2*
coverage/
typesversions
.cache
.yarnrc
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
*.tgz
.yalc
yalc.lock
yalc.sig
example
必要なパッケージ導入する
- 公開するnpmパッケージを開発する際に、必要なパッケージの導入します。
- 後続の説明で、パッケージを追加する手順もありますが、この記事での最終的な
package.json
は以下の通りです。
pacakge.json
{
"name": "react-component-lib-sample",
"version": "1.0.0",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"author": "[author]",
"license": "MIT",
"dependencies": {
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/core": "^7.17.4",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-typescript": "^8.3.0",
"@storybook/addon-actions": "^6.4.19",
"@storybook/addon-essentials": "^6.4.19",
"@storybook/addon-interactions": "^6.4.19",
"@storybook/addon-links": "^6.4.19",
"@storybook/react": "^6.4.19",
"@storybook/testing-library": "^0.0.9",
"@types/react": "^17.0.39",
"babel-loader": "^8.2.3",
"eslint": "^8.9.0",
"eslint-config-prettier": "^8.3.0",
"prettier": "^2.5.1",
"rollup": "^2.67.2",
"rollup-plugin-dts": "^4.1.0",
"tslib": "^2.3.1",
"typescript": "^4.5.5"
},
"peerDependencies": {
"@emotion/react": "^11.0.0",
"@emotion/styled": "^11.6.0",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"files": [
"package.json",
"README.md",
"LICENSE",
"types",
"dist"
],
"scripts": {
"build": "rollup -c",
"lint": "eslint --ext ts,tsx .",
"format": "prettier --write .",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
}
}
$ yarn add react react-dom
$ yarn add -D typescript @types/react
$ node_modules/.bin/tsc --init
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"jsx": "react",
"sourceMap": false,
"outDir": "dist",
"strict": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationDir": "types",
"emitDeclarationOnly": true,
"rootDir": "./src"
}
}
- eslint導入
$ yarn add -D eslint
$ yarn eslint --init
$ yarn add -D eslint-config-prettier
.eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ['plugin:react/recommended', 'prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react'],
settings: {},
rules: {
'react/prop-types': 0,
'react/jsx-props-no-spreading': [
2,
{
html: 'enforce',
custom: 'ignore',
explicitSpread: 'enforce',
},
],
},
};
package.jsonのスクリプトを更新する
...
"lint": "eslint --ext ts,tsx .",
...
- prettier 導入
$ yarn add -D prettier
$ touch .prettierrc.json
.prettierrc.json
{
"singleQuote": true
}
package.jsonのスクリプトを更新する
...
"format": "prettier --write .",
...
- rollup導入
rollupとは
- Rollup とは JS のモジュールバンドラー
- ドキュメント https://rollupjs.org/guide/en/
$ yarn add -D rollup @rollup/plugin-commonjs @rollup/plugin-typescript tslib rollup-plugin-dts
$ touch rollup.config.js
rollup.config.js
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import dts from 'rollup-plugin-dts';
const pkg = require('./package.json');
export default [
{
input: 'src/index.ts',
output: [
{
file: pkg.main,
format: 'cjs',
sourcemap: false,
},
{
file: pkg.module,
format: 'esm',
sourcemap: false,
},
],
plugins: [
commonjs({
include: ['node_modules/**'],
}),
typescript({
tsconfig: './tsconfig.json',
exclude: ['**/__tests__/**'],
}),
],
external: ['react', 'react-dom', '@emotion/styled', '@emotion/react'],
},
{
input: 'dist/cjs/types/index.d.ts',
output: [{ file: 'dist/cjs/index.d.ts', format: 'cjs' }],
plugins: [dts()],
},
{
input: 'dist/esm/types/index.d.ts',
output: [{ file: 'dist/esm/index.d.ts', format: 'esm' }],
plugins: [dts()],
},
];
バンドルするときに使うコマンドを package.json
のスクリプトに追記する
...
"build": "rollup -c",
...
- emotion導入する(今回作成するコンポーネントで利用するため)
$ yarn add @emotion/react @emotion/styled
- storybookを導入する
$ npx sb init
- storybookを起動する
$ yarn run storybook
- ブラウザからアクセスできたらちゃんと起動してます。
- 導入時の
example
コンポーネントは不要なので削除しておきます。
$ rm -rf stories/
コンポーネントライブラリの実装する
Textコンポーネントを実装してみます。
$ mkdir -p src/constants
$ touch src/constants/index.ts
$ mkdir -p src/components/Text
$ touch src/components/Text/Text.tsx
$ touch src/components/Text/index.ts
$ touch src/components/Text/Text.stories.tsx
$ touch src/components/index.ts
$ touch src/index.ts
src/constants/index.ts
export const fontSize = {
s: '10px',
m: '16px',
l: '24px',
} as const;
src/components/Text/Text.tsx
import React, { VFC } from 'react';
import styled from '@emotion/styled';
import { fontSize } from '../../constants';
export type TextProps = {
text: string;
className?: string;
};
export const Text: VFC<TextProps> = ({ text, className = '' }) => {
return <Wrapper className={className}>{text}</Wrapper>;
};
const Wrapper = styled.p`
font-size: ${fontSize.m};
`;
src/components/Text/index.ts
export { TextProps, Text } from './Text';
src/components/Text/Text.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Text } from './Text';
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Text/Text',
component: Text,
} as ComponentMeta<typeof Text>;
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof Text> = (args) => <Text {...args} />;
export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Default.args = {
text: 'hello, world',
};
src/components/index.ts
export { TextProps, Text } from './Text';
src/index.ts
export { TextProps, Text } from './components';
- storybook を立ち上げて作成したコンポーネントを見てみる
$ yarn run storybook
- ブラウザからアクセスすると、作成したテキストコンポーネントが表示されてること。
以上で、公開用のパッケージ作成は終わりです。続いて、作成したパッケージを公開していきましょう。
ビルドする
-
package.json
のエントリーポイントを以下のように修正します。
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
- ビルドコマンドを実行します。
dist
ディレクトリ配下にファイルが生成されてること
$ yarn run build
yarn run v1.22.11
$ rollup -c
src/index.ts → dist/cjs/index.js, dist/esm/index.js...
created dist/cjs/index.js, dist/esm/index.js in 2.4s
dist/cjs/types/index.d.ts → dist/cjs/index.d.ts...
created dist/cjs/index.d.ts in 18ms
dist/esm/types/index.d.ts → dist/esm/index.d.ts...
created dist/esm/index.d.ts in 11ms
パッケージ公開する
- npmに公開するファイルを、package.jsonに記述します。
"files": [
"package.json",
"README.md",
"LICENSE",
"types",
"dist"
],
- 作成したパッケージ(Textコンポーネント)は
emotion
に依存しているので、package.json
に以下を記述します。
"peerDependencies": {
"@emotion/react": "^11.0.0",
"@emotion/styled": "^11.6.0",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
-
npmパッケージを公開は下記のサイトから行います。まずはアカウントが必要なのでsingupページから新規登録します。
-
アカウント作成したらログインします。
$ npm login
- パッケージ公開
$ npm publish
- パッケージ検索
$ npm search react-component-lib-sample
- パッケージ削除
$ npm unpublish react-component-lib-sample --force
別プロジェクトに公開したパッケージをインストールして使ってみる
- 今回公開したパッケージを実際に動かしてみましょう。動かしてみる用のプロジェクトを作成し、導入して動かしてみます。以下、create-next-appして、nextjsで確認してみます。
$ npx create-next-app@latest --typescript example
$ cd example
- 公開したパッケージ導入します
$ yarn add react-component-lib-sample @emotion/react @emotion/styled
- package.jsonの差分を見ると、公開したパッケージが追加されてることを確認できます。
"dependencies": {
"next": "12.0.10",
"react": "17.0.2",
+ "react-component-lib-sample": "^1.0.0",
"react-dom": "17.0.2"
},
- テキストコンポーネントを呼び出してみます。
pages/index.tsx
を以下のように編集します。
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import { Text } from 'react-component-lib-sample'
const Home: NextPage = () => {
return (
<div>
<Text text="hello, world" />
</div>
)
}
export default Home
- 起動します。
$ yarn run dev
- hello, worldが表示されること
おわりに
- 作ったライブラリ自体は簡単なものでしたが、npm公開の仕組みがわかってよかったです。
- 公開の仕方がわかったので、今度はもっと有益な何かを作って出したいです。
-
yarn add react-component-lib-sample
できたのは感動した。
参考
参考にしたリンクたち
Discussion
とても参考になりました。ありがとうございました。