1、lerna
工程中的lerna.json配置
{
"packages": [
"packages/*"
],
"version": "independent",
"useWorkspaces": true,
"npmClient": "pnpm",
"command": {
"run": {
"stream": true
},
"bootstrap": {
"hoist": true
}
}
}
Lerna 是什么?
- Lerna 是 Babel 为实现 Monorepo 开发的工具;最擅长管理依赖关系和发布
- Lerna 优化了多包工作流,解决了多包依赖、发版手动维护版本等问题
- Lerna 不提供构建、测试等任务,工程能力较弱,项目中往往需要基于它进行顶层能力的封装
Lerna 主要做三件事
- 为单个包或多个包运行命令 (lerna run)
- 管理依赖项 (lerna bootstrap)
- 发布依赖包,处理版本管理,并生成变更日志 (lerna publish)
Lerna 能解决了什么问题?
- 代码共享,调试便捷: 一个依赖包更新,其他依赖此包的包/项目无需安装最新版本,因为 Lerna 自动 Link
- 安装依赖,减少冗余:多个包都使用相同版本的依赖包时,Lerna 优先将依赖包安装在根目录
- 规范版本管理: Lerna 通过 Git 检测代码变动,自动发版、更新版本号;两种模式管理多个依赖包的版本号
- 自动生成发版日志:使用插件,根据 Git Commit 记录,自动生成 ChangeLog
Lerna 自动检测发布,判断逻辑
- 校验本地是否有没有被
commit内容? - 判断当前的分支是否正常?
- 判断当前分支是否在
remote存在? - 判断当前分支是否在
lerna.json允许的allowBranch设置之中? - 判断当前分支提交是否落后于 remote
Lerna 工作模式
Lerna 允许您使用两种模式来管理您的项目:固定模式(Fixed)、独立模式(Independent)
① 固定模式(Locked mode):项目初始化时,lerna init 默认是 Locked mode
- Lerna 把多个软件包当做一个整体工程,每次发布所有软件包版本号统一升级(版本一致),无论是否修改
{
"version": "0.0.0"
}
② 独立模式(Independent mode):项目初始化时,lerna init --independent
- Lerna 单独管理每个软件包的版本号,每次执行发布指令,Git 检查文件变动,只发版升级有调整的软件包
{
"version": "independent"
}
Lerna 常用指令
lerna add -h 可以查看帮助文档
示例:
lerna add module-1 packages/prefix-* Adds the module-1 package to the packages in the 'prefix-' prefixed folders
lerna add module-1 --scope=module-2 Install module-1 to module-2
lerna add module-1 --scope=module-2 --dev Install module-1 to module-2 in devDependencies
lerna add module-1 --scope=module-2 --peer Install module-1 to module-2 in peerDependencies
lerna add module-1 Install module-1 in all modules except module-1
lerna add module-1 --no-bootstrap Skip automatic `lerna bootstrap`
lerna add babel-core Install babel-core in all modules
1.脚手架项目初始化
初始化npm项目→安装lerna→lerna init 初始化项目
2.创建packege
lerna create 创建package→lerna add 安装依赖 → lerna link 链接依赖
① 初始化:init: lerna **init**
② 创建 package:create: lerna create <name> [location] lerna create package1
# 在 packages/pwd1 目录下,生成 package2 依赖包
lerna create package2 packages/pwd1
③ 给 package 添加依赖:add
安装的依赖,如果是本地包,Lerna 会自动 npm link 到本地包
//指定目录下安装依赖
lerna add @medicine-brand-operation-center/utils packages/landing_page_gundam
# 给所有包安装依赖,默认作为 dependencies
lerna add module-1
lerna add module-1 --dev # 作为 devDependencies
lerna add module-1 --peer # 作为 peerDependencies
lerna add module-1[@version] --exact # 安装准确版本的依赖
lerna add module-1 --scope=module-2 # 给指定包安装依赖
lerna add module-1 packages/prefix-* # 给前缀为 xxx 的包,安装依赖
④ 给所有 package 安装依赖:bootstrap
执行 lerna bootstrap 指令:会自动为每个依赖包进行 npm install 和 npm link 操作
# 项目根目录下执行,将安装所有依赖
lerna bootstrap
关于冗余依赖的安装:
- npm 场景下
lerna bootstrap会安装冗余依赖(多个 package 共用依赖,每个目录都会安装) - yarn 会自动 hosit 依赖包(相同版本的依赖,安装在根目录),无需关心
npm 场景下冗余依赖解决方案:
- 方案一:
lerna bootstrap --hoist - 方案二:配置
lerna.json/command.bootsrap.hoist = true
3.脚手架开发和测试
-
lerna exec 执行shell脚本
-
lerna exec -- rm -rf node_modules/ # 指定某个包中的 lerna exec --scope @medicine-brand-operation-center/landing_page_gundam -- rm -rf node_modules/ # 删除所有包内的 lib 目录 lerna exec -- rm -rf lib # 给xxx软件包,删除依赖 lerna exec --scope=xxx -- yarn remove yyy
-
-
lerna run 执行npm scripts命令
-
lerna run # 所有依赖执行 package.json 文件 scripts 中的指令 xxx lerna run xxx # 指定依赖执行 package.json 文件 scripts 中的指令 xxx lerna run --scope=my-component xxx
-
-
lerna clean 清空依赖
-
注意:只是删除了包,但是package.json中的依赖并没有删除:lerna WARN No packages found where @medicine-brand-operation-center/utils can be added. 所以需要手动删除
-
➜ medicine_brand_operation_center_monorepo git:(master) ✗ lerna add @medicine-brand-operation-center/utils packages/landing_page_gundam info cli using local version of lerna lerna notice cli v4.0.0 lerna info versioning independent lerna WARN No packages found where @medicine-brand-operation-center/utils can be added.
-
-
lerna bootstrap 重装依赖
4.脚手架发布上线
-
lerna version bump version 提升版本号
-
lerna changed 查看上版本以来的所有变更
-
lerna diff 查看diff
-
lerna publish 项目发布: 发布软件包,自动检测
-
运行lerna updated来决定哪一个包需要被publish
如果有必要,将会更新lerna.json中的version
将所有更新过的的包中的package.json的version字段更新
将所有更新过的包中的依赖更新
为新版本创建一个git commit或tag
将包publish到npm上
-
git status //注意版本注意私有版本需要登录 npm login lerna publish
-
对于Lerna,你可以运行
lerna list命令来查看所有的包。这个命令应该会列出你项目中的所有包。对于Yarn Workspaces,你可以运行
yarn workspaces info命令来查看所有的工作区。这个命令会列出你的所有工作区,以及它们的依赖关系。
查看自上次发布的变更:diff、changed
# 查看自上次relase tag以来有修改的包的差异
lerna diff
# 查看自上次relase tag以来有修改的包名
lerna changed
导入已有包:import
lerna import [npm 包所在本地路径]
列出所有包:list
lerna list
lerna.json 配置
{
"packages": [
"packages/*"
],
"version": "independent",
"useWorkspaces": true,
"npmClient": "pnpm",
"command": {
"run": {
"stream": true
},
"bootstrap": {
"hoist": true
}
}
}
这段配置是 lerna.json 文件的内容,它定义了 Lerna 工具在管理 monorepo 时的行为。下面是每个属性的含义:
"packages": 这是一个数组,指定了 Lerna 应该管理哪些包。在这个例子中,"packages/*"意味着 Lerna 将会管理packages目录下的所有子目录中的包。"version": 设置为"independent"指示 Lerna 使用独立版本控制模式。在这种模式下,每个包都有自己的版本号,可以独立于其他包进行版本更新。"useWorkspaces": 设置为true表示 Lerna 将会使用 Yarn 工作区(workspaces)或者类似的 pnpm/npm 工作区功能来管理包的依赖和链接。这允许 Lerna 利用包管理器的工作区功能来优化包之间的依赖安装和链接。"npmClient": 指定 Lerna 应该使用哪个包管理器来执行命令。在这个配置中,它被设置为"pnpm",这意味着 Lerna 将使用 pnpm 来安装依赖和运行脚本。"command": 这是一个对象,用于配置 Lerna 执行不同命令时的行为。它包含以下子属性:"run":"stream": 设置为true表示 Lerna 在运行lerna run命令时会实时地将子包的输出流式传输到控制台。这有助于在执行脚本时跟踪每个包的输出。
"bootstrap":"hoist": 设置为true表示 Lerna 将尝试提升(hoist)子包的依赖到 monorepo 的根目录中。这可以减少安装的依赖数量,节省空间并加快安装速度。Lerna 会将共享的依赖提升到根目录的node_modules文件夹中,并确保子包能够访问到这些依赖。
总的来说,这段配置告诉 Lerna 在管理 monorepo 时使用 pnpm 作为包管理器,并开启工作区支持。每个包可以独立地进行版本控制,且 Lerna 会尝试提升依赖以优化依赖管理。当运行命令时,Lerna 会将输出实时传输到控制台。
2、pnpm
为什么现在我更推荐 pnpm 而不是 npm/yarn?:juejin.cn/post/693204…
项目中的.npmrc配置
# 所有的依赖项都应该被提升到工作空间的根目录
# public-hoist-pattern=*
registry=http://r.npm.sankuai.com
disturl=http://npm.sankuai.com/dist/node
sass_binary_site=http://npm.sankuai.com/dist/node-sass
phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
profiler_binary_host_mirror=https://npm.taobao.org/mirrors/node-inspector/
fse_binary_host_mirror=https://npm.taobao.org/mirrors/fsevents/
# 工作区间包的自动链接功能:true=启用;false=禁用
link-workspace-packages=false
pnpm-workspace.yaml
packages:
# all packages in direct subdirs of packages/
- 'packages/*'
- 'public/*'
- 'shared-lib'
- 'ui-components'
# all packages in subdirs of components/
# - 'components/**'
# exclude packages that are inside test directories
# - '!**/test/**'
# 如果你想在工作区根目录安装依赖,并且不想看到警告
# settings:
# ignore-workspace-root-check: true
安装依赖
pnpm add -D @esbuild-plugins/node-globals-polyfill @esbuild-plugins/node-modules-polyfill
如果是安装在workspace root 则需要加上 -w
3、prettier&&eslint配置等
.prettierignore
*.md
.prettierrc.js
module.exports = {
// 使用箭头函数时,避免不必要的括号,以提高代码可读性
arrowParens: 'avoid',
// 不允许将对象的结束括号与其中的最后一个属性位于同一行,以保持一致性
bracketSameLine: false,
// 根据运行环境自动选择换行符,使得代码在不同系统中保持一致性
endOfLine: 'auto',
// 设置打印宽度为120字符,以确保代码行不超出此长度,提高可读性
printWidth: 120,
// 对象或数组内的属性或元素的键值对引号使用一致,优化代码美观度
quoteProps: 'consistent',
// 强制每个属性单独一行,增强代码的可读性和格式化一致性
singleAttributePerLine: true,
// 使用单引号替代双引号,统一代码风格
singleQuote: true,
// 设置每个制表符的宽度为2个空格,提高代码缩进的一致性
tabWidth: 2,
// 在需要的地方添加尾逗号,遵循es5语法,提高代码的可读性和后期维护性
trailingComma: 'es5',
// 禁止使用制表符进行缩进,统一使用空格,以确保代码在所有环境中正确对齐
useTabs: false,
// 定义导入语句的顺序规则,通过正则表达式匹配来组织导入顺序,可以根据自己项目的实际情况定制
importOrder: ['^react', '^[a-z].*$', '^@[^/].*$', '^@/.*$', '^(?!.*.(css|less|scss)$)', '.*(css|less|scss)$'],
// 禁止在导入语句之间添加额外的空行,保持代码的紧凑性
importOrderSeparation: false,
// 导入语句中的路径按照字母顺序进行排序,增强代码的可读性
importOrderSortSpecifiers: true,
// 引入第三方插件以实现导入语句的自动排序功能
plugins: ['@trivago/prettier-plugin-sort-imports'],
};
commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional']
};
.editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.js]
indent_style = space
indent_size = 2
[*.ts, *.tsx]
indent_style = space
indent_size = 2
semicolon = always
quote_type = double
jsx_single_quote = false
jsx_bracket_same_line = false
[*.json]
indent_style = space
indent_size = 2
[*.md, *.markdown]
trim_trailing_whitespace = false
[*.yaml, *.yml]
indent_style = space
indent_size = 2
[*.html]
indent_style = space
indent_size = 2
[*.css, *.scss, *.less]
indent_style = space
indent_size = 2
[*.vue]
indent_style = space
indent_size = 2
.eslintrc.js
module.exports = {
env: {
browser: true,
amd: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
// 根据自己项目的实际情况加入一些自定义的 rules 的配置
'@typescript-eslint/no-var-requires': 'off',
},
root: true,
ignorePatterns: ['!.*', 'node_modules/', 'dist/', '*.min.js', '**/*.mock.ts'],
};
generate-readme.js
const fs = require('fs');
const path = require('path');
const srcPath = path.join(__dirname, 'packages');
// const srcPath = __dirname; // 设置为根目录
const readmePath = path.join(__dirname, 'README.md');
function getDirectoryTreeMarkdown(dirPath, level = 0) {
let treeMarkdown = '';
const indent = ' '.repeat(level * 2); // Markdown缩进使用空格
const filesAndDirs = fs.readdirSync(dirPath);
filesAndDirs.forEach((name) => {
// 忽略 node_modules 目录
if (name === 'node_modules') {
return;
}
const filePath = path.join(dirPath, name);
const stats = fs.statSync(filePath);
const relativePath = path.relative(__dirname, filePath);
if (stats.isDirectory()) {
treeMarkdown += `${indent}- ${name}/\n`;
// 递归获取子目录Markdown
treeMarkdown += getDirectoryTreeMarkdown(filePath, level + 1);
} else {
treeMarkdown += `${indent}- [${name}]\n`;
// treeMarkdown += `${indent}- [${name}](${relativePath})\n`;
}
});
return treeMarkdown;
}
const directoryTreeMarkdown = getDirectoryTreeMarkdown(srcPath);
// 读取现有的README.md内容
const readmeContent = fs.readFileSync(readmePath, 'utf8');
// 正则表达式匹配README.md中的src目录结构部分
const readmeSrcSectionRegex = /<!-- src-directory-start -->([\s\S]*?)<!-- src-directory-end -->/;
const updatedReadmeContent = readmeContent.replace(readmeSrcSectionRegex, `<!-- src-directory-start -->\n${directoryTreeMarkdown}<!-- src-directory-end -->`);
// 写回更新后的README.md内容
fs.writeFileSync(readmePath, updatedReadmeContent, 'utf8');
console.log('README.md has been updated with src directory structure.');
4、lerna+workspace+pnpm
yarn workspace 更突出对依赖的管理: 依赖提升到根目录的 node_modules 下,安装更快,体积更小
Lerna 更突出工作流方面:使用 Lerna 命令来优化多个包的管理,如:依赖发包、版本管理,批量执行脚本
pnpm 是新一代 Node 包管理器,它由 npm/yarn 衍生而来,解决了 npm/yarn 内部潜在的风险,并且极大提升依赖安装速度。pnpm 内部使用基于内容寻址的文件系统,来管理磁盘上依赖,减少依赖安装;node_modules/.pnmp为虚拟存储目录,该目录通过<package-name>@<version>来实现相同模块不同版本之间隔离和复用,由于它只会根据项目中的依赖生成,并不存在提升。
CAS 内容寻址存储,是一种存储信息的方式,根据内容而不是位置进行检索信息的存储方式。
Virtual store 虚拟存储,指向存储的链接的目录,所有直接和间接依赖项都链接到此目录中,项目当中的.pnpm目录
pnpm 相比于 npm、yarn 的包管理器,优势如下,同理是 Lerna + yarn + workspace 优势:
- 装包速度极快: 缓存中有的依赖,直接硬链接到项目的 node_module 中;减少了 copy 的大量 IO 操作
- 磁盘利用率极高: 软/硬链接方式,同一版本的依赖共用一个磁盘空间;不同版本依赖,只额外存储 diff 内容
- 解决了幽灵依赖: node_modules 目录结构 与 package.json 依赖列表一致
常用命令梳理
-
确保你已经安装了 Lerna,pnpm 和 Yarn。如果没有,你可以使用 npm 安装它们:
npm install -g lerna pnpm yarn
-
然后,你可以创建一个新的 Lerna 项目:
lerna init
这将在当前目录下创建一个新的 Lerna 项目,包括一个 packages 目录和一个 lerna.json 文件。
-
然后,你可以使用 pnpm 作为 Lerna 的 npmClient。在
lerna.json文件中添加以下配置:
{
"npmClient": "pnpm",
"useWorkspaces": true,
"version": "independent"
}
- 接下来,你需要在项目的
package.json文件中启用 Yarn Workspaces。添加以下配置:
{
"name": "root",
"private": true,
"workspaces": [
"packages/*"
]
}
5.现在,你可以在 packages 目录下创建新的包。例如,你可以使用以下命令创建一个新的 npm 包:
cd packages
pnpm init -y
这将创建一个新的 package.json 文件。你可以在这个文件中添加你的包的信息和依赖。
当你添加或更新包的依赖时,你可以运行 lerna bootstrap 命令。这将在所有包中安装依赖,并链接跨包的依赖。
最后,你可以使用 lerna publish 命令来发布你的包。这将更新包的版本号,创建一个新的 git 标签,并将包推送到 npm。
6.pnpm安装workspace依赖,命令:
pnpm add lodash --filter @medicine-brand-operation-center/landing_page_gundam add lodash
pnpm add lodash --filter @medicine-brand-operation-center/landing_page_gundam add lodash
yarn workspace @medicine-brand-operation-center/landing_page_gundam add lodash
这个命令会在 "landing_page_gundam" 工作空间中添加 "lodash" 依赖。
你也可以使用以下的命令来查看你的所有工作空间:
pnpm list --filter . --depth -1
这个命令会列出你的所有工作空间及其直接依赖。
清除pnpm的缓存:这个命令会清除所有未被项目依赖的包的缓存。
pnpm store prune
查看pnpm的缓存位置:这个命令会显示pnpm的缓存路径。
pnpm store path
删除 node_modules 文件夹和 yarn.lock 文件
rm -rf node_modules yarn.lock
pnpm 来重新安装你的依赖
pnpm install
7.创建react子工程
首先,你需要在你的工作空间下创建一个新的文件夹来存放你的React项目。你可以使用以下命令来创建:
mkdir my-react-app && cd my-react-app
然后,你可以使用以下命令来初始化一个新的React项目:
pnpm create vite . --template react
pnpm install
在初始化过程中,选择 "react" 作为你的框架,然后选择 "JavaScript" 或 "TypeScript" 作为你的语言。
接下来,你需要在你的工作空间配置文件(通常是 "lerna.json" 或 "package.json")中添加你的新项目。例如,如果你的工作空间配置在 "package.json" 中,你需要添加以下内容:
{
"workspaces": [
"my-react-app"
// 其他工作空间
]
}
然后,你可以在你的新项目中安装所有的依赖:
pnpm install
现在,你的React项目已经被创建并配置好了。你可以在你的 "my-react-app" 文件夹中找到你的项目,并使用以下命令来启动你的项目:
pnpm run dev
这会启动一个开发服务器,你可以在浏览器中通过 "http://localhost:5000" 来访问你的项目。
8.查看workspace中有哪些包
yarn workspaces info
pnpm list -r --filter ./packages
5、vite配置相关
vite-build/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { createHtmlPlugin } from 'vite-plugin-html';
// @ts-ignore
import { createHtmlConfigFun } from './html.config'; // 所有子项目通用 HTML 配置模块
const { PUBLIC_URL = '/', TALOS_SUBAPP_FLAG, AWP_DEPLOY_ENV, SOURCEMAP_PUBLIC_URL } = process.env;
interface ProxyTargets {
[key: string]: string;
}
// @ts-ignore
// 判断是否为生产模式
const bocProdMode = ['production', 'staging'].includes(AWP_DEPLOY_ENV);
console.log('bocProdMode', typeof bocProdMode, bocProdMode);
// sourcemap URL 处理
function getSourcemapBaseUrl() {
if (SOURCEMAP_PUBLIC_URL && !SOURCEMAP_PUBLIC_URL.startsWith('http')) {
return new URL(`${SOURCEMAP_PUBLIC_URL}assets/`, 'https://awp-assets.sankuai.com').href;
}
return undefined;
}
// 获取 npm 脚本生命周期事件
const npmLifecycleEvent: string | undefined = process.env.npm_lifecycle_event;
// 定义代理目标映射
const proxyTargets:ProxyTargets = {
'dev:sn': 'http://XXXXXXXXX.com',
'dev:snst': 'http://CCCCC.com',
'dev:st': 'http://cccccccccc.com',
'dev': 'http://yyyyyyyyyy.com',
'dev:mock': 'http://yapi.CCCC.com',
};
// 获取代理目标,如果没有匹配的,则使用默认值
const defaultProxyTarget: string = 'http://XXXXXXXXXXXX.com';
const proxyTarget: string = (npmLifecycleEvent && proxyTargets[npmLifecycleEvent]) || defaultProxyTarget;
console.log('Proxy Target:', proxyTarget);
// Vite 配置
// @ts-ignore
export default defineConfig((subprojectSpecificHtmlConfig = {}) => {
// 模版参数
const htmlConfig = createHtmlConfigFun({
// @ts-ignore
title: subprojectSpecificHtmlConfig?.title || 'XXXXXXX1',
// @ts-ignore
description: subprojectSpecificHtmlConfig?.description || 'XXXXXXX',
AWP_DEPLOY_ENV,
bocProdMode,
// @ts-ignore
subprojectSpecificTags: subprojectSpecificHtmlConfig?.subprojectSpecificTags || [] // 将子项目特定标签作为参数传递
});
return {
base: PUBLIC_URL,
build: {
minify: bocProdMode ? 'terser' : false, // 使用terser进行代码压缩
terserOptions: bocProdMode
? {
compress: {
drop_console: true,
drop_debugger: true
}
}
: {},
outDir: TALOS_SUBAPP_FLAG ? `build/${TALOS_SUBAPP_FLAG}` : 'build',
sourcemap: true, // 不区分环境
// sourcemap: !bocProdMode, //优化:在生产环境中,不生成SourceMap,减少构建体积和提高加载速度。 production-false
rollupOptions: {
output: {
sourcemapBaseUrl: getSourcemapBaseUrl()
}
}
},
server: {
port: 2024,
open: true,
proxy: {
'^/(health|open|api)': {
target: proxyTarget,
changeOrigin: true
}
}
},
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true
},
scss: {}
}
},
define: {
AWP_DEPLOY_ENV: JSON.stringify(AWP_DEPLOY_ENV),
BOC_PROD_MODE: bocProdMode,
VITE_ROUTE_BASE: JSON.stringify(AWP_DEPLOY_ENV ? `/monorepo/${TALOS_SUBAPP_FLAG}` : undefined)
},
plugins: [
react({
babel: {
presets: [
[
'@babel/preset-env',
{
modules: false, // 确保输出格式为 ES 模块
useBuiltIns: 'usage', // 自动按需引入 Polyfill
corejs: { version: 3, proposals: true }, // 使用 core-js 版本 3
targets: '> 0.25%, not dead' // 明确支持的浏览器范围
}
]
]
}
}),
createHtmlPlugin(htmlConfig)
]
};
});
vite-build/html.config.js
// 导出模版配置函数
export function createHtmlConfigFun({ title, description, AWP_DEPLOY_ENV, bocProdMode, subprojectSpecificTags }) {
const timestamp = Date.now();
// yoda唤起测试用,环境参数有 dev|test|staging|pro
const envMapping = {
production: 'pro',
staging: 'staging',
newtest: 'test',
test01: 'test',
};
const yodaEnv = envMapping[AWP_DEPLOY_ENV] || 'test';
// 是否是线上环境
const isPro = yodaEnv === 'pro';
// 根据环境选择合适的白屏检测脚本
const wsBundleScript = `https://xxxxx${AWP_DEPLOY_ENV === 'production' ? '' : '-dev'}/VVVVV.js`;
// 公共配置,所有项目都需要的配置
const commonTags = [
// 注入 meta 标签
{
tag: 'meta',
attrs: { name: 'viewport', content: 'width=device-width, initial-scale=1, shrink-to-fit=no' },
injectTo: 'head',
},
{
tag: 'meta',
attrs: { 'http-equiv': 'Cache-Control', 'content': 'no-store,no-cache,must-revalidate' },
injectTo: 'head',
},
{
tag: 'meta',
attrs: { 'http-equiv': 'Pragma', 'content': 'no-cache' },
injectTo: 'head',
},
// 注入外部脚本
{
tag: 'script',
attrs: {
src: 'https://vvvvvvvvvvvvvvvvvvv.js',
defer: true, // 增加defer属性,在文档解析完成后才会执行,从而不会阻塞DOM的解析
},
injectTo: 'head',
},
{
tag: 'script',
injectTo: 'body',
position: 'after',
attrs: {
src: wsBundleScript,
},
},
];
return {
minify: true,
inject: {
data: {
title,
description,
},
tags: [...commonTags, ...subprojectSpecificTags], // 合并公共配置和特定子项目配置
},
};
}
1.alias配置
在 Vite 中,__dirname 是不可用的,因为 Vite 是基于 ES Modules 的,而 __dirname 是一个 Node.js 的全局变量,只在 CommonJS 模块中可用。
不过不用担心,你可以使用 import.meta.url 来代替 __dirname。以下是一个例子:
// vite.config.js
import { defineConfig } from 'vite'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
})
resolve: {
alias: {
'@assets': `${__dirname}/src/assets`,
'@components': `${__dirname}/src/components`,
'@utils': `${__dirname}/src/utils`,
'@api': `${__dirname}/src/api`,
'@': `${__dirname}/src`
}
},
这样,你就可以在 Vite 中使用 @ 来代替 src 目录了。
2.build
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const timestamp = new Date().getTime();
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: './src/index.tsx',
name: 'MyReactPackage',
formats: ['es', 'cjs'],
fileName: (format) => `landing_page_gundam.${timestamp}.${format}.js`,
},
rollupOptions: {
external: ['react', 'react-dom'],
},
},
})
3.proxy配置
server: {
port: 2023, // 将此处的2023改为你要设置的新端口
open: true,
proxy: {
'^/health': {
target: 'http://',
changeOrigin: true,
},
'^/shoppingguide': {
target: 'http://.com',
changeOrigin: true,
},
'^/open': {
target: 'http://',
changeOrigin: true,
}
},
},
4.plugins
// 解决构建打包时候报错:(node:822) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill';
plugins: [
react(),
NodeGlobalsPolyfillPlugin({
buffer: true
}),
NodeModulesPolyfillPlugin()
],
6、git相关
git相关的配置.gitattributes
text
* text=auto eol=lf
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.bmp binary
*.ico binary
*.tif binary
*.tiff binary
.gitignore
# dependencies
**/node_modules
# production
**/dist
**/build
# misc
.DS_Store
npm-debug.log*
yarn-error.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json
packages/**/package-lock.json
*bak
# visual studio code
.history
*.log
**/rollup-visualizer.html
本地的一个已有项目与 Git 进行关联
- 首先,打开命令行,切换到你的项目目录下:
cd your_project_path
- 初始化 Git:
git init
这将在你的项目目录下创建一个新的 .git 子目录,所有 Git 需要的数据和资源都存储在这个目录中。
- 将所有文件添加到 Git:
git add .
这将把你的所有文件(除了 .gitignore 中指定的文件)添加到 Git 的暂存区。
- 提交你的文件:
git commit -m "Initial commit"
这将把暂存区的所有文件提交到 Git 仓库,你的文件现在已经被 Git 跟踪了。
- 关联远程仓库:
git remote add origin your_remote_repository_url
这将添加一个名为 "origin" 的新远程,URL 是你的远程仓库的 URL。
- 将你的代码推送到远程仓库:
git push -u origin master
这将把你的代码推送到远程仓库的 "master" 分支。如果你的远程仓库使用的是 "main" 作为默认分支,你应该使用 git push -u origin main。
完成以上步骤后,你的本地项目就已经与 Git 仓库关联起来了。
.gitattributes
在版本控制系统(如Git)中,跨平台的行结束符问题是一个常见的困扰。这是因为不同的操作系统使用不同的行结束符:
- Windows系统使用回车(CR)和换行(LF)两个字符序列
\r\n作为行结束符。 - Unix/Linux系统和Mac OS(自从OS X开始)使用单个换行符
\n作为行结束符。
当开发者在不同平台上协作时,可能会遇到因为行结束符不一致而导致的奇怪问题,比如额外的空白行或者在文本编辑器中显示乱码。
在根目录下创建.gitattributes文件并添加以下内容:
text
* text=auto eol=lf
这个配置有以下含义:
* text=auto: 这条规则告诉Git,对待所有文件(*表示所有文件)应该自动检测其是否为文本文件。如果是文本文件,Git将进一步应用下面的eol设置。eol=lf: 这条规定了所有文本文件在库中的行结束符应统一为 LF(换行符\n)。
通过这样的配置,你可以确保以下几点:
- Git会自动识别哪些文件是文本文件,哪些是二进制文件。
- 对于识别为文本文件的,Git会在检入(check-in)时将所有行结束符转换为LF。
- 在检出(check-out)时,Git会将LF转换为操作系统的默认行结束符,因此在Windows上,你仍然会看到正确的行结束符(CR+LF)。
.gitignore
text
node_modules/
dist/
*.log
7、TS相关
tsconfig.json配置
{
"compilerOptions": {
"downlevelIteration": true, // 当目标是 es5 或 es3 时,提供对迭代器的全面支持,包括对 for-of 循环和扩展操作符的支持。
"target": "esnext", // 设置编译后的代码目标为最新的 ECMAScript 版本
"module": "esnext", // 使用 ESNext 作为模块标准
"moduleResolution": "node", // 使用 Node.js 风格的模块解析
"lib": ["dom", "dom.iterable", "esnext", "es2018"], // 包含的库文件,这里包括了 DOM 和最新的 ECMAScript 功能
"allowJs": true,
"jsx": "react-jsx", // 使用 React JSX 转换
"allowSyntheticDefaultImports": true, // 设置import方式
"strict": true, // 开启所有严格类型检查选项
"esModuleInterop": true, // 允许默认导出与 CommonJS 模块互操作
"skipLibCheck": true, // 跳过库文件(*.d.ts)的类型检查
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
"isolatedModules": true, // 确保每个文件可以被单独编译
"resolveJsonModule": true, // 允许导入 JSON 模块
"declaration": true, // 生成 .d.ts 声明文件
"declarationMap": true, // 生成 .d.ts.map 声明源码映射文件
"sourceMap": true, // 生成源码映射文件 (.js.map),用于调试
"composite": true, // 启用项目编译
"noUnusedLocals": false, // 未使用的局部变量是否报错
"noUnusedParameters": false, // 控制函数中未使用的参数是否报错
"noEmitOnError": false, // 未使用的局部变量或参数而报错
"noImplicitAny": false,// 不会对隐式的any类型进行检查
"outDir": "./dist"
},
"include": [
"packages/*/src/**/*.ts",
"packages/*/src/**/*.tsx",
"shared-lib/src/**/*.ts",
"shared-lib/src/**/*.tsx",
"ui-components/src/**/*.ts",
"ui-components/src/**/*.tsx",
],
// "include": ["./src"],
"exclude": [
"vite-build/html.config.js",
"vite.config.ts",
"build",
"node_modules", // 排除 node_modules 目录
"**/__tests__/*", // 排除测试文件
"html.config.d.ts"
]
}
tsconfig.json 配置文件
{
"compilerOptions": {
"target": "esnext", // 设置编译后的代码目标为最新的 ECMAScript 版本
"module": "esnext", // 使用 ESNext 作为模块标准
"moduleResolution": "node", // 使用 Node.js 风格的模块解析
"lib": ["dom", "dom.iterable", "esnext"], // 包含的库文件,这里包括了 DOM 和最新的 ECMAScript 功能
"jsx": "react", // 使用 React JSX 转换
"strict": true, // 开启所有严格类型检查选项
"esModuleInterop": true, // 允许默认导出与 CommonJS 模块互操作
"skipLibCheck": true, // 跳过库文件(*.d.ts)的类型检查
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
"isolatedModules": true, // 确保每个文件可以被单独编译
"resolveJsonModule": true, // 允许导入 JSON 模块
"baseUrl": ".", // 基础目录,用于解析非相对模块名
"paths": { // 路径映射,用于设置别名
"@/*": ["./src/*"]
},
"outDir": "./dist", // 指定输出文件夹
"declaration": true, // 生成 .d.ts 声明文件
"declarationMap": true, // 生成 .d.ts.map 声明源码映射文件
"sourceMap": true, // 生成源码映射文件 (.js.map),用于调试
"composite": true // 启用项目编译
},
"include": [
"src" // 包含 src 目录下的所有文件
],
"exclude": [
"node_modules", // 排除 node_modules 目录
"**/__tests__/*" // 排除测试文件
]
}
解释每个参数的意思:
target: 设置编译后的代码目标为最新的 ECMAScript 版本,这样你就可以使用最新的 JavaScript 特性。module: 设置模块标准,Vite 推荐使用原生 ES 模块。moduleResolution: 设置模块解析策略,Node.js 风格意味着遵循 CommonJS 的解析算法。lib: 包含的库文件,这里包括了 DOM 和最新的 ECMAScript 功能。jsx: 设置 JSX 转换方式,这里使用 React 模式。strict: 开启所有严格类型检查选项。esModuleInterop: 允许默认导出与 CommonJS 模块互操作。skipLibCheck: 跳过库文件的类型检查,可以提高编译速度。forceConsistentCasingInFileNames: 强制文件名大小写一致,避免在大小写敏感的文件系统中出现问题。isolatedModules: 确保每个文件可以被单独编译,这对于 Babel 类型的转换是必要的。resolveJsonModule: 允许导入 JSON 模块。baseUrl: 设置基础目录,用于解析非相对模块名。paths: 设置路径映射,这里用于设置 src 目录的别名。outDir: 指定编译后文件的输出目录
8、代码规范相关
检测eslintrc
在最外层(根目录)和每个子项目中都添加.eslintrc.js文件
在使用Lerna、pnpm Workspaces、React和TypeScript以及Vite的项目中,你可以选择在最外层(根目录)和每个子项目中都添加.eslintrc.js文件。这取决于你想要实现的代码风格和规则的一致性程度。
以下是一种可能的配置方式:
- 在最外层(根目录)创建一个
.eslintrc.js文件,用于定义整个工作区的通用规则和设置。例如:
javascript
// .eslintrc.js (位于根目录)
module.exports = {
root: true,
env: {
browser: true,
es6: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended', // 如果你使用Prettier进行代码格式化
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: './tsconfig.json', // 指向根目录的tsconfig.json
},
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
rules: {
// 这里可以定义一些通用的规则
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
'max-len': ['error', { code: 120, tabWidth: 2, ignoreUrls: true }],
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
},
};
- 在每个子项目中创建或修改
.eslintrc.js文件,用于覆盖或扩展根目录中的通用规则。例如,在一个名为my-react-package的子项目中:
javascript
// my-react-package/.eslintrc.js
module.exports = {
extends: '../../../.eslintrc.js', // 指向根目录的.eslintrc.js
rules: {
// 这里可以定义子项目特有的规则,或者覆盖根目录中的规则
'react/prop-types': 'off', // 如果你不希望在这个子项目中检查propTypes
},
};
通过这种方式,你可以在根目录中定义通用的规则和设置,然后在每个子项目中根据需要进行定制。这样可以确保代码风格和规则在一定程度上保持一致,同时允许每个子项目有自己的特定规则。
请注意,你需要确保已经安装了必要的ESLint插件和依赖,如eslint, @typescript-eslint/eslint-plugin, @typescript-eslint/parser, eslint-plugin-react, 等。你可以在根目录运行以下命令进行安装:
**--workspace-root= ** || 在pnpm-workspace.yaml文件中添加 settings: ignore-workspace-root-check: true 否则跟路径下安装会告警
pnpm install --save-dev --workspace-root=eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react eslint-plugin-react-hooks
然后在每个子项目的package.json文件中添加一个脚本,如 "lint": "eslint .",以便通过运行 pnpm run lint 来检查代码风格和错误。
忽略检测的配置.eslintignore
.eslintignore文件用于指定哪些文件或目录应该被ESLint忽略,不进行代码风格和错误检查
txt
# .eslintignore (位于根目录)
# 忽略以下文件和目录
node_modules/
dist/
coverage/
*.min.js
**/*.d.ts
# 忽略特定子项目的特定文件
packages/my-react-package/public/*.js
packages/my-react-package/src/__tests__/*
在这个例子中:
node_modules/、dist/和coverage/是通常需要忽略的目录,因为它们包含生成的文件或第三方依赖。*.min.js忽略了所有以.min.js结尾的压缩文件。**/*.d.ts忽略了所有的TypeScript声明文件。packages/my-react-package/public/*.js和packages/my-react-package/src/__tests__/*是针对特定子项目的文件忽略规则。你可以根据你的项目结构和需求添加类似的规则。
请注意,.eslintignore文件中的路径可以是相对路径(相对于.eslintignore文件的位置)或绝对路径。你可以使用通配符(*)和双星号(**)来匹配多个文件或目录。
将.eslintignore文件放在根目录可以确保在整个工作区范围内应用这些忽略规则。如果你需要为某个子项目添加特定的忽略规则,你可以在该子项目的根目录下创建一个单独的.eslintignore文件,并在那里定义额外的规则。子项目的.eslintignore文件会与根目录的.eslintignore文件一起生效。
Package.json
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint --fix --ext .ts,.tsx src",
"preview": "vite preview"
},
//我想忽略检查时候不运行 eslint 命令
"lint": "echo 'Skipping lint'",
.eslintrc.cjs
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs',"node_modules/", "build/"],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
"@typescript-eslint/no-explicit-any": "off",
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-var-requires": "off"
},
}
统一代码编辑器格式设置.editorconfig
.editorconfig文件是一个用于统一代码编辑器格式设置的工具。它可以帮助你在团队中保持一致的编码风格和格式。以下是一个在使用Lerna、pnpm Workspaces、React、TypeScript和Vite的项目中的基本.editorconfig文件设置示例:
ini
# .editorconfig (位于根目录)
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.js]
indent_style = space
indent_size = 2
[*.ts, *.tsx]
indent_style = space
indent_size = 2
semicolon = always
quote_type = double
jsx_single_quote = false
jsx_bracket_same_line = false
[*.json]
indent_style = space
indent_size = 2
[*.md, *.markdown]
trim_trailing_whitespace = false
[*.yaml, *.yml]
indent_style = space
indent_size = 2
[*.html]
indent_style = space
indent_size = 2
[*.css, *.scss, *.less]
indent_style = space
indent_size = 2
[*.vue]
indent_style = space
indent_size = 2
在这个例子中,各个属性的含义如下:
root = true:表示这是项目的根目录。[section]:方括号定义了一个新的配置段,其中section可以是文件名、通配符或两者组合,用于指定这些设置应用到哪些文件。charset:设置字符集为UTF-8。end_of_line:设置行尾结束符为LF(Linux和macOS)。insert_final_newline:在文件末尾插入一个新行。trim_trailing_whitespace:删除行尾的空格。indent_style:设置缩进样式,可以是tab或space。indent_size:设置缩进大小,如果indent_style为tab,这个值可能被编辑器忽略。semicolon:对于TypeScript和JavaScript文件,设置是否总是使用分号。quote_type:对于TypeScript和JavaScript文件,设置字符串引号类型,可以是single或double。jsx_single_quote:对于JSX文件,设置是否使用单引号。jsx_bracket_same_line:对于JSX文件,设置标签的闭合括号是否与开始标签在同一行。
你可以根据你的项目需求和团队规范调整这些设置。将.editorconfig文件放在根目录可以确保在整个工作区范围内应用这些格式设置。大多数现代代码编辑器都支持.editorconfig,包括Visual Studio Code、Sublime Text、Atom等。