前端项目流程
项目设计阶段:
- 业务/研发过程中的痛点是什么?有哪些需要优化
- PD提需求,形成PRD原型图(产品提供的需求文档)(给需要达成的量化指标)
需求分析: 架构师给出技术方案:形成技术文档(技术选型,技术方案设计,技术调研,技术风险)
项目立项
- 确认项目组成员:前端,后端,UI,测试
- 项目排期
项目实施:
- 交互/视觉设计
- 前后端开发
- 前后端联调
- 测试 前端开发测试、单元测试,冒烟测试,性能测试
- 项目验收(产品和视觉)
- 发布上线
脚手架具备的功能
-
项目初始化
-
静态资源服务器
-
自动发布
-
写在简历中,面试官说不定也不太会脚手架开发,那是不可能的,哈哈
-
开发脚手架目的:
- 提升前端研发效率
- 创建项目和通用代码的集成(埋点,HTTP请求,工具方法,组件库)可供多个前端项目的开发
- 例如公用的common 库,里面封装了http的请求方法,可能每个子项目都需要使用
- git操作
- 构建+发布上线(依赖安装和构建、资源上传CDN、域名绑定、测试/正式服务器)
-
脚手架的核心价值
- 将研发过程:
- 自动化:项目重复代码拷贝/git操作/发布上线操作
- 标准化:项目创建/git flow/发布流程/回滚流程
- 数据化:研发过程系统化、数据化、使得研发过程量化,精确计算发布的时间。
- 将研发过程:
脚手架是什么,脚手架的原理?
让我们来看看大型脚手架@vue/cli
create-react-cli
都是在终端使用命令去执行并创建项目的,通过命令去执行对应的js文件,所以我们首先来看看命令和执行文件是怎么创建的
如何找到命令的真实执行文件
方式一:通过which ${命令名}
which create-react-app
// /usr/local/bin/create-react-app
ll /usr/local/bin/create-react-app
//lrwxr-xr-x 1 rainbow admin 45 8 22 13:25 create-react-app@ -> ../lib/node_modules/create-react-app/index.js
//所以真正执行文件为:/usr/local/lib/node_modules/create-react-app/index.js
//该命令的目录为/usr/local/lib/node_modules/create-react-app
方式二:通过环境变量找到该命令和执行文件
例如:查看nodemon命令的执行文件;
- 查看环境变量:
echo $PATH
/usr/local/Homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
- 查看环境变量中nodemon命令对应的软链接(即真实的命令运行文件)
- 软链接是通过ll /usr/local/lib/nodemon/package.json中配置项的'bin'
ll /usr/local/bin/nodemon // lrwxr-xr-x 1 rainbow admin 42 3 28 13:17 /usr/local/bin/nodemon@ -> ../lib/node_modules/nodemon/bin/nodemon.js
- 平时输入命令报错
command not fund
- 因为环境变量中没有这个命令
cat /usr/local/lib/node_modules/create-react-app/index.js
#!/usr/bin/env node
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var currentNodeVersion = process.versions.node;
var semver = currentNodeVersion.split('.');
var major = semver[0];
if (major < 10) {
console.error(
'You are running Node ' +
currentNodeVersion +
'.\n' +
'Create React App requires Node 10 or higher. \n' +
'Please update your version of Node.'
);
process.exit(1);
}
const { init } = require('./createReactApp');
init();
命令和对应执行文件的软链接怎么创建的?
问题:
我们下载的是@vue/cli
后面确实使用vue create myProject
进行项目搭建?
- 命令所在目录的package.json文件中配置的:'bin'所在的配置
- 'bin'配置中指明了使用的命令是什么,执行的文件是什么
- 查找到了create-react-app命令链接的执行文件,就是环境变量中该命令软链接到的文件
查看项目中的package.json文件
cat /usr/local/lib/node_modules/create-react-app/package.json
"bin": {
"create-react-app": "index.js"
},
回答上面的问题:
- 因为@vue/cli这个项目在packag.json中指定了命令名称; 上图:
为什么我们编写了一个test.js文件,却不能像命令一样去执行?
- 自己写一个js执行文件,能够执行吗? 如下会报错:
在自己的桌面上编辑一个test.js
文件,只写了一行js代码console.log('hello test')
vim /Users/rainbow/Desktop/test.js
./test.js
//rainbow@MacBook-Pro-673 Desktop % ./test.js
//zsh: permission denied: ./test.js
chmod 777 ./test.js
./test.js
// ./test.js: line 2: syntax error near unexpected token `'hello test''
// ./test.js: line 2: `console.log('hello test')'
-
参照
creat-react-app
命令的执行文件- cat /usr/local/lib/node_modules/create-react-app/index.js
- 也只是普通的JS文件,但是首行有
#!/usr/bin/env node
-
给test.js文件也写上首行的代码
#!/usr/bin/env node
console.log('hello test');
发现test.js也可以执行了
首行代码的意思:
- 告诉操作系统,在环境变量中找到node命令,用node命令去执行文件。
- 如果是python的文件,则就要使用Python的解释器去执行了。
如何自己配置一个命令
-
在环境变量中创建一个软链接即可
ln -s 源文件 目标文件
-
都需要是绝对路径
-
如何重名令命令名称,或者创建别名
- 再创建一个软链接链接到源文件,或者刚链接好的文件即可
rainbow@192 bin % ln -s rainbow rainbow2 rainbow@192 bin % cd .. rainbow@192 local % rainbow2 hello test
总结:当全局下载一个命令时发生了什么?
- 将包下载到node下面的node_modules下
- 解析包中package.json中的“bin”配置
- 创建软链接
- 执行该命令就是执行软链接的文件
- 执行命令等同于执行执行文件
- 脚手架是操作系统的客户端:通过执行命令
- 比如:vue create
- creat为vue的子命令;查找命令软链接的执行文件:
- 我们执行
node test.js
实际上是将test.js作为node的可执行文件去执行 - 相当于执行
node -e "console.log('hello test')"
- 我们执行
开发脚手架的流程
脚手架开发流程
- 创建
npm
项目 - 创建脚手架入口文件,最上方添加
#!/usr/bin/env node
- 配置package.json,添加
bin
属性 - 编写脚手架代码
- 将脚手架发布到
npm
脚手架使用
- 安装脚手架
npm install -g own-cli
- 使用脚手架
own-cli
脚手架开发难点
-
分包:将复杂的系统拆分成若干个模块
-
命令注册
- 例如
vue create
vue add
vue invoke
- 例如
-
参数解析
vue command [options] <params>
-
option全程:
--version
--help
-
option简写:
-V
-h
-
带params的options:
--path /Users/sam/Desktop/vue-test
-
帮助文档:
- global help
-
Usage:
Usage: vue <command> [options]
-
Options
-
Commands
-
- global help
-
- 其他
- 命令行交互
- 日志打印
- 命令行文字变色
- 网络通信:
HTTP/WebSocket
- 文件处理
- 等等...
实操:脚手架开发
- 目标:开发一个叫
rainbow-test-cli
脚手架 - 新建
rainbow-test-cli
npm项目:rainbow-test-cli项目地址package.json
:添加bin配置- 新建
bin/index.js
:配置首行的node环境变量的申明:#!/usr/bin/env node
- 发布
rainbow-test-cli
这个本地脚手架npm login
npm publish
- 使用
本地调试和开发脚手架
- 方式一:创建软链接到本地执行文件
- 方式二:
npm link
创建软链接- 含义就是根据
package.json
配置,将本地文件软链接到全局的node_modules
下
//prefix = ${npm config get prefix} 我本地是:/usr/local ${prefix}/bin/rainbow-test-cli -> ${prefix}/lib/node_modules/rainbow-test-cli/bin/index.js ${prefix}/lib/node_modules/rainbow-test-cli -> /Users/rainbow/Documents/前端/脚手架开发/rainbow-test-cli
- 含义就是根据
分包进行本地调试
场景:
- 脚手架场景比较复杂,出现了分包,不止当前npm包,还引用了其他npm包:
- 比如:已经存在的
rainbow-test-cli
项目需要引用到rainbow-test
的npm包 rainbow-test-cli
为运行的项目,rainbow-test
作为rainbow-test-cli
的依赖包
运行项目
依赖库
思路:
- 用link的方式,让依赖包链接到本地运行项目的
node_modules
中; - 问题:
- 但是在
rainbow-test-cli
这个项目中进行npm link rainbow-test
会报错; - 原因是
rainbow-test
还没有发布到npm上
- 但是在
思考?
- 如何在
rainbow-test
还没有发布到npm上时,进行本地调试呢?
方法
-
执行
npm link
命令(在rainbow-test
依赖包中):- 将
rainbow-test
这个命令和对应的执行文件也软链接到全局的node_modules
中作为一个库文件,并解析库文件中的package.json文件的bin
配置;此时在全局都可以使用。
${prefix}/lib/node_modules/rainbow-test-lib -> ${本地项目路径}/rainbow-test //此时全局任何地方都可以使用`rainbow-test`方法执行文件
- 将
-
执行
npm link rainbow-test
(在rainbow-test-cli
运行项目中)- 将
rainbow-test
链接到rainbow-test-cli
的node_modules下
/Users/rainbow/Documents/前端/脚手架开发/rainbow-test-cli/node_modules/rainbow-test -> /usr/local/lib/node_modules/rainbow-test -> /Users/rainbow/Documents/前端/脚手架开发/rainbow-test
-
需要在
package.json
中保留依赖:dependencies:{"rainbow-test": "^1.0.0"}
-
这样,本地的依赖包修改,在运行项目中可以马上拿到修改后的结果;
- 将
本地调试目的:
- 方便开发依赖包,如果不进行本地调试,改动一点都需要发布到npm再下载下来调试
rainbow-test
依赖包发布到npm上
rainbow-test
的依赖包开发好了,不需要本地链接了,删除本地软链接,模拟真实的环境,将依赖包发布到npm上
-
npm remove -g rainbow-test
- 删除全局的rainbow-test包;可以查看全局的${prefix}/lib/node_modules/是否删除了
-
cd ../rainbow-test
npm publish
发布依赖包 -
cd ../rainbow-test-cli
npm i
- 安装远程依赖:
//报错npm WARN checkPermissions Missing write access to /Users/rainbow/Documents/前端/脚手架开发/rainbow-test-cli/node_modules/rainbow-test
- 原因是因为会到全局目录中去安装,因为之前使用npm link软链接到了全局的node_modules中,有了软链接;系统会认为库文件(依赖包)始终应该存在于本地;需要解除软链接
-
npm unlink rainbow-test
如果link存在,将当前的库文件移除//报错: npm WARN checkPermissions Missing write access to /Users/rainbow/Documents/前端/脚手架开发/rainbow-test-cli/node_modules/rainbow-test //因为之前全局删除了rainbow-test软链接;
-
npm link rainbow-test
npm unlink rainbow-test
重新link再unlink成功解除软链接 -
npm i
如果node_modules
没有下载到对应的依赖包,是因为package.json
中没有写入依赖 -
npm i rainbow-test -s
rainbow-test库则指向了远程
脚手架注册命令
任务:完成一个简单的命令注册
依赖库中rainbow-test/bin/index.js
module.exports = {
//分包测试
sum(a, b) {
return a + b;
},
//注册命令
init({ option, param }) {
console.log("正在进行项目初始化~", option, param);
},
};
运行项目中rainbow-test-cli/bin/index.js
#!/usr/bin/env node
// 分包
const lib = require("rainbow-test");
lib.sum(2, 5);
// 任务:注册一个命令 rainbow-test-cli init
const argvs = require("process").argv;
let command = argvs[2];
// options解析
let options = argvs.slice(3);
let [option, param] = options;
option = option && option.replace(/--/g, "");
if (command) {
if (lib[command]) {
lib[command]({ option, param });
}
} else {
console.log("请输入命令参数");
}
// 全局命令的解析
if (command) {
if (command.startsWith("--") || command.startsWith("-")) {
let globaOptions = command.replace(/--|-/g, "");
if (globaOptions === "version" || globaOptions === "V") {
//目前先写死
console.log("1.0.0");
}
}
} else {
console.log("请输入命令");
}
本地效果如下:
完成命令注册:发布到npm上
分别发布rainbow-test
和rainbow-test-cli
遇到此报错:
- 原因是当前的版本已经发布了,不能再发布或者修改;
- 解决:升级package.json中的version为下一个版本
version: 1.1.0
,再发布
发布成功
小知识点:
// rainbow-test-cli运行项目中这样写,去监听依赖包中C为版本的更新
"dependencies": {
"rainbow-test": "^1.1.0"
}
发布成功后,在真实环境进行测试:
本地执行unlink操作,
- 运行项目(rainbow-test-cli)
npm unlink
npm unlink rainbow-test
- 运行项目解除本地到全局node_modules的链接,解除本地rainbow-test到运行项目node_modules的链接(unlink后会看到运行项目的node_modules将没有任何依赖包)
- 运行项目手动
npm i rainbow-test
安装远程项目
- 依赖库项目(rainbow-test)
npm unlink
- 解除全局node_modules依赖
npm i -g rainbow-test-cli
全部重新下载新的测试脚手架命令
真实环境测试:
Lerna源码分析
源码阅读准备:
- 下载源码
- 安装依赖
- IDE 打开
源码阅读准备完成的标准:
- 找到入口文件
-
package.json bin 配置
"bin": { "lerna": "core/lerna/cli.js" }
-
如果没有 bin 配置 能够本地调试
-
- 查看vscode debuger nodejs
- 方法
-
根目录新建
.vscode
目录 -
新建.vscode/launch.json文件
{ "configurations": [ { "type": "node", "request": "launch", "name": "nodemon", "runtimeExecutable": "nodemon", "program": "${workspaceFolder}/core/lerna/cli.js", "restart": true, "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", //参数是名称和值一组,多个参数,数组里添加即可,调试时会自动附加上去 "args": ["--param", "ls"] } ] }
自己完成一个脚手架项目
项目痛点
- 创建项目+通用代码(埋点、HTTP请求、工具方法、组件局)
- git操作(创建仓库、代码冲突、远程代码同步、创建版本、发布打tag)
- 构建+发布上线(依赖安装和构建、资源上传CDN、域名绑定、测试/正式服务器)
痛点分析
- 创建项目/组件时,存在大量重复代码拷贝:希望能够快速复用已有沉淀
- 协同开发时,由于git操作不规范,导致分支混乱,操作耗时:制定标准的git操作规范并集成到脚手架
- 发不上线耗时,而去容易出现各种错误:指定标准的上线流程和规范并集成到脚手架
需求分析
- 通用的研发脚手架
- 通用的项目/组件创建能力
- 模板支持定制,定制后能够发布生效
- 模板支持快速接入,级低的接入成本
- 通用的项目/组件发布能力
- 发布过程自动完成标准的git操作
- 发布成功后自动删除开发分支并创建tag
- 发布后自动完成云构建、OSS上传、CDN上传、域名绑定
- 发布过程支持测试/正式两种模式
技术方案设计
core模块技术方案
- 准备阶段
- 命令注册
- 命令执行
core
- prepare:检查版本号、检查node版本、检查root启动、检查用户主目录、检查入参、检查环境变量、检查是否为最新版本、提示更新
- registerCommand:注册init命令、注册publish命令、注册clean命令、支持debug
- execCommand:
核心库
- import-local
- commander
工具库
- npmlog
- fs-extra
- semver
- colors
- user-home
- dotenv
- root-check
脚手架拆包策略:
- 核心流程:core
- 命令:
- 初始化
- 发布
- 清除缓存
- 模型层:models
- Command命令
- Project项目
- Component组件
- Npm模块
- Git仓库
- 支撑模块:utils
- Git操作
- 云构建
- 工具方法
- API请求
- Git API
- 拆分原则(根据模块的功能拆分)
- 核心模块
- 命令模块
- 模型模块
- 工具模块