开发脚手架

1,003 阅读23分钟

一、前端工程化

1.解释说明:

​ 前端工程化是指遵循一定的标准和规范,通过工具去提高效率、降低成本的一种手段; 近些年被广发的关注和探讨, 原因: 前端应用功能要求不断提高, 业务逻辑日益复杂;

2.日常开发所面临的问题: => 前端工程化主要解决的问题

​ 1.想要使用 ES6+ 新特性, 但是兼容问题; => 传统语言或语法的弊端; ​ 2.想要使用 Less/Sass/Postcss 增强 CSS 的编程性, 但是运行环境不能直接支持; => 传统语言或语法的弊端; ​ 3.想要使用模块化的方式提高项目的可维护性, 但运行环境不能直接; => 无法使用模块化/组件化; ​ 4.部署上线前需要手动压缩代码及资源文件, 部署过程需要手动上传代码到服务器; => 重复的机械式工作; ​ 5.多人协作开发, 无法硬性统一大家的代码风格, 从仓库中 pull 回来的代码质量无法保证; => 代码风格统一、质量保证; ​ 6.部分功能开发是需要等待后端服务接口提前完成; => 依赖后端服务接支持; 整体依赖后端项目;

3.一个项目过程中工程化的表现:

​ 1.创建项目阶段: 使用脚手架工具自动的帮我们完成基础结构的搭建

​ 2.编码阶段: 借助于工程化工具, 自动化的帮我们格式化代码, 检验代码风格, 保证项目的代码风格一致; 还可以借助编译工具, 让我们在开发过程中使用一些新的特性, 提高代码编程效率;

​ 3.预览/测试阶段: 可以借助现代化的Web Server提供热更新的体验; 如果代码出现问题, 可以通过Source Map 定位源代码位置; 可以通过mock的方式去解决后端服务未完成的情况下, 提前开发业务功能; ​ 4.代码提交阶段: 可以使用Git Hooks在代码提交之前为项目的整体自动做检查(包括代码质量的检查和代码风格的检查);

​ 5.部署阶段: 可以用一行命令代替传统的FTP上传; 在代码提交之后, 通过持续集成或持续部署的方式自动将代码部署到服务器, 避免人为操作产生的不稳定因素;

4.工程化 ≠ 某个工具

​ 1.说明: 在现阶段, 有部分的工具过于强大, 例如 webpack; 很多程序员会误认为工程化就是webpack, 误认为用了webpack就代表有了工程化; 其实, 工具并不是工程化的核心, 工程化的核心是: 对项目整体的一种规划或者架构, 而工具只是在这个过程中为我们去落地实现这种规划或架构的手段;

​ 我们需要去规划一个项目整体的工作流架构, 其中包括文件的组织结构、源代码的开发范式(使用什么样的语法, 什么样的规范, 什么样的标准去编写代码)、用什么方式做前后端分离(基于Ajax做分离还是基于中间层做分离), 上述的这些就是开发前明确的规划, 有了这些整体规划之后, 在具体去考虑应该选用哪些工具、做哪些具体的配置选项来去实现工程化的规划;

5.Powered by Node.js: 厥功至伟的 Node.js

二、脚手架工具

1.脚手架工具概要

  • 脚手架的本质作用: 用来自动帮我们创建项目基础文件的工具; 更重要的作用是提供项目约定和规范;开发相同项目时都有一些相同的约定:
    1. 相同的组织结构
    2. 相同的开发规范
    3. 相同的模块依赖
    4. 相同的工具配置
    5. 相同的基础代码
  • 意义: 在创建项目环节大大提高了工作效率;

2.常用的脚手架工具

  1. React项目 => create-react-app
  2. Vue项目 => vue-cli
  3. Angular项目 => angular-cli
  4. 以Yeoman 为代表的通用型脚手架工具
  5. 以 Plop 为代表的创建特定类型文件的脚手架工具

前三种脚手架: 根据信息创建对应的项目基础结构 => 根据创建者提供的信息自动去生成项目所需要的特定文件和相关的配置; 只适用于自身所服务框架的项目;

第四种类型脚手架工具可以通过一套模板生成一个对应的项目结构, 这种类型的脚手架很灵和, 也很容易扩展;

第五种类型脚手架工具用于在项目开发过程中去创建一些特定类型的文件 => 例如: 在一个组件化的/模块中创建一个新的组件/模块, 这些新的组件/模块一般是有特定的几个文件组成的, 而且每个文件都有一些基本的代码结构, 相对于我们手动一个一个去创建, 脚手架会提供更为便捷, 更为稳定的操作方式;

