手把手实现脚手架

·  阅读 371
手把手实现脚手架

目标

  1. 最少代码实现
  2. 选择包管理器,npm源功能
  3. 插件化,自由进行扩展

输入命令,准备创建项目

  1. 解析用户命令,弹出交互语句,用户自己选择
  2. 脚手架创建package.json文件
  3. 执行npm i安装依赖

技术解决点:

1. 处理用户命令:commander.js(解析用户的命令,提取出用户的输入交给脚手架)

#!/usr/bin/env node
const program = require('commander')
const create = require('../lib/create')

program
.version('0.1.0')
.command('gz <name>')
.description('create a new project')
.action(name => { 
    create(name)
})

program.parse()

注册了gz命令,放在项目bin目录,cli.js,在package.json里添加“bin”:{ “gz”:“./bin/cli.js” },npm link将其注册成全局命令。

2. 和用户交互

询问用户创建项目需要哪些功能。使用Inquirer.js, 弹出一个问题和一些选项,让用户选择。

例如下面的代码:

const questions = [
    {
    type: "input",
    name: "compZhName",
    prefix: "欢迎使用dvp-cli快速搭建新组件,",
    message: "请输入组件中文名",
    validate(value) {
      if (value) return true;
      return "组件中文名称必填"
    }
  },
  {
    type: "rawlist",
    name: "compClassify",
    prefix: "欢迎使用dvp-cli快速搭建新组件,",
    message: "请选择组件分类",
    choices: [
      { name: "图表" },
      { name: "控件" },
      { name: "媒体" },
      { name: "3D" },
      { name: "GIS" },
      { name: "容器" },
    ]
  }
]

inquirer.prompt(questions)

3. 渲染模板

使用ejs和prettier

fs.writeFileSync(`${getCompPath()}/index.vue`, createVueTemplate(answer));
import fs from 'fs';
import ejs from 'ejs';
import path from 'path';
import { fileURLToPath } from "url";
import prettier from "prettier";
export default (config) => {
  const __dirname = fileURLToPath(import.meta.url);
  const template = fs.readFileSync(path.resolve(__dirname, "../template/view.ejs"));
  const code = ejs.render(template.toString(), {
    compEnName: config.compEnName,
  })
  return prettier.format(code, { "trailingComma": "none", parser: "vue" });
}
{
"name": "<%= packageName %>",
  "version": "0.1.0",
  "private": true,
  "scripts": {
  "dev": "vue-cli-service serve",
  "build": "vue-cli-service build",
  "lint": "vue-cli-service lint"
  <% if(middleware.husky) { %>
    ,"prepare": "husky install"
    <% } %>
      },
      "dependencies": {
      "vue": "3.2.8",
      "lint-staged": "^12.3.2",
      "axios": "^0.25.0",
      "vue-router": "4.0.11",
      "vuex": "4.0.2"
      <% if(middleware.dayjs) { %>
        ,"dayjs": "^1.10.7"
        <% } %>
          <% if(middleware.driver) { %>
            ,"driver.js": "^0.9.8"
            <% } %>
              <% if(middleware.element) { %>
                ,"element-plus": "^1.3.0-beta.9"
                <% } %>
                  <% if(middleware.screenfull) { %>
                    ,"screenfull": "5.1.0"
                    <% } %>
                      <% if(middleware.i18n) { %>
                        ,"vue-i18n": "^9.2.0-beta.30"
                        <% } %>
                          },
                          "devDependencies": {
                          "@vue/cli-plugin-babel": "~4.5.0",
                          "@vue/cli-service": "~4.5.0",
                          "@vue/compiler-sfc": "^3.0.0",
                          "core-js": "^3.20.3",
                          "cz-customizable": "^6.3.0",
                          "svg-sprite-loader": "^6.0.9",
                          "unplugin-auto-import": "^0.5.11",
                          "unplugin-vue-components": "^0.17.15",
                          "@vue/cli-plugin-router": "~4.5.0",
                          "@vue/cli-plugin-vuex": "~4.5.0",
                          "sass": "^1.26.5",
                          "sass-loader": "^8.0.2"
                          <% if(middleware.husky) { %>
                            ,"husky": "7.0.1"
                            <% } %>
                              <% if(middleware.eslint) { %>
                                ,"eslint": "^6.7.2",
                                "@vue/cli-plugin-eslint": "~4.5.0",
                                "@vue/eslint-config-standard": "^5.1.2",
                                "babel-eslint": "^10.1.0",
                                "eslint-plugin-import": "^2.20.2",
                                "eslint-plugin-node": "^11.1.0",
                                "eslint-plugin-promise": "^4.2.1",
                                "eslint-plugin-standard": "^4.0.0",
                                "eslint-plugin-vue": "^7.0.0"
                                <% } %>
                                  <% if(middleware.commitLink) { %>
                                    ,"@commitlint/cli": "12.1.4"
                                    <% } %>
                                      <% if(middleware.commitLink) { %>
                                        ,"@commitlint/config-conventional": "^12.1.4"
                                        <% } %>
                                          },
                                          "config": {
                                          "commitizen": {
                                          "path": "node_modules/cz-customizable"
                                          }
                                          },
                                          "lint-staged": {
                                          "src/**/*.{js,vue}": [
                                          "eslint --fix",
                                          "git add"
                                          ]
                                          }
                                          }

4. 下载依赖

execa,它可以调用子进程执行命令。

execa("npm i", {
  cwd: getRootPath(),
  stdio: [2, 2, 2]
})
console.log('\n依赖下载完成! 执行下列命令开始开发:\n')
console.log(`cd ${name}`)
console.log(`npm run dev`)

调用 executeCommand() 开始下载依赖,参数为 npm install 和用户创建的项目路径。为了能让用户看到下载依赖的过程,我们需要使用下面的代码将子进程的输出传给主进程,也就是输出到控制台:

child.stdout.on('data', buffer => {
    process.stdout.write(buffer)
})

5. 未来完善点

  1. 创建项目时判断该项目是否已存在,支持覆盖和合并创建。(fs.existsSync(targetDir))
  2. 选择功能时提供默认配置和手动选择两种模式。
  3. 如果用户的环境同时存在 yarn 和 npm,则会提示用户要使用哪个包管理器。
  4. 如果 npm 的默认源速度比较慢,则提示用户是否要切换到淘宝源。
  5. 如果用户是手动选择功能,在结束后会询问用户是否要将这次的选择保存为默认配置。

参考资料:https://github.com/vuejs/vue-cli

完!

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改