工程化和脚手架工具

206 阅读4分钟

一. 工程化

1. 主要解决的问题

  • 传统语言或语法的弊端
  • 无法使用模块化 / 组件化
  • 重复的机械式工作
  • 代码风格统一,质量保证
  • 依赖后端服务接口支持
  • 整体依赖后端项目

2. 工程化的表现

  • 一切以提高效率,降低成本,质量保证为目的的手段都属于工程化

二. 脚手架工具

1. 概念

  • 本质作用:创建项目的基础结构,提供项目规范和约定

2. 常用的脚手架工具

  • 针对某个框架的脚手架

    • create-react-app
    • vue-cli
    • angular-cli
  • 通用型脚手架

    • yeoman
      • 基础使用

        • 安装 yeoman,yarn global add yo
        • 开发 node 项目
          • 安装 generator-node 模块,yarn global add generator-node
          • 创建目录,mkdir my-module
          • 进入目录,cd my-module
          • 创建 node 目录结构,yo node
        • sub generator
          • 生成 cli 应用
            • yo node:cli
            • package.json 中出现一个 bin: lib/cli.js 的配置
            • 项目根目录生成 lib 目录
      • yarn link 模块名 将模块链接到全局

      • yarn unlink 模块名 将模块从全局移除

      • 使用步骤

        • 明确需求
        • 找到合适的 Generator
        • 全局范围安装找到的 Generator
        • 通过 yo 运行对应的 Generator
        • 通过命令行交互填写选项
        • 生成所需要的项目结构
      • 自定义 Generator

        • 创建 Generator 模块

          • 基础结构

            |- generators ---------------------- 生成器目录
            	|- app ------------------------- 默认生成器目录
            		|- index.js ---------------- 默认生成器实现
            	|- components ------------------ 其他生成器目录
            		|- index.js ---------------- 其他生成器实现
            |- package.json -------------------- 模块包配置文件
            
          • 步骤

            • 创建模块目录,mkdir generator-sample,注意:模块必须是 generator- 的模式,否则 yeoman 找不到

            • 进入模块目录,cd generator-sample

            • 初始化 package.json 文件

            • 安装 generator 的基类,yarn add yeoman-generator

            • 创建基础目录

            • 编写代码

              /**
               * 此文件作为 Generator 的核心入口
               * 需要导出一个继承自 Yeoman Generator 的类型
               * Yeoman Generator 在工作时会自动调用在此类型中定义的一些生命周期方法
               * 在这些方法中通过调用父类提供的一些工具方法,例如文件写入
               */
              
              const Generator = require('yeoman-generator')
              
              module.exports = class extends Generator {
                // yeoman 会在生成文件时自动调用 writing 方法
                writing() {
                  this.fs.write(
                    // 通过 this.destinationPath() 获取绝对路径
                    this.destinationPath('temp.txt'),
                    Math.random().toString()
                  )
                }
              }
              
          • 根据模板创建文件

          • 模板文件 foo.txt,模板文件创建在 app/templates 中

            这是一个模板
            内部可以使用 EJS 模板标记输出数据
            例如 <%= title%>
            
            其他的 EJS 语法也支持
            <% if (success) { %>
            我是判断逻辑
            <% } %>
            
          • 模板生成逻辑

            /**
             * 此文件作为 Generator 的核心入口
             * 需要导出一个继承自 Yeoman Generator 的类型
             * Yeoman Generator 再工作时会自动调用在此类型中定义的一些生命周期方法
             * 在这些方法中通过调用父类提供的一些工具方法,例如文件写入
             */
            
            const Generator = require('yeoman-generator')
            
            module.exports = class extends Generator {
              // yeoman 会在生成文件时自动调用 writing 方法
              writing() {
                // 模板文件路径
                const temp = this.templatePath('foo.txt')
                // 输出路径
                const dest = this.destinationPath('foo.txt')
                // 模板上下文
                const context = { title: 'Hello kjy', success: false }
                // 自动 copy context 输出到输出文件上
                this.fs.copyTpl(temp, dest, context)
              }
            }
            
          • 用户输入

            • 模板文件 bar.html

              <!DOCTYPE html>
              <html lang="en">
              <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title><%= name%></title>
              </head>
              <body>
                <h1><%= name%></h1>
              </body>
              </html>
              
            • 模板生成逻辑

              /**
               * 此文件作为 Generator 的核心入口
               * 需要导出一个继承自 Yeoman Generator 的类型
               * Yeoman Generator 再工作时会自动调用在此类型中定义的一些生命周期方法
               * 在这些方法中通过调用父类提供的一些工具方法,例如文件写入
               */
              
              const Generator = require('yeoman-generator')
              
              module.exports = class extends Generator {
                // 命令行交互
                prompting () {
                  // 通过 this.prompt 方法发出对用户命令行询问
                  // 这个方法返回一个 Promise
                  // 加入 return,有更好的异步流程体验
                  return this.prompt([
                    {
                      type: 'input',
                      name: 'name',
                      message: 'Your projsct name',
                      default: this.appname // appname 为项目生成目录名称
                    }
                  ])
                  .then(answers => {
                    // 接受完用户输入的结果
                    this.answers = answers
                  })
              
                }
                // yeoman 会在生成文件时自动调用 writing 方法
                writing() {
                  // 模板文件路径
                  const temp = this.templatePath('bar.html')
                  // 输出路径
                  const dest = this.destinationPath('bar.html')
                  // 模板上下文
                  const context = this.answers
                  // 自动 copy context 输出到输出文件上
                  this.fs.copyTpl(temp, dest, context)
                }
              }
              
          • 发布 Generator

            • 创建一个远端仓库
              • echo node_modules > .gitignore
              • git init
              • git add .
              • git commit -m '提交代码'
              • github 上创建仓库
              • git remote add origin 远端仓库地址
              • git push -u origin master
            • 通过 npm publish 或 yarn publish 发布模块
              • 注:yarn 的是国外的,需要修改镜像配置,需要使用官方的镜像
  • 脚手架工作原理

    • 自定义脚手架

      • 初始化 package.json 文件

      • 在 package.json 文件中添加 bin 属性,"bin":"cli.js"

      • 项目根目录新建 cli.js

        #!/usr/bin.env node
        
        // 脚手架的工作过程
        // 1. 通过命令行交互的方式询问用户问题
        // 2. 根据用户回答的问题生成文件
        
        const inquirer = require('inquirer')
        const path = require('path')
        const fs = require('fs')
        const ejs = require('ejs')
        
        inquirer.prompt([
          {
            type: 'input',
            name: 'name',
            message: 'Project name',
          }
        ])
        .then(answers => {
          // 模板路径
          const temp = path.join(__dirname, 'templates')
          // 目标目录
          const dest = process.cwd()
          // 将模板下的文件全部转换到目标目录
          fs.readdir(temp, (err, files) => {
            if (err) throw err
            files.forEach(file => {
              // 通过模板引擎渲染文件
              ejs.renderFile(path.join(temp, file), answers, (err, result) => {
                if(err) throw err
                fs.writeFileSync(path.join(dest, file), result)
              })
            })
          })
        })