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>
)
/* eslint-disable @typescript-eslint/no-var-requires */constpath=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'] } } }, 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 } ] }}
/* eslint-disable @typescript-eslint/no-var-requires */constpath=require('path')constHtmlWebpackPlugin=require('html-webpack-plugin')constCopyWebpackPlugin=require('copy-webpack-plugin')constMiniCssExtractPlugin=require('mini-css-extract-plugin')constCompressionPlugin=require('compression-webpack-plugin')constCssMinimizerPlugin=require('css-minimizer-webpack-plugin')const { CleanWebpackPlugin } =require('clean-webpack-plugin')constESLintPlugin=require('eslint-webpack-plugin')constDotenv=require('dotenv-webpack')constBundleAnalyzerPlugin=require('webpack-bundle-analyzer').BundleAnalyzerPluginconstwebpack=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) => {constisProduction=argv.mode ==='production'constisAnalyze=Boolean(env?.analyze)/** @type{import('webpack').Configuration} **/constconfig= {// 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'] },// 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 .jsnewMiniCssExtractPlugin({ filename: isProduction ?'static/css/[name].[contenthash:6].css':'[name].css' }),// Dùng biến môi trường env trong dự ánnewDotenv(),// Copy mọi files trong folder public trừ file index.htmlnewCopyWebpackPlugin({ patterns: [ { from:'public', to:'.',filter: (name) => {return!name.endsWith('index.html') } } ] }),// Plugin hỗ trợ thêm thẻ style và script vào index.htmlnewHtmlWebpackPlugin({ template:path.resolve(__dirname,'public','index.html'), filename:'index.html' }),// Thêm eslint cho webpacknewESLintPlugin({ extensions: ['.tsx','.ts','.js','.jsx'] }) ] }//🚀 Nếu build thì sẽ thêm một số configif (isProduction) {config.plugins = [...config.plugins,newwebpack.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 🥲newCompressionPlugin({ test: /\.(css|js)$/, algorithm:'brotliCompress' }),newCleanWebpackPlugin() // 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,newBundleAnalyzerPlugin()] }config.optimization = { minimizer: [`...`,// Cú pháp kế thừa bộ minimizers mặc định trong webpack 5 (i.e. `terser-webpack-plugin`)newCssMinimizerPlugin() // minify css ] } }return config}
clean-webpack-plugin: Giúp dọn dẹp thư mục build trước khi build webpack. Ví dụ thư mục build của bạn đang chứa bản build trước, bây giờ bạn build lại thì plugin này sẽ xóa bản build trước.
compression-webpack-plugin: Giúp bạn nén các file css, js, html… thành gzip
copy-webpack-plugin: Giúp bạn copy các file ở thư mục dev vào thư mục build. Ví dụ bạn có các file như favicon.ico, robots.txt cùng cấp với index.html, bạn muốn khi build xong thì các file này cũng có mặt ở bản build. Nếu không có plugin này thì bạn phải copy thủ công.
dotenv-webpack: Giúp bạn dùng được các biến môi trường ở file .env và trong app của bạn
mini-css-extract-plugin: Bình thường thì css sẽ nằm trong file js sau khi build. Và khi chạy app thì js sẽ thêm các đoạn css đó vào thẻ <style></style>. Bây giờ mình không muốn như vậy, mình muốn css phải nằm ở file riêng biệt với js và khi chạy app thì js sẽ tự import bằng thẻ <link>. Đó là chức năng của plugin này
webpack-bundle-analyzer: Giúp bạn phân tích bản build, coi thử thư viện nào đang chiếm bao nhiêu % bản build,…
Giải thích: Ngoài prettier, eslint-config-prettier và eslint-plugin-prettier thì còn lại đều là các plugin tiêu chuẩn tương tự như bộ cài Create React App.
isDev: Chúng ta có 2 mode là development và production tương đương với dev và build. 2 mode này được truyền vào thông qua –mode ở script trong package.json.
isAnalyze: Nhìn vào file package.json chúng ta có câu lệnh build:analyze, mình có truyền biến analyze vào webpack thông qua –env. Biến này dùng để xác định bạn có dùng plugin
BundleAnalyzerPlugin hay không.
basePlugins: Những plugins dùng trong mode development.Trong CopyPlugin ta thực hiện copy các file từ public sang thư mục build
CopyPlugin: Mình copy mọi file trong thư mục public vào thư mục build, ngoại trừ file index.html. Vì index.html đã có HtmlWebpackPlugin thực hiện việc copy và generate code.
webpack.ProgressPlugin() giúp chúng ta hiện % khi chạy webpack
CompressionPlugin() giúp chúng ta nén file build thành gzip, thỉnh thoảng bạn sẽ thấy một số file kích thước nhỏ không được nén, bạn có thể xem cấu hình nén và điều kiện được nén tại đây
prodPlugins: Những plugins dùng trong mode production.
entry: File đầu vào cho webpack, file này thường là file import mọi file khác
module.rules: Chứa các loader của webpack
Các bạn để ý chỗ option.name ở file-loader: Đây là nơi bạn có thể thay đổi tên và đường dẫn file sau khi build. Môi trường dev thì mình giữ nguyên tên và đường dẫn (như vậy khi inspect trên trình duyệt sẽ dễ dàng thấy nguồn gốc file từ đâu ra), còn môi trường production thì mình sẽ chuyển vào thư mục static.
contenthash:6 nghĩa là thêm 1 đoạn hash gồm 6 ký tự vào tên file.
resolve.extensions: Thứ tự ưu tiên các file khi import
alias: Tạo alias thuận tiện cho việc import trong webpack. Những nơi cần đường dẫn tuyệt đối thì ta phải dùng path.resolve() hoặc path.join()
output.path: Đường dẫn thư mục build. Mình đặt tên thư mục build là build luôn, cho giống với Create React App
output.filename: Tên file bundle sau khi được build. Cũng có thể quy định thư mục mà file build thuộc về
output.publicPath: Chứa đường dẫn tương đối mà từ file index.html trỏ đến các file trong thư mục build sau khi build. Lưu ý là file index.html được build nằm trong thư mục tên là build. Ở các bài trước các bạn sẽ thấy mình để giá trị này là “”, nhưng bây giờ “/”. Lý do khi deploy thì “” trình duyệt nó bảo lỗi không load file được, nên phải thêm “/” vào nhé.
output.environment: Mặc định webpack generate ra code dùng 1 số cú pháp của ES6, nhưng target mình mong muốn là ES5 nên mình cần chỉnh một số thông số như sau.
arrowFunction: Hỗ trợ arrow function.
bigIntLiteral: Hỗ trợ BigInt
const: Hỗ trợ khai báo const và let
destructuring: Hỗ trợ destructuring
dynamicImport: Hỗ trợ async import
forOf: Hỗ trợ vòng lặp forOf cho các array
module: Hỗ trợ moudle ES6 (import … from ‘…’)’
output.devtool: tùy chọn sourcemap
devServer.contentBase: Chứa đường dẫn tương đối đến file index.html
devServer.port: port khi chạy localhost
devServer.hot: Chế độ hot reload. Mặc định thì ở dev server thì webpack sẽ refresh lại trang mỗi khi có thay đổi nhỏ trong code.
devServer.publicPath: Chứa đường dẫn tương đối từ thư mục root trỏ đến thư mục build (ở đây là dist). Chú ý phải thêm / ở trước và sau. Nhưng vì dùng HtmlWebpackPlugin nên ta sẽ tính từ chính thư mục dist. Vì thế giá trị cần dùng là /. Ở đây mình không dùng giá trị nào cả, vì mặc định nó đã là /
devServer.watchContentBase: Nếu bạn có thay đổi gì trong file index.html thì trình duyệt cũng tự động reload.
devServer.historyApiFallback: 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.
plugins: Chứa các plugin Webpack.
performance.maxEntrypointSize: Khi có 1 file build vượt quá giới hạn này (tính bằng byte) thì sẽ bị warning trên terminal.
Trên đây là giải thích cho một số cấu hình của webpack, còn lại mình nghĩ các bạn nhìn vào sẽ dễ dàng hiểu, nếu không hiểu đoạn nào thì có thể comment phía dưới, mình sẽ giải đáp sớm nhất. Hoặc coi lại Bài 1 và Bài 2 của mình
Để chạy khi devyarn start
Để build ra thành phẩm phục vụ deployyarn build
Để build và phân tích source codeyarn build:analyze
Ngoài ra bạn có thể yarn lint, yarn lint:fix, yarn prettier, yarn prettier:fix như đã định nghĩa trong file package.json
Thư mục build
Tóm lại
Cảm ơn mọi người đã đọc đến đây, bài này khá dài và yêu cầu kiến thức khá nhiều, hy vọng mình đã giải thích chi tiết để mọi người có thể hiểu được. Hẹn gặp lại mọi người ở các bài tiếp theo
Với Create react app, bạn chỉ mất vài click để tạo 1 project React hoàn chỉnh, không yêu cầu kiến thức chuyên sâu gì về webpack hay babel. Nhưng nếu bạn vẫn chưa hài lòng với những tính năng mà CRA mang đến, ví dụ không hiện source-map CSS khi dev hay đơn giản là muốn cấu hình sâu hơn thì đã đến lúc bạn phải làm việc với Webpack rồi. Công ty mình toàn dùng webpack thôi chứ không có dùng Create react app