프론트엔드 현재 환경 설정 - woowacourse-teams/2023-shook GitHub Wiki
23.09.01 - webpack 및 stylelint 설정 최신화, 설명 추가
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.tsx',
output: {
assetModuleFilename: 'assets/[hash][ext]',
publicPath: '/',
},
module: {
rules: [
{
test: /\.(png|svg|jpe?g)$/,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'],
alias: {
'@': path.resolve(__dirname, 'src/'),
},
},
};
- output.assetModuleFilename - asset의 정적 리소스 빌드될 파일명.
-
output.publicPath - asset에 대한 기본 경로 설정. 설정하지 않는다면 새로 고침시 해당 경로에서 파일을 불러오려고 함. (ex -
http://localhost:3000/songs/main.js
) -
resolve.alias - 모듈 경로의 별칭 지정. (ex -
import GlobalStyles from '@/shared/styles/GlobalStyles';
)
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const webpack = require('webpack');
const dotenv = require('dotenv');
dotenv.config({ path: '.env/.env.development' });
module.exports = merge(common, {
mode: 'development',
devServer: {
historyApiFallback: true,
open: true,
port: 3000,
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
plugins: ['react-refresh/babel'],
},
},
'ts-loader',
],
},
],
},
plugins: [
new ReactRefreshWebpackPlugin({ overlay: false }),
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env),
}),
],
});
- devServer.historyApiFallback - 404 응답에 index.html을 제공.
- devServer.port - 현재 개발서버 8080 포트로 api 요청시 CORS 에러가 발생하여서 개발환경시 3000 포트 사용중.
- devServer.static - 기본적으로 활성화되어 있으며, 개발서버 빌드시 public 폴더를 정적 제공함.
const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const webpack = require('webpack');
const dotenv = require('dotenv');
dotenv.config({ path: '.env/.env.production' });
module.exports = merge(common, {
mode: 'production',
output: {
filename: 'static/[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: ['babel-loader', 'ts-loader'],
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env),
}),
],
});
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
// TODO: 브라우저 지원 범위
chrome: '51',
},
useBuiltIns: 'entry',
corejs: '3.31.1',
},
],
['@babel/preset-react'],
['@babel/preset-typescript'],
],
};
{
"compilerOptions": {
"target": "ES6",
"lib": ["DOM", "DOM.Iterable", "ES2023"],
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"allowJs": true,
"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src", ".storybook/*"]
}
-
"target": "ES6"
: 컴파일러가 ES6버전의 자바스크립트로 변환합니다. -
"lib": ["DOM", "DOM.Iterable", "ES2023"]
: 컴파일 과정에서 사용하는 라이브러리 목록입니다.DOM.Iterable
의 경우 DOM요소를 루프를 돌릴 수 있도록 합니다. -
"jsx": "react-jsx"
:_jsx()
을 사용하여 React 가상 dom 객체를 만들도록 합니다. -
"module": "ESNext"
: 컴파일을 완료한 파일이 어떤 모듈 시스템으로 적용되는 지 정합니다. -
"moduleResolution": "Node"
: 컴파일 과정에서 어떤 모듈 시스템으로 해결할 지 정합니다. -
"resolveJsonModule": true
: json 파일도 허용해줍니다. -
"allowJs": true
: javascript 파일도 허용해줍니다. -
"isolatedModules": true
: 모듈을 export 하도록 강제합니다. -
"esModuleInterop": true
: 각각의 소스파일을 모듈로 만들도록 강제합니다. 타입스크립트에선 import/export가 없으면 전역으로 접근이 가능합니다. 해당 속성을 통해 해당 문제를 방지할 수 있습니다. -
"forceConsistentCasingInFileNames": true
: 파일 이름의 대소문자를 구분합니다. -
"strict": true
: 타입스크립트 타입 체크를 엄격하게 합니다. -
"noFallthroughCasesInSwitch": true
: switch fall through를 막습니다. -
"skipLibCheck": true
: TypeScript가 라이브러리 파일을 체크하지 않도록 설정합니다. -
"baseUrl": "."
: baseUrl을 root로 지정합니다. -
"paths": { "@/*": ["src/*"] }
: path @/*를 src/*로 읽습니다. -
"include": ["src", ".storybook/*"]
: src와 .storybook에 있는 파일을 컴파일합니다.
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5",
"endOfLine": "auto"
}
module.exports = {
env: {
browser: true,
es2023: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2023,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
plugins: ['import', 'react', '@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:prettier/recommended',
'plugin:storybook/recommended',
],
settings: {
'import/resolver': {
typescript: {},
node: {},
},
'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'] },
react: {
version: 'detect',
},
},
rules: {
'prettier/prettier': 'warn',
'prefer-const': 'warn',
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'react/self-closing-comp': [
'error',
{
component: true,
html: true,
},
],
'import/no-named-as-default': 'off',
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
'newlines-between': 'never',
},
],
},
ignorePatterns: ['/*', '!/src'],
};
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
};
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import type { StorybookConfig } from '@storybook/react-webpack5';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-styling',
],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
docs: {
autodocs: true,
},
webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.plugins = [
...(config.resolve?.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve?.extensions,
}),
];
return config;
}
return config;
},
};
export default config;
import type { Preview } from '@storybook/react';
import { withThemeFromJSXProvider } from '@storybook/addon-styling';
import GlobalStyles from '../src/shared/styles/GlobalStyles';
import { ThemeProvider } from 'styled-components';
import theme from '../src/shared/styles/theme';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
decorators: [
(Story) => (
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
),
withThemeFromJSXProvider({ GlobalStyles }),
],
};
export default preview;
{
"extends": ["stylelint-config-clean-order"],
"customSyntax": "postcss-styled-syntax"
}