Vite + React(ts) + Eslint(standard) + Prettier + Stylelint + Emotion + Storybook + jest の開発環境構築
GOAL
- 過去に作成した環境が古くなったのでアップデートをかねて構築しなおす
- node v18 ではstorybook(vite)が起動できなくなったので、起動できるようにする
過去:Vite +TypeScript + React + Eslint(airbnb) + Prettier + Stylelint + Emotion + Storybook の開発環境構築
構築環境
- MacBook Pro M1
- Node18.12.0(fnm)
- Vscode
※構築後はwindowsでも動作確認する
機能
- Vite
- typescript
- Eslint
- airbnb
- Prettiter
- Stylelint
- storybook
- vitest
Viteのインストール
公式を参考に設定
https://ja.vitejs.dev/guide/
npm create vite@latest starter-vite-react-typescript-airbnb -- --template react-ts
cd starter-vite-react-typescript-airbnb
npm install
.gitignoreを更新
vscode設定ファイルはgit 管理する
.gitignore 下記を削除
.vscode/*
!.vscode/extensions.json
.editorconfigとvscodeの推奨拡張機能を追加
.editorconfig を作成して下記を追加
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
Nodeのバージョン固定と自動切り替え
package.jsonに追加
"engines": {
"node": "18.12.0",
"npm": "8.19.2"
}
.npmrc
をルートに追加
engine-strict=true
nodeのバージョンが違う場合は、警告がでるようになる
.node-version
と .nvmrc
をルートに追加
node -v > .node-version
node -v > .nvmrc
package-lock.json(engines) 更新
npm i
Eslintを導入
インストール
npm i -D eslint
npx eslint --init
You can also run this command directly using 'npm init @eslint/config'.
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser, node
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · standard
✔ What format do you want your config file to be in? · JavaScript
airbnbの設定内容
コンソール上で選択
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser, node
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-airbnb@latest
Local ESLint installation not found.
The config that you've selected requires the following dependencies:
eslint-plugin-react@^7.28.0 @typescript-eslint/eslint-plugin@latest eslint-config-airbnb@latest eslint@^7.32.0 || ^8.2.0 eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react-hooks@^4.3.0 @typescript-eslint/parser@latest
✔ Would you like to install them now? · No / Yes
✔ Which package manager do you want to use? · npm
Eslint の拡張
- AirbnbのESLintコンフィグをTypeScriptに対応させて強化する
- eslintrc.cjs の更新(ルールの拡張)
-
React17からJSXを使う際にReactをインポート不要になったため無効化(rulesに追加)
'react/react-in-jsx-scope': 'off',
-
devDependenciesのimportを許可(rulesに追加)
'import/no-extraneous-dependencies': 'off',
-
コンポーネントはアロー関数で統一(rulesに追加)
'react/function-component-definition': [ 'error', { namedComponents: 'arrow-function', // 'function-declaration' | 'function-expression' | 'arrow-function' unnamedComponents: 'arrow-function' // 'function-declaration' | 'function-expression' | 'arrow-function' } ],
-
- eslintrc.cjs の更新(parserOptions の project に 「./tsconfig.json」を指定)
"include": ["src",".eslintrc.cjs","vite.config.ts"],
- tsconfig.jsonの更新(
Parsing error: "parserOptions.project"
)エラー解消のため
"include": ["src",".eslintrc.cjs","vite.config.ts"],
Eslintの見直しをして最終の設定ファイルをはっておく
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"airbnb",
"airbnb/hooks",
"airbnb-typescript",
"plugin:react/recommended",
"plugin:storybook/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "react-hooks", "@typescript-eslint", "testing-library", "jest-dom"],
"settings": {
"import/extensions": [".js", ".jsx", ".ts", ".tsx"],
"import/resolver": {
"typescript": {
"directory": "./tsconfig.json"
},
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
"rules": {
//関数や変数が定義される前に使われているとエラーになるデフォルトの機能をoff
"no-use-before-define": "off",
// 一貫した型定義
"@typescript-eslint/consistent-type-definitions": ["error", "interface"], // or type
// 一貫した型のインポート
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports" }],
// 明示的な関数の戻り値の型
"@typescript-eslint/explicit-function-return-type": "error",
//typescript側のno-use-before-defineを使うようにする
"@typescript-eslint/no-use-before-define": ["error"],
//TypeScriptでチェックしているから不要
"react/prop-types": "off",
"react/require-default-props": "off",
"react/no-unknown-property": ["error", { "ignore": ["css"] }],
"react/react-in-jsx-scope": "off",
"react/function-component-definition": [
"error",
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
],
//jsx形式のファイル拡張子をjsxもしくはtsxに限定
"react/jsx-filename-extension": [
"error",
{
"extensions": [".jsx", ".tsx"]
}
],
//named exportがエラーになるので使えるようにoff
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"vite.config.ts",
".storybook/**/*.{cjs,jsx,tsx}",
"src/setupTest.ts",
"src/stories/**/*.stories.{ts,tsx}",
"src/**/*.test.{ts,tsx}"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
],
// importの自動整列
"import/order": [
"error",
{
"groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"],
"pathGroups": [
{
"pattern": "{react,react-dom/**,react-router-dom}",
"group": "builtin",
"position": "before"
},
{
"pattern": "@src/**",
"group": "parent",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["builtin"],
"alphabetize": {
"order": "asc"
},
"newlines-between": "always"
}
]
},
"overrides": [
{
"files": ["**/tests/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
"extends": ["plugin:jest-dom/recommended", "plugin:testing-library/react"]
}
]
}
eslintrc.cjsの中身
追記:eslintの見直しをした
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'plugin:react/recommended',
'plugin:jsx-a11y/strict',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended',
'standard-with-typescript',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json',
},
plugins: ['react', 'react-hooks', '@typescript-eslint', 'testing-library', 'jest-dom', 'import'],
rules: {
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'react/require-default-props': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'react/no-unknown-property': [
'error',
{
ignore: ['css'],
},
],
'react/react-in-jsx-scope': 'off',
'react/function-component-definition': [
'error',
{
namedComponents: 'arrow-function',
// 'function-declaration' | 'function-expression' | 'arrow-function'
unnamedComponents: 'arrow-function', // 'function-declaration' | 'function-expression' | 'arrow-function'
},
],
'import/no-extraneous-dependencies': 'off',
// importの自動整列
'import/order': [
'error',
{
groups: ['builtin', 'external', 'parent', 'sibling', 'index', 'object', 'type'],
pathGroups: [
{
pattern: '{react,react-dom/**,react-router-dom}',
group: 'builtin',
position: 'before',
},
{
pattern: '@src/**',
group: 'parent',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['builtin'],
alphabetize: {
order: 'asc',
},
'newlines-between': 'always',
},
],
},
overrides: [
{
files: ['**/tests/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
extends: ['plugin:jest-dom/recommended', 'plugin:testing-library/react'],
},
],
settings: {
react: {
version: 'detect',
},
},
}
tsconfig.jsonの中身
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src",".eslintrc.cjs","vite.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
npm scriptsの追加
eslint 構文チェックと自動修正ができるように
{
"scripts": {
・・・中略・・・
"lint:eslint": "eslint --ignore-path .gitignore . --ext .js,.jsx,.ts,.tsx,cjs",
"lintfix:eslint": "npm run lint:eslint -- --fix",
},
}
Eslintのlintエラー修正
自動修正
npm run lintfix:eslint
手動修正
Prettierの導入
1. インストール
npm i -D prettier eslint-config-prettier
2. Prettierの設定ファイルを作成
プロジェクトのルートに.prettierrc
を作成
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "all"
}
semi や カンマを無効にしたい場合の設定
{
"printWidth": 120,
"semi": false,
"singleQuote": true,
"trailingComma": "none"
}
3.eslintとprettierの競合ルールをoffに
.eslintrc.cjs
に追加(prettierは必ず最後に追加する)
extends: [
・・・中略・・・
"prettier"
],
4. npm scriptsの追加
"format:check": "prettier --check --ignore-path .gitignore './**/*.{js,jsx,ts,tsx,cjs,json,css}'",
"format:fix": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx,cjs,json,css}'"
vscodeの推奨拡張機能を追加
emotionの導入
1.インストール
npm i @emotion/react
2.Emotionの自動インポート(JSXプラズマを自動挿入)
tsconfig.json の compilerOptionsに追加
"compilerOptions": {
・・・中略・・・
"jsxImportSource": "@emotion/react",
vite.config.tsが、tsconfig.json の jsxImportSource を参照できるように
plugins: [
react({
jsxImportSource: '@emotion/react',
}),
]
3. eslintのルール追加|css props は許容する
https://emotion.sh/docs/eslint-plugin-react
'react/no-unknown-property': ['error', { ignore: ['css'] }]
4.vscodeの推奨拡張機能を追加
vscode-styled-components - Visual Studio Marketplace
5. 既存スタイルをemotion使ってcss-in-js仕様にする
グローバルスタイルの実装
1.src/styles/globalStyle.tsxファイルを作成してindex.cssを移植
import { css } from '@emotion/react'
const globalStyle = css`
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
`
export default globalStyle
2.main.tsxにglobalスタイルをインポート
import React from 'react'
import { Global } from '@emotion/react'
import ReactDOM from 'react-dom/client'
import App from './App'
import globalStyle from './styles/globalStyle'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Global styles={globalStyle} />
<App />
</React.StrictMode>
)
src/App.css はコンポートスタイルに
-
import { css } from '@emotion/react'
-
const styleApp = css`` にまるっとコピー(#rootは&に書き換え)
-
styleApp をcssprops でスタイルを定義
<div className="App" css={styleApp}>
完成
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import { css } from '@emotion/react'
const styleApp = css`
& {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
`
const App = (): JSX.Element => {
const [count, setCount] = useState(0)
return (
<div className="App" css={styleApp}>
<div>
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</div>
)
}
export default App
ユニークなクラス名が付与され、コンポート別にスタイルを閉じ込めることができる
6. 不要になったcssファイルを削除しておく
Stylelintの導入
インストール
npm i -D stylelint stylelint-config-recess-order stylelint-config-standard postcss-syntax @stylelint/postcss-css-in-js stylelint-config-prettier
設定ファイルの作成とルールを拡張
プロジェクトルートに.stylelintrcを作成して以下を追加
{
"extends": [
"stylelint-config-standard",
"stylelint-config-recess-order",
"stylelint-config-prettier"
],
"rules": {},
"overrides": [
{
"files": [
"**/*.jsx",
"**/*.tsx"
],
"rules": {
},
"customSyntax": "@stylelint/postcss-css-in-js"
}
]
}
npm scriptsを追加
"lint:stylelint": "stylelint ./src/**/*.{css,jsx,tsx}",
"lintfix:stylelint": "stylelint --fix ./src/**/*.{css,jsx,tsx}",
storybook(for vite)の導入
npx sb@next init --builder=vite
✔ Do you want to run the 'eslintPlugin' migration on your project? … yes
✔ Do you want to run the 'npm7' migration on your project? … no
✔ Do you want to run the 'mdx1to2' migration on your project? … yes
起動!! docsが別タブではなくなっている・・・w
導入したeslintのルール順を変更
prettierより前にしておく
.eslintrc.cjs
extends: [
・・・中略
+ 'plugin:storybook/recommended',
'prettier'
],
動作確認のため src/stories/App.stories.tsx を作成する
コード
import type { Meta, StoryObj } from '@storybook/react'
import App from '../App'
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
const meta: Meta<typeof App> = {
title: 'App',
component: App,
// This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/react/writing-docs/docs-page
tags: ['docsPage'],
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {}
}
export default meta
type Story = StoryObj<typeof App>
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
export const Default: Story = {
// More on args: https://storybook.js.org/docs/react/writing-stories/args
args: {}
}
動いた!storybookv7はいろいろ書き方変わっている?
stylelintのルール拡張
no-empty-first-line lintエラー解消のため
ルールoffでもよかったがprettierの競合ルール無効化でいいかとおもったので・・この設定にした
npm i -D stylelint-config-prettier
{
"extends": [
// other configs ...
"stylelint-config-prettier"
]
}
Vitestの導入
guideを参考に設定する
Vitest 例
インストール
npm install -D vitest
npm scriptsにtestコマンドを追加
{
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
}
}
vscodeの拡張機能も入れておく
testing-library をインストールする
npm install --D @testing-library/react jsdom @testing-library/user-event @testing-library/jest-dom
テストファイルのimportの省略できるように
上記を参考にsrc/setupTest.ts」を作成する
import matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';
expect.extend(matchers);
vite.config.tsも更新
/* eslint-disable @typescript-eslint/triple-slash-reference */
/// <reference types="vitest" />
/// <reference types="vite/client" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react({
jsxImportSource: '@emotion/react'
})
],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/setupTest.ts']
}
})
参考記事などをここにはっておく
vite react typescript jest
.gitignore に coverage を追加
App.test.tsx の作成
src/tests/App.test.tsx を作成
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import App from '../App'
test('should first', async () => {
// 描画
render(<App />)
let button = screen.getByRole('button', { name: /count is/i })
expect(button).toHaveTextContent('count is 0')
const user = userEvent.setup()
await user.click(button)
button = await screen.findByRole('button', { name: /count is \d/ })
expect(button).toHaveTextContent('count is 1')
})
test実行
npm run test
✓ src/tests/App.test.tsx (1)
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 19:18:44
Duration 849ms (transform 276ms, setup 50ms, collect 208ms, tests 65ms)
coverageの実行
npm run coverage
starter-vite-react-ts@0.0.0 coverage
vitest run --coverage
✓ src/tests/App.test.tsx (1)
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 18:48:07
Duration 1.68s (transform 358ms, setup 153ms, collect 273ms, tests 100ms)
% Coverage report from c8
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---|---|---|---|---|---|
All files | 100 | 100 | 100 | 100 | |
App.tsx | 100 | 100 | 100 | 100 | |
setupTest.ts | 100 | 100 | 100 | 100 | |
-------------- | --------- | ---------- | --------- | --------- | ------------------- |
eslintの設定追加
npm i -D eslint-plugin-testing-library eslint-plugin-jest-dom
eslint の設定ファイルを更新
追加
plugins: ['react', 'react-hooks', '@typescript-eslint', 'testing-library', 'jest-dom'],
上書き
testファイルのみ
overrides: [
{
files: ['**/tests/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
extends: ['plugin:jest-dom/recommended', 'plugin:testing-library/react']
}
],
path エイリアスの設定
npm i -D vite-tsconfig-paths
vite.config.ts
export default defineConfig({
plugins: [
react({
jsxImportSource: '@emotion/react'
}),
+ tsconfigPaths()
],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/setupTest.ts']
}
})
tsconfig.json にパスエイリアスの設定を追加
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
vscodeでもパス補完できるように
拡張機能のインストール
"path-autocomplete.pathMappings": {
"@": "${folder}/src"
},
"path-autocomplete.extensionOnImport": true
「extensionOnImport」は拡張子を入力するのがめんどくさい方向けw
なくてもいい
検証ビルド
npm run build
結果
エラーなし
プレビュー
npm run preview
検証ビルド|storybook
npm run build-storybook
結果
エラ〜なし
プレビュー
エラ〜なし
npx http-server ./storybook-static
作ったものをあげておく
eslint | airbnb
eslint | standard
nodeのデフォルトモジュールで型の補完がきくようにする
やること
npm i -D @types/node
Vite v4 がリリースされたのアップデートする
やること
- そのほか、パッケージも併せてやっておく
- ビルドを高速できるようにvite pluginを変更する
- 「@vitejs/plugin-react」 →「@vitejs/plugin-react-swc」
- 使用されていないパッケージの整理
参考記事
npm outdated
npm-check-updateshttps://github.com/tjunnone/npm-check-updates#options
オプション一覧:やったこと
更新するパッケージの確認
npx npm-check-updates
アップデート
npx npm-check-updates -u
以下を削除して、再インストール
- node_modules
- package-lock.json
npm i
storybookv7 もベータ版になっていたので手動アップデート
起動確認
OK!
storybook
OK!
vite-plugin-react-swcの設定
Vercel社が作っている。Babelの代替えとなるコンパイラーだそうですw
SWC is 20x faster than Babel on a single thread and 70x faster on four cores.
公式を参考に設定する
起動も早くなったw
前より、半分の時間になった・・・
VITE v4.0.1 ready in 122 ms
GlobalStyle にリセットcssを追加する
import { css } from '@emotion/react';
+ // https://github.com/csstools/sanitize.css#install
+ import 'sanitize.css';
+ import 'sanitize.css/forms.css';
const GlobalStyle = css`
///省略
`;
export default GlobalStyle;
GlobalStyle のベーススタイルを定義しなおす
以下のようにした
import { css } from '@emotion/react';
// reset css
// https://github.com/csstools/sanitize.css#install
import 'sanitize.css';
import 'sanitize.css/forms.css';
const globalStyle = css`
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;500;700;900&family=Roboto:wght@400;700&display=swap');
:root {
--font-color-base: #090a0a;
--font-family-base: Inter, Avenir, 'Helvetica Neue', Helvetica, Arial, sans-serif;
--font-size-base: 16px;
--font-weight-base: 400;
--line-height-base: 1.5;
--bg-color-base: #fcfcfc;
}
[lang='ja'] {
--font-family-base: 'Roboto', 'Noto Sans JP', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
body {
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: var(--font-weight-base);
line-height: var(--line-height-base);
color: var(--font-color-base);
background-color: var(--bg-color-base);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
`;
export default globalStyle;
stylelintのルール更新
css のクラス名はローワーキャメルに
"rules": {
"selector-class-pattern": "^[a-z][a-zA-Z0-9]+$"
},
#参考