Skip to content

工程化和脚手架入门

工程化概述

面临的问题

技术是为了解决问题而存在的

  • 想要使用 ES6+ 新特性,但是兼容有问题
  • 想要使用 Less/Sass/PostCSS 增强 CSS 的编程性,但是运行环境不能直接支持
  • 想要使用模块化的方式提高项目的可维护性,但运行环境不能直接支持
  • 部署上线前需要手动压缩代码及资源文件,部署过程需要手动上传代码到服务器
  • 多人协作开发,无法硬性统一大家的代码风格,从仓库中 pull 回来的代码质量无法保证
  • 部分功能开发时需要等待后端服务接口提前完成

工程化解决的问题

工程化在项目中的表现

一切重复的工作都应该被自动化

项目构建流程

创建项目

  • 使用脚手架工具自动完成项目搭建(创建项目结构、特定类型文件)

编码

  • 自动格式化代码、校验代码风格、借助编译工具在开发阶段就可以使用新特性

预览/测试

  • 传统预览需要借助 Apache、Nginx 提供基础 Web 服务,让应用在上面运行

    现在可以使用 Live Reloading/HMR 热更新方式,不用手动刷新

  • Web Server/Mock 解决后端服务未完成情况下提前开发具体业务功能

  • 实际过程出现问题,我们想定位源代码位置可以借助 Source Map

提交

  • 使用 Git Hooks 在代码提交前,整体进行检查(项目质量、风格)
  • Lint-staged
  • 持续集成

部署

  • CI/CD
  • 自动部署到服务器

工程化不等于工具

  • Powered by Node.js

工程化不等于工具

脚手架工具

脚手架本质作用:创建项目基础结构、提供项目规范和约定

  • 相同的组织结构
  • 相同的开发范式
  • 相同的模块依赖
  • 相同的工具配置
  • 相同的基础代码

vue-clicreate-react-appangular-cliyeoman 这些都是可以提供根据自己模板自定义的脚手架

Yeoman 基本使用

  • 在全局安装 yo
bash
yarn global add yo
npm install
  • yeoman 必须搭配对应的 generator 使用
bash
yarn global add generator-node
npm install -g generator-node
  • 通过 yo 运行 generator
bash
cd project-dir
mkdir my-module
yo node

Sub Generator

  • 有时候并不需要生成全新项目,指示想给原有项目增加文件(README、babel)可以用 Sub Generator

    bash
    yo node:cli
    
    # link 到全局范围
    yarn link
    
    # 安装依赖
    yarn

使用步骤

  1. 明确你的需求
  2. 找到合适的 Generator
  3. 全局范围安装找到的 Generator
  4. 通过 Yo 运行对应的 Generator
  5. 通过命令行交互填写选项
  6. 生成你所需要的项目结构
bash
yo webapp

使用 webapp 生成器生成项目,它会依赖一些 C++ 模块,这些模块在安装时会下载二进制文件,这些模块并不会通过 npm 镜像去加速

yoman加速配置

Generator

Generator 基本结构

bash
 |- generators/ ............................生成器目录
 |			|- app/ ............................默认生成器目录
 |			|		|- index.js.....................默认生成器实现
+|			|- component/ ......................其他生成器目录
+|					|- index.js.....................其他生成器实现
 |-package.json ............................模块包配置文件

模块名必须是 generator-<name> 格式,不然无法找到

bash
mkdir generator-sample
cd generator-sample
yarn init
yarn add yoman-generator
yarn link
yo sample

Yo 报错

  • nvm use v8... Node 切换至 8 版本即可
bash
Error sample

You don't seem to have a generator with the name “sample” installed.
But help is on the way:

You can see available generators via npm search yeoman-generator or via http://yeoman.io/generators/. 
Install them with npm install generator-sample.

To see all your installed generators run yo --generators. Adding the --help option will also show subgenerators.

If yo cannot find the generator, run yo doctor to troubleshoot your system

code . 打开vscode,创建 generators/app/index 文件

  • 此文件作为 Generator 的核心入口
  • 需要导出一个继承自 Yeoman Generator 的类型
  • Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
  • 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
js
const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  writing() {
    this.fs.write(
      this.destinationPath('temp.txt'),
      Math.random().toString()
    )
  }
}

使用模板文件

js
const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  writing() {
    // 模板文件路径
    const tmpl = this.templatePath('bar.html')
    // 输出目标路径
    const output = this.destinationPath('bar.html')
    // 模板数据上下文
    const context = this.answers

    this.fs.copyTpl(tmpl, output, context)
  }
}

接收用户输入

js
const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  prompting () {
    // Yeoman 在询问用户环节会自动调用此方法
    // 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: this.appname // appname 为项目生成目录名称
      }
    ])
    .then(answers => {
      this.answers = answers
    })
  }
  writing () {
    const tmpl = this.templatePath('bar.html')
    const output = this.destinationPath('bar.html')
    const context = this.answers
    this.fs.copyTpl(tmpl, output, context)
  }
}

根据模板创建文件

  1. 创建 generators/app/index.js

  2. 将要批量生成提前准备好的文件,拷贝模板到 template 目录

  3. 可能发生变化的地方替换成 ejs 模板标记 <%= name %>

    html
    <title><%= name %></title>

    如果想原封输出 ejs 模板标记,需要多加个 %

    html
    <link rel="icon" href="<%%= BASE_URL %>favicon.ico">
  4. 遍历每一个路径,生成目标目录对应文件

    js
    const Generator = require('yeoman-generator')
    module.exports = class extends Generator {
      writing () {
        // 把每一个文件都通过模板转换到目标路径
        const templates = [
          '.browserslistrc',
          '.editorconfig',
          '.env.development',
          '.env.production',
          '.eslintrc.js',
          '.gitignore',
          'babel.config.js',
          'package.json',
          'postcss.config.js',
          'README.md',
          'public/favicon.ico',
          'public/index.html',
          'src/App.vue',
          'src/main.js',
          'src/router.js',
          'src/assets/logo.png',
          'src/components/HelloWorld.vue',
          'src/store/actions.js',
          'src/store/getters.js',
          'src/store/index.js',
          'src/store/mutations.js',
          'src/store/state.js',
          'src/utils/request.js',
          'src/views/About.vue',
          'src/views/Home.vue'
        ]
        templates.forEach(item => {
          // item => 每个文件路径
          this.fs.copyTpl(
            this.templatePath(item),
            this.destinationPath(item),
            this.answers
          )
        })
      }
    }
  5. yarn link 到全局

  6. yo zce-vue

发布 Generator

bash
echo node_modules .gitignore
git init
git add .
git commit -m 'feat: initial commit'
git remote add ...
git push -u origin master
yarn publish --registry=https://registry.yarnpkg.com
yarn logout

Plop

类似 sub-generator,一般使用在已有项目中创建同样的文件

bash
yarn add plop --dev

plop.setGenerator

  • 参数1:生成器的名字

  • 参数2:生成器的配置选项

    description:描述

    prompts:命令行问题

    actions:完成命令行交互后的动作

js
// Plop 入口文件,需要导出一个函数
// 此函数接收一个 plop 对象,用于创建生成器任务
module.exports = plop => {
  plop.setGenerator('component', {
    description: 'create a component',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'component name',
        default: 'MyComponent'
      }
    ],
    actions: [
      {
        type: 'add', // 代表添加文件
        path: 'src/components/{{name}}/{{name}}.js',
        templateFile: 'plop-templates/component.hbs'
      },
      {
        type: 'add', // 代表添加文件
        path: 'src/components/{{name}}/{{name}}.css',
        templateFile: 'plop-templates/component.css.hbs'
      },
      {
        type: 'add', // 代表添加文件
        path: 'src/components/{{name}}/{{name}}.test.js',
        templateFile: 'plop-templates/component.test.hbs'
      }
    ]
  })
}

配置好后,执行

bash
yarn plop component

? yarn name Sidebar
  ++ \src\components\Sidebar\Sidebar.js
  ++ \src\components\Sidebar\Sidebar.css
  ++ \src\components\Sidebar\Sidebar.test.js
  • 将 plop 模块作为项目开发依赖安装
  • 在项目根目录下创建一个 plopfile.js 文件
  • 在 plopfile.js 文件中定义脚手架任务
  • 编写用于生成特定类型文件的模板
  • 通过 plop 提的 cli 运行脚手架任务

脚手架工作原理

package.json 中添加 bin 字段,指定 cli 入口

  • yarn link 生成软链接,之后执行 sample-scaffolding 即可
json
{
  "name": "sample-scaffolding",
  "version": "0.1.0",
  "main": "index.js",
  "bin": "cli.js", // 添加bin 字段
  "author": "zce <w@zce.me> (https://zce.me)",
  "license": "MIT"
}
  • Node CLI 应用入口文件必须要有文件头 #!/usr/bin/env node

  • 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755

    具体就是通过 chmod 755 cli.js 实现修改

bash
yarn add inquirer

inquirer生成模板文件

脚手架的工作过程:

  1. 通过命令行交互询问用户问题
  2. 根据用户回答的结果生成文件
js
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')

inquirer
  .prompt([
    {
      type: 'input',
      name: 'name',
      message: 'Project name?',
    },
  ])
  .then(answers => {
    // 模板目录
    const tmplDir = path.join(__dirname, 'templates')
    // 目标目录
    const destDir = process.cwd()
    // 将模板下的文件全部转换到目标目录
    fs.readdir(tmplDir, (err, files) => {
      if (err) throw err
      files.forEach(file => {
        // 通过模板引擎渲染文件
        ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
          if (err) throw err
          // 将结果写入目标文件路径
          fs.writeFileSync(path.join(destDir, file), result)
        })
      })
    })
  })

常备不懈,才能有备无患