3.Yeoman脚手架:

1.yeoman简介:The Web’s scaffolding tool for modern webapps
  • 定义: 一款用于创造现代化Web应用的脚手架工具;
  • 介绍: 不同于cli 这样的脚手架工具, Yeoman更像一个脚手架的运行平台, 通过Yeoman 搭配不同的 Generator 去创建任何类型的项目, 可以通过创建自己的Generator , 从而去定制属于自己的前端脚手架;
  • Yeoman的优点也是他的缺点: 在很多专注基于框架开发的开发者眼中, Yeoman 过于通用, 不够专注;
2.Yeoman 基础使用
  • 首先需要安装node环境;
  • 这里通过yarn 安装Yeoman: yarn global add yo(全局安装Yeoman)
  • 通过之前的介绍, 我们知道单有 Yeoman 是不够的, 因为Yeoman他是搭配特定的Generator 才能去使用, 我们要使用Yeoman 去创建项目, 必须要找到对应项目类型的Generator: 例如我们要生成nodeModule的项目, 引入一个弄得模块, 我们可以使用generator-node这样一个模块 => 安装Generator-node => yarn global add generator-node;
  • 通过yarn去运行generator-node的generator 生成器自动的创建一个全新的nodeModule: 定位到项目所在的目录(我这里是G:/MyStudy/part2/moduleOne) => G: => cd MyStudy/part2/moduleOne => mkdir my-module(创建新的文件夹) => cd my-module => yo node(通过yo命令去运行Generator-node生成器, PS: 运行特定的generator, 就是把包的名字前面的generator-前缀去掉) => module Name(模块的名字) => The name above already exists on npm, choose another?(Y/n)(PS: 该模块已经在npm上存在了, 是否要选择另外一个) => Description(PS: 项目的描述) => Project homepage url(PS: 项目的主页) => Author’s Name (作者名字) => Author’s Email(作者邮箱) => Author’s Homepage(作者主页) => Package keywords(comma to split)(PS: 模块的关键词) => Sent coverage reports to coveralls(Y/n)(PS: 是否发送代码覆盖率的报告到一个平台上) => Enter Node versions(comma separated)(PS: 输入node的版本) => GitHub username or organization(PS: 输入GitHub的用户名) => Which license do you want to use?(Use arrow kes)(选择MIT)
3.Yeoman Sub Generator
  • 说明: 有时候我们并不需要去创建完整的项目结构, 只需要在已有的项目基础上去创建一些特定类型的文件: 例如 我们给一个已经存在的项目去创建ready-me, 又或者我们在原有的项目支持上去添加某些类型的配置文件(比如说: es-lint或者babel的配置文件), 这些文件都有一些基础代码, 如果手动去写的话, 很容易配置错误, 我们可以通过生成器自动生成该配置文件, 这样会提高效率以及正确率;
  • 上诉需求可以通过Yeoman所提供的Sub Generator 特性来实现;
  • 我们在项目目录下去运行一个特定的Sub Generator 的命令去生成对应的文件;
  • 举例: 使用generator-node提供的一个子集生成器(cli生成器), 去生成一个cli应用所需要的文件, 让我们这个模块变成一个cli 应用;
  • 运行: yo node:cli(PS:运行Sub Generator的方式, 就是在原有的Generator名字后面 + 冒号(:) + Sub Generator的名字) => conflist package.json(PS: 是否要重写package.json(原因: 在添加Sub Generator 的支持的时候, 会添加一些新的模块和配置)) => 有了这些操作, 我们就可以将这个模块作为全局命令行模块去使用: 通过yarn link 到全局范围 => 安装模块所需的依赖: 运行yarn 命令 => 通过命令行 my-module --help运行刚刚添加进来的模块
  • 注意: 并不是每一个generator 都提供Sub Generator, 所以我们在使用志强,我们需要通过该Generator 的官方文档来去明确这个Generator 下有没有Sub Generator: 例如 generator-node只提供如下图的Sub Generator
4.Yeoman 使用步骤总结
  • 说明: Yeoman是一款通用性的脚手架工具, 我们可以使用它去创建任何类型的项目, 我们总结一下使用Yeoman一般需要去遵循那几个步骤:

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

    1. 创建webapp
    2. 找到generator-webapp;
    3. 全局安装generator-webapp: yarn global add generator-webapp;
    4. 运行: yo webapp 生成webapp 项目结构
