Setup React Typescript với Webpack & Babel & ESLint
Châm ngôn của mình là học để kiếm tiền.
Vì thế mình build các khóa học của mình để giúp anh em tiến bộ nhanh hơn x10 lần , để kiếm được nhiều tiền hơn
- 🏆 React.js Super: Trở thành React.js Developer trong 7 ngày với mức thu nhập 20 triệu/tháng
- 🏆 Node.js Super: Giúp bạn học cách phân tích, thiết kế, deploy 1 API Backend bằng Node.js
- 🏆 Next.js Super: Mình sẽ chia sẻ từ A-Z kiến thức về Next.js, thứ giúp mình kiếm hơn 1 tỉ/năm
Trong bài viết này chúng ta sẽ học cách setup một dự án ReactJs hoàn chỉnh với Webpack, Typescript kết hợp Babel, Prettier, ESLint
Repository dự án: https://github.com/duthanhduoc/React-Webpack-Typescript
🥇Cấu trúc thư mục full
Dưới đây là cấu trúc thư mục hoàn chỉnh của dự án ReactJs Typescript Webpack Babel ESLint
📦React-Webpack-Typescript
┣ 📂dist
┣ 📂public
┃ ┣ 📜icon.png
┃ ┗ 📜index.html
┣ 📂src
┃ ┣ 📂assets
┃ ┃ ┣ 📂images
┃ ┃ ┃ ┗ 📜react.svg
┃ ┃ ┗ 📂styles
┃ ┃ ┃ ┣ 📜app.css
┃ ┃ ┃ ┗ 📜app.scss
┃ ┣ 📜App.tsx
┃ ┣ 📜index.tsx
┃ ┗ 📜react-app-env.d.ts
┣ 📜.babelrc
┣ 📜.browserslistrc
┣ 📜.editorconfig
┣ 📜.env
┣ 📜.eslintignore
┣ 📜.eslintrc.js
┣ 📜.gitignore
┣ 📜.prettierignore
┣ 📜.prettierrc
┣ 📜package.json
┣ 📜tsconfig.json
┣ 📜webpack.config.js
┗ 📜yarn.lock
- Thư mục
dist
: Thư mục chứa các file build - Thư mục
public
: Chứa fileindex.html
và các file liên quan nhưfavicon.ico
,robots.txt
,... - Thư mục
src
: Chứa mã nguồn chính của chúng ta - Thư mục
src/assets
: Chứa media, css, fonts - Còn lại những file config sẽ được mình giới thiệu ở những phần dưới
🥇Khởi tạo dự án React
🥈Khởi tạo một dự án nodejs
Khởi tạo bằng yarn
hoặc npm
tùy mọi người, nhưng mình khuyến khích mọi người dùng yarn
vì yarn
nhanh hơn, ít lỗi hơn.
yarn init --yes
Hoặc
npm init --yes
🥈Cài các package cần thiết
Cài đặt React vào dependencies
yarn add react react-dom
Cài type cho React vào devDependencies
yarn add -D @types/react @types/react-dom typescript
Cài đặt Webpack
yarn add -D webpack webpack-cli webpack-dev-server
Cài đặt Babel
yarn add -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript babel-loader
Cài đặt các loader và plugins bổ trợ
yarn add -D clean-webpack-plugin compression-webpack-plugin copy-webpack-plugin css-loader css-minimizer-webpack-plugin dotenv-webpack file-loader html-webpack-plugin mini-css-extract-plugin sass sass-loader serve webpack-bundle-analyzer
clean-webpack-plugin
: Dọn dẹp thư mục buildcompression-webpack-plugin
: Nén gzip, brotli file buildcopy-webpack-plugin
: Copy các file trong thư mụcpublic
vào thư mụcdist
css-loader
: import css trong dự áncss-minimizer-webpack-plugin
: minify cssdotenv-webpack
: Giúp dùng được các biến môi trường trong file.env
file-loader
: import ảnh, font trong dự ánhtml-webpack-plugin
: Tự động thêmscript
vàstyle
tag vào file htmlmini-css-extract-plugin
: Tách css ra thành file riêng khi build thay vì đưa vào file jssass
: Giúp dùng sass cho dự ánsass-loader
: Cũng giúp dùng Sass cho dự án, phải cài cả 2sass sass-loader
serve
: Giúp preview file buildwebpack-bundle-analyzer
: Phân tích kích thước file build
Cài đặt ESLint và Prettier
yarn add -D eslint prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks eslint-webpack-plugin eslint-import-resolver-typescript
🥇Config dự án
🥈Tạo file public/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/icon.png" />
<title>React Typescript Webpack | duthanhduoc.com</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
🥈Thêm file ảnh public/icon.png
vào
🥈Tạo file .babelrc
ở thư mục root
Mục đích là chứa các preset babel
{
"presets": ["@babel/preset-env", "@babel/preset-typescript", ["@babel/preset-react", { "runtime": "automatic" }]]
}
🥈Tạo file .browserslistrc
ở thư mục root
Mục đích là cấu hình target trình duyệt mà dự án chúng ta hỗ trợ
# Hỗ trợ các phiên bản trình duyệt mà có lượng người dùng lớn hơn 0.2% và chưa bị khai tử
> 0.2% and not dead
🥈Tạo file .editorconfig
ở thư mục root
Mục đích là cấu hình các config đồng bộ các editor với nhau nếu dự án có nhiều người tham gia.
Để VS Code hiểu được file này thì anh em cài Extension là EditorConfig for VS Code
nhé
[*]
indent_size = 2
indent_style = space
🥈Tạo file .env
ở thư mục root
Mục đích là chứa các biến môi trường dùng trong dự án React của chúng ta
HOST=https://duthanhduoc.com
🥈Tạo file .eslintrc.js
ở thư mục root
Mục đích là chứa cấu hình ESLint
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path')
module.exports = {
extends: [
// Chúng ta sẽ dùng các rule mặc định từ các plugin mà chúng ta đã cài.
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:import/recommended',
'plugin:jsx-a11y/recommended',
'plugin:@typescript-eslint/recommended',
// Disable các rule mà eslint xung đột với prettier.
// Để cái này ở dưới để nó override các rule phía trên!.
'eslint-config-prettier',
'prettier'
],
plugins: ['prettier'],
settings: {
react: {
// Nói eslint-plugin-react tự động biết version của React.
version: 'detect'
},
// Nói ESLint cách xử lý các import
'import/resolver': {
node: {
paths: [path.resolve(__dirname)],
extensions: ['.js', '.jsx', '.ts', '.tsx']
},
typescript: {
project: path.resolve(__dirname, './tsconfig.json')
}
}
},
env: {
node: true
},
rules: {
// Tắt rule yêu cầu import React trong file jsx
'react/react-in-jsx-scope': 'off',
// Cảnh báo khi thẻ <a target='_blank'> mà không có rel="noreferrer"
'react/jsx-no-target-blank': 'warn',
// Tăng cường một số rule prettier (copy từ file .prettierrc qua)
'prettier/prettier': [
'warn',
{
arrowParens: 'always',
semi: false,
trailingComma: 'none',
tabWidth: 2,
endOfLine: 'auto',
useTabs: false,
singleQuote: true,
printWidth: 120,
jsxSingleQuote: true
}
]
}
}
🥈Tạo file .eslintignore
ở thư mục root
Mục đích là ESLint bỏ qua những file không cần thiết
node_modules/
dist/
🥈Tạo file .gitignore
ở thư mục root
Mục đích là Git bỏ qua những file không cần thiết
node_modules/
dist/
🥈Tạo file .prettierrc
ở thư mục root
Mục đích là cấu hình prettier. Anh em nên cài Extension Prettier - Code formatter
cho VS Code để nó hiểu nhé.
{
"arrowParens": "always",
"semi": false,
"trailingComma": "none",
"tabWidth": 2,
"endOfLine": "auto",
"useTabs": false,
"singleQuote": true,
"printWidth": 120,
"jsxSingleQuote": true
}
🥈Tạo file .prettierignore
ở thư mục root
Mục đích là Prettier bỏ qua các file không cần thiết
node_modules/
dist/
🥈Tạo file tsconfig.json
ở thư mục root
Mục đích là cấu hình Typescript cho dự án chúng ta
{
"compilerOptions": {
"target": "ES2015",
"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",
"baseUrl": ".",
"paths": {
"@pages/*": ["src/pages/*"]
}
},
"include": ["src"]
}
🥈Mở file package.json
lên, thêm đoạn script
dưới vào
"scripts": {
"dev": "webpack-dev-server --mode development",
"build": "webpack --mode production",
"build:analyze": "webpack --mode production --env analyze",
"preview": "serve dist/ -s",
"lint": "eslint --ext js,jsx,ts,tsx src/",
"lint:fix": "eslint --fix --ext js,jsx,ts,tsx src/",
"prettier": "prettier --check \"src/**/(*.tsx|*.ts|*.jsx|*.js|*.scss|*.css)\"",
"prettier:fix": "prettier --write \"src/**/(*.tsx|*.ts|*.jsx|*.js|*.scss|*.css)\""
}
🥈Tạo file webpack.config.js
tại thư mục root
Mục đích là cấu hình webpack cho dự án ReactJs Typescript.
Chi tiết mình có giải thích hết trong file cấu hình này, các bạn đọc là sẽ hiểu nhé.
File cấu hình này áp dụng cho 2 môi trường là development và production luôn.
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin')
const Dotenv = require('dotenv-webpack')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const webpack = require('webpack')
// Cái dòng này giúp Editor gợi ý được các giá trị cho dòng code config ngay phía dưới nó
// (giống như đang dùng Typescript vậy đó 😉)
/** @type {(env: any, arg: {mode: string}) => import('webpack').Configuration} **/
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production'
const isAnalyze = Boolean(env?.analyze)
/** @type {import('webpack').Configuration} **/
const config = {
// Quy định cách webpack giải quyết các file
resolve: {
// Giải quyết các file theo thứ tự ưu tiên từ trái sang phải nếu import
// các file cùng một tên nhưng các đuôi mở rộng
extensions: ['.tsx', '.ts', '.jsx', '.js'],
alias: {
// Cấu hình alias cho webpack
// để khi import cho ngắn gọn
// Ví dụ: import Login from '@pages/Login'
// Thay vì: import Login from '../pages/Login' chẳng hạn
'@pages': path.resolve(__dirname, './src/pages')
}
},
// File đầu vào cho webpack, file này thường là file import mọi file khác
entry: ['./src/index.tsx'],
// Khai báo các module dùng trong webpack
module: {
rules: [
{
test: /\.tsx?$/, // duyệt các file .ts || .tsx
exclude: /node_modules/,
use: ['babel-loader'] // Giúp dịch code TS, React sang JS,
},
{
test: /\.(s[ac]ss|css)$/, // duyệt các file sass || scss || css
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader', // dùng import 'filename.css' trong file tsx, ts
options: { sourceMap: !isProduction } // Hiển thị sourcemap ở môi trường dev cho dễ debug
},
{
loader: 'sass-loader', // biên dịch sass sang css
options: { sourceMap: !isProduction }
}
]
},
{
test: /\.(png|svg|jpg|gif)$/, // Dùng để import file ảnh, nếu có video/ảnh định dạng khác thì thêm vào đây
use: [
{
loader: 'file-loader',
options: {
name: isProduction ? 'static/media/[name].[contenthash:6].[ext]' : '[path][name].[ext]'
}
}
]
},
{
test: /\.(eot|ttf|woff|woff2)$/, // Dùng để import font
use: [
{
loader: 'file-loader',
options: {
name: isProduction ? 'static/fonts/[name].[ext]' : '[path][name].[ext]'
}
}
]
}
]
},
output: {
filename: 'static/js/main.[contenthash:6].js', // Thêm mã hash tên file dựa vào content để tránh bị cache bởi CDN hay browser.
path: path.resolve(__dirname, 'dist'), // Build ra thư mục dist
publicPath: '/'
},
devServer: {
hot: true, // enable Hot Module Replacement, kiểu như reload nhanh
port: 3000, // Chạy port 3000 khi dev
historyApiFallback: true, // Phải set true nếu không khi bạn dùng lazyload module React thì sẽ gặp lỗi không load được file.
// Cấu hình phục vụ file html trong public
static: {
directory: path.resolve(__dirname, 'public', 'index.html'),
serveIndex: true,
watch: true // khi thay đổi content trong index.html thì cũng sẽ reload
}
},
devtool: isProduction ? false : 'source-map',
plugins: [
// Đưa css ra thành một file .css riêng biệt thay vì bỏ vào file .js
new MiniCssExtractPlugin({
filename: isProduction ? 'static/css/[name].[contenthash:6].css' : '[name].css'
}),
// Dùng biến môi trường env trong dự án
new Dotenv(),
// Copy mọi files trong folder public trừ file index.html
new CopyWebpackPlugin({
patterns: [
{
from: 'public',
to: '.',
filter: (name) => {
return !name.endsWith('index.html')
}
}
]
}),
// Plugin hỗ trợ thêm thẻ style và script vào index.html
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public', 'index.html'),
filename: 'index.html'
}),
// Thêm eslint cho webpack
new ESLintPlugin({
extensions: ['.tsx', '.ts', '.js', '.jsx']
})
]
}
//🚀 Nếu build thì sẽ thêm một số config
if (isProduction) {
config.plugins = [
...config.plugins,
new webpack.ProgressPlugin(), // Hiển thị % khi build
// Nén brotli css và js nhưng không hiểu sao chỉ có js được nén 🥲
new CompressionPlugin({
test: /\.(css|js)$/,
algorithm: 'brotliCompress'
}),
new CleanWebpackPlugin() // Dọn dẹp thư mục build trước đó để chuẩn bị cho bản build hiện tại
]
if (isAnalyze) {
config.plugins = [...config.plugins, new BundleAnalyzerPlugin()]
}
config.optimization = {
minimizer: [
`...`, // Cú pháp kế thừa bộ minimizers mặc định trong webpack 5 (i.e. `terser-webpack-plugin`)
new CssMinimizerPlugin() // minify css
]
}
}
return config
}
🥈Thêm code React vào
src/App.tsx
import { FC, useState } from 'react'
import reactlogo from './assets/images/react.svg'
import './assets/styles/app.css'
import './assets/styles/app.scss'
const App: FC = () => {
const [fullname, setFullname] = useState('Dư Thanh Được')
console.log(fullname)
return (
<div>
<img src={reactlogo} alt='React Logo' width={100} height={100} />
<h1>{fullname}</h1>
<h2>Bài viết được viết tại blog {process.env.HOST}</h2>
</div>
)
}
export default App
src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
Thêm file src/react-app-env.d.ts
để import ảnh không bị typescript bắt lỗi.
Nếu sau này có thêm những định dạng video, ảnh khác thì khai báo thêm vào trong file này
// images
declare module '*.png' {
const src: string
export default src
}
declare module '*.jpg' {
const src: string
export default src
}
declare module '*.jpeg' {
const src: string
export default src
}
declare module '*.svg' {
const src: string
export default src
}
Tạo file src/assets/styles/app.css
* {
margin: 0;
padding: 0;
}
Tạo file src/assets/styles/app.scss
$bg-color: #e6e92c;
body {
background: $bg-color;
font-size: 20px;
}
Tạo thêm folder images
nằm trong src/assets
. Thêm tấm ảnh vào để test luôn.
Ở đây mình thêm react.svg
vào
🥇Chạy thử
🥈Môi trường Dev
Chạy yarn dev
để xem thành quả
🥈Build dự án
Để build dự án thì chạy yarn build
Nó sẽ xuất hiện thư mục dist
với cấu trúc như dưới đây
📦dist
┣ 📂static
┃ ┣ 📂css
┃ ┃ ┗ 📜main.3a69ca.css
┃ ┣ 📂js
┃ ┃ ┣ 📜main.3ac995.js
┃ ┃ ┣ 📜main.3ac995.js.br
┃ ┃ ┗ 📜main.3ac995.js.LICENSE.txt
┃ ┗ 📂media
┃ ┃ ┗ 📜react.f57b34.svg
┣ 📜icon.png
┗ 📜index.html
🥈Preview dự án React sau khi build
Chạy yarn preview
để preview nhé, yêu cầu là trong thư mục dist
phải có bản build mới preview được
🥈Build dự án kèm phân tích file build
Để phân tích file build thì ae chỉ cần chạy câu lệnh yarn build:analyze
🥈Kiểm tra ESLint và Prettier
- Kiểm tra dự án có bị lỗi gì liên quan ESLint không:
yarn lint
- Tự động fix các lỗi liên quan ESLint (không phải cái gì cũng fix được, nhưng fix cũng nhiều):
yarn lint:fix
- Kiểm tra dự án có bị lỗi gì liên quan Prettier không:
yarn prettier
- Tự động fix các lỗi liên quan Prettier:
yarn prettier:fix
Kiến thức trong khóa học Next.js này đã giúp mình kiếm hơn 1 tỉ đồng/năm
Phew! Cuối cùng bạn cũng đã đọc xong. Bài viết này có hơi dài một tí vì mình muốn nó đầy đủ nhất có thể 😅
Website bạn đang đọc được viết bằng Next.js TypeScript và tối ưu từng chi tiết nhỏ như SEO, hiệu suất, nội dung để đảm bảo bạn có trải nghiệm tốt nhất.
Với lượt view trung bình là 30k/tháng (dù website rất ít bài viết). Website này đem lại doanh thu 1 năm vừa qua là hơn 1 tỉ đồng
Đó chính là sức mạnh của SEO, sức mạnh của Next.js.
Mình luôn tin rằng kiến thức là chìa khóa giúp chúng ta đi nhanh nhất.
Mình đã dành hơn 6 tháng để phát triển khóa học Next.js Super | Dự án quản lý quán ăn & gọi món bằng QR Code. Trong khóa này các bạn sẽ được học mọi thứ về framework Next.js, các kiến thức từ cơ bản cho đến nâng cao nhất, mục đích của mình là giúp bạn chinh phục mức lương 25 - 30 triêu/tháng
Nếu bạn cảm thấy bài viết này của mình hữu ích, mình nghĩ bạn sẽ thích hợp với phong cách dạy của mình. Không như bài viết này, khóa học là sự kết hợp giữa các bài viết, video, bài tập nhỏ và dự án lớn có thể xin việc được ngay. Học xong mình đảm bảo bạn sẽ lên tay ngay. 💪🏻