🎁
ReactコンポーネントのnpmライブラリをTypescriptで作成する
ReactコンポーネントのnpmライブラリをTypescriptで作成する
前回の続き
前回作成したJavascriptで作成したコンポーネントをTypescriptに変更します
typescript install
npm install --save-dev typescript
rollup plugin
rollup.config.jsで使用します
npm install --save-dev @rollup/plugin-typescript
eslint install
create-react-app --template typescriptでinstallされるものを設定しました
npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks
typesync
package.jsonを見て足りない型定義パッケージがあれば自動で追加してくれるパッケージです
typesync
npm install
下記がinstallされた
package.json
+ "@types/babel__core": "^7.1.18",
+ "@types/babel__plugin-transform-runtime": "^7.9.2",
+ "@types/babel__preset-env": "^7.9.2",
+ "@types/eslint": "^8.2.2",
tsconfig.json
create-react-app --template typescriptで作成したものを同じ内容を設定しました
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
.npmignore
.npmignore追記
tsconfig.json
rollup.config.js
rollup.config.js
react-component $ git diff rollup.config.js
diff --git a/rollup.config.js b/rollup.config.js
index 8f3fbfd..956d2f5 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -4,12 +4,13 @@ import styles from "rollup-plugin-styles";
import babel from '@rollup/plugin-babel';
import sourcemaps from 'rollup-plugin-sourcemaps';
import del from 'rollup-plugin-delete';
+import typescript from '@rollup/plugin-typescript';
const autoprefixer = require('autoprefixer');
const conf = {
- input: 'src/index.js',
+ input: 'src/index.tsx',
output: {
file: `dist/index.cjs.js`,
format: "cjs",
@@ -18,6 +19,7 @@ const conf = {
// this externelizes react to prevent rollup from compiling it
external: ["react", /@babel\/runtime/],
plugins: [
+ typescript(),
// these are babel comfigurations
babel({
exclude: 'node_modules/**',
拡張子変更してTypescriptに変更
jsx -> tsx
index.js -> index.tsx
Javascriptで型チェックを行うために、prop-typesを使用してましたが、Typescriptに変更したので、prop-typesは不要になるため削除しました
変更後のソース
index.tsx
import { Header } from './stories/Header'
const returnLibrary = () => {
return {
Header: Header
// you can add here other components that you want to export
}
}
export default returnLibrary()
Button.tsx
import './button.css';
type Props = {
primary?: boolean,
size: 'small'|'medium'|'large',
backgroundColor?: string,
label?: string,
onClick?: React.MouseEventHandler<HTMLButtonElement>
}
/**
* Primary UI component for user interaction
*/
export const Button: React.VFC<Props> = ({
primary = false,
size = 'medium',
backgroundColor,
label,
onClick
}) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
const className = ['storybook-button', `storybook-button--${size}`, mode].join(' ');
const style: React.CSSProperties = {
background: backgroundColor
};
return (
<button onClick={onClick} className={className} style={style}>{label}</button>
);
};
Button.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Button } from './Button';
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Example/Button',
component: Button,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof Button>;
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
export const Primary = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Primary.args = {
primary: true,
label: 'Button',
};
export const Secondary = Template.bind({});
Secondary.args = {
label: 'Button',
};
export const Large = Template.bind({});
Large.args = {
size: 'large',
label: 'Button',
};
export const Small = Template.bind({});
Small.args = {
size: 'small',
label: 'Button',
};
Header.tsx
import { Button } from './Button';
import './header.css';
type Props = {
user?: boolean,
onLogin?: React.MouseEventHandler<HTMLButtonElement>,
onLogout?: React.MouseEventHandler<HTMLButtonElement>,
onCreateAccount?: React.MouseEventHandler<HTMLButtonElement>
}
export const Header: React.VFC<Props> = ({
user,
onLogin,
onLogout,
onCreateAccount
}) => (
<header>
<div className="wrapper">
<div>
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
fill="#FFF"
/>
<path
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
fill="#555AB9"
/>
<path
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
fill="#91BAF8"
/>
</g>
</svg>
<h1>Acme</h1>
</div>
<div>
{user ? (
<Button size="small" onClick={onLogout} label="Log out" />
) : (
<>
<Button size="small" onClick={onLogin} label="Log in" />
<Button primary size="small" onClick={onCreateAccount} label="Sign up" />
</>
)}
</div>
</div>
</header>
);
Header.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Header } from './Header';
export default {
title: 'Example/Header',
component: Header,
} as ComponentMeta<typeof Header>;
const Template: ComponentStory<typeof Header> = (args) => <Header {...args} />;
export const LoggedIn = Template.bind({});
LoggedIn.args = {
user: true,
};
export const LoggedOut = Template.bind({});
LoggedOut.args = {};
Page.tsx
import { Header } from './Header';
import './page.css';
export type PageType = {
user?: boolean,
onLogin?: React.MouseEventHandler<HTMLButtonElement>,
onLogout?: React.MouseEventHandler<HTMLButtonElement>,
onCreateAccount?: React.MouseEventHandler<HTMLButtonElement>
}
export const Page = ({
user,
onLogin,
onLogout,
onCreateAccount
}: PageType) => (
<article>
<Header user={user} onLogin={onLogin} onLogout={onLogout} onCreateAccount={onCreateAccount} />
<section>
<h2>Pages in Storybook</h2>
<p>
We recommend building UIs with a{' '}
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
<strong>component-driven</strong>
</a>{' '}
process starting with atomic components and ending with pages.
</p>
<p>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page data
in Storybook:
</p>
<ul>
<li>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</li>
<li>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</li>
</ul>
<p>
Get a guided tutorial on component-driven development at{' '}
<a href="https://storybook.js.org/tutorials/" target="_blank" rel="noopener noreferrer">
Storybook tutorials
</a>
. Read more in the{' '}
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
docs
</a>
.
</p>
<div className="tip-wrapper">
<span className="tip">Tip</span> Adjust the width of the canvas with the{' '}
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
id="a"
fill="#999"
/>
</g>
</svg>
Viewports addon in the toolbar
</div>
</section>
</article>
);
Page.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Page , PageType} from './Page';
import * as HeaderStories from './Header.stories';
export default {
title: 'Example/Page',
component: Page,
};
const Template: ComponentStory<typeof Page> = (args) => <Page {...args} />;
export const LoggedIn = Template.bind({});
LoggedIn.args = {
// More on composing args: https://storybook.js.org/docs/react/writing-stories/args#args-composition
...HeaderStories.LoggedIn.args,
};
export const LoggedOut = Template.bind({});
LoggedOut.args = {
...HeaderStories.LoggedOut.args,
};
package.json
最終的にpackage.jsonはこの様になりました
package.josnの最終形
{
"name": "@marumarumeruru/react-component",
"version": "2.0.0",
"description": "",
"main": "dist/index.cjs.js",
"scripts": {
"build": "rollup -c",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.16.7",
"@babel/core": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.16.7",
"@babel/preset-env": "^7.16.7",
"@babel/preset-react": "^7.16.7",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-node-resolve": "^13.1.3",
"@rollup/plugin-typescript": "^8.3.0",
"@storybook/addon-actions": "^6.4.9",
"@storybook/addon-essentials": "^6.4.9",
"@storybook/addon-links": "^6.4.9",
"@storybook/react": "^6.4.9",
"@types/babel__core": "^7.1.18",
"@types/babel__plugin-transform-runtime": "^7.9.2",
"@types/babel__preset-env": "^7.9.2",
"@types/eslint": "^8.2.2",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"autoprefixer": "^10.4.2",
"babel-loader": "^8.2.3",
"eslint": "^8.6.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"rollup": "^2.63.0",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-styles": "^3.14.1",
"typescript": "^4.5.4"
},
"dependencies": {
"@babel/runtime": "^7.16.7"
}
}
build
react-component $ npm run build
> @marumarumeruru/react-component@1.0.14 build
> rollup -c
src/index.tsx → dist/index.cjs.js...
created dist/index.cjs.js in 4s
storybook
react-component $ npm run storybook
最終的な構成
react-component $ tree -a -I "node_modules|.git|storybook-static"
.
├── .DS_Store
├── .babelrc
├── .gitignore
├── .npmignore
├── .storybook
│ ├── main.js
│ └── preview.js
├── README.md
├── dist
│ └── index.cjs.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
│ ├── index.tsx
│ └── stories
│ ├── Button.stories.tsx
│ ├── Button.tsx
│ ├── Header.stories.tsx
│ ├── Header.tsx
│ ├── Introduction.stories.mdx
│ ├── Page.stories.tsx
│ ├── Page.tsx
│ ├── assets
│ │ ├── code-brackets.svg
│ │ ├── colors.svg
│ │ ├── comments.svg
│ │ ├── direction.svg
│ │ ├── flow.svg
│ │ ├── plugin.svg
│ │ ├── repo.svg
│ │ └── stackalt.svg
│ ├── button.css
│ ├── header.css
│ └── page.css
└── tsconfig.json
5 directories, 31 files
作成したパッケージを利用する
前回と同じ方法で利用できます(JavascriptのままでOK)
Discussion