项目创建
这里将使用
pnpm
,至于为什么,一个字:快、省、狠!
pnpm
、
npm
和
yarn
都是
Node.js
和
JavaScript
项目的包管理器。每种都有自己的优点和缺点,
选择使用哪一种最终取决于你的具体需求和偏好。 以下是
pnpm
与
npm
和
yarn
相比的一些优势:
1.
更快的安装和更新时间:
pnpm
使用独特的方法来安装包,避免重复包,从而加快安装和更新时
间。特别是,当安装共享依赖的多个包时,
pnpm
只会为每个依赖安装一次,而
npm
和
yarn
会
为每个包分别安装每个依赖。
2.
更少的磁盘空间使用:由于
pnpm
避免了包重复,它使用的磁盘空间比
npm
和
yarn
更少。如果
你的磁盘空间有限,或者你正在处理具有许多依赖项的大型项目,那么这一点尤其重要。
3.
更好地支持
monorepos
:
pnpm
旨在与
monorepo
项目很好地配合使用,这些项目是在单个存储
库中包含多个包或模块的项目。
pnpm
独特的包管理方法可以更轻松地管理
monorepo
中多个包和
模块的依赖关系。
4.
更好地支持对等依赖:
pnpm
比
npm
和
yarn
对对等依赖有更好的支持。对等依赖项是包所需的
依赖项,但不应与包一起安装,因为它们已由应用程序或其他依赖项提供。
pnpm
可以比
npm
和
yarn
更高效、更准确地处理对等依赖。
5.
更清晰的依赖树:
pnpm
生成比
npm
和
yarn
更清晰的依赖树。这是因为
pnpm
对依赖项使用平
面目录结构,而
npm
和
yarn
使用嵌套目录。扁平结构使得依赖树更容易理解和调试。
总的来说,对于具有大量依赖关系或使用
monorepo
结构的项目,
pnpm
是一个不错的选择。但
是,值得注意的是,
pnpm
不像
npm
和
yarn
那样广泛使用,因此也可能会遇到某些包或工具的
兼容性问题。
# 我的 pnpm 版本
pnpm -v
7.27.1
# 初始化package.json文件
pnpm init
会在根目录生成一个
package.json
文件:
{
"name": "fe",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
基本项目结构
在根目录新建基本的项目结构:
├── build| ├── webpack . base . ts # 公共配置| ├── webpack . dev . ts # 开发环境配置| └── webpack . prod . ts # 打包环境配置├── public│ └── index . html # html 模板├── src| ├── App . tsx| ├── App . css│ └── index . tsx # react 应用入口页面└── 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.getElementById('root');
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 - Dpnpm i babel - loader ts - node @ babel / core @ babel / preset - react @ babel / preset -typescript @ babel / preset - env core - js - D
初始化 tsconfig.json :
npx tsc -- init# 如果全局安装了 typescript ,也可以通过下面的命令创建tsc -- init
就会在根目录生成一个tsconfig.json文件:
webpack配置
安装依赖:
pnpm i webpack webpack - cli - D
webpack.base.ts
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: []
};
另外因为我们在 App.tsx 中引入了 css 文件,所以还需要安装相关的 loader :
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"],
},
因为
webpack.base.ts
文件承载了基本的配置,随着
webpack
做的事情越来越多,会逐渐变得很庞
大,我们可以将其中的
babel-loader
相关的配置抽离出来进行管理。在根目录新建
babel.config.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",
],
};
然后在
webpack.base.ts
文件中,就可以将
babel-loader
配置简化成:
module: {
rules: [
{
test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
use: "babel-loader"
},
// ...
],
},
webpack.dev.ts
接下来,我们需要通过
webpack-dev-server
来启动我们的项目,所以需要安装相关的依赖:
pnpm i webpack-dev-server webpack-merge -D
接着,配置开发环境配置: webpack.dev.ts
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;
然后再 package.json 中添加启动脚本:
"scripts": {
"dev": "webpack serve -c build/webpack.dev.ts"
},
此时我们会发现一个错误,只需要在 tsconfig.json 中加入一行 "jsx": "react-jsx" 即可:
{
"compilerOptions": {
"target": "es2016",
"esModuleInterop": true,
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"jsx": "react-jsx" // 这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src"]
}
运行 pnpm run dev 脚本启动项目,就可以看到页面跑出来了!
webpack.prod.ts
配置
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;
在 package.json 中添加:
"scripts": {
// ...
"build": "webpack -c build/webpack.prod.ts"
},
运行 pnpm run build ,如果想看打包结果,可以通过一个小工具来查看:
# 如果之前使用npm,最简单的方法就是使用如下命令
npm i serve -g
# 如果是首次使用pnpm安装全局依赖,通过如下命令
pnpm setup
source ~/.zshrc
pnpm i serve -g
然后通过
serve -S dist
命令,启动一个服务来查看打包结果,如果不出意外,打开控制台启动的服
务,就能看到页面了!
copy 静态资源
一般
public
文件夹都会放一些静态资源
,
可以直接根据绝对路径引入,比如图片、
css
、
js
文件等,不
需要
webpack
进行解析,只需要打包的时候把
public
下内容复制到构建出口文件夹中,可以借助
copy-webpack-plugin
插件,安装依赖:
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;
配置环境变量
corss-env + DefinePlugin
环境变量按作用分为两种:
1.
区分是开发模式还是打包构建模式
2.
区分项目业务环境,开发
/
测试
/
预测
/
正式环境
cross-env :运行跨平台设置和使用环境变量的脚本,兼容各系统的设置环境变量的包webpack.DefinePlugin : webpack 内置的插件 , 可以为业务代码注入环境变量
安装 cross-env : pnpm i cross-env -D
修改 package.json 的 scripts :
"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"
},
process.env.NODE_ENV
环境变量
webpack
会自动根据设置的
mode
字段来给业务代码注入对应的
development
和
prodction
,这里在命令中再次设置环境变量
NODE_ENV
是为了在
webpack
和
babel
的配置文件中访问到。
在
webpack.base.ts
中打印一下设置的环境变量
console . log ( 'NODE_ENV' , process . env . NODE_ENV )console . log ( 'BASE_ENV' , process . env . BASE_ENV )
执行 pnpm run build:dev ,就可以在控制台打印出:
// NODE_ENV production// BASE_ENV development
当前是打包模式,业务环境是开发环境,这里需要把
process.env.BASE_ENV
注入到业务代码里面,就
可以通过该环境变量设置对应环境的接口地址和其他数据,要借助
webpack.DefinePlugin
插件。
修改 webpack.base.ts
import { DefinePlugin } from 'webpack'
module.export = {
// ...
plugins: [
// ...
new DefinePlugin({
'process.env': JSON.stringify(process.env)
})
]
}
在根目录下新建 typings/global.d.ts 文件:
declare module 'process' {
global {
namespace NodeJS {
export interface ProcessEnv {
BASE_ENV: 'development' | 'test' | 'pre' | 'production'
NODE_ENV: 'development' | 'production'
}
}
}
}
并在
tsconfig.json
中配置:
{
"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"] // 指定哪些文件或目录应该被包含在编译范
围内
}
配置后会把值注入到业务代码里面去,
webpack
解析代码匹配到
process.env.BASE_ENV
,就会设置到
对应的值。测试一下,在
src/index.tsx
打印一下两个环境变量:
console . log ( 'NODE_ENV' , process . env . NODE_ENV )console . log ( 'BASE_ENV' , process . env . BASE_ENV )
执行 pnpm run dev:test ,可以在浏览器控制台看到打印的信息:
// NODE_ENV development
// BASE_ENV test