如何搭建一个前端脚手架

为什么我们需要脚手架?

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

前置准备

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

项目创建过程

创建初始化项目

mkdir cli && cd cli
npm init --yes

创建cli函数

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

export function cli(args) {
 console.log(args);
}

创建入口文件

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

#!/usr/bin/env node

require = require('esm')(module /*, options*/);

require('../src/cli').cli(process.argv);

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

{
 "name": "xbc-cli",
 "version": "1.0.0",
 "main": "src/index.js",
 "bin": {
   "xbc": "bin/index.js"
 },
 "dependencies": {
   "esm": "^3.2.18"
 }
}

解析处理命令行输入

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

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

编写命令行参数解析逻辑,在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);
}

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

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);
}

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

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

npm install ncp chalk

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;
}

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

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

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

  • listr:给出当前进度 progress。

npm install execa pkg-install listr

更新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;
}

总结

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

源码地址

github

猜你喜欢

转载自juejin.im/post/7126853578374774791