前端工程化之脚手架创建工具

概述

开发此工具的目的是为了统一公司前端项目脚手架,技术选型,集成CICD方案,集成内部依赖包等。

原理

跟常规的npm包一样,通过在package.json中配置bin(PATH环境变量软链)安装的时候可以在shell终端使用命令来执行我们的nodejs脚本,通过提问式获取最终配置项,来创建项目的脚手架。

程序设计图:

核心设计

提问式模式(Prompt Design)

  • 将脚手架功能细化成程序运行平台、基础框架选型、脚手架能力、CICD集成方案等模块化能力,通过用户选择将其动态组合起来,最终构建出目标工程。具体代码实现:
js 复制代码
const inquirer = require('inquirer')
// 获取提供给用户的选择项
const getPromptModules = () => {
  return ['feature-x', 'feature-xx', 'feature-xxx', 'feature-xxxx'].map(file =>
    require(`../promptModules/${file}`)
  )
}
const promptAPI = new PromptModuleAPI(this)
getPromptModules().forEach(m => m(promptAPI))
// 展示选项在终端中,合并最终选择项目
const resolveFinalPrompts = () => {
    this.injectedPrompts.forEach(prompt => {
      const originalWhen = prompt.when || (() => true)
      prompt.when = answers => {
        return isManualMode(answers) && originalWhen(answers)
      }
    })
    let prompts = ''
    if (this.featurePrompt.choices.length > 0) {
      prompts = [
        this.presetPrompt,
        this.featurePrompt,
        ...this.injectedPrompts,
        ...this.outroPrompts,
      ]
    } else {
      prompts = [this.presetPrompt, ...this.injectedPrompts, ...this.outroPrompts]
    }
    debug('paxc:prompts')(prompts)
    return prompts
}
const answers = await inquirer.prompt(resolveFinalPrompts())

插件机制(Plugin System)

  • 提供给前端用户选择的内容都通过插件式的方式实现,方便第三方继承和扩展该能力,丰富功能。
  • 有利于将核心能力聚合,对外暴露接口,形成高内聚低耦合的设计方式

具体代码:

js 复制代码
// 获取本地插件
async resolvePlugins(rawPlugins, preset) {
    for (const plugin of rawPlugins) {
      let apply = ''
      if (plugin.isCore) {
        apply = require(`./plugins/core/${plugin.id}`).apply
      } else {
        apply = require(`./plugins/${plugin.id}`).apply
      }
      plugin.apply = apply
      if (!plugin.options) {
        plugin.options = preset
      }
    }
}
// 对插件的执行优先级排序,初始化插件,应用插件
_sortPlugins() {
    const plugins = this.plugins
    for (let i = 0; i < plugins.length; i++) {
      const plugin = plugins[i]
      if (plugin.isCore && i === 0) break
      if (plugin.isCore) {
        plugins[i] = plugins[0]
        plugins[0] = plugin
        break
      }
    }
}
async initPlugins() {
    const pluginIds = this.plugins.map(p => p.id)
    // apply generators from plugins
    for (const plugin of this.plugins) {
      const { id, apply, options } = plugin
      if (apply) {
        const api = new GeneratorAPI(id, this, options, this.preset)
        await apply(api, options, this.preset)
        if (apply.hooks) {
          await apply.hooks(api, options, this.preset, pluginIds)
        }
      }
    }
}
// 生成脚手架文件
async generate({ sortPackageJson = true } = {}) {
    // core plugin first
    this._sortPlugins()
    await this.initPlugins()
    const initialFiles = Object.assign({}, this.files)
    // wait for file resolve
    await this.resolveFiles()
    if (this.pkg) {
      // set package.json
      if (sortPackageJson) {
        this.sortPkg()
      }

      this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
    }
    // write/update file tree to disk
    await writeFileTree(this.context, this.files, initialFiles)
}
相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试