
Monorepo + pnpm 的 workspace 模式
优点
缺点:
优点:
缺点:
npm install pnpm -g
pnpm init
# 更改根目录的 package.json 文件
# 增加private字段 防止我们的根目录被当作包发布
{
"private": true
}管理多个项目,就可以采用 pnpm 的 monorepo。我们在仓库的根目录下创建一个 pnpm-workspace.yaml 文件,可以在 pnpm-workspace.yaml 配置文件中指定这个仓库中有多少个项目
packages:
- play # 存放组件测试的代码
- docs # 存放组件文档
- packages/* # packages 目录下都是组件包在 packages 目录中又可以放很多包的项目目录,比如,组件包目录:components、主题包目录:theme-chalk、工具包目录:utils 等
以 components 包为例,我们进入components 目录底下初始化一个 package.json 文件,更改包名:@xxx/components
pnpm init其他两个的包名创建规则一样,至此已经搭建了一个初步的项目目录
├── README.md
├── package.json
├── packages
│ ├── components
│ │ └── package.json
│ ├── theme-chalk
│ │ └── package.json
│ └── utils
│ └── package.json
├── play
└── pnpm-workspace.yaml@xxx/components 、@xxx/theme-chalk 、@xxx/utils 这几个包要互相进行调用呢,就需要把它们安装到仓库根目录下的 node_modules 目录中。 然后我们在根目录下进行安装操作
pnpm install @xxx/components -w
pnpm install @xxx/theme-chalk -w
pnpm install @xxx/utils -w安装后根目录下的 package.json 的内容为:
{
"dependencies": {
"@xxx/components": "workspace:*",
"@xxx/theme-chalk": "workspace:*",
"@xxx/utils": "workspace:*"
},
}
// 注意:workspace:* 将来发布的时候会被转换成具体的版本号。npm install fs-extra mustache log-symbols inquirer注意:如果出现 Error [ERR_REQUIRE_ESM]: require() of ES Module not supported 说明是某个包不支持 require,就需要看对应的包哪个版本支持 require,我这边碰到两个包最新版本不支持 require,因此我选择了低版本(inquirer@7.1.0, log-symbols@4.0.0)
bin 文件夹下创建 new/index.js 用于生成模板文件
const rimraf = require("rimraf");
const inquirer = require("inquirer");
const fs = require("fs-extra");
const path = require("path");
const Mustache = require("mustache");
const createModuleFiles = (moduleName, moduleType, moduleDesc) => {
const outputName =
moduleName[0].toLowerCase() + moduleName.slice(1, moduleName.length);
const templates = [
{
template: "moduleComponentTsx.tpl",
output: `app/components/${moduleName}/${moduleName}.tsx`
},
{
template: "moduleComponentStyle.tpl",
output: `app/components/${moduleName}/${moduleName}.module.css`
}
];
try {
let tpl, output;
templates.forEach(temp => {
tpl = fs.readFileSync(
path.resolve(__dirname, `./templates/component/${temp.template}`),
"utf8"
);
output = Mustache.render(tpl, { moduleName, outputName, moduleDesc });
fs.outputFileSync(path.resolve(process.cwd(), temp.output), output);
});
console.log("模块文件创建完成");
} catch (error) {
console.error(error);
}
};
class NewModule {
constructor() {
this.createModule();
}
async createModule() {
// 模块类型
const moduleType = await this.inputType();
// 模块名
const moduleName = await this.inputName();
// 模块描述
const moduleDesc = await this.inputDesc();
// 清除重名文件
await this.clearFile(moduleName, moduleType);
createModuleFiles(moduleName, moduleType, moduleDesc);
}
async inputType() {
const { moduleType } = await inquirer.prompt([
{
name: "moduleType",
message: "请选择创建类型",
type: "list",
choices: [
{
name: "UI组件",
value: "component"
},
{
name: "页面",
value: "page"
}
],
default: "component"
}
]);
return moduleType;
}
async inputName() {
const { moduleName } = await inquirer.prompt([
{
name: "moduleName",
message: "请输入模块名称",
type: "input"
}
]);
return moduleName;
}
async inputDesc() {
const { moduleDesc } = await inquirer.prompt([
{
name: "moduleDesc",
message: "请输入模块描述",
type: "input"
}
]);
return moduleDesc;
}
async clearFile(moduleName, moduleType) {
if (moduleType === "page") {
rimraf.rimraf(
path.resolve(process.cwd(), "app/component", `${moduleName}.module.css`)
);
rimraf.rimraf(
path.resolve(process.cwd(), "app/app", `${moduleName}.tsx`)
);
} else if (moduleType === "component") {
}
}
}
new NewModule();模板文件:可以读取命令行参数,然后根据参数生成模板文件
// {{moduleDesc}}
import styles from './{{outputName}}.module.css';
export interface I{{outputName}} {
sampleTextProp: string;
}
const {{outputName}}: React.FC<I{{outputName}}> = ({sampleTextProp}) => {
return (
<div className={styles.{{outputName}}}>
{ sampleTextProp }
</div>
)
}
export default {{outputName}};运行命令:npm run create
{
"scripts: {
"create": "node bin/new"
}
}