在当今的前端开发领域,构建现代化的前端开发环境对于项目的成功至关重要。本篇博客将介绍如何结合 React 18、TypeScript 和 Webpack 5 来打造高效、现代化的前端开发环境
一、项目创建
本篇博客使用pnpm包管理器
运行 pnpm -v 命令查看本机pnpm版本
运行 pnpm i 命令,它会在根目录下生成一个package.json文件
二、基本项目结构
在根目录下新建基本的项目结构
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">
<title>webpack5-react-ts</title>
</head>
<body>
<!-- 容器节点 -->
<div id="root"></div>
</body>
</html>
三、引入react
首先安装依赖
pnpm i react react-dom
声明依赖
pnpm i @types/react @types/react-dom -D
然后将入口文件写好(src/index.tsx)
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const root = document.querySelector('#root')
if(root) {
createRoot(root).render(<App />)
}
App.css中写入内容
h2 {
color: red;
}
最后在App.tsx中写入内容
import React from 'react'
import './App.css'
function App() {
return <h2>Hello East_White</h2>
}
export default App
四、引入typescript
运行以下命令安装typescript依赖
pnpm i typescript -D
pnpm i babel-loader ts-node @babel/core @babel/preset-react @babel/presettypescript @babel/preset-env core-js -D
初始化tsconfig.json
npx tsc --init
运行了npx tsc --init后,会在根目录下自动生成一个tsconfig.json文件
五、webpack配置
1、安装webpack依赖
pnpm i webpack webpack-cli -D
2、配置webpack.base.ts文件
import { Configuration } from 'webpack';
const path = require("path");
const baseConfig: Configuration = {
entry: path.join(__dirname, "../src/index.tsx"), // 入口文件
// 打包出口文件
output: {
filename: "static/js/[name].js", // 每个输出js的名称
path: path.join(__dirname, "../dist"), // 打包结果输出路径
clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了
publicPath: "/", // 打包后文件的公共前缀路径
},
// loader 配置
module: {
rules: [],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
// plugins 的配置
plugins: []
};
那咱们就安装一下@types/node依赖
pnpm i @types/node -D
pnpm i style-loader css-loader html-webpack-plugin -D
然后完善 webpack.base.ts
import { Configuration } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
const path = require("path");
const baseConfig: Configuration = {
entry: path.join(__dirname, "../src/index.tsx"), // 入口文件
// 打包出口文件
output: {
filename: "static/js/[name].js", // 每个输出js的名称
path: path.join(__dirname, "../dist"), // 打包结果输出路径
clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了
publicPath: "/", // 打包后文件的公共前缀路径
},
// loader 配置
module: {
rules: [
{
test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
use: {
loader: "babel-loader",
options: {
// 预设执行顺序由右往左,所以先处理ts,再处理tsx
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文件,babel-loader会自动寻找上面配置好的文件.browserslistrc
targets: { browsers: ["> 1%", "last 2 versions", "not ie<= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime":"automatic" 添加到配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
},
},
},
{
test: /.css$/, //匹配 css 文件
use: ["style-loader", "css-loader"],
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js"],
},
}
module.exports = {
// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc
// "targets": {
// "chrome": 35,
// "ie": 9
// },
targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
};
module: {
rules: [
{
test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
use: "babel-loader"
},
],
},
3、webpack.dev.ts
pnpm i webpack-dev-server webpack-merge -D
import path from "path";
import { merge } from "webpack-merge";
import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from "webpack-devserver";
import baseConfig from "./webpack.base";
interface Configuration extends WebpackConfiguration {
devServer?: WebpackDevServerConfiguration;
}
const host = "127.0.0.1";
const port = "8082";
// 合并公共配置,并添加开发环境配置
const devConfig: Configuration = merge(baseConfig, {
mode: "development", // 开发模式,打包更加快速,省了代码优化步骤
devtool: "eval-cheap-module-source-map",
devServer: {
host,
port,
open: true, // 是否自动打开
compress: false, // gzip压缩,开发环境不开启,提升热更新速度
hot: true, // 开启热更新
historyApiFallback: true, // 解决history路由404问题
setupExitSignals: true, // 允许在 SIGINT 和 SIGTERM 信号时关闭开发服务器和退出进程。
static: {
directory: path.join(__dirname, "../public"), // 托管静态资源public文件夹
},
headers: { "Access-Control-Allow-Origin": "*" }, // HTTP响应头设置,允许任何来源进行跨域请求
},
});
export default devConfig;
"scripts": {
"dev": "webpack serve -c build/webpack.dev.ts"
},
只需要在 tsconfig.json 中加入一行 "jsx": "react-jsx" 即可
{
},
"include": ["./src"
"compilerOptions": {
"target": "es2016",
"esModuleInterop": true,
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"jsx": "react-jsx" // 这里改成react-jsx,就不需要在tsx文件中手动引入React了
]
}
4、webpack.prod.ts
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import baseConfig from "./webpack.base";
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
});
export default prodConfig
"scripts": {
"build": "webpack -c build/webpack.prod.ts"
},
# 如果之前使用npm,最简单的方法就是使用如下命令
npm i serve -g
# 如果是首次使用pnpm安装全局依赖,通过如下命令
pnpm setup
source ~/.zshrc
pnpm i serve -g
5、copy静态资源
pnpm i copy-webpack-plugin -D
然后修改 webpack.base.ts
const baseConfig: Configuration = {
// ...
plugins: [
new HtmlWebpackPlugin({
title: "webpack5-react-ts",
filename: "index.html",
// 复制 'index.html' 文件,并自动引入打包输出的所有资源(js/css)
template: path.join(__dirname, "../public/index.html"),
inject: true, // 自动注入静态资源
hash: true,
cache: false,
// 压缩html资源
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true, //去空格
removeComments: true, // 去注释
minifyJS: true, // 在脚本元素和事件属性中缩小JavaScript(使用UglifyJS)
minifyCSS: true, // 缩小CSS样式元素和样式属性
}
})
],
};
export default baseConfig;
import path from "path";
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import CopyPlugin from "copy-webpack-plugin";
import baseConfig from "./webpack.base";
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"), // 复制public下文件
to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
filter: (source) => !source.includes("index.html"), // 忽略
index.html
},
],
}),
],
});
export default prodConfig;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- 绝对路径引入图标文件 -->
<link data-n-head="ssr" rel="icon" type="image/x-icon"
href="/favicon.ico">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webpack5-react-ts</title>
</head>
<body>
<!-- 容器节点 -->
<div id="root"></div>
</body>
</html>
六、配置环境变量
1、corss-env + DefinePlugin
安装 cross-env
pnpm i cross-env -D
"scripts": {
"dev:dev": "cross-env NODE_ENV=development BASE_ENV=development webpack
serve -c build/webpack.dev.ts",
"dev:test": "cross-env NODE_ENV=development BASE_ENV=test webpack serve
-c build/webpack.dev.ts",
"dev:pre": "cross-env NODE_ENV=development BASE_ENV=pre webpack serve -c
build/webpack.dev.ts",
"dev:prod": "cross-env NODE_ENV=development BASE_ENV=production webpack
serve -c build/webpack.dev.ts",
"build:dev": "cross-env NODE_ENV=production BASE_ENV=development webpack
-c build/webpack.prod.ts",
"build:test": "cross-env NODE_ENV=production BASE_ENV=test webpack -c
build/webpack.prod.ts",
"build:pre": "cross-env NODE_ENV=production BASE_ENV=pre webpack -c
build/webpack.prod.ts",
"build:prod": "cross-env NODE_ENV=production BASE_ENV=production webpack
-c build/webpack.prod.ts"
},
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
// NODE_ENV production
// BASE_ENV development
import { DefinePlugin } from 'webpack'
module.export = {
// ...
plugins: [
// ...
new DefinePlugin({
'process.env': JSON.stringify(process.env)
})
]
}
declare module 'process' {
global {
namespace NodeJS {
export interface ProcessEnv {
BASE_ENV: 'development' | 'test' | 'pre' | 'production'
NODE_ENV: 'development' | 'production'
}
}
}
}
{
"compilerOptions": {
"target": "es2016", // 编译输出的JavaScript版本为ES2016
"esModuleInterop": true, // 允许更好的兼容性与ECMAScript模块导入
"module": "commonjs", // 指定生成哪个模块系统代码,这里是CommonJS
"forceConsistentCasingInFileNames": true, // 确保文件名大小写一致,有助于跨平台开发
"strict": true, // 启用所有严格的类型检查选项
"skipLibCheck": true, // 跳过声明文件的类型检查,可以提高编译速度
"typeRoots": ["./typings/*.d.ts", "node_modules/@types"], // 指定类型声明文件的路径
"jsx": "react-jsx" // react18这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src", "./typings/*.d.ts"] // 指定哪些文件或目录应该被包含在编译范围内
}
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
// NODE_ENV development
// BASE_ENV test
2、配置多环境运行配置
安装依赖
pnpm i dotenv
├── env
├── .env.development # 开发环境
├── .env.test # 测试环境
├── .env.pre # 预发布环境
└── .env.production # 生产环境
// env/.env.development
REACT_APP_API_URL=https://api-dev.com
// env/.env.test
REACT_APP_API_URL=https://api-test.com
// env/.env.pre
REACT_APP_API_URL=https://api-pre.com
// env/.env.production
REACT_APP_API_URL=https://api-prod.com
import path from "path";
import { Configuration, DefinePlugin } from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin";
import * as dotenv from "dotenv";
// 加载配置文件
const envConfig = dotenv.config({
path: path.resolve(__dirname, "../env/.env." + process.env.BASE_ENV),
});
// console.log("process.env", process.env);
// console.log("NODE_ENV", process.env.BASE_ENV);
// console.log("REACT_APP_API_URL", process.env.REACT_APP_API_URL);
const baseConfig: Configuration = {
// ...
plugins: [
// 注入到业务
new DefinePlugin({
"process.env": JSON.stringify(envConfig.parsed),
"process.env.BASE_ENV": JSON.stringify(process.env.BASE_ENV),
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
}),
].filter(Boolean), // // 去除数组中的假值(例如,如果某些插件的条件不满足而导致未定义)
};
export default baseConfig;
import { createRoot } from 'react-dom/client';
import App from './App';
// const root = document.getElementById('root');
const root = document.querySelector('#root')
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
console.log("process.env", process.env);
if(root) {
createRoot(root).render(<App />)
}
七、文件别名
先在 webpack.base.ts 中配置
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".less", ".css"],
// 别名需要配置两个地方,这里和 tsconfig.json
alias: {
"@": path.join(__dirname, "../src")
},
},
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
},
}
import '@/App.css'
function App() {
return <h2>webpack5 - react - ts < /h2>
}
export default App
八、引入 less、sass(scss)、stylus
pnpm i less less-loader sass-loader sass stylus stylus-loader -D
const cssRegex = /\.css$/;
const sassRegex = /\.(scss|sass)$/;
const lessRegex = /\.less$/;
const stylRegex = /\.styl$/;
const styleLoadersArray = [
"style-loader",
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[path][name]__[local]--[hash:5]",
},
},
},
];
const baseConfig: Configuration = {
// ...
module: {
rules: [
// ...
{
test: cssRegex, // 匹配css文件
use: styleLoadersArray,
},
{
test: lessRegex,
use: [
...styleLoadersArray,
{
loader: 'less-loader',
options: {
lessOptions: {
// 如果要在less中写js的语法,需要加这一配置
javascriptEnabled: true
}
}
}
]
},
{
test: sassRegex,
use: [
...styleLoadersArray,
{
loader: 'sass-loader',
options: {
implementation: require('sass') // 使用dart-sass代替node-sass
}
}
]
},
{
test: stylRegex,
use: [
...styleLoadersArray,
'stylus-loader'
]
}
],
},
// ...
};
export default baseConfig;
/* src/app.less */
@color: red;
.lessBox {
.box {
color: @color;
background - color: lightblue;
transform: translateX(100);
&:before{
@arr: 'hello', 'world';
content: `@{arr}.join(' ').toUpperCase()`;
}
}
}
/* src/app.scss */
$blue: #1875e7;
$side: left;
.scssBox {
margin: 20px 0;
.box {
color: $blue;
background - color: rgb(226, 223, 223);
border: 1px solid grey;
margin -#{ $side }: 20px;
padding: (20px / 2);
}
}
/* src/app.styl */
.stylBox
.box
color: red;
background - color: yellow;
在 App.tsx 中引入:
import '@/App.css'
import lessStyles from './app.less'
import scssStyles from './app.scss'
import stylStyles from './app.styl'
function App() {
return <div>
<h2>webpack5 - react - ts < /h2>
< div className = { lessStyles['lessBox']} >
<div className={ lessStyles['box'] }> lessBox < /div>
< /div>
< div className = { scssStyles['scssBox']} >
<div className={ scssStyles['box'] }> scssBox < /div>
< /div>
< div className = { stylStyles['stylBox']} >
<div className={ stylStyles['box'] }> stylBox < /div>
< /div>
< /div>
}
export default App
module: {
rules: [
// ...
{
test: lessRegex,
use: [
...styleLoadersArray,
{
loader: "less-loader",
options: {
lessOptions: {
importLoaders: 2,
// 可以加入modules: true,这样就不需要在less文件名加module了
modules: true,
// 如果要在less中写类型js的语法,需要加这一个配置
javascriptEnabled: true
},
},
},
],
},
// ...
],
},
// ...
};
export default baseConfig
// src/typings/global.d.ts
/* CSS MODULES */
declare module '*.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.scss' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.sass' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.less' {const classes: { [key: string]: string };
export default classes;
}
declare module '*.styl' {
const classes: { [key: string]: string };
export default classes;
}
九、处理其他常用资源
1、处理图片
{
output: {
// ... 这里自定义输出文件名的方式是,将某些资源发送到指定目录
assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
// ...
{
test: /\.(png|jpe?g|gif|svg)$/i, // 匹配图片文件
type: "asset", // type选择asset
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb转base64
}
},
generator: {
filename: 'static/images/[hash][ext][query]', // 文件输出目录和命名
},
},
]
}
}
/* IMAGES */
declare module '*.svg' {
const ref: string;
export default ref;
}
declare module '*.bmp' {
const ref: string;
export default ref;
}
declare module '*.gif' {
const ref: string;
export default ref;
}
declare module '*.jpg' {
const ref: string;
export default ref;
}
declare module '*.jpeg' {
const ref: string;
export default ref;
}
declare module '*.png' {
const ref: string;
export default ref;
}
import '@/App.css'
import lessStyles from '@/app.less'
import scssStyles from '@/app.scss'
import stylStyles from '@/app.styl'
import smallImg from '@/assets/images/5kb_img.jpeg'
import bigImg from '@/assets/images/10kb_img.png'
function App() {
return <div>
<h2>webpack5-react-ts</h2>
<div className={lessStyles['lessBox']}>
<div className={lessStyles['box']}>lessBox
<img src={smallImg} alt="小于10kb的图片" />
<img src={bigImg} alt="大于于10kb的图片" />
<div className={lessStyles['smallImg']}>小图片背景</div>
<div className={lessStyles['bigImg']}>大图片背景</div>
</div>
</div>
<div className={scssStyles['scssBox']}>
<div className={scssStyles['box']}>scssBox</div>
</div>
<div className={stylStyles['stylBox']}>
<div className={stylStyles['box']}>stylBox</div>
</div>
</div>
}
export default App
@color: red;
.lessBox {
.box {
color: @color;
background-color: lightblue;
transform: translateX(100);
&:before{
@arr: 'hello', 'world';
content: `@{arr}.join(' ').toUpperCase()`;
}
}
.smallImg {
width: 69px;
height: 75px;
background: url('@/assets/imgs/5kb_img.jpeg') no-repeat;
}
.bigImg {
width: 232px;
height: 154px;
background: url('@/assets/imgs/10kb_img.png') no-repeat;
}
}
2、处理字体和媒体
module: {
rules: [
// ...
{
test: /.(woff2?|eot|ttf|otf)$/, // 匹配字体图标文件
type: "asset", // type选择asset
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb转base64
}
},
generator: {
filename: 'static/fonts/[hash][ext][query]', // 文件输出目录和命名
},
},
{
test: /.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件
type: "asset", // type选择asset
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb转base64
}
},
generator: {
filename: 'static/media/[hash][ext][query]', // 文件输出目录和命名
},
},
]
}
@color: red;
@font-face {
font-family: "GoldmanBold";
src: local("GoldmanBold"),
url("@/assets/fonts/Phudu-Bold.ttf") format("truetype");
font-weight: bold;
}
.lessBox {
.box {
color: @color;
background-color: lightblue;
transform: translateX(100);
font-family: "GoldmanBold";
&:before{
@arr: 'hello', 'world';
content: `@{arr}.join(' ').toUpperCase()`;
}
}
}
3、处理json资源
// ...
{
// 匹配json文件
test: /\.json$/,
type: "json", // 模块资源类型为json模块
generator: {
// 这里专门针对json文件的处理
filename: "static/json/[name].[hash][ext][query]",
},
},
// ...
在src下增加一个json文件test.json
[
{
"name": "ian1",
"age": 18
},
{
"name": "ian2",
"age": 22
},
{
"name": "ian3",
"age": 23
}
]
import memberList from './test.json'
Cannot find module './test.json'. Consider using '--resolveJsonModule' to import
module with '.json' extension.
{
"compilerOptions": {
// ...
"resolveJsonModule": true,
// ...
},
// ...
}
import React, { PureComponent } from "react";
// 装饰器为,组件添加age属性
function addAge(Target: Function) {
Target.prototype.age = 111
}
// 使用装饰器
@addAge
class Cls extends PureComponent {
age?: number
render() {
return (
<h2>我是类组件-- - { this.age } < /h2>
)
}
}
export default Cls
// tsconfig.json
{
"compilerOptions": {
// ...
// 开启装饰器使用
"experimentalDecorators": true
}
}
pnpm i @babel/plugin-proposal-decorators -D
const isDEV = process.env.NODE_ENV === "development"; // 是否是开发模式
module.exports = {
// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
presets: [
[
"@babel/preset-env",
{
// 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文件,babel-loader会自动寻找上面配置好的文件.browserlistrc
// "targets": {
// "chrome": 35,
// "ie": 9
// },
targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
corejs: 3, // 配置使用core-js使用的版本
loose: true,
},
],
// 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
// 否则可能会出现错误:Uncaught ReferenceError: React is not defined
["@babel/preset-react", { runtime: "automatic" }],
"@babel/preset-typescript",
],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
].filter(Boolean), // 过滤空值
};
十、webpack构建速度优化
1、webpack进度条
pnpm i webpackbar -D
// ...
import WebpackBar from 'webpackbar';
// ...
const baseConfig: Configuration = {
// ...
// plugins 的配置
plugins: [
// ...
new WebpackBar({
color: "#85d", // 默认green,进度条颜色支持HEX
basic: false, // 默认true,启用一个简单的日志报告器
profile: false, // 默认false,启用探查器。
})
],
};
export default baseConfig;
{ // 注册一个自定义数组
start(context) {
// 在(重新)编译开始时调用
const { start, progress, message, details, request, hasErrors } = context
},
change(context) {
// 在 watch 模式下文件更改时调用
},
update(context) {
// 在每次进度更新后调用
},
done(context) {
// 编译完成时调用
},
progress(context) {
// 构建进度更新时调用
},
allDone(context) {
// 当编译完成时调用
},
beforeAllDone(context) {
// 当编译完成前调用
},
afterAllDone(context) {
// 当编译完成后调用
},
}
2、构建耗时
pnpm i speed-measure-webpack-plugin -D
import { Configuration } from 'webpack'
import prodConfig from './webpack.prod' // 引入打包配置
import { merge } from 'webpack-merge' // 引入合并webpack配置方法
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); // 引入webpack打包速度分析插件
const smp = new SpeedMeasurePlugin(); // 实例化分析插件
// 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
const analyConfig: Configuration = smp.wrap(merge(prodConfig, {
}))
export default analyConfig
{
// ...
"scripts": {
// ...
"build:analy": "cross-env NODE_ENV=production BASE_ENV=production webpack -c build/webpack.analy.ts"
}
// ...
}
3、开启持久化存储缓存
// webpack.base.ts
// ...
module.exports = {
// ...
cache: {
type: 'filesystem', // 使用文件缓存
},
}
4、开启多线程loader
thread-loader
parallel-webpack
HappyPack
pnpm i thread-loader -D
module: {
rules: [
{
test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
use: [
// 开启多进程打包。
// 进程启动大概为600ms,进程通信也有开销。
// 只有工作消耗时间比较长,才需要多进程打包
{
loader: 'thread-loader',
options: {
wokers: 4 // 进程数
}
},
'babel-loader']
},
]
}
5、缩小构建目标
module: {
rules: [
{
test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
exclude: /node_modules/,
use: [
// 开启多进程打包。
// 进程启动大概为600ms,进程通信也有开销。
// 只有工作消耗时间比较长,才需要多进程打包
{
loader: 'thread-loader',
options: {
wokers: 4 // 进程数
}
},
'babel-loader']
},
]
}
6、devtools配置
// webpack.dev.ts
module.exports = {
// ...
devtool: 'eval-cheap-module-source-map'
}
// webpack.prod.ts
module.exports = {
// ...
// devtool: '', // 不用配置devtool此项
}
十一、webpack构建产物优化
1、bundle 体积分析工具
pnpm i webpack-bundle-analyzer -D
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import prodConfig from "./webpack.prod";
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
// 引入webpack打包速度分析插件
const smp = new SpeedMeasurePlugin();
// 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
const analyConfig: Configuration = smp.wrap(merge(prodConfig, {
plugins: [
new BundleAnalyzerPlugin() // 配置分析打包结果插件
]
}))
export default analyConfig;
2、样式提取
pnpm i mini-css-extract-plugin -D
修改 webpack.base.ts ,根据环境变量设置开发环境使用 style-looader ,打包模式抽离 css
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const isDev = process.env.NODE_ENV === 'development' // 是否是开发模式
const styleLoadersArray = [
isDev ? "style-loader" : MiniCssExtractPlugin.loader, // 开发环境使用stylelooader,打包模式抽离css
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[path][name]__[local]--[hash:5]",
},
},
},
'postcss-loader'
];
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const prodConfig: Configuration = merge(baseConfig, {
// ...
plugins: [
// ...
new MiniCssExtractPlugin({
filename: 'static/css/[name].css' // 抽离css的输出目录和名称
}),
],
});
export default prodConfig;
3、样式压缩
pnpm i css-minimizer-webpack-plugin -D
// ...
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
module.exports = {
// ...
optimization: {
minimizer: [
new CssMinimizerPlugin(), // 压缩css
],
},
}
4、js压缩
pnpm i terser-webpack-plugin compression-webpack-plugin -D
import path from "path";
import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import CopyPlugin from "copy-webpack-plugin";
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin'
import CompressionPlugin from 'compression-webpack-plugin'
import baseConfig from "./webpack.base";
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const prodConfig: Configuration = merge(baseConfig, {
mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
/**
* 打包环境推荐:none(就是不配置devtool选项了,不是配置devtool: 'none')
* ● none话调试只能看到编译后的代码,也不会泄露源代码,打包速度也会比较快。
* ● 只是不方便线上排查问题, 但一般都可以根据报错信息在本地环境很快找出问题所在。
*/
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"), // 复制public下文件
to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
filter: (source) => !source.includes("index.html"), // 忽略index.html
},
],
}),
new MiniCssExtractPlugin({
filename: 'static/css/[name].css' // 抽离css的输出目录和名称
}),
// 打包时生成gzip文件
new CompressionPlugin({
test: /\.(js|css)$/, // 只生成css,js压缩文件
filename: '[path][base].gz', // 文件命名
algorithm: 'gzip', // 压缩格式,默认是gzip
threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
minRatio: 0.8 // 压缩率,默认值是 0.8
})
],
optimization: {
// splitChunks: {
// chunks: "all",
// },
runtimeChunk: {
name: 'mainifels'
},
minimize: true,
minimizer: [
new CssMinimizerPlugin(), // 压缩css
new TerserPlugin({
parallel: true, // 开启多线程压缩
terserOptions: {
compress: {
pure_funcs: ['console.log'] // 删除console.log
}
}
})
],
},
performance: { // 配置与性能相关的选项的对象
hints: false, // 设置为false将关闭性能提示。默认情况下,Webpack会显示有关入口点和资产大小的警告和错误消息。将hints设置为false可以禁用这些消息。
maxAssetSize: 4000000, // 设置一个整数,表示以字节为单位的单个资源文件的最大允许大小。如果任何资源的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为4000000字节(约4MB)。
maxEntrypointSize: 5000000 // 设置一个整数,表示以字节为单位的入口点文件的最大允许大小。入口点是Webpack构建产生的主要JS文件,通常是应用程序的主要代码。如果入口点的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为5000000字节(约5MB)。
}
});
export default prodConfig;
5、文件指纹
// webpack.base.ts
// ...
const baseConfig: Configuration = {
// 打包文件出口
output: {
filename: 'static/js/[name].[chunkhash:8].js', // 加上[chunkhash:8]
// ...
},
module: {
rules: [
// ...
{
test: mediaRegex, // 匹配媒体文件
// ...
generator: {
filename: 'static/media/[name].[contenthash:8][ext]' // 文件输出目录和命名
}
},
{
test: fontRegex, // 匹配字体图标文件
// ...
generator: {
filename: 'static/json/[name].[contenthash:8][ext]' // 文件输出目录和命名
}
},
{
test: imageRegex, // 匹配图片文件
// ...
generator: {
filename: 'static/images/[name].[contenthash:8][ext]' // 加上
[contenthash: 8]
},
},
{
// 匹配json文件
test: jsonRegex,
type: 'json', // 模块资源类型为json模块
generator: {
filename: 'static/json/[name].[hash][ext][query]', // 专门针对json文件的处
理
}
}
],
// ...
}
// webpack.prod.ts
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = merge(baseConfig, {
mode: 'production',
plugins: [
// 抽离css插件
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css' // 加上[contenthash:8]
}),
// ...
],
// ...
})
6、代码分隔
module.exports = {
// ...
optimization: {
// ...
splitChunks: { // 分隔代码
cacheGroups: {
vendors: { // 提取node_modules代码
test: /node_modules/, // 只匹配node_modules里面的模块
name: 'vendors', // 提取文件命名为vendors,js后缀和chunkhash会自动加
minChunks: 1, // 只要使用一次就提取出来
chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
minSize: 0, // 提取代码体积大于0就提取出来
priority: 1, // 提取优先级为1
},
commons: { // 提取页面公共代码
name: 'commons', // 提取文件命名为commons
minChunks: 2, // 只要使用两次就提取出来
chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的
minSize: 0, // 提取代码体积大于0就提取出来
}
}
}
}
}
7、gzip压缩
pnpm i compression-webpack-plugin -D
const glob = require('glob')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
// ...
plugins: [
// ...
new CompressionPlugin({
test: /.(js|css)$/, // 只生成css,js压缩文件
filename: '[path][base].gz', // 文件命名
algorithm: 'gzip', // 压缩格式,默认是gzip
threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
minRatio: 0.8 // 压缩率,默认值是 0.8
})
]
}
十二、代码提交工具
1、安装EditorConfig for VS Code
{
"recommendations": ["editorconfig.editorconfig"]
}
2、新建 .editorconfig
在根目录新建 .editorconfig 文件
# https://editorconfig.org
root = true # 设置为true表示根目录,控制配置文件 .editorconfig 是否生效的字段
[*] # 匹配全部文件,匹配除了 `/` 路径分隔符之外的任意字符串
charset = utf-8 # 设置字符编码,取值为 latin1,utf-8,utf-8-bom,
utf-16be 和 utf-16le,当然 utf-8-bom 不推荐使用
end_of_line = lf # 设置使用的换行符,取值为 lf,cr 或者 crlf
indent_size = 2 # 设置缩进的大小,即缩进的列数,当 indexstyle 取值 tab
时,indentsize 会使用 tab_width 的值
indent_style = space # 缩进风格,可选space|tab
insert_final_newline = true # 设为true表示使文件以一个空白行结尾
trim_trailing_whitespace = true # 删除一行中的前后空格
[*.md] # 匹配全部 .md 文件
trim_trailing_whitespace = false
3、prettier
pnpm i prettier -D
4、新建.prettierrc.js
// .prettierrc.js
module.exports = {
tabWidth: 2, // 一个tab代表几个空格数,默认就是2
useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为
false
printWidth: 100, // 一行的字符数,如果超过会进行换行
semi: false, // 行尾是否使用分号,默认为true
singleQuote: true, // 字符串是否使用单引号
trailingComma: 'all', // 对象或数组末尾是否添加逗号 none| es5| all
jsxSingleQuote: true, // 在jsx里是否使用单引号,你看着办
bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
arrowParens: 'avoid' // 箭头函数如果只有一个参数则省略括号
}
// .prettierignore
node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky
5、配置 .vscode/settings.json
{
"search.exclude": {
"/node_modules": true,
"dist": true,
"pnpm-lock.yaml": true
},
"files.autoSave": "onFocusChange",
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"javascript.validate.enable": false,
}
6、脚本命令检查和修复格式
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.
{js,ts,json,tsx,css,less,scss,stylus,html,md}\""
7、stylelint
7.1、安装stylelint插件和依赖
在vs code中找到stylelint插件并安装
然后再安装以下依赖
pnpm i stylelint stylelint-config-css-modules stylelint-config-prettier
stylelint-config-standard stylelint-order -D
7.2、新建 .stylelintrc.js 和 .stylelintignore
// @see: https://stylelint.io
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-prettier'
// "stylelint-config-css-modules",
// "stylelint-config-recess-order" // 配置stylelint css属性书写顺序插件,
],
plugins: ['stylelint-order'],
rules: {
/**
* indentation: null, // 指定缩进空格
"no-descending-specificity": null, // 禁止在具有较高优先级的选择器后出现被其覆盖的
较低优先级的选择器
"function-url-quotes": "always", // 要求或禁止 URL 的引号 "always(必须加上引
号)"|"never(没有引号)"
"string-quotes": "double", // 指定字符串使用单引号或双引号
"unit-case": null, // 指定单位的大小写 "lower(全小写)"|"upper(全大写)"
"color-hex-case": "lower", // 指定 16 进制颜色的大小写 "lower(全小写)"|"upper(全
大写)"
"color-hex-length": "long", // 指定 16 进制颜色的简写或扩写 "short(16进制简
写)"|"long(16进制扩写)"
"rule-empty-line-before": "never", // 要求或禁止在规则之前的空行 "always(规则之前
必须始终有一个空行)"|"never(规则前绝不能有空行)"|"always-multi-line(多行规则之前必须始终有
一个空行)"|"never-multi-line(多行规则之前绝不能有空行。)"
"font-family-no-missing-generic-family-keyword": null, // 禁止在字体族名称列表
中缺少通用字体族关键字
"block-opening-brace-space-before": "always", // 要求在块的开大括号之前必须有一
个空格或不能有空白符 "always(大括号前必须始终有一个空格)"|"never(左大括号之前绝不能有空
格)"|"always-single-line(在单行块中的左大括号之前必须始终有一个空格)"|"never-singleline(在单行块中的左大括号之前绝不能有空格)"|"always-multi-line(在多行块中,左大括号之前必须
始终有一个空格)"|"never-multi-line(多行块中的左大括号之前绝不能有空格)"
"property-no-unknown": null, // 禁止未知的属性(true 为不允许)
"no-empty-source": null, // 禁止空源码
"declaration-block-trailing-semicolon": null, // 要求或不允许在声明块中使用尾随
分号 string:"always(必须始终有一个尾随分号)"|"never(不得有尾随分号)"
"selector-class-pattern": null, // 强制选择器类名的格式
"value-no-vendor-prefix": null, // 关闭 vendor-prefix(为了解决多行省略 -
webkit-box)
"at-rule-no-unknown": null,
"selector-pseudo-class-no-unknown": [
true,
{
ignorePseudoClasses: ["global", "v-deep", "deep"]
}
]
}
*/
'selector-class-pattern': [
// 命名规范 -
'^([a-z][a-z0-9]*)(-[a-z0-9]+)*$',
{
message: 'Expected class selector to be kebab-case'
}
],
'string-quotes': 'double', // 单引号
'at-rule-empty-line-before': null,
'at-rule-no-unknown': null,
'at-rule-name-case': 'lower', // 指定@规则名的大小写
'length-zero-no-unit': true, // 禁止零长度的单位(可自动修复)
'shorthand-property-no-redundant-values': true, // 简写属性
'number-leading-zero': 'always', // 小数不带0
'declaration-block-no-duplicate-properties': true, // 禁止声明快重复属性
'no-descending-specificity': true, // 禁止在具有较高优先级的选择器后出现被其覆盖的较
低优先级的选择器。
'selector-max-id': null, // 限制一个选择器中 ID 选择器的数量
'max-nesting-depth': 10,
'declaration-block-single-line-max-declarations': 1,
'block-opening-brace-space-before': 'always',
// 'selector-max-type': [0, { ignore: ['child', 'descendant', 'compounded']
}],
indentation: [
2,
{
// 指定缩进 warning 提醒
severity: 'warning'
}
],
'order/order': ['custom-properties', 'dollar-variables', 'declarations',
'rules', 'at-rules'],
'order/properties-order': [
// 规则顺序
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'float',
'width',
'height',
'max-width',
'max-height',
'min-width',
'min-height',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'margin-collapse',
'margin-top-collapse',
'margin-right-collapse',
'margin-bottom-collapse',
'margin-left-collapse',
'overflow',
'overflow-x',
'overflow-y',
'clip',
'clear',
'font',
'font-family',
'font-size',
'font-smoothing',
'osx-font-smoothing',
'font-style',
'font-weight',
'line-height',
'letter-spacing',
'word-spacing',
'color',
'text-align',
'text-decoration',
'text-indent',
'text-overflow',
'text-rendering',
'text-size-adjust',
'text-shadow',
'text-transform',
'word-break',
'word-wrap',
'white-space',
'vertical-align',
'list-style',
'list-style-type',
'list-style-position',
'list-style-image',
'pointer-events',
'cursor',
'background',
'background-color',
'border',
'border-radius',
'content',
'outline',
'outline-offset',
'opacity',
'filter',
'visibility',
'size',
'transform'
]
}
}
*.js
*.tsx
*.ts
*.json
*.png
*.eot
*.ttf
*.woff
src/styles/antd-overrides.less
node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky
7.3、配置 .vscode/settings.json
{
// ...
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": true
},
"stylelint.validate": [
"css",
"less",
"sass",
"stylus",
"postcss"
]
}
8、eslint
8.1、安装eslint插件和包
在vs code中找到eslint插件并安装
pnpm i eslint eslint-config-airbnb eslint-config-standard eslint-friendlyformatter eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-node eslintplugin-promise eslint-plugin-react-hooks eslint-plugin-react @typescripteslint/eslint-plugin @typescript-eslint/parser eslint-plugin-prettier eslintconfig-prettier -D
8.2、新建.eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'airbnb-base',
'eslint:recommended',
'plugin:import/recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['react', '@typescript-eslint'],
rules: {
// eslint (http://eslint.cn/docs/rules)
'react/jsx-filename-extension': ['error', {
extensions: ['.js', '.jsx',
'.ts', '.tsx']
}],
'class-methods-use-this': 'off',
'no-param-reassign': 'off',
'no-unused-expressions': 'off',
'no-plusplus': 0,
'no-restricted-syntax': 0,
'consistent-return': 0,
'@typescript-eslint/ban-types': 'off',
// "import/no-extraneous-dependencies": "off",
'@typescript-eslint/no-non-null-assertion': 'off',
'import/no-unresolved': 'off',
'import/prefer-default-export': 'off', // 关闭默认使用 export default 方式导出
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'@typescript-eslint/no-use-before-define': 0,
'no-use-before-define': 0,
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
'no-shadow': 'off',
// "@typescript-eslint/no-var-requires": "off"
'import/extensions': [
'error',
'ignorePackages',
{
'': 'never',
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never'
}
]
// "no-var": "error", // 要求使用 let 或 const 而不是 var
// "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
// "no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们
// "prefer-const": "off", // 此规则旨在标记使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const
// "no-irregular-whitespace": "off", // 禁止不规则的空白
// // typeScript (https://typescript-eslint.io/rules)
// "@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量
// "@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长
// "@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间。
// "@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型
// "@typescript-eslint/ban-ts-ignore": "off", // 禁止使用 @ts-ignore
// "@typescript-eslint/ban-types": "off", // 禁止使用特定类型
// "@typescript-eslint/explicit-function-return-type": "off", // 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明
// "@typescript-eslint/no-var-requires": "off", // 不允许在 import 语句中使用require 语句
// "@typescript-eslint/no-empty-function": "off", // 禁止空函数
// "@typescript-eslint/no-use-before-define": "off", // 禁止在变量定义之前使用它们
// "@typescript-eslint/ban-ts-comment": "off", // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述
// "@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!)
// "@typescript-eslint/explicit-module-boundary-types": "off", // 要求导出函数和类的公共类方法的显式返回和参数类型
// // react (https://github.com/jsx-eslint/eslint-plugin-react)
// "react-hooks/rules-of-hooks": "error",
// "react-hooks/exhaustive-deps": "off"
},
settings: {
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx']
},
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
moduleDirectory: ['node_modules', 'src/']
}
},
react: {
version: 'detect'
}
}
}
/* eslint-disable */ --禁用全部规则 放在文件顶部则整个文件范围都不检查
/* eslint-disable no-alert, no-console */ --禁用某些规则
// eslint-disable-line --当前行上禁用规则
// eslint-disable-next-line --下一行上禁用规则
8.3 新建 .eslintignore
node_modules
dist
env
.gitignore
pnpm-lock.yaml
README.md
src/assets/*
.vscode
public
.github
.husky
8.4 添加eslint语法检测脚本
// --fix:此项指示 ESLint 尝试修复尽可能多的问题。这些修复是对实际文件本身进行的,只有剩余的未
修复的问题才会被输出。
"lint:eslint": "eslint --fix --ext .js,.ts,.tsx ./src",
React version not specified in eslint-plugin-react settings
settings: {
"react": {
"version": "detect"
}
}
8.6 eslint 与 prettier 冲突
pnpm i eslint-config-prettier eslint-plugin-prettier -D
module.exports = {
// ...
extends: [
// ...
'plugin:prettier/recommended', // <==== 增加一行
],
// ...
}
{
// ...
"eslint.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
}
9、husky + lint-statged
9.1 使用lint-staged优化eslint检测速度
"lint-staged": {
"src/**/*.{ts,tsx}": [
"pnpm run lint:eslint",
"pnpm run lint:prettier"
]
}
git init
git add src/App.tsx
然后在 App.tsx 中加入一个未使用的变量:
// ...
const a = 1
//
git add src/App.tsx
npx lint-staged
9.2 使用tsc检测类型和报错
{
"compilerOptions": {
"target": "es2016", // 编译输出的ECMAScript目标版本为ES2016
"esModuleInterop": true, // 允许默认导入与CommonJS模块操作
"module": "commonjs", // 使用CommonJS模块系统
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
"strict": true, // 启用所有严格的类型检查选项
"skipLibCheck": true, // 跳过所有声明文件(.d.ts)的类型检查
"resolveJsonModule": true, // 允许从.json文件中导入数据
"allowJs": false, // 不允许在项目中包含JavaScript文件
"noEmit": true, // 不生成输出文件(例如.js文件)
"noImplicitAny": false, // 在表达式和声明上有隐含的any类型时,不发出警告
"experimentalDecorators": true, // 启用实验性的装饰器
"baseUrl": ".", // 用于解析非相对模块名称的基目录
"paths": {
"@/*": ["./src/*"] // 将路径别名“@”映射到“./src/”
},
"typeRoots": ["./typings/*.d.ts", "node_modules/@types"], // 指定类型声明文件的查找路径
"jsx": "react-jsx" // react18这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src/*", "./src/**/*.ts", "./src/**/*.tsx", "./typings/*.d.ts"],
// 指定需要包含的文件和目录
"exclude": ["node_modules", "dist"] // 指定需要排除的文件和目录
}
"pre-check": "tsc && npx lint-staged"