ssm 项目cannot resolve package_前端工程化之创建项目

前言

在我们团队,刚开始创建项目,是直接使用框架的 cli 进行创建项目,并修改相关配置。随着项目的增多,沉淀了两套模板,平台端及移动端。后来,我们自己写了一个简单的 cli,并提供了 create 及 lint 命令。但由于模板的问题,一直没有派上用场。
最近,我们正在进一步完善团队的基础设施。因此,期望将创建项目的功能独立出来,并做得更加简单易用。

实现方案

目前社区主流的创建项目主要有两种方案。一种是集成在 cli 当中,全局安装后进行创建项目,另外一种是使用 npm 或 yarn 提供的 create 方案,这也是我们这次选择的方案。
使用方式如下:

$ npm init company-app [appName]
or
$ yarn create company-app [appName]

一般是执行 create 命令后,输入项目名称及选择相应模板即可创建项目。在我们的团队,是有约定项目命名 admin 结尾为平台端项目,mobile 结尾为移动端项目。因此,可以通过判断输入的目录名称判断是否可以直接自动选择模板。

梳理下来的方案流程图如下:

0e598c4bfe8edd26123cc7d87d9b6afd.png

技术选型

在确定我们的方案后,通过阅读社区的一些相关项目源码,了解到在命令行及其交互方面,是有挺多的选择的。在了解相关类库后,可以通过 NPM Trends 可以查询相关类库的下载量、stars、forks、issues、updated、created、size 等数据比较。

命令行相关类库比较:

d1b69eb303712895c795127754fa16fe.png

命令行交互相关类库比较:

fbfc39352c68bba63f0018a2ed38c6b9.png

在类库选择方面,这次我们的主要考量因素有:主流、维护情况好、体积小。因此,命令行类库选择了 commander、prompts。
另外,还使用 chalk 做命令行文案样式处理、cross-spawn 做跨平台执行命令、@zeit/ncc 来打包构建项目。
值得一提的是,@zeit/ncc 会将整个项目及相关依赖打包成一个文件。这使得我们的创建项目时,非常快速。因为只需要安装一个包,而无需对包相关的依赖进行分析、下载、执行等。

代码实现

  1. 初始化项目,并安装依赖。
    目录结构如下:

├── src
│ ├── create/ # create 逻辑目录
│ ├── utils/ # 工具函数目录
│ └── index.ts # 命令入口
├── templates/ # 模板目录
├── package.json

└── tsconfig.json

package.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"]

}

  1. 写一个简单的文件夹判断函数,及从 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 判断是否使用 yarn:

import { 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 || []),],}}
  1. 编写命令行的入口文件 /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);
  1. 实现创建项目核心逻辑/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): Promise<string> {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): Promise<string> {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): Promise<void> {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();})});

}

  1. 调试发包,本地可以使用 link 进行调试。

$ yarn run dev
$ yarn link

结语

以上就是一个简单的创建项目命令行库的代码实现。包括模板,构建打包后,gzip 体积不到 100kb。不算安装依赖,创建项目非常快。
随着业务的发展,我们可能会增加更多功能。比如集成在 Gitlab 创建项目、在 Jenkins 上做好相关配置等。

参考资料

  • create-next-app: https://github.com/zeit/next.js/tree/canary/packages/create-next-app

  • create-react-native-app: https://github.com/expo/create-react-native-app

  • create-react-app: https://github.com/facebook/create-react-app

  • create-umi: https://github.com/umijs/create-umi

  • commander vs yargs vs @oclif/command vs cac vs func: https://www.npmtrends.com/commander-vs-yargs-vs-@oclif/command-vs-cac-vs-func

  • inquirer vs enquirer vs prompts: https://www.npmtrends.com/inquirer-vs-enquirer-vs-prompts

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/505013.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android_secure写权限,android.permission.WRITE_SECURE_SETTINGS权限报错

在做Android的GPS这一块时&#xff0c;根据原生代码写的Widget&#xff0c;运行时总是会报错说需要android.permission.WRITE_SECURE_SETTINGS权限&#xff0c;于是便在Manifest.xml中添加该权限&#xff0c;但是保存时会报错提示该权限仅用于系统的app查看了很多资料都说需要将…

