济南外贸建站,北京网站建设网页设计,有没有便宜做网站的 我要做个,wordpress链接排序前言在我们团队#xff0c;刚开始创建项目#xff0c;是直接使用框架的 cli 进行创建项目#xff0c;并修改相关配置。随着项目的增多#xff0c;沉淀了两套模板#xff0c;平台端及移动端。后来#xff0c;我们自己写了一个简单的 cli#xff0c;并提供了 create 及 li… 前言在我们团队刚开始创建项目是直接使用框架的 cli 进行创建项目并修改相关配置。随着项目的增多沉淀了两套模板平台端及移动端。后来我们自己写了一个简单的 cli并提供了 create 及 lint 命令。但由于模板的问题一直没有派上用场。最近我们正在进一步完善团队的基础设施。因此期望将创建项目的功能独立出来并做得更加简单易用。实现方案目前社区主流的创建项目主要有两种方案。一种是集成在 cli 当中全局安装后进行创建项目另外一种是使用 npm 或 yarn 提供的 create 方案这也是我们这次选择的方案。使用方式如下$ npm init company-app [appName]or$ yarn create company-app [appName]一般是执行 create 命令后输入项目名称及选择相应模板即可创建项目。在我们的团队是有约定项目命名 admin 结尾为平台端项目mobile 结尾为移动端项目。因此可以通过判断输入的目录名称判断是否可以直接自动选择模板。梳理下来的方案流程图如下技术选型在确定我们的方案后通过阅读社区的一些相关项目源码了解到在命令行及其交互方面是有挺多的选择的。在了解相关类库后可以通过 NPM Trends 可以查询相关类库的下载量、stars、forks、issues、updated、created、size 等数据比较。命令行相关类库比较命令行交互相关类库比较在类库选择方面这次我们的主要考量因素有主流、维护情况好、体积小。因此命令行类库选择了 commander、prompts。另外还使用 chalk 做命令行文案样式处理、cross-spawn 做跨平台执行命令、zeit/ncc 来打包构建项目。值得一提的是zeit/ncc 会将整个项目及相关依赖打包成一个文件。这使得我们的创建项目时非常快速。因为只需要安装一个包而无需对包相关的依赖进行分析、下载、执行等。代码实现初始化项目并安装依赖。目录结构如下├── src│ ├── create/ # create 逻辑目录│ ├── utils/ # 工具函数目录│ └── index.ts # 命令入口├── templates/ # 模板目录├── package.json└── tsconfig.jsonpackage.json 如下{name: create-company-app,version: 0.0.1,description: Create apps with one command,bin: {create-company-app: ./dist/index.js },files: [dist ],scripts: {clean: rimraf ./dist/,dev: yarn run clean ncc build ./src/index.ts -o dist/ -w,build: yarn run clean ncc build ./src/index.ts -o ./dist/ --minify --no-cache --no-source-map-register },devDependencies: {types/fs-extra: ^9.0.0,types/node: ^14.0.1,types/prompts: ^2.0.8,types/rimraf: ^3.0.0,types/validate-npm-package-name: ^3.0.0,zeit/ncc: ^0.22.1,chalk: ^4.0.0,commander: ^5.1.0,cross-spawn: ^7.0.2,fs-extra: ^9.0.0,prompts: ^2.3.2,rimraf: ^3.0.2,typescript: ^3.9.2,validate-npm-package-name: ^3.0.0 }}tsconfig.json 如下{compilerOptions: {target: es2015,moduleResolution: node,strict: true,resolveJsonModule: true,esModuleInterop: true,skipLibCheck: false },include: [./src]}写一个简单的文件夹判断函数及从 create-next-app 复制几个工具函数主要是项目名校验及判断 npm 包管理。/utils/is-folder-exists.ts 判断文件夹是否为空import { existsSync } from fs;import chalk from chalk;export default function isFolderExists(appPath: string, appName: string) {if (existsSync(appPath)) {console.log(The folder ${chalk.green(appName)} already exists.);console.log(Either try using a new directory name, or remove it.);return true;}return false;}/utils/should-use-yarn.ts 判断是否使用 yarnimport { execSync } from child_process;export default function shouldUseYarn(): boolean {try {const userAgent process.env.npm_config_user_agent;if (userAgent) {return Boolean(userAgent userAgent.startsWith(yarn));}execSync(yarnpkg --version, { stdio: ignore });return true;} catch (e) {return false;}}/utils/validate-pkg.ts 验证包名是否合法import validateProjectName from validate-npm-package-name;export function validateNpmName(name: string): { valid: boolean; problems?: string[] } {const nameValidation validateProjectName(name);if (nameValidation.validForNewPackages) {return { valid: true };}return {valid: false,problems: [ ...(nameValidation.errors || []), ...(nameValidation.warnings || []),],}}编写命令行的入口文件 /src/index.ts 。需要注意的是文件前面的 #!/usr/bin/env node 是必须的具体原因可见What exactly does “/usr/bin/env node” do at the beginning of node files?。#!/usr/bin/env nodeimport chalk from chalk;import { Command } from commander;import create from ./create;import packageJson from ../package.json;new Command(packageJson.name).version(packageJson.version).arguments([project-directory]).usage(chalk.green()).action(create).allowUnknownOption().parse(process.argv);实现创建项目核心逻辑/src/create/index.ts 创建项目流程入口文件import path from path;import chalk from chalk;import resolvePath from ./resolve-path;import resolveType from ./resolve-type;import copyTemplate from ./copy-template;import installPkg from ./install-pkg;import shouldUseYarn from ../utils/should-use-yarn;import isFolderExists from ../utils/is-folder-exists;export default async function create(inputPath: any) {const useYarn shouldUseYarn();const originalDirectory process.cwd();const displayedCommand useYarn ? yarn : npm run;const appPath await resolvePath(inputPath);const appType await resolveType(appPath);const appName path.basename(appPath);const cdPath path.join(originalDirectory, appName) appPath ? appName : appPath;if (isFolderExists(appPath, appName)) {process.exit(1);}console.log(Creating a new app in ${chalk.green(appPath)}.);console.log();await copyTemplate({ appPath, appType,});console.log(Installing packages. This might take a couple of minutes.);console.log();await installPkg({ appPath, useYarn,});console.log(${chalk.green(Success!)} Created ${appName} at ${appPath});console.log(Inside that directory, you can run several commands:);console.log();console.log(chalk.cyan( ${displayedCommand} dev));console.log( Starts the development server.);console.log();console.log(chalk.cyan( ${displayedCommand} build));console.log( Builds the app for production.);console.log();console.log(We suggest that you begin by typing:);console.log();console.log(chalk.cyan( cd), cdPath);console.log( ${chalk.cyan(${displayedCommand} dev)});console.log();}/src/create/resolve-path.ts 解析项目名称import path from path;import chalk from chalk;import prompts from prompts;import packageJson from ../../package.json;import { validateNpmName } from ../utils/validate-pkg;const commandName packageJson.name;export default async function resolvePath(input: string): Promisestring {let name input?.trim();if (!name) {const { answer } await prompts({type: text,name: answer,message: What is your project named?,validate: name {const validation validateNpmName(path.basename(path.resolve(name)));if (validation.valid) {return true;}return Invalid project name: validation.problems![0];},});console.log(answer);if (typeof answer string) {name answer.trim();}}if (!name) {console.log()console.log(Please specify the project directory:)console.log( ${chalk.cyan(commandName)} ${chalk.green()})console.log()console.log(For example:)console.log( ${chalk.cyan(commandName)} ${chalk.green(app-admin)})console.log()console.log(Run ${chalk.cyan(${commandName} --help)} to see all options.)process.exit(1);}const projectPath path.resolve(name);const projectName path.basename(projectPath);const { valid, problems } validateNpmName(projectName);if (!valid) {console.error(Could not create a project called ${chalk.red( ${projectName} )} because of npm naming restrictions:)problems!.forEach(p console.error( ${chalk.red.bold(*)} ${p}))process.exit(1)}return projectPath;}/src/create/resolve-type.ts 解析项目模板类型import * as path from path;import prompts from prompts;const appTypeList [admin, mobile];export default async function resolveType(input: string): Promisestring {let appType;const projectPath path.resolve(input);const lastStr path.basename(projectPath).split(-).pop();if (lastStr appTypeList.includes(lastStr)) {appType lastStr;} else {const { answer } await prompts({type: select,name: answer,message: Pick a template,choices: appTypeList.map(i ({ title: i, value: i })),});appType answer;}return appType;}/src/create/copy-template.ts 复制模板并创建项目(需要自行准备一些模板)import { copySync, readFileSync, writeFileSync } from fs-extra;import path from path;type Params {appName: string;appType: string;appPath: string;};export default async function copyTemplate({ appName, appPath, appType }: Params) {const templatePath path.join(__dirname, ../../templates/${appType});copySync(templatePath, appPath);const pkgPath path.join(appPath, package.json);const pkg JSON.parse(readFileSync(pkgPath, utf-8));pkg.name appName;writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));}/src/create/install-pkg.ts 安装项目依赖import spawn from cross-spawn;type Params {appPath: string;useYarn: boolean;};export default async function installPkg({ appPath, useYarn }: Params): Promisevoid {return new Promise((resolve, reject) {process.chdir(appPath);const command useYarn ? yarn : npm;const args [install];const child spawn(command, args, {stdio: inherit,env: { ...process.env, ADBLOCK: 1, DISABLE_OPENCOLLECTIVE: 1 },});child.on(close, code {if (code ! 0) {reject({ command: ${command} ${args.join( )} });return;}resolve();})});}调试发包本地可以使用 link 进行调试。$ yarn run dev$ yarn link结语以上就是一个简单的创建项目命令行库的代码实现。包括模板构建打包后gzip 体积不到 100kb。不算安装依赖创建项目非常快。随着业务的发展我们可能会增加更多功能。比如集成在 Gitlab 创建项目、在 Jenkins 上做好相关配置等。参考资料create-next-app: https://github.com/zeit/next.js/tree/canary/packages/create-next-appcreate-react-native-app: https://github.com/expo/create-react-native-appcreate-react-app: https://github.com/facebook/create-react-appcreate-umi: https://github.com/umijs/create-umicommander vs yargs vs oclif/command vs cac vs func: https://www.npmtrends.com/commander-vs-yargs-vs-oclif/command-vs-cac-vs-funcinquirer vs enquirer vs prompts: https://www.npmtrends.com/inquirer-vs-enquirer-vs-prompts