5.自定义Generator: 基于 Yeoman 搭建自己的脚手架
  • 说明: 通过前面对Yeoman 的基本使用以及对Yeoman的介绍, 发现不同的Generator可以用来生成不同的项目, 我们可以创建自己的Generator来帮我们生成自定义的项目结构, 即便是市面上已经有了很多的Generator, 我们还是有创建自定义Generator 的必要(原因: 市面上的Generator都是通用的, 而我们在实际开发过程中会出现一部分基础代码甚至是业务代码在相同类型项目时还是可能会是重复的), 那我们这时我们就把公共的部分都放到脚手架上去生成, 让脚手架工具去发挥更大的价值;

  • 例如: 在创建vue.js项目的时候, 官方默认的脚手架工具 vue-cli 只会去创建一个最基础的项目骨架, 但这并不包含我们经常要用到的模块, 例如: vue-router, 需要每次在创建完项目之后再去手动引入这些模块,并且编写一些基础的使用代码, 那试想一下: 如果我们把这些也放到脚手架中, 那么就会提高开发效率,避免重复操作;

  • 创建Generator 模块
    • 说明: 创建Generator 实际上就是创建一个NPM 模块, 但是Generator 有特定结构, 需要在根目录下有一个generators的文件夹, 在generators 文件夹下再去存放一个app的文件夹(用于去存放生成器对应的代码)

    • 如果你需要提供多个的Sub Generator ,可以在app的同级目录再去添加一个新的生成器目录,例如我们添加一个component目录, 此时模块就有了component的子生成器(Sub Generator);

    • 除了特定的结构, Generator 还有一个和NPM模块不同的是: Yeoman的generator 的名称必须是 generator- 的这种格式;如果在具体的开发过程中没有使用这种命名方式, 那Yeoman在后续工作的时候, 就找不到你所提供的这个生成器模块;

    • 第一步: 创建generator文件夹

      mkdir generator-sample
      
    • 第二步: 进入 generator-sample, 初始化一个package.json

      //进入generator-sample
      cd generator-sample
      //初始化package.json
      yarn init
      
    • 第三步: 安装yeoman-generator 模块, 这个模块提供了生成器的一个基类, 这个基类中提供了一些工具、函数, 让我们可以在创建生成器的时候更加便捷;

      yarn add yeoman-generator
      
    • 第四步: 通过编译工具在项目目录下创建generators/app/index.js

    • 第五步: 编写index.js

    • 第六步: 把自定义模块链接到全局, 使之成为一个全局模块包, 这样的话, Yeoman在工作的时候就可以找到自定义的Generator

      //在自定义生成器目录下执行命令
      yarn link
      
    • 第七步: 测试自定义Generator

      //在一个目录下
      cd ../
      //创建项目文件夹
      mkdir testMyGenerator
      //到项目目录
      cd testMyGenerator
      //运行自定义Generator
      yo demo
      //查看在相应的文件下是否有对应的文件
      
  • 根据模板创建文件
    • 说明: 很多时候, 需要自动去创建的文件有很多, 而且文件的内容先对复杂, 在这样的一种情况下, 我们可以使用模板去创建文件, 这样会更加便捷;

    • 第一步: 在生成器app目录下添加一个templates文件夹, 将我们需要生成的文件放在templates 下作为模板, 模板文件完全遵循 EJS 模板引擎的模板语法;

      这是一个模板文件
      内部可以使用 EJS 模板标记输出数据
      例如: <%= title%>
      其他的 EJS 语法也支持
      <% if (success) { %>
      哈哈哈
      <% } %>
      
    • 第二步: 有了模板之后, 我们可以利用模板生成文件: fs 中有使用模板引擎的方法 => this.fs.copyTpl(): 会自动将模板文件映射到生成的输出文件上; copyTpl() 接收三个参数: 模板文件的路径、输出文件的路径、模板数据的上下文

      //改写index.js
      writing () {
        //将this.fs.write 方法注释掉
        /* this.fs.write(
        	this.destinationPath('temp.txt'),
          Math.random().toString()
        ); */
        //通过 this.fs.copyTpl() 写入文件到目录目标
        //this.fs.copyTpl() 需要三个参数: 模板文件的路径, 输出文件的路径, 模板数据的上下文, 该方法会自动将模板文件映射到生成的输出文件上
        //模板文件的路径
        const tmpUrl = this.templatePath('foo.txt');//自动获取模板文件路径
        //输出文件的路径
        const outputUrl = this.destinationPath('foo.txt')//获取输出文件路径
        //模板数据上下文
        const context = { title: 'hello lcy', success: false };
        this.fs.copyTpl(tmpUrl, outputUrl, context);
      }
      
    • 第三步: 进入目录重新运行自定义Generator

      //重新运行Generator
      yo sample
      
    • 优点: 相对于手动去创建每一个文件, 模板创建文件的方式大大提高了效率, 特别是在文件比较多, 文件内容个比较复杂的情况下;

  • 接收用户输入数据
    • 说明: 对于模板中的一些动态数据, 例如项目的标题, 项目的名称, 这样的数据我们一般通过命令行交互的方式去询问使用者, 从而得到;

    • 在Generator 中想要发起一个命令行交互的询问, 可以实现Gnenrator 类型中的 prompting() 方法: => prompting() 方法中, 可以去调用父类所提供 prompt() 方法发出对用户的命令行询问; =>prompt() 方法: 返回一个Promise, 是一个Promise 方法, 我们在调用这个方法是需要return, 这样Yeoman在工作的时候就有更好的流程控制,该方法接收一个数组参数, 数组中的每一个成员都是一个问题对象 => 这个问题对象: 可以传入 type(PS: 类型), name(PS:得到最终结果的一个键), massage(PS: 在界面上给用户的提示, 也就是我们所提的问题), default(PS:这个问题的默认值): 一般为 this.appname(appname: 为当前项目生成目录名称) =>在上一个Promise 执行完成以后, 我们会得到一个answers, 这个answers里面就是我们当前这个问题在接受完用户输入完的结果, 他会以一个对象的形式出现, 对象里面的键就是刚刚 prompt 参数对象里面的name, 值是用户输入的value, 我们将这个值挂载到this对象上, 以便于后面再writing的时候去使用它; => 有了这个answers数据之后, 就可以在writing的时候传入到模板引擎, 使用这个数据作为模板引擎的上下文;

      //定义prompting 方法
      prompting () {
        //Yeoman 在询问用户环节会自动调用此方法
        //在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
        //prompt() 方法是一个Promise 方法, 返回一个Promise 对象
        return this.prompt([{
          type: 'input',
          name: 'name',
          message: 'Your projiect name',
          default: this.appname //appname 为项目生成目录名称
        }])
          .then(answers => {
          //answer => { name: 'user input value }
          this.answers = answers;
        });
        //重新定义模板
        //模板文件路径
        const tempUrl = this.templatePath('bar.html');
        //输出文件路径
        const outputUrl = this.destinationPath('bar.html');
        //模板数据上下文
        const contentText = this.answers;
      
6.搭建一个带有一定基础代码的vueJs项目脚手架:
  1. 第一步: 先按照原始的方式去创建一个理想的项目结构, 把你要重复使用的基础代码全部包含在里面;

  2. 第二步: 封装一个全新的Generator ,用于去生成理想的项目结构;

    1. 创建一个全新的Generator 目录

      mkdir generator-lcyVue//这里命名在后面会出错 修改为 mkdir generator-lcy-vue
      
    2. 初始化package.json

      //进入Generator目录
      cd generator-lcy-vue
      //初始化package.json
      yarn init
      
    3. 安装yeoman-generator 依赖

      yarn add yeoman-generator
      
    4. 用编译器打开Generator, 在目录下创建generators/app/index.js,

      => 在app下创建 templates 文件夹, 将准备好的项目结构复制到templates 目录下

    5. 编写index.js

      const Generator = require('yeoman-generator');
      
      module.exports = class extends Generator {
         //用户交互方法
         promptiong  () {
            return this.prompt([{
               type: 'input',
               name: 'name', 
               message: 'Your project name',
               default: this.appname
            }])
            .then(answers => {
               this.answers = answers;
            })
         }
         //写入文件方法
         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
               );
            });
         }
      };
      
    6. 发布到全局

      yarn link
      
    7. 全局调用

      yo lcy-vue
      