百度seo排名规则_百度关键词seo优化排名如何上首页

无涯孤客百度关键词seo优化排名快速上首页&#xff0c;是通过使用多种百度算法优化&#xff0c;让网站在搜索引擎上排名更好&#xff0c;我们做百度关键词排名的话&#xff0c;要比市面上绝大公司做的要稳定&#xff0c;也希望各位可以相信我们&#xff0c;我们可以将百度关键词…

数学难题html5小游戏答案,小学数学难题讲解及答案

第一讲速算与巧算例1 计算9&#xff0b;99&#xff0b;999&#xff0b;9999&#xff0b;99999解&#xff1a;在涉及所有数字都是9的计算中&#xff0c;常使用凑整法.例如将999化成1000—1去计算.这是小学数学中常用的一种技巧.9&#xff0b;99&#xff0b;999&#xff0b;9999&…

distinct过滤掉重复记录并且显示所有字段_MySQL的所有姿势,我都帮你准备好了...

mysql登录远程登录方式本地登陆方式mysql&#xff1a;mysql -h 主机名 -P 端口号 -u 用户名 -p密码 mysql&#xff1a;mysql -uroot -p密码数据库操作命令创建数据库、删除数据库、展示所有数据库名。查看当前数据库名、查看所有表、查看其他数据库的表。查看数据库的版本、表结…

HTML5海报生成器源码,原生js小项目 - canvas海报生成器

1.背景之前做过一个营销类移动端h5项目-海报生成器&#xff0c;上传用户本地图片合成海报并支持下载&#xff0c;这次有时间了整理整理。2.几个重点上传本地图片并支持预览处理ios照片翻转使用canvas对图片等比拉伸缩放并居中裁剪使用canvas绘制图片以及文本输出base64并支持下…

matlab length_MATLAB入门

一、操作界面1.功能区 2.当前文件夹 3.命令窗口4.工作区 5.程序编辑窗口 6.脚本文件&#xff08;1&#xff09;当前文件夹是MATLAB读取和存储文件的默认文件夹。 &#xff08;2&#xff09;当前文件夹可以修改或新建命令行窗口&#xff1a;用于输入命令&#xff08;或语句&…

docker 端口映射 udp_Docker 制作一键安装的本地无污染 DNS 域名服务

国庆休假回来&#xff0c;感觉和整个世界失联了&#xff0c;各种不通&#xff0c;不得不就自己本地的应用环境进一步的升级&#xff1a;搭建一个本地版本的无污染 DNS 域名服务。各种网络文章扫了一遍&#xff0c;推荐以下这篇文章&#xff1a;CoreDNS搭建无污染DNS指导思想基本…

快能通小学生计算机的游戏,亲子小游戏,帮助孩子更快学会交通安全知识

随着我国机动化进程加快和小汽车进入千家万户&#xff0c;如何安全乘坐机动车&#xff0c;如何在上放学途中保障儿童青少年的交通安全&#xff0c;成为儿童青少年交通安全保护的重要内容。下面小编介绍几个亲子小游戏&#xff0c;帮助孩子更快学会交通安全知识。认识交通标志游…

latex 三线表_LaTeX学习记录(3):使用图表

学习记录(3)&#xff1a;使用图表每次跟朋友安利 的时候我都会讲这么一个故事&#xff1a;❝当时设计天琴一号加速度计的控制器参数&#xff0c;出于数字控制参数切换的便利性&#xff0c;疯狂设计了十几套参数。然而写报告的时候傻了&#xff1a;老板要求每套参数六个自由度的…

kubernetes怎么读_每个 Kubernetes 应聘者应该知道的 5 个面试题 | Linux 中国

如果你是要面试 Kubernetes 相关职位的应聘者&#xff0c;这里给出了要提问的问题以及这些问题的重要性。-- Jessica Repka面试对面试官及候选人来说都很不容易。最近&#xff0c;我发现面试 Kubernetes 相关工作的候选人似乎尤其困难。为什么呢&#xff1f;一方面&#xff0c;…

英语作文谈谈你对计算机的看法,英语作文:谈谈你对网络语言的看法

