前端脚手架之原理和搭建基础

304 阅读7分钟

前端项目流程

项目设计阶段:

  • 业务/研发过程中的痛点是什么?有哪些需要优化
  • 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命令的执行文件;

  1. 查看环境变量:echo $PATH
    • /usr/local/Homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
  2. 查看环境变量中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进行项目搭建?

  1. 命令所在目录的package.json文件中配置的:'bin'所在的配置
  2. 'bin'配置中指明了使用的命令是什么,执行的文件是什么
  3. 查找到了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中指定了命令名称; 上图:

vuecli.png

为什么我们编写了一个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也可以执行了 image.png

首行代码的意思

  • 告诉操作系统,在环境变量中找到node命令,用node命令去执行文件。
  • 如果是python的文件,则就要使用Python的解释器去执行了。

如何自己配置一个命令

  • 在环境变量中创建一个软链接即可ln -s 源文件 目标文件

  • 都需要是绝对路径 创建命令.png

  • 如何重名令命令名称,或者创建别名

    • 再创建一个软链接链接到源文件,或者刚链接好的文件即可
    rainbow@192 bin % ln -s rainbow rainbow2
    rainbow@192 bin % cd ..
    rainbow@192 local % rainbow2
    hello test
    

总结:当全局下载一个命令时发生了什么?

  1. 将包下载到node下面的node_modules下
  2. 解析包中package.json中的“bin”配置
  3. 创建软链接
  4. 执行该命令就是执行软链接的文件
  5. 执行命令等同于执行执行文件
  • 脚手架是操作系统的客户端:通过执行命令
    • 比如:vue create
    • creat为vue的子命令;查找命令软链接的执行文件:
      • 我们执行node test.js实际上是将test.js作为node的可执行文件去执行
      • 相当于执行 node -e "console.log('hello test')"

截屏2021-09-04 下午7.05.04.png

开发脚手架的流程

脚手架开发流程

  1. 创建npm项目
  2. 创建脚手架入口文件,最上方添加#!/usr/bin/env node
  3. 配置package.json,添加bin属性
  4. 编写脚手架代码
  5. 将脚手架发布到npm

脚手架使用

  • 安装脚手架npm install -g own-cli
  • 使用脚手架own-cli

脚手架开发难点

  1. 分包:将复杂的系统拆分成若干个模块

  2. 命令注册

    • 例如vue create vue add vue invoke
  3. 参数解析

    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

vue 帮助文档.png

  1. 其他
    • 命令行交互
    • 日志打印
    • 命令行文字变色
    • 网络通信:HTTP/WebSocket
    • 文件处理
    • 等等...

实操:脚手架开发

  • 目标:开发一个叫rainbow-test-cli脚手架
  • 新建rainbow-test-clinpm项目:rainbow-test-cli项目地址
    • package.json:添加bin配置
    • 新建bin/index.js:配置首行的node环境变量的申明:#!/usr/bin/env node
  • 发布rainbow-test-cli这个本地脚手架 npm login npm publish

npm发布.png

  • 使用

下载开发的npm包.png

本地调试和开发脚手架

  • 方式一:创建软链接到本地执行文件
  • 方式二: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的依赖包

运行项目 运行项目.png

依赖库 截屏2021-09-05 下午8.12.43.png

思路:

  • 用link的方式,让依赖包链接到本地运行项目的node_modules中;
  • 问题:
    • 但是在rainbow-test-cli这个项目中进行npm link rainbow-test会报错;
    • 原因是rainbow-test还没有发布到npm上

思考?

  • 如何在rainbow-test还没有发布到npm上时,进行本地调试呢?

方法

  1. 执行npm link命令(在rainbow-test依赖包中):

    • rainbow-test这个命令和对应的执行文件也软链接到全局的node_modules中作为一个库文件,并解析库文件中的package.json文件的bin配置;此时在全局都可以使用。
    ${prefix}/lib/node_modules/rainbow-test-lib -> ${本地项目路径}/rainbow-test
    
    //此时全局任何地方都可以使用`rainbow-test`方法执行文件
    
  2. 执行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上
  1. npm remove -g rainbow-test

    • 删除全局的rainbow-test包;可以查看全局的${prefix}/lib/node_modules/是否删除了
  2. cd ../rainbow-test npm publish发布依赖包

  3. 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中,有了软链接;系统会认为库文件(依赖包)始终应该存在于本地;需要解除软链接
  4. npm unlink rainbow-test 如果link存在,将当前的库文件移除

    //报错: npm WARN checkPermissions Missing write access to /Users/rainbow/Documents/前端/脚手架开发/rainbow-test-cli/node_modules/rainbow-test
    //因为之前全局删除了rainbow-test软链接;
    
  5. npm link rainbow-test npm unlink rainbow-test重新link再unlink成功解除软链接

  6. npm i 如果node_modules没有下载到对应的依赖包,是因为package.json中没有写入依赖

  7. 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("请输入命令");
}

本地效果如下:

version.png

完成命令注册:发布到npm上

分别发布rainbow-testrainbow-test-cli

遇到此报错:

  • 原因是当前的版本已经发布了,不能再发布或者修改;
  • 解决:升级package.json中的version为下一个版本version: 1.1.0,再发布 publish.png

发布成功 publish.png

小知识点:

// 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 全部重新下载新的测试脚手架命令

zhixiang.png

真实环境测试: test.png

Lerna源码分析

源码阅读准备:

  • 下载源码
  • 安装依赖
  • IDE 打开

源码阅读准备完成的标准:

  • 找到入口文件
    • package.json bin 配置

      "bin": {
          "lerna": "core/lerna/cli.js"
        }
      
    • 如果没有 bin 配置 能够本地调试

  • 查看vscode debuger nodejs
  • 方法
  1. 根目录新建.vscode目录

  2. 新建.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
  • 拆分原则(根据模块的功能拆分)
    • 核心模块
    • 命令模块
    • 模型模块
    • 工具模块