7.发布Generator
  • 说明: 因为Generator 实际上就是一个NPM 模块, 我们去发布Generator , 实际就是去发布一个NPM 模块, 我们只需要将我们写好的Generator 模块去通过 npm publish 去发布成一个公开的模块就可以了;

  • 在开始之前, 我们一般会将源代码托管到一个公开的源代码仓库上

    • 通过命令行创建一个本地仓库

      1. 先去创建一个gitignore, 用于去忽略一下项目的node_modulus

        echo node_modules > .gitignore
        
      2. 有了这个文件之后, 然后去初始化本地的空仓库

        git init
        
    • 有了本地仓库以后, 查看本地仓库的状态

      git status
      
    • 如果发现有文件没有被跟踪, 通过命令行跟踪所有的文件

      git add .
      
    • 创建一次提交

      git commit -m "feat: initial commit"
      
    • 创建完提交之后, 需要一个远端的仓库, 把本地的提交日志同步到远端

      1. 打开GitHub, 创建一个新的仓库(这里的截图是已经创建好的之后的截图)

      2. 创建完成后, 得到远端仓库的链接:

    • 为本地仓库添加一个远端仓库的别名: 命令行中的 origin 为本地仓库的别名

      get remote add origin 远端仓库地址
      
    • 将本地master 分支的代码推送到远端仓库的master 分支

      git push -u origin master//创建完成后页面会有提示
      
  • 推送完成后, 托管完成后, 发布这个模块

    //使用npm
    npm publish
    //使用yarn
    yarn publish
    

    =>在publish 的时候, 会提示你是否要对package.josn 里面的版本进行修改

    => 然后输入用户名和密码

    • 在国内的开发者一般会使用淘宝的镜像源去取代官方的镜像, 因为淘宝的镜像是只读镜像, 在发布的时候会报错, 解决方法:

      • 先去修改本地的镜像配置

      • 在publish 的时候带上官方的镜像地址

        //registry= 跟上官方镜像地址, 这里用的yarn 的官方镜像地址
        yarn publish --registry=https://registry.yarnpkg.com
        
    • 发布完成后, 可以通过npm 或者 yarn 的方式去安装该Generator, 然后再通过Yeoman去使用该Generator

    • 如果你需要该Generator 在官方的仓库列表当中出现, 你可以为你的项目添加yeoman-generator的关键词, 添加完成后, 官方就会发现你这个项目;

