前端工程化-Feflow实践

  • 作者:zhibinxie
  • 时间:2021-03-19
  • 345人已阅读
导语:Feflow是一个用于提升开发效率的前端工作流和规范工具,本文将介绍如何如何应用Feflow打造属于团队自己的研发利器

前言

前端工程化是指将开发阶段的代码转变成生产环境的代码的一系列步骤。主要包括构建,分支管理,自动化测试,部署等步骤。本文将介绍团队在前端工程化的实践,主要采用feflow作为前端工作流,并结合Git工作流,将前端开发流程中的各个步骤进行流程化,从而提高开发效率。

Feflow 简介

Feflow是一个用于提升开发效率的前端工作流和规范工具,托管在Github上:Tencent/feflow,目前start 941,官网:https://feflowjs.com/zh/

设计思想

Feflow 借鉴了 Pipline 的思想,将日常的研发工作划分为:初始化、本地开发、打包构建、检查、发布上线五个步骤。分别对应 init、dev、build、test和deploy五个基本命令。除了服务好基本的开发工作流和规范,Feflow 提供了易于扩展的插件机制,用于打造团队统一的工具链生态。

image
每个阶段做的事情:

  • init 阶段:项目初始化,init阶段我们可以根据模板快速的生成项目仓库
  • dev 阶段:本地开发阶段
  • build 阶段:使用构建工具对项目进行打包,生成用于生成环境的产物
  • test 阶段:部署项目到测试环境
  • deploy 阶段:部署项目到正式环境

概念

要使用feflow,首先需要了解以下概念:

  • 脚手架
  • 开发套件
  • 插件

脚手架

在不少前端团队中,都存在项目开发不智能的问题。很多开发者开发新项目的时候都是基于原有项目拷贝的方式进行,这样就造成了一个团队中不同人开发的项目目录结构各不相同,后续项目交接和维护起来费时费力。
为了解决这个问题,Feflow 引入了社区主流的脚手架进行项目的初始化。
脚手架对应的是项目模板,Felow的脚手架基于Yeoman,在它基础上进行扩展,会将标准化的日志、CLI工具函数通过上下文对象传递给脚手架;同时提供脚手架的增量更新机制,创建项目时,当本地版本和远程版本不兼容时会提升增量更新。引入脚手架以后,一方面可以让团队保持统一的技术栈和统一的目录结构;另一方面,还可以在项目初始化的时候做一些自动化的事情,比如自动创建远程GIT仓库并分配开发者master/developer权限、申请监控ID、内部CI/CD系统快速打通等。

开发套件

项目创建好之后,接下来的问题就是本地开发和代码打包构建了。通常的做法是将构建脚本直接放在业务项目里面,每个业务开发者都可以根据自己的需求自行修改,这样导致的问题是每个项目的构建脚本都存在差异,构建不统一,同时每个项目都需要重复安装构建依赖。
Feflow 引入了开发套件这个概念,它的基本思想是将构建脚本进行统一管理,由团队里面熟悉构建的同学进行统一的维护。开发套件需要发布到 npm 上进行版本管理,同时建议每个项目采用相同的开发套件。开发套件通过 feflow 安装后会存放在 ~/.feflow/node_modules下,这样每个项目都可以共用一套构建脚本了。
开发套件它将项目的构建、部署、代码检查等功能都整合在一起,并且套件会跟随项目一起走,存储在项目的 node_modules 里。把构建的代码抽离出项目形成的 NPM 包。这样做的好处在于,团队内部的项目遇到构建工具升级的时候,无需对每个项目都进行一遍升级,只需要升级开发套件并更改项目中 .feflowrc.json 中的配置即可

套件加载机制
如果你在某个业务项目下运行 Feflow 的命令,这个时候 Feflow 的命令加载机制是:
初始化 Feflow -> 加载原生命令 -> 加载插件命令 -> 加载套件命令。
其中,套件的加载机制分为项目配置文件加载和套件命令注册两个步骤。
首先 Feflow 会读取项目配置文件,项目文件名称可以是:.feflowrc.js, .feflowrc.yaml, .feflowrc.yml, .feflowrc.json, .feflowrc, package.json 中的一个。在这个配置文件里面会描述这个项目拥有的套件命令和套件命令对应的 npm 包的实现映射关系

插件

除了提供基础的功能之外,Feflow里面还通过一套插件机制去方便的扩展子命令。你可以利用插件去做很多自动化的事情,比如批量压缩图片、搭建运营活动本地开发SDK、代码统计功能等等。有一些常用的命令是不依赖项目框架的,就抽象成插件。

命令设计

