• 如何搭建一个前端脚手架


    为什么我们需要脚手架?

    • 对于前端来说,从零开始建立一个项目是复杂的
    • 完成新项目的启动和搭建,能够帮助开发者提升效率和开发体验

    前置准备

    • inquirer、enquirer、prompts:可以处理复杂的用户输入,完成命令行输入交互。
    • chalk:使终端可以输出彩色信息文案。
    • arg:可以进行基础的命令行参数解析。
    • commander、yargs:可以进行更加复杂的命令行参数解析。

    项目创建过程

    创建初始化项目

    mkdir cli && cd cli
    npm init --yes
    
    • 1
    • 2

    创建cli函数

    创建src目录及src/cli.js文件,cli.js文件内容如下

    export function cli(args) {
     console.log(args);
    }
    
    • 1
    • 2
    • 3

    创建入口文件

    创建src/bin/index.js,为了能够正常使用esm 模块,我们需要先安装,执行npm install esm

    #!/usr/bin/env node
    
    require = require('esm')(module /*, options*/);
    
    require('../src/cli').cli(process.argv);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时使用npm link就能在终端使用xbc执行脚本啦

    {
     "name": "xbc-cli",
     "version": "1.0.0",
     "main": "src/index.js",
     "bin": {
       "xbc": "bin/index.js"
     },
     "dependencies": {
       "esm": "^3.2.18"
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    解析处理命令行输入

    在解析处理命令行输入之前,我们需要设计命令行支持的几个选项,如下。

    • [template]:支持默认的几种模板类型,用户可以通过 select 进行选择。
    • --git:等同于git init去创建一个新的 Git 项目。
    • --install:支持自动下载项目依赖。
    • --yes:跳过命令行交互,直接使用默认配置。
    npm install inquirer arg
    
    • 1

    编写命令行参数解析逻辑,在cli.js中添加:

    import arg from 'arg';
    // 解析命令行参数为 options
    function parseArgumentsIntoOptions(rawArgs) {
        // 使用 arg 进行解析
        const args = arg({
            '--git': Boolean,
            '--yes': Boolean,
            '--install': Boolean,
            '-g': '--git',
            '-y': '--yes',
            '-i': '--install',
        }, {
            argv: rawArgs.slice(2),
        });
        return {
            skipPrompts: args['--yes'] || false,
            git: args['--git'] || false,
            template: args._[0],
            runInstall: args['--install'] || false,
        }
    }
    export function cli(args) {
        // 获取命令行配置
        let options = parseArgumentsIntoOptions(args);
        console.log(options);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    实现使用默认配置和交互式配置选择逻辑,如下代码:

    import arg from 'arg';
    import inquirer from 'inquirer';
    
    function parseArgumentsIntoOptions(rawArgs) {
        // ...
    }
    async function promptForMissingOptions(options) {
        // 默认使用名为 JavaScript 的模板
        const defaultTemplate = 'JavaScript';
        // 使用默认模板则直接返回
        if (options.skipPrompts) {
            return {...options,
                template: options.template || defaultTemplate,
            };
        }
        // 准备交互式问题 
        const questions = [];
        if (!options.template) {
            questions.push({
                type: 'list',
                name: 'template',
                message: 'Please choose which project template to use',
                choices: ['JavaScript', 'TypeScript'],
                default: defaultTemplate,
            });
        }
        if (!options.git) {
            questions.push({
                type: 'confirm',
                name: 'git',
                message: 'Initialize a git repository?',
                default: false,
            });
        }
        // 使用 inquirer 进行交互式查询,并获取用户答案选项
        const answers = await inquirer.prompt(questions);
        return {...options,
            template: options.template || answers.template,
            git: options.git || answers.git,
        };
    }
    export async function cli(args) {
        let options = parseArgumentsIntoOptions(args);
        options = await promptForMissingOptions(options);
        console.log(options);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    下面我们需要完成下载模板到本地的逻辑,我们事先准备好两种名为typescriptjavascript的模板,并将相关的模板存储在项目的根目录中

    我们使用ncp包实现跨平台递归拷贝文件,使用chalk做个性化输出。安装相关依赖如下:

    npm install ncp chalk
    
    • 1

    src/目录下,创建新的文件main.js,代码如下:

    
    import chalk from 'chalk';
    import fs from 'fs';
    import ncp from 'ncp';
    import path from 'path';
    import {
        promisify
    }
    from 'util';
    const access = promisify(fs.access);
    const copy = promisify(ncp);
    // 递归拷贝文件
    async function copyTemplateFiles(options) {
        return copy(options.templateDirectory, options.targetDirectory, {
            clobber: false,
        });
    }
    // 创建项目
    export async function createProject(options) {
        options = {...options,
            targetDirectory: options.targetDirectory || process.cwd(),
        };
        const currentFileUrl = import.meta.url;
        const templateDir = path.resolve(new URL(currentFileUrl).pathname, '../../templates', options.template.toLowerCase());
        options.templateDirectory = templateDir;
        try {
            // 判断模板是否存在
            await access(templateDir, fs.constants.R_OK);
        } catch (err) {
            // 模板不存在 
            console.error('%s Invalid template name', chalk.red.bold('ERROR'));
            process.exit(1);
        }
        // 拷贝模板
        await copyTemplateFiles(options);
        console.log('%s Project ready', chalk.green.bold('DONE'));
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    接下来,我们需要完成git的初始化以及依赖安装工作,这时候需要用到以下内容。

    • execa:允许开发中使用类似git的外部命令。

    • pkg-install:使用yarn installnpm install安装依赖。

    • listr:给出当前进度 progress。

    npm install execa pkg-install listr
    
    • 1

    更新main.js

    const { projectInstall } = require('pkg-install');
    const { access } = require('fs/promises');
    const chalk = require('chalk');
    const fs = require('fs');
    const path = require('path');
    const execa = require('execa');
    const Listr = require('listr');
    const util = require('util');
    const ncp = require('ncp');
    const copy = util.promisify(ncp);
    
    // 拷贝模板
    async function copyTemplateFiles(options) {
      return copy(options.templateDirectory, options.targetDirectory, {
        clobber: false,
      });
    }
    
    // 初始化 git
    async function initGit(options) {
      // 执行 git init
      const result = await execa('git', ['init'], {
        cwd: options.targetDirectory,
      });
    
      if (result.failed) {
        return Promise.reject(new Error('Failed to initialize git'));
      }
      return;
    }
    
    // 创建项目
    export async function createProject(options) {
      options = {
        ...options,
        targetDirectory: options.targetDirectory || process.cwd()
    
      };
    
      const templateDir = path.resolve(
        __dirname,
        '../templates',
        options.template
      );
    
      options.templateDirectory = templateDir;
    
      try {
        console.log(templateDir);
    
        // 判断模板是否存在
        await access(templateDir, fs.constants.R_OK);
      } catch (err) {
        console.error('%s Invalid template name', chalk.red.bold('ERROR'));
        process.exit(1);
      }
    
      // 声明 tasks
    
      const tasks = new Listr([
        {
          title: 'Copy project files',
          task: () => copyTemplateFiles(options),
        },
        {
          title: 'Initialize git',
          task: () => initGit(options),
          enabled: () => options.git,
        },
        {
          title: 'Install dependencies',
          task: () =>
            projectInstall({
              cwd: options.targetDirectory,
            }),
          skip: () =>
            !options.runInstall
              ? 'Pass --install to automatically install dependencies'
              : undefined,
        },
      ]);
    
      // 并行执行 tasks
      await tasks.run();
      console.log('%s Project ready', chalk.green.bold('DONE'));
      return true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    总结

    此时一个简单的脚手架已经完成,文件模板和cli命令根据实际需求自行配置扩展

    源码地址

    github

  • 相关阅读:
    前端周刊第三十二期
    【Python】基础语法(安装,常变量,类型,注释,运算符)
    6W+字记录实验全过程 | 探索Alluxio经济化数据存储策略
    SpringCloud接入nacos配置中心
    java计算机毕业设计在线专业培养方案系统源码+mysql数据库+系统+lw文档+部署
    HTML+CSS大作业:基于HMTL校园学校网页设计题材【我的学校网站】
    安陆TD使用问题记录1---使用modelsim联合仿真
    关于学习编程的心得体会
    【Python基础】Python十进制二进制八进制十六进制 ascii转换
    Windows平台Qt6中UTF8与GBK文本编码互相转换、理解文本编码本质
  • 原文地址:https://blog.csdn.net/github_39506988/article/details/96274010