4.Plop 脚手架

1.说明

​ 出了一些大型的脚手架工具, 还有一些小型的脚手架工具也非常出色, 这里推荐Plop => Plop是一款主要用于创建项目中特定类型文件的脚手架工具, 有点类似于Yeoman 当中的Sub Generator, 一般不会独立去使用, 一般我们会把Plop集成到项目当中, 用来自动化的创建同类型的项目文件;

2.Plop的具体使用
  1. 第一步: 将Plop作为NPM 模块安装到开发依赖当中

    yarn add plop --dev
    
  2. 第二步: 添加对应的模板文件, 一般会把 Plop 的模板文件放在项目根目录下的 plop-templates 的文件夹下, 在模板文件中可以遵循 handlebar 模板引擎的语法

    //编写的模板文件
    //在模板文件中可以通过 {{}} 的方式去插入对应的数据
    import React form 'react';
    
    export default () => (
    	<div className="{{name}}">
    		<h1>{{name}}</h1>
    	</div>
    )
    
  3. 第三步: 在项目的根目录下新建 plopfile.js 文件: 这个文件是 Plop工作的入口文件, 需要导出一个函数 => 此函数接收一个 Plop 对象, Plop 对象提供了一系列的工具函数, 用于帮我们创建生成器任务

    //Plop 入口文件, 需要到处一个函数
    import default from './../test-lcy-vue/src/store/state';
    //此函数接收一个 plop 对象, plop对象提供一系列的工具函数, 用于帮我们创建生成器任务
    module.exports = plop => {
       //setGenerator() 方法接收两个参数: 第一个是生成器的名字, 第二个是生成器的一些配置选项;
       plop.setGenerator('component', {
          description: 'crate a component', //生成器的描述
          prompts: [//指定: 生成器(Generator)工作时会发出的命令行问题
             {
                type: 'input', //指定: 问题的输入方式
                name: 'name', //指定: 问题返回值的键
                massage: 'component name', //命令行提示语句
                default: 'propReactProj'//命令的默认答案
             }
          ],
          actions: [//生成器(Generator)在完成命令行交互之后, 需要执行的动作, 可以是一个数组, 数组的每一个成员就是一个动作对象
             {
                type: 'add', //指定: 动作的类型 add: 代表添加一个全新的文件
                path: 'src/components/{{name}}/{{name}}.js', //指定: 需要添加的文件会被添加到哪个具体的路径, 在这个路径中可以使用 {{}} 的方式去插入刚刚在命令行交互中得到的数据
                templateFile: 'plop-templates/component.hbs' //指定: 本次添加的文件的模板文件是什么
             }
          ]
       });
    }
    
  4. 完成Plop 任务定义之后, 在安装Plop 模块的时候, Plop 提供了CLi程序, 我们通过命令行启动 Plop 程序

    yarn plop component
    

5.脚手架的工作原理(创建自己的脚手架):

1.说明:

​ 大部门的脚手架工作原理都很简单: 在启动脚手架之后, 通过命令行交付询问一些预设的问题, 通过回答的结果结合一些模板文件生成项目的结构;

2.通过 nodeJs 开发一个小型的脚手架工具
  • 说明: 脚手架工具实际上就是一个node-CLi 应用, 去创建脚手架工具就是创建一个 CLi 应用
  1. 第一步: 命令行创建文件夹, 并进入该文件夹

    mkdir lcy-scaffolding
    cd lcy-scaffolding
    
  2. 第二步: 初始化 package.json

    yarn init
    
  3. 第三步: 通过编译器打开文件夹, 在package.json 中添加一个 bin 字段, 用于指定该CLi应用的入口文件, 这里添加: bin: ‘cli.js’

  4. 第四步: 在根目录下创建 cli.js, 并编辑 cli.js, 和传统的js 不同的是, cli.js 需要一个特定的文件头: #!/usr/bin/env node

    #!/usr/bin/env node
    //Node CLI 应用入口文件必须要这样的文件头
    //如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
    //具体就是通过 chmod 755 cli.js 实现修改
    console.log('cli working!');
    
  5. 第五步: 将该模块 link 到全局

    yarn link
    
  6. 第六步: 在全局范围内调用该模块

    lcy-sacffolding //服务器打印出 cli working!
    
  • 完成脚手架工具的基础之后, 我们来实现脚手架的具体业务(脚手架的工作过程)
    • 编写cli.js

      #!/usr/bin/env node
      //Node CLI 应用入口文件必须要这样的文件头
      //如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
      //具体就是通过 chmod 755 cli.js 实现修改
      
      //脚手架的工作过程:
      //1. 通过命令行交互询问用户问题
      //2. 根据用户回答的结果生成文件
      
      //在 node 中发起命令行交互询问, 我们使用 inquirer 模块
      //安装 inquirer 模块: yarn add inquirer
      
      const inquirer = require('inquirer');
      const path = require('path');
      const fs = require('fs');
      const ejs = require('ejs');
      //inquirer 中提供一个 prompt() 方法: 该方法发起命令行提问, 参数为一个数组, 数组的每一个成员就是一个询问对象
      inquirer.prompt([
        {
          type: 'input',//指定: 命令行的方式
          name: 'name', //指定: 问题返回值的键
          message: 'Project name? '//命令行的提示
        }
      ])
      //在then() 方法中接收用户输入的答案
      .then(answer => {
        //通过用户的输入结果动态生成文件
        //生成文件一般通过模板文件生成: 在根目录下创建 templates 文件夹, 在templates 文件夹中添加模板
        //模板目录: 我们可以通过path.join 的方式拿到
        const templDir = path.join(__dirname, 'templates');
        //输出目标目录: 一般是命令行在哪个目录去执行就是哪个目录, 也就是我们的CWD 目录, 在node中我们可以通过process.cwd() 得到
        const destDir = process.cwd();
        //通过fs模块读取模板目录下的文件, 将这些文件全部输出到目标目录
        fs.readdir(templDir, (err, files) => {//该方法会自动扫面第一个参数目录下的所有文件, 在回调函数中可以通过files 拿到文件列表
          if (err) throw err;
          //通过forEach 去遍历每一个文件: 通过模板引擎渲染文件
          files.forEach(file => {
          //安装模板引擎: yarn add ejs
          ejs.renderFile(path.join(templDir, file), answer, (err, result) => {
            if (err) throw err;
            //将渲染后的文件写入到目标目录
            fs.writeFileSync(path.join(destDir, file), result);
          });//参数: 第一个是文件的绝对路径, 第二个是模板引擎在工作的时的数据上下文, 第三个是一个回调函数
          });    
        });
      })
      
    • 在全局调用 lcy-sacffolding