0.背景
我们新建一个项目的时候需要对项目的初始文件进行配置,但是我们希望创建项目的时候,有一些团队的公共配置需要移植到这个新建的项目,例如: 1.eslint等代码规范; 2.commitlint配置; 3.业务公共功能配置:用户权限,sso登陆,字体风格等
1.前端脚手架
1.1 脚手架的功能
我们先来看一下使用vue-cli脚手架时候,开发用户执行了那些步骤,然后看一下脚手架自身执行了哪些步骤。然后将这个步骤抽象,然后在抽象的基础上对应到我们自生的功能,再生成我们的脚手架实例。
vue-cli创建项目过程:
step0 - 安装vue脚手架vue-cli;
step1 - 使用创建命令: vue create projetName
step2 - 用户交互选择:(在命令行中选择自己需要的版本或者配置)
![]()
step3 - 根据用户交互中用户选择的配置,生成对应的项目初始化文件。
所以,总的来说,从脚手架的用户角度来看,脚手架的基本执行流程是:
1.使用脚手架命令配置项目名称
2.用户自己选择需要对项目进行哪些配置
3.自动生成所需项目
2.脚手架的封装
1.脚手架命令配置 - bin字段
1.1 初始化项目 npm init -y
1.2 配置项目中的package.json文件内的bin字段
// package.json
// bin 字段也支持对象模式配置
"bin": { "lee-cli": "./bin/www.js" }, // 这个时候这个 lee-cli 就是这个脚手架的命令
所以bin字段中的属性名就是命令名,属性值就是执行的文件地址
bin字段设置的是“命令”到“本地可执行文件地址”的映射。但我们使用npm对包进行安装的时候,如果这个包的package.json文件中配置了bin字段,那么npm会自动将"bin"字段对应的可执行文件复制一份放在node_modules中(这样做的好处就是:我们如果想执行对应的可执行文件,可以不用输入可执行文件的路径,直接输入文件名即可,那么npm会自动查看node_modles下的.bin文件)
参考文献:juejin.cn/post/684490…
1.3 配置与bin字段地址对应的可执行文件
#! /usr/bin/env node
// 为什么要在文件头部添加这段代码,#!符号Shebang,用于指定脚本的解释程序,如果开发npm包的时候
console.log("hello demo");
需要注意的是,这个文件中的#!/user/bin/env node 是指:我要用系统中的这个目录/usr/bin/env的node环境来执行此文件,且需要注意必须放在文件开头,不能有空格,否则会语法报错。
如果是基于已有的脚手架进行开发时,想去查看脚手架的配置,需要去nodemodule中找到脚手架包,查看里面的package.json文件中的bin字段,因为bin是设置在“脚手架”项目中的json文件中,所以只能在脚手架项目中的package.json文件中找。
1.4 npm link到全局
进入到项目的根目录,执行npm link,执行完之后就可以直接执行脚手架的命令语句了,自动通过package.json中的bin字段映射,执行到bin下面的可执行文件
1.5 直接运行脚手架命令
2. commander命令后的指令参数配置
2.1 commander配置指令参数
前面我们只是实现了在终端可以直接执行pro-cli这个指令,但是我们知道我们使用vue-cli创建项目的第一步是输入:vue create projectName。此时我们仅仅识别了指令“vue”,但是指令后面的“create projectName”具体是要执行什么,还没有解析。
我们除了上面直接执行pro-cli这个脚手架命令之外,有时候还需要在这个命令后面加上一些参数,表示区分详细的命令.
例如:
pro-cli create 项目名
pro-cli --help,表示查看这个命令的使用信息;
pro-cli -v 表示目前使用的这个脚手架的版本。
所以我们首先需要知道,用户在命令之后是否还有额 外的参数,并且拿到这些参数值,然后才能根据这些参数值走到不同的逻辑。
commander这个库就是帮助我们获取命令后面的参数值的js库。
命令后面的参数配置如果是我们设定好的逻辑,就会走我们的代码,类似于switch逻辑,所以我们需要提前设置一下相应的指令
#!/usr/bin/env node
const program = require('commander');
const create = require('../src/create');
console.log('123123')
program
.command('create <projectName>') // 这里输入对应的指令参数‘create’,后面的<name>参数标志,例如vu create app,中的app就是projectName,这个会在action中的回调函数参数中获取到。
.alias('c') // 配置参数的简写,pro-cli create 可以简写为“pro-cli c”
.description('create a new project')
.option('-f, --force', 'message') // 此时执行的命令全称为:pro-cli create app -f,action通过name拿到create app,options用来拿到后面的-f
.action(name => { // 这里的name就是获取指令参数后面的 参数变量
console.log("project name is " + name)
})
program.parse(process.argv)
// 注意:如果测试阶段,需要在执行aciton命令后,在控制台打印相关信息,一定要在pargram.parse函数中增加参数process.argv,否则会报错。(文献:https://juejin.cn/post/6959750919491682318#heading-2)
所以create作为pro-cli命令的参数,而命令的参数后面还有参数:pro-cli create projectName
以下是打印的结果:我们输入pro-cli create app,这里的app就是我们需要知道的项目名称(作为文件名)
API配置描述:
option('-n, --name [p2]', '描述', '默认值') // 一共三个参数:
- -n, --name [p2] 分别为指令参数的 -n简写名称,或者 --name(全称谓) 参数p2 2.'描述' 3.'默认值'
2.2 chalk库-美化控制台UI
有时候我们需要在用户拿到返回结果中,在控制台中标志一下哪些是重点字符,而不是在大量的控制台输出中自己去查找,这个时候我们可以使用chalk这个UI库。所以,我们可以在action中的回调打印到控制台的字符作为chalk的函数参数中,进行美化,将需要设置颜色的字符放在对应的颜色API的参数中:chalk.cyan(name)
结果:
至此,第一行命令:pro-cli create projectName,就已经可以正式执行了。这里就等价于vue create app。下面就会进入用户配置阶段,在下一阶段我们需要通过终端与用户的交互,来获取用户的具体配置项目
3.获取用户的配置&交互 - inquirer
3.1 inquirer
inquirer是一个强大的交互命令行工具,我们可以利用这个交互工具让用户自己配置项目所需要的配置项。例如:
那么我们怎么样让用户终端出现这个选项,并且在用户选择完毕之后拿到选中值:
const inquirer = require('inquirer');
inquirer
.prompt([ // 这个数组中存放的元素是对象元素,对象的的属性有:
// 1.name,表示选择之后的key,也是回调通过key拿到选中的值
// 2.type: 以input形式还是box形式给用户选择
// 3.message:用户的提示信息
// 4.chioce:type为checkbox时候的选择信息
])
.then(answers => {
// 回调函数,answers 就是用户输入的内容,是个对象
});
例如通过执行
program.parse(process.argv)
new inquirer.prompt([
{
name: "dependency", // 多选交互功能 // 单选将这里修改为 list 即可
type: "checkbox",
message: "请选择项目需要的配置:",
choices: [
{
name: "Babel",
value: "Babel",
},
{
name: "TypeScript",
value: "TypeScript",
},
{
name: "Progressive Web App (PWA) Support",
value: "Progressive Web App (PWA) Support",
},
{
name: "Router",
value: "Router",
},
],
},
{
name: "projectName",
// 多选交互功能
// 单选将这里修改为 list 即可
type: "input",
message: "请输入项目名称:",
},
]).then((data) => {
console.log(111, data);
});
界面展示如下:
最后会根据用户的输入在回调的参数中获得一个用户选择之后的对象,对象中的key就是元素中的name属性值,value就是用户选择的值,如下:
所以,inquirer最后用户交互返回的值是一个对象,key是参数元素的name字段,value是choice中各个元素的value值。
4.创建动态导入&创建项目初始文件
参考文献:juejin.cn/post/693261…
脚手架项目的目录树如下:
├─.vscode
├─bin
│ ├─enter.js # pro-cli 全局命令
├─lib
│ ├─generator # 各个功能的模板
│ │ ├─babel # babel 模板
│ │ ├─linter # eslint 模板
│ │ ├─router # vue-router 模板
│ │ ├─vue # vue 模板
│ │ ├─vuex # vuex 模板
│ │ └─webpack # webpack 模板
│ ├─promptModules # 各个模块的交互提示语
│ └─utils # 一系列工具函数
│ ├─create.js # create 命令处理函数
│ ├─Creator.js # 处理交互提示
│ ├─Generator.js # 渲染模板
│ ├─PromptModuleAPI.js # 将各个功能的提示语注入 Creator
└─scripts # commit message 验证脚本 和项目无关 不需关注
其中需要注意的是,lib文件夹中的babel、linter、vue-router、vuex四个文件包含了与他们对应插件相关的四个交互提示语相关的问题。 整体流程: 用户输入create创建命令,用户交互配置&获取用户交互配置,创建相关文件 其中用户输入命令:pro-cli的时候就会自动根据bin字段的地址,执行可执行文件enter.js:
#!/usr/bin/env node
const program = require('commander')
const create = require('../lib/create')
program
.version('0.1.0')
.command('create <name>')
.description('create a new project')
.action(name => {
create(name)
})
program.parse()
1.create.js文件
const path = require('path');
const fs = require('fs-extra'); // fs-extra是在fs文件模块的基础上对fs文件模块的扩展(同时继承了原有API)
const inquirer = require('inquirer');
module.exports = async function createNewFile(name, options) {
const work_cwd = process.cwd();
// 1.拿到当前工作目录,注意process.cwd是得到当前node命令执行时所在的目录(即项目运行目录),而__dirname则是针对当前文件所在的目录
const project_cwd = path.join(work_cwd, name)
// 此时project_cwd的值为:/Users/luweidong/Desktop/project-cli/project
console.log('pwd.js', project_cwd)
}
这里要注意一下工作区目录和创建文件的目录,工作区目录是指你这个项目所在的文件夹(桌面中的app文件夹下新建一个项目),还有一个是想在哪个目录下创建文件(当前项目所在文件夹/项目名称).也就是说,我想在当前的文件夹下面新建一个app项目,那么我就要新建一个app文件夹,所以我就要看一下这个当前的工作区是否存在相同的文件,如果存在,那么我就不能叫这个项目名称/或者覆盖。
前面的都是脚手架搭建的前置工作,后面就要进入我们真正的项目创建阶段了。 为了更好的管理,将创建项目部分抽象成了一个Creator类
脚手架的功能用一句话来说:创建项目的初始文件,脚手架的目的就是得到项目的初始文件,当然你也可以手动创建,甚至基于已有项目复制更改。
脚手架的本质:方案的封装
参考文献:juejin.cn/post/712563… 1.项目初始化 2.创建入口-在你的包文件夹下,建立一个bin目录,里面增加一个main文件(注意,纯main文件,没有.js这种后缀)
5.如何远端下载文件
前端项目基础配置
eslint配置代码风格&质量的校验;prettier负责代码格式的校验;lint-stage负责过滤文件;其中eslint和prettier两个配合使用,一个做代码的质量校验 && 一个做代码的风格校验。
1.Eslint
1.1 使用Eslint配置规则的两种方式
使用eslint进行规则配置的方式主要有两种:
1.嵌入注释,就是将配置规则以注释形式放在你的业务代码中;
2.配置文件,通过配置文件的方式来对整个项目的代码进行代码质量检查,这里的eslint配置文件主要有两种方式存在
(1) 单独的配置文件 .eslintrc.*
单独的文件(.eslintrc.js)三种文件的格式都可以被eslint插件找到配置文件 js/json/YAML。这种配置方式要非常熟悉,项目中常用的就是单独的.eslintrc.js文件。
(2) 将配置属性。即写在package.json中。
总结:eslint配置项无论是单独的文件,还是合并在package.json中,eslint插件都可以找到对应的配置项。
1.2 查找Eslint配置文件的方式
eslint默认的查找方式是向上查找
(层叠配置规则,即默认情况下eslint会一直向上寻找父项目对应的配置文件,直到根目录),这也就意味着,如果我们是两个项目存在父子关系,那么这子项目的.eslintrc.js文件或继承父项目.eslintrc.js文件中的配置项目。如果发生冲突的话是按照最近优先原则,即子项目.eslintrc.js中的配置项为准。
这就存在一个问题,比如我们在使用qiankun的时候,不希望子项目继承父项目的.eslintrc.js文件中的配置,这个时候可以在通过配置root: true。
- demo
- packages
- projectA
- index.js
- .eslintrc.js
- projectB
- index.js
.eslintrc.js
package.json
比如,如果在 demo/packages/projectA/.eslintrc.js 中设置了 root: true,那么此时 projectA/index.js 仅会有 projectA/eslintrc.js 的配置生效。
demo/.eslintrc.js 仅会影响 projectB/index.js 并不会影响 packageA/index.js。
1.3 .eslintrc.js配置文件
module.exports = {
root: true,
env: { // `env` 配置来告诉 EsLint 当前项目支持的运行环境
node: true
},
extends: [
'plugin:vue/recommended',
'eslint:recommended',
'@vue/typescript/recommended'
],
parserOptions: {
ecmaVersion: 2020 // 使用es2020来检查我们的代码
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
... // 其他配置规则
}
}
解析:
1.root :eslint配置文件不支持向上查找
2.parserOptions: eslint使用哪种规范来负责检查我们的代码,默认是使用“ES5规范”检查我们的项目代码。
3.env: eslint检查的这个项目需要的运行环境
4.rules:eslint这个插件中内置了一系列可以配置的规则属性,可供我们选择放在rules属性中,那么eslint就会通过rules属性来详细检查代码规范
例如:
rule: {
"no-console" : "warn", // // 对于 console 进行警告检测
'no-unused-vars': ['error'], // 对于未使用的变量进行错误检测
}
这里规则属性的值一共有三种:off、warn、error
"off"或0表示关闭本条规则检测"warn"或1表示开启规则检测,使用警告级别的错误:warn(不会导致程序退出)"error"或2表示开启规则,使用错误级别的错误:error(当被触发的时候,程序会退出)
5.extends:从eslint插件本身/或者某个项目中继承这个项目中的eslint配置文件,Extends 继承关键字存在三种写法(情况):
- 从 EsLint 本身的规则进行继承,比如
extends: ['eslint:recommended']- 从第三方的 NPM 包规则进行继承,比如
extends : ['eslint-config-airbnb']- 从 ESLint 的插件进行继承,比如
extends: ['plugin:react/recommended']- 从绝对路径继承而来,比如
extends: ["./node_modules/coding-standard/eslintDefaults.js"]
例如:
// .eslintrc.js
module.exports = {
"extends": [
// 直接从 EsLint 本身集成的规则继承
"eslint:recommended",
// 从一些第三方NPM包进行继承,比如 eslint-config-standard、eslint-config-airbnb
// eslint-config-* 中 eslint-config- 可以省略
"airbnb",
// 直接从插件继承规则,可以省略包名中的 `eslint-plugin`
// 继承于 @typescript-eslint 插件下的推荐配置
"plugin:@typescript-eslint/recommended",
]
}
注意,rules规则可以改变继承extends中的规则,或者覆盖extends中的规则。
2.Prettier
3.Commitlint
4.Husky
5.Jest
6.Github Actions
7.Semantic Release
8.mock
blog.csdn.net/m0_50140502… vue项目mock数据方案之一:webpack的devServer.before 重点参考文献,前端基建:juejin.cn/post/714488…