英语作文&#xff1a;谈谈你对网络语言的看法在学习、工作、生活中&#xff0c;大家或多或少都会接触过作文吧&#xff0c;作文要求篇章结构完整&#xff0c;一定要避免无结尾作文的.出现。一篇什么样的作文才能称之为优秀作文呢&#xff1f;以下是小编帮大家整理的英语作文&am…

华为云域名注册_华为云域名专场钜惠,助推中小企业云速建站

域名不仅是一个简单的网址&#xff0c;更是企业在市场竞争中获得持久优势的有力工具。所以对于中小企业而言&#xff0c;拥有一个优质的域名对企业发展而言是很重要的。为了帮助中小企业轻松注册域名&#xff0c;快速搭建好网站&#xff0c;让用户在最短的时间内&#xff0c;最…

html button跳转页面_HTML跳转到页面指定位置的几种方法

前言有时候&#xff0c;我们想阅读页面中某段精彩的内容&#xff0c;但由于页面太长&#xff0c;用户需要自己滚动页面&#xff0c;查找起来非常麻烦 &#xff0c;很容易让人失去继续往下阅读的兴趣。这样体验非常不好&#xff0c;所以我们可以想办法 实现点击某段文字或者图片…

用计算机新字库打出的文字,为什么用五笔打字有很多字打不出来(GBK和GB2312字库的区别)...

五笔输入法有很多版本&#xff0c;有的版本只能输入GB2312字库中的字(6763个字)&#xff0c;大部分版本的五笔输入法能输入GBK字库中的字(21003个字)。所以只要你选用支持GBK字库的五笔输入法&#xff0c;一般的繁体字和偏僻字就能输入了。简单一点的说&#xff1a;两个原因&am…

c语言exit_看了这几个C语言例子,你一定会说5个哇塞,声音一次比一次大

曾经我一直以为自己C语言学的还挺好的&#xff0c;直到看到这几个例子。例1首先来看一下&#xff0c;大师是如何求圆周率的&#xff0c;一口君实在词穷&#xff0c;first哇塞。#include long a10000,b0,c10000,d,e,f[10001],g; void main() { for(;b ! c; f[b] a…

html整合vue elementui,vue2.0结合Element-ui实战案例

前言我们将会选择使用一些 vue 周边的库vue-cli, vue-router,axios,moment,Element-ui搭建一个前端项目案例&#xff0c;后端数据接口&#xff0c;会使用json-server快速搭建一个本地的服务&#xff0c;方便对数据的增删改查&#xff0c;利用以上技术我们会搭建一个vue案例&…

计算机二级考试模拟表单答题,2016年计算机二级考试《VFP》模拟简答试题

1[简答题]1.建立一个表单文件myform&#xff0c;将employee表添加到表单的数据环境中&#xff0c;然后在表单中添加表格控件gridl&#xff0c;指定其记录源类型为“别名”、记录源为employee表文件&#xff0c;最后添加一个“退出”命令按钮控件Commandl&#xff0c;程序运行时…

中兴新支点操作系统_中兴新支点国产操作系统体验报告:使用流畅,性能稳定!...

笔者早就有听闻中兴新支点国产操作系统挺好用的&#xff0c;对配置要求不高&#xff0c;软件不少又没广告&#xff0c;一直想安装尝尝鲜&#xff0c;看看国产操作系统现在发展得怎样了。但是我又怕用不习惯要重装系统太麻烦&#xff0c;最后在朋友的建议下&#xff0c;我在虚拟…

tensorflowgpu利用率为0_直流电压利用率的提高方法-梯形波调制法

接上篇&#xff08;直流&#xff08;母线&#xff09;电压利用率的提高方式-三次谐波注入法&#xff09;&#xff1a;Payton Sun&#xff1a;直流电压利用率的提高方法-三次谐波注入法​zhuanlan.zhihu.com&#xff0c;本篇介绍下另一种经典的提高方式-梯形波调制法 &#xff0…

python开发信息系统权限设置_利用Python实现权限设置的详细教程

一、私有化的实现 在Python中想定义一个类是比较简单的&#xff0c;比如要定义一个Person类&#xff0c;如下代码即可&#xff1a;当然我们也可以给类添加相应的属性&#xff0c;比如Person的姓名&#xff0c;年龄&#xff0c;性别等&#xff0c;并且在new一个Person对象后可以…