加载中...
  • 实现一个项目脚手架 loading

    命令行工具的关键依赖

    1
    2
    3
    4
    5
    6
    7
    inquier, enquirer, prompts: 可以处理复杂的用户输入, 完成命令行输入交互
    chalk, kleur: 使终端可以输出彩色信息文案
    ora: 使命令行可以输出好看的Spinners
    boxen: 可以在命令行画出Boxes区块
    listr: 可以在命令行中画出进度列表
    meow, arg: 可以进行更加复杂的命令行参数解析
    commander, yargs: 可以进行更加复杂的命令行参数解析

    bin/index.js

    1
    2
    require = require(‘esm’)(module)
    require(‘../src/index.js’).cli(process.argv)

    解析处理命令行输入

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


    pnpm i inquirer arg
    function parseArgumentsIntoOptions(rawArgs) {
    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
    }
    }

    async function promptForMissingOptions(options) {
    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
    })
    }
    const answers = await inquierer.prompt(questions)
    return {
    ...options,
    template: options.template || answers.template,
    git: options.git || answers.git
    }
    }

    拷贝模板

    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
    ncp包实现跨平台递归拷贝文件, chalk做个性化输出

    import chalk from 'chalk'
    import fs from 'fs'
    import ncp from 'ncp'
    import path from 'path'
    // 该方法的作用是将一个存在回调函数的异步函数转换为返回Promise对象的异步函数,以便更方便地使用async/await语法。
    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) {
    console.log('options: ', options)
    options = {
    ...options,
    // cwd 是 "current working directory" 的缩写。该方法返回一个字符串,它表示当前工作目录的路径。
    targetDirectory: options.targetDirectory || process.cwd()
    }

    // import.meta.url:一个字符串,表示当前模块的绝对 URL。
    const currentFileUrl = import.meta.url
    const templateDir = path.resolve(
    new URL(currentFileUrl).pathname,
    '../../templates',
    options.template.toLowerCase()
    )
    options.templateDirectory = templateDir
    console.log('templateDir: ', templateDir)
    try {
    await access(templateDir, fs.constants.R_OK)
    } catch (e) {
    console.error('%s Invalid template name', chalk.red.bold('ERROR'))
    // process.exit() 方法用于直接退出 Node.js 进程。
    process.exit(1)
    }
    await copyTemplateFiles(options)
    console.log('%s Project ready', chalk.green.bold('DONE'))
    return true
    }

    完成 Git 的初始化以及依赖安装工作

    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
    execa: 允许在开发中使用类似Git的外部命令
    pkg-install: 使用yarn install或npm install命令安装依赖
    listr: 给出当前进度

    import chalk from 'chalk'
    import fs from 'fs'
    import ncp from 'ncp'
    import path from 'path'
    // 该方法的作用是将一个存在回调函数的异步函数转换为返回Promise对象的异步函数,以便更方便地使用async/await语法。
    import { promisify } from 'util'
    import { execa } from 'execa'
    import Listr from 'listr'
    import { projectInstall } from 'pkg-install'

    // 检查文件或目录是否可访问的方法
    const access = promisify(fs.access)
    const copy = promisify(ncp)

    async function copyTemplateFiles(options) {
    return copy(options.templateDirectory, options.targetDirectory, {
    clobber: false
    })
    }

    async function initGit(options) {
    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) {
    console.log('options: ', options)
    options = {
    ...options,
    // cwd 是 "current working directory" 的缩写。该方法返回一个字符串,它表示当前工作目录的路径。
    targetDirectory: options.targetDirectory || process.cwd()
    }

    // import.meta.url:一个字符串,表示当前模块的绝对 URL。
    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 (e) {
    console.error('%s Invalid template name', chalk.red.bold('ERROR'))
    // process.exit() 方法用于直接退出 Node.js 进程。
    process.exit(1)
    }
    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
    }
    ])

    await tasks.run()
    console.log('%s Project ready', chalk.green.bold('DONE'))
    return true
    }

    上一篇:
    常见数据结构与算法
    下一篇:
    Advanced JavaScript
    本文目录
    本文目录