pnpm dlx create-umi@latest 是在做什么
由于好奇 umi 运行这行命令的时候就行是在做什么,于是扒了源码看下
pnpm dlx create-umi@latest
其实就是一个cli的创建过程, 详create-umi,我们可以看看package.json就知道,使用father打包, bin文件入口bin/create-umi.js
"bin": {
"create-umi": "bin/create-umi.js"
},
"scripts": {
"build": "umi-scripts father build",
"build:deps": "umi-scripts bundleDeps",
"dev": "umi-scripts father dev",
"test": "umi-scripts jest-turbo"
},
看看bin/create-umi.js里面是啥
// bin/create-umi.js
#!/usr/bin/env node
process.env.FS_LOGGER = 'none';
require('../dist/cli');
依赖于dist的cli文件
import { chalk, isLocalDev, yParser } from '@umijs/utils';
const args = yParser(process.argv.slice(2), {
alias: {
version: ['v'],
help: ['h'],
},
boolean: ['version'],
});
if (args.version && !args._[0]) {
args._[0] = 'version';
const local = isLocalDev() ? chalk.cyan('@local') : '';
const { name, version } = require('../package.json');
console.log(`${name}@${version}${local}`);
} else {
require('./')
.default({
cwd: process.cwd(),
args,
})
.catch((err: Error) => {
console.error(`Create failed, ${err.message}`);
console.error(err);
});
}
然后找到 index.ts的入口,这里做的事情就是:
1、 根据@clack/prompts交互式命令获取用户选择的参数
import {
BaseGenerator,
chalk,
clackPrompts,
execa,
fsExtra,
getGitInfo,
installWithNpmClient,
logger,
pkgUp,
tryPaths,
yParser,
} from '@umijs/utils';
import { existsSync } from 'fs';
import { dirname, join } from 'path';
const internalTemplatePrompts = async () => {
intro(chalk.bgHex('#19BDD2')(' create-umi '));
await selectAppTemplate();
if (isCancel(appTemplate)) {
exitPrompt();
}
await selectNpmClient();
if (isCancel(npmClient)) {
exitPrompt();
}
await selectRegistry();
if (isCancel(registry)) {
exitPrompt();
}
outro(chalk.green(`You're all set!`));
};
2、 然后根据这些参数去生成模板
const injectInternalTemplateFiles = async () => {
// BaseGenerator 这个类就是拷贝模板文件
const generator = new BaseGenerator({
path: join(__dirname, '..', 'templates', appTemplate),
target,
slient: true,
data: useDefaultData
? defaultData
: ({
version: version.includes('-canary.') ? version : `^${version}`,
npmClient,
registry,
author,
email,
withHusky,
extraNpmrc: isPnpm ? pnpmExtraNpmrc : '',
pluginName,
} satisfies ITemplateParams),
});
await generator.run();
};
这里有几个点,
3、注意点
3.1、umi-scripts是在做什么
详细可以见umi-scripts, 其目的就是在node环境中可以执行ts 或者esm
3.2、为什么chalk、@clack/prompts等要从@umi/utils引入,
答案在上篇【umi】02 如何在cjs 环境用esm包的回答,主要就是借助了umi-scripts bundleDeps命令将对应的node_modules 用ncc进行了cjs的转化
手写一个cli
自己也仿照umi,动手写了一个,功能在就是在指定的文件目录创建文件模板,因为在日常工作中,我们新建一个文件,总是复制,然后改成自己的名字,觉得很麻烦
1、在package.json 写入
"bin": {
"create-tpl": "bin/create-tpl.js"
},
2、bin文件
// bin/create-tpl.js
#!/usr/bin/env node
// 表示禁用文件系统的日志记录
process.env.FS_LOGGER = 'none';
require('../dist/cjs/cli');
3、入口文件
// index.ts
import { writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import { renderTep } from './template';
enum ETemplate {
compoent = 'component',
page = 'page',
}
const DEFAULT_DATA = {
appTemplate: ETemplate.page,
};
interface IGeneratorOpts {
cwd: string;
args: any;
defaultData?: {
appTemplate?: ETemplate;
};
}
export default async ({
cwd,
args,
defaultData = DEFAULT_DATA,
}: IGeneratorOpts) => {
const chalkModule = await import('chalk'); // 由于不想从@umi/utils 引入 chalk ,所以想用动态导入的方式
const clackPromptsModule = await import('@clack/prompts');
const chalk = chalkModule.default;
const { select, text, isCancel, outro } = clackPromptsModule;
let appTemplate = defaultData?.appTemplate || ETemplate.page;
let fileName = '';
let [name] = args._;
let basePath = name ? join(cwd, name) : join(cwd, 'src');
const exitPrompt = () => {
outro(chalk.red('Exit!'));
process.exit(1);
};
const selectAppTemplate = async () => {
appTemplate = (await select({
message: 'Pick Template Name',
options: [
{ label: 'Component', value: ETemplate.compoent },
{
label: 'Page',
value: ETemplate.page,
},
],
})) as ETemplate;
};
const inputFileName = async () => {
fileName = (await text({
message: 'Input File Name',
validate: (value) => {
if (!value.match(/[a-z]+[A-Z]{1}[a-z]+/g)) {
return 'Please Input Camel Case Name!';
}
},
})) as ETemplate;
};
const internalTemplatePrompts = async () => {
await selectAppTemplate();
if (isCancel(appTemplate)) {
exitPrompt();
}
await inputFileName();
if (isCancel(appTemplate)) {
exitPrompt();
}
const isPage = appTemplate === ETemplate.page;
const { LessTep, TsxTep, TsTep } = renderTep(fileName, isPage);
const styleFileName = isPage ? 'style.module.less' : 'style.less';
const path = isPage ? join(basePath, 'pages') : join(basePath, 'compoents');
mkdirSync(`${path}/${fileName}`); // 新建文件夹
process.chdir(`${path}/${fileName}`); // 更改目录
writeFileSync(styleFileName, LessTep);
writeFileSync(`${fileName}.tsx`, TsxTep);
writeFileSync(`index.ts`, TsTep);
};
await internalTemplatePrompts();
};
4、联调测试
pnpm link <dir>将 中的软件包链接到执行命令的软件包的 node_modules。pnpm link --dir <dir>将当前工作目录中的软件包链接到 。pnpm link --global将当前目录的软件包链接到全局pnpm link --global <pkg>链接全局指定的pkg- 或者在
package.json写入
"devDependencies": {
"create-tpl": "workspace:*"
}
然后pnpm i皆可,就不用pnpm link了
举例
如果foo 项目是公用的项目
cd ~/projects/foo
pnpm install # install dependencies of foo
cd ~/projects/my-project
pnpm link ~/projects/foo # link foo to my-project
cd ~/projects/foo
pnpm install # install dependencies of foo
cd ~/projects/my-project
pnpm link ~/projects/foo # link foo to my-project
5、效果
6、问题
由于我不是很想从@umi/utils里面导入chalk、@clack/prompts等,
于是我在代码里面采用动态导入了,但是在执行
father doctor && npm run build 后,却包这个错误
但是换成从
@umi/utils导入就可以了
明明对应的声明文件都有,可是偏偏不行, 所以father doctor是在做什么
7、 其他注意点
用esm的方式写cli,有很多包不想在cjs直接引入,比如path、fs等, 安装"@types/node"即可