Feflow 将命令划分为3类,分别是:

  1. 普通命令:Feflow 原生实现的命令,也就是内置命令
  2. 开发套件命令:项目维度的命令,不同的项目类型下命令或多或少存在差异,由开发套件提供。
  3. 插件命令:通过插件进行扩展的命令,插件命令更具普适性,由插件提供。
    image

Feflow 安装

首先全局安装feflow,接着通过feflow init选择相关的脚手架创建项目。也可以通过feflow install安装自定义的插件和脚手架,如果官方提供的脚手架加载的模板不符合自己的业务,可以自行开发一个脚手架。

Feflow CLI 安装

npm i feflow-cli -g

脚手架安装

Feflow 的核心部分并没有一个脚手架,脚手架都是通过 Feflow 来安装的,为了快速上手,我们先用已有的脚手架来创建项目,选择generator-ivweb模板创建项目

feflow install generator-ivweb
feflow init

image

知道如何使用后,接下来是最重要的环节,如何接入feflow,开发属于我们自己的脚手架,开发套件和插件

Feflow 开发接入

接入feflow,首先需要将日常的页面进行抽象,形成脚手架模板,接着将项目开发中用到的命令封装成开发套件,同时,可以将一些通用的功能封装成插件,比如图片上传。

  • 开发模板脚手架
  • 实现开发套件
  • 实现插件

实现业务模板

Felow的脚手架基于Yeoman,在它基础上进行扩展,开发Yeoman 脚手架可以参考官网:https://yeoman.io/authoring/

准备工作

安装Yoeman,全局安装 ​yo​ 和 ​generator-generator​:

npm install yo generator-generator

生成脚手架模版

yo generator

生成过程中询问的 ​Description​ 一定要填写,这里会关联到脚手架项目下 ​package.json​ 文件的 ​description​ 字段,Feflow 会使用它来提供脚手架说明。
生成出来的脚手架模版的项目名是以 ​generator-​ 开头的,如果你自己手动创建一个脚手架,这也是必须的。

生成的脚手架模版的主要目录结构:

|- ​generator
  |- app:
    |- index.js(创建模板逻辑)
    |- templates(模板代码)
      |- dummyfile.text

创建自己的脚手架

项目模版:每个项目都有自己的项目目录结构规范,转换成 Yeoman 脚手架的项目模版很简单,直接找个项目或者把项目模版复制粘贴到 ​generators/templates/​下。

对于个人或者还没有项目模版的团队来说,首要任务当然是规划好自己的项目模版了。在本文示例中,我们将创建一个非常简单的、支持 React 的项目模版。

|- src # 示例源码
    |- index.html # HTML 入口
    |- index.js # JS 入口,也是 Webpack 打包的入口
|- .babelrc # 处理 JSX 的配置
|- .eslintrc # eslint规则
|- .eslintignore # 忽略eslint校验的文件配置
|- .editorconfig # 协同团队开发人员之间的代码的风格及样式规范化
|- .feflow.json # 这个文件是必须的,作为项目和 Feflow 的桥梁 

其中.feflowrc.json文件是开发套件相关的命令配置文件,作为项目和 Feflow 的桥梁,Feflow 会读取其中的feflow命令配置来执行响应的命令。

动态模版内容

Yeoman 模版支持 <%= variable > 这样的语法来填充动态内容,variable 是通过Yeoman传递进来的。
如果你想在你的项目模版中加入动态的内容,例如根据询问用户得到的答案来填充项目名称,你就可以在项目模版下的 package.json 中这样写:

{
  "name": "<%-name %>",
  "version: "1.0.0",
  "descriptiton": "<%-descriptiton %>"
}

项目生成逻辑

有了项目模版之后,我们还缺少根据项目模版创建一个项目的逻辑。最简单逻辑就是复制一份模版到当前目录下,
高级点的脚手架一般都会有如下过程:

  1. 询问并接收用户的输入;
  2. 执行一些自定义的脚本;
  3. 根据用户输入和脚本执行的结果渲染项目模版,并生成于当前目录下。
  4. 上述的这些逻辑统统都写在 ​generators/app/index.js​ 文件中。通常来说,这个文件都满足如图格式:
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
  // 初始化阶段
  initializing(){ /* code */ }
  //接收用户输入阶段
  prompting(){ /* code */} 
  //保存配置信息和文件
  configuring (){ /* code */ }
  // 执行自定义函数阶段
  default(){ /* code */ )
  //生成项目目录阶段
  writing () { /* code */ }
  //统一处理冲突,如要生成的文件已经存在是否覆盖
  conflicts (){ /* code */ }
  // 安装依赖阶段
  install (){ /* code */ }
  // 结束阶段
  end () { /* code */}
}

这里我们在创建好项目文件后,初始化git仓库,并增加一条git提交记录。

const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');
const simpleGit = require('simple-git');
module.exports = class extends Generator {
  prompting() {
    this.log(
      yosay(`欢迎使用 ${chalk.red('generator-h-5-project')} generator!`)
    );
    return this.prompt([
      {
        type: "input",
        name: "name",
        message: "请输入项目目录名(英文)",
        default: "demo"
      },
      {
        type: "input",
        name: "remote",
        message: "请输入远程仓库地址",
        default: "项目基本描述"
      }
    ]).then(answers => {
      this.answers = answers;
    });
  }
  writing() {
    const { name } = this.answers;
    this.destinationRoot(this.destinationPath(name));
    this.fs.copyTpl(
      `${this.templatePath()}/**/!(_)*`,
      this.destinationPath(),
      this.answers,
      {},
      { globOptions: { dot: true } } // Copy all dots files.
    );
  }
  install() {
    console.log("安装依赖,过程持续1~2分钟");
    this.yarnInstall();
  }
  async end(){
    const { name, remote } = this.answers;
    console.log("本次初始化过程结束, 请通过以下命令运行项目: ");
    console.log();
    console.log(chalk.cyan("  cd"), name);
    console.log(`  ${chalk.cyan("feflow dev:本地调试")}`);
    console.log(`  ${chalk.cyan("feflow commit:代码提交")}`);
    console.log(`  ${chalk.cyan("feflow build:本地构建")}`);
    console.log(`  ${chalk.cyan("feflow deploy:发布正式环境")}`);
    console.log();
    console.log("编码愉快!");
    // 初始化git仓库,并增加一条提交记录
    const git = simpleGit({
      baseDir: this.destinationPath()
    });
    await git.init().addRemote('origin', remote);
    await git.add('.');
    await git.commit('feature:first commit')
  }
};

脚手架调试

现在,你的脚手架就定制好了,让我们来试一试吧。先安装在 Feflow 主目录下

cd <your-path>/​generator-startkit-demo(你本地脚手架开发目录)
npm link 
cd ~/.feflow
npm link ​generator-startkit-demo(你的脚手架包名~脚手架开发目录里package.json里的name属性)
修改 ~/.feflow/package.json里的dependencies,把你的脚手架包名写进去,版本号随便写
feflow init //就可以看到脚手架

接下来看看实际效果
image

进入到创建的目录react_demo,可以参看远程仓库地址设置成功,同时新增了一条提交记录
image
image

实现开发套件

开发套件用于提供某种类型的项目的命令,通常是提供多个命令的集合。Feflow的开发套件需要以 feflow-devkit-* 开头,开发套件开发完成需要发布到npm或者npm。
这里我们重点对如何开发一个 Feflow 套件进行讲解,以 feflow-devkit-demo 为例。

配置 devkit.json 文件

首先,我们需要创建一个空文件夹,命名为 feflow-devkit-demo,并且在文件夹下新建一个 devkit.json 或者 devkit.js 文件,以 devkit.json 为例,配置如下:

{
  "devkit": {
    "commands": {
      "dev": {
        // dev 命令对应的真正实现脚本
        "implementation": "./lib/command/dev.js",
        // dev 的描述,会在 feflow -h 的时候显示出来
        "description": "开发模式.",
        // dev 的子命令
        "optionsDescription": {
          // 简单的命令描述
          "p": "选择端口构建",
          // 支持别名
          "dist": {
            "description": "构建产物目录",
            "alias": "d"
          }
        }
      },
      "build": {
        "implementation": "./lib/command/build.js",
        "description": "构建页面",
      },
      "deploy": {
        "implementation": "./lib/command/deploy.js",
        "description": "构建并发布页面",
      },
    }
  }
}

创建命令对应的实现文件

配置好了 devkit.json 文件,接下来就是一一实现里面定义的各个命令对应的脚本。以 dev 命令为例,通过上述脚本地址可以知道我们需要在 feflow-devkit-demo 下创建一个 lib 文件夹,然后在 lib 文件夹下创建 command 文件夹,最后在 command 文件加下创建 dev.js 文件。当然,文件路径可以自定义,这样的创建文件就按照自己定义的路径去创建。

dev.js 文件需要向外部暴露一个函数,当执行 feflow dev 的时候其实就是执行暴露出来的函数,如下所示:

const buildDev = () => {
    // 真正的构建脚本
    const devServer = require('../build/dev-server');

    return devServer.then((response) => {
        console.log('response', response);
    })
        .catch(error => {
            console.log('error', error);
        });
};
module.exports = buildDev;

具体构建的脚本代码是什么样子,根据不同的构建需求决定,我们这里就不继续展示了。

调试和发布

开发好了套件,就要开始调试了,这里和以前构建器的调试有点区别,你需要将套件 npm link 在项目的 node_modules 里面,而不是 ~/.feflow 的 node_modules 里面,项目里通过 .feflowrc.json 文件来声明项目的命令所对应的套件命令,格式如下(以 .js 格式的文件为例)

module.exports = {
    // 必须
    devkit: {
        // 必须,代表着 feflow 的命令
        commands: {
            // 推荐,代表着 feflow dev 的命令
            dev: {
                // 必须,代表着 feflow dev 命令对应执行的套件命令,这里表示使用套件 feflow-devkit-demo 的 dev 命令,配置格式为 `<套件>:<命令>`
                builder: "feflow-devkit-demo:dev",
                // 可选,如果上述套件命令支持一些配置,可以写在这里
                options: {}
            },
            // 推荐,代表着 feflow build 的命令
            build: {
                // 必须,代表着 feflow build 命令对应执行的套件命令,这里表示使用套件 feflow-devkit-demo 的 build 命令
                "builder": "feflow-devkit-demo:build",
                // 可选,如果上述套件命令支持一些配置,可以写在这里
                "options": {}
            }
        }
    }
}

调试 OK 后就可以发布了。

实现插件

插件是为了扩展更佳通用的子命令而设计的,Feflow插件需要以 feflow-plugin-* 开头,插件开发完成需要发布到npm。
接下来,我们实现一个git-commit Angular规范的git提交命令feflow commit。
当我们在项目下执行 feflow commit 的时候,可以自动生成一条提交记录,并且进行推送,实现的效果跟我们在命令行执行以下命令相似

git add . 
git commit –m 'feat:新增新功能'
git pull
git push

创建项目

创建一个名为 feflow-plugin-commit 的文件夹,并用 npm init 命令将它初始化。
注意,所有 Feflow 插件的项目目录名都必须以 feflow-plugin- 开头,并且项目内的 package.json 文件中的 name 字段必须和项目目录名保持一致。

编写逻辑,注册命令

新建一个 index.js 文件,实现git commit逻辑
在编写代码之前,我们先梳理下整体的逻辑
image

具体实现如下:

feflow.cmd.register('commit', 'git 提交', async function(args) {
  // 判断是否git项目
  if (!fs.existsSync(cwd('./.git'))) return errorLog('当前项目不是git项目');

  // 检查是否需要 commit 或者 add
  const { workDirHasFile } = await validateCommit();
  
  // 需要push的话,先判断是否有远端分支,且本地是否落后于远端
  // 是则先暂存(git stash)本地修改,更新代码后再恢复 (git stash pop)

  const [error] = await pullUpdates();
  if (error) return;
  const answer = await inquirer.prompt(commitAnswers);
  const commitMsg = getCommitMsg(answer);
  
  await commit({
    workDirHasFile,
    commitMsg,
  });

  await push();
});

完整的代码可参考这里:https://github.com/Yahiko7/feflow-plugin-commit

你无需担心 feflow 没有声明或者没有任何模块的引用,Feflow 在使用这个插件的时候,会自动将 feflow 变量注入到入口文件(由 package.json 中的 main 字段决定,这里是 index.js)的全局作用域中。

feflow.cmd.register 函数接收三个参数:
第一个是想要给 Feflow 增加的命令名称
第二个是对这个命令的描述说明信息(会在 feflow --help 中显示出来)
第三个是新增的命令对应的执行函数。
执行函数中,Feflow 会传递一个参数 args,它是一个对象,包含着 Feflow 运行该命令时所有在该命令后的参数。例如,运行 feflow commit 1 2 3 时,args 就是 { _: [1, 2, 3] }。

插件调试

截至目前,你已经完成了一个插件。你可能迫不及待想试试。别急,让我们一步一步来。

cd <your-path>/feflow-plugin-commit
npm link
cd ~/.feflow
npm link feflow-plugin-commit

编辑 ~/.feflow/package.json 文件(可用 vi ~/.feflow/package.json 编辑),在 dependencies 字段中添加一行 "feflow-plugin-commit": "1.0.0"。
运行 feflow commit 启用插件。
image
image

Feflow 启动时会加载 ~/.feflow/node_modules 里面所有的 feflow-plugin-xx 包提供的命令,插件机制的实现类似 Redux 和 Koa2 里面的 compose 机制。
插件开发好并且发布到 npm 后,接下来就是插件安装使用了。通过以下命令安装一个插件:

$ feflow install <package>

Feflow 会将插件安装在 ​~/.feflow/node_modules​ 下。

Top