手写Vue-cli

1.实现自定义指令

  • 1.通过npm init --y初始化一个node项目

  • 2.创建一个JS文件, 并且在JS文件的头部通过
    #! /usr/bin/env node告诉系统将来这个文件需要在 NodeJS环境 下执行

  • 3.在package.json中新增bin的key, 然后这个key的取值中告诉系统需要新增什么指令, 这个指令执行哪个文件

    "bin": {
    	"nue-cli": "./bin/index.js"
    }
    
  • 4.通过npm link将本地的包链接到全局

在这里插入图片描述

2.编码规范检查

1.安装

npm install eslint

2.初始化eslint配置

npx eslint --init

选择第三个:检查语法,发现问题,统一编码规范

在这里插入图片描述

在这里插入图片描述

后续的操作按照项目的内容进行选择…

初始化配置操作执行后,生成.eslintrc.js文件
在这里插入图片描述

3. 添加帮助和版本号

1.process.argv获取传递的参数

在这里插入图片描述

if(process.argv[2] === '--help'){
    // 输出帮助文档
}else if(process.argv[2] === '--version'){
    // 输出当前版本号
}

2.使用第三方的工具包

commander包

能够快速的处理自定义指令传递进来的参数

安装

npm install commander

只需要将传递进来的参数直接传递给parse方法, 那么commander包就自动帮我们实现了–help

const program = require('commander');
program.parse(process.argv)
sue-cli --help

只需要在调用parse方法之前先调用version方法, 告诉它当前的版本号, 那么commander包就自动帮我们实现了–version

program.version('1.0.0').parse(process.argv);

动态version

const { version } = require("../package.json")
// console.log('version', version)
module.exports = {
    version
};
const program = require('commander');
const { version } = require('./const')
program.version(version).parse(process.argv);
sue-cli --version

–help和–version是所有工具包通用的

4.添加自定义指令

4.1 自定义指令

不确定、非通用并且并非每个包都有的指令需要自己去实现

const program = require('commander');
program
  .command('create') // 指令名称
  .alias('c') // 指令简写
  .description('create a new project powered by vue-cli-service') // 指令描述
  .action(()=>{ // 指令具体的操作
    console.log('创建一个Vue项目');
  })

在这里插入图片描述

–help的时候想要输出一个示例

监听–help

program
  .command('create') // 指令名称
  .alias('c') // 指令简写
  .description('create a new project powered by vue-cli-service') // 指令描述
  .action(()=>{ // 指令具体的操作
    console.log('创建一个Vue项目');
  })
program.on('--help',()=>{
    console.log('Example:');
    console.log('   sue-cli create <app-name>   ');
})  

在这里插入图片描述

4.2 自定义多条指令

const program = require('commander');
const commandMap = {
    create: {
        alias:'c',
        description:'create a new project powered by vue-cli-service',
        example:'sue-cli create <app-name>'
    },
    add: {
        alias:'a',
        description:'install a plugin and invoke its generator in an already created project',
        example:'sue-cli add [options] <plugin> [pluginOptions]'
    },
    // 不存在的使用通配符
    '*':{
        alias:'',
        description:'',
        example:''
    }
}
// nodeJS中Reflect.ownKeys获取对象的key
// Reflect.ownKeys(commandMap) [create, add, *]
Reflect.ownKeys(commandMap).forEach(key=>{
    const value = commandMap[key]
    program
        .command(key) // 指令名称
        .alias(value.alias) // 指令简写
        .description(value.description) // 指令描述
        .action(()=>{ // 指令具体的操作
            console.log('创建一个Vue项目');
            if(key === '*'){
                console.log('指令不存在');
            }else {
                console.log(value.description);
            }
        })
})
program.on('--help',()=>{
    console.log('Example:');
    // 自定义指令相关的示例
    Reflect.ownKeys(commandMap).forEach(key=>{
        const value = commandMap[key]
        console.log(`   ${value.description}  `);
    })
})

在这里插入图片描述

5.处理不同的指令

action方法中处理自定义指令

const program = require('commander');
const { version } = require('./const')
const path = require('path')
const commandMap = {
    create: {
        alias:'c',
        description:'create a new project powered by vue-cli-service',
        example:'sue-cli create <app-name>'
    },
    add: {
        alias:'a',
        description:'install a plugin and invoke its generator in an already created project',
        example:'sue-cli add [options] <plugin> [pluginOptions]'
    },
    // 不存在的使用通配符
    '*':{
        alias:'',
        description:'',
        example:''
    }
}
// nodeJS中Reflect.ownKeys获取对象的key
// Reflect.ownKeys(commandMap) [create, add, *]
Reflect.ownKeys(commandMap).forEach(key=>{
    const value = commandMap[key]
    program
        .command(key) // 指令名称
        .alias(value.alias) // 指令简写
        .description(value.description) // 指令描述
        .action(()=>{ // 指令具体的操作
            console.log('创建一个Vue项目');
            if(key === '*'){
                console.log('指令不存在');
            }else {
                // console.log(value.description);
                // console.log(process.argv.slice(3)); // 获取参数
                require(path.resolve(__dirname, key))(...process.argv.slice(3)) // 文件的名称与key一致,通过require导入对应的文件方法去执行,处理不同指令
            }
        })
})
program.on('--help',()=>{
    console.log('Example:');
    // 自定义指令相关的示例
    Reflect.ownKeys(commandMap).forEach(key=>{
        const value = commandMap[key]
        console.log(`   ${value.description}  `);
    })
})

program.version(version).parse(process.argv)

在这里插入图片描述

6 实现create指令

create指令的本质

从网络上下载提前准备好的模板,然后再自动安装模板中的相关依赖

实现create指令分为两步

  • 下载指定模板
  • 安装相关依赖

6.1 获取项目模板名称

GET  /orgs/:org(组织机构的名称)/repos

在这里插入图片描述
安装axios

npm i axios

action中的操作:

获取过程中,会卡,显示loading提示用户当前正在下载

ora 实现命令行loading效果

npm install ora
// create.js
const axios = require('axios')
const ora = require('ora');
const getTemplateNames = async ()=>{
    const { data } = await axios.get('https://api.github.com/orgs/it666-com/repos')
    return data
}

module.exports = async (projectName) =>{
    /**
     * 获取过程中,会卡,显示loading提示用户当前正在下载
     *  ora
     */
    const spinner = ora('downloading template names')
    spinner.start()
    console.log('create', projectName);
    let data = await getTemplateNames()
    let templateNames = data.map(obj => obj.name) // 模板名称
    spinner.succeed('downloading template names success')
    console.log(templateNames);
    
}

在这里插入图片描述

添加终端用户交互

用户和终端进行交互:inquirer包

安装
npm install inquirer
参数的意义
  • type:表示提问的类型,包括:input, confirm, list, rawlist, expand, checkbox, password, editor;
  • name: 存储当前问题回答的变量;
  • message:问题的描述;
  • default:默认值;
  • choices:列表选项,在某些type下可用,并且包含一个分隔符(separator);
  • validate:对用户的回答进行校验;
  • filter:对用户的回答进行过滤处理,返回处理后的值;
  • transformer:对用户回答的显示效果进行处理(如:修改回答的字体或背景颜色),但不会影响最终的答案的内容;
  • when:根据前面问题的回答,判断当前问题是否需要被回答;
  • pageSize:修改某些type类型下的渲染行数;
  • prefix:修改message默认前缀;
  • suffix:修改message默认后缀。
const axios = require('axios');
const inquirer = require('inquirer');
const ora = require('ora');


const getTemplateNames = async () => {
  const { data } = await axios.get('https://api.github.com/orgs/it666-com/repos');
  return data;
};

module.exports = async (projectName) => {
  const spinner = ora('downloading template names');
  spinner.start();
  const data = await getTemplateNames();
  const templateNames = data.map((obj) => obj.name);
  spinner.succeed('download template names successfully')
  console.log(templateNames);
  // 2.让用户选择制定的模板名称
  const {currentTemplateName} = await inquirer.prompt({
    name:"currentTemplateName",
    type:"list",
    choices:templateNames,
    message:"请选择要使用哪个模板来创建项目"
  })
  console.log(currentTemplateName); // vue-simple-template
};

在这里插入图片描述

上面可以获取到用户选择的模板名称,可根据模板名称去下载对应的模板,但是模板区分版本号

6.2 获取版本号

GET  / repos / owner (组织机构的名称)/ :repo(模板的名称) / tags
const axios = require('axios');
const inquirer = require('inquirer');
const ora = require('ora');


const getTemplateNames = async () => {
  const { data } = await axios.get('https://api.github.com/orgs/it666-com/repos');
  return data;
};

const getTemplateTags = async (currentTemplateName) => {
  const { data } = await axios.get(`https://api.github.com/repos/it666-com/${currentTemplateName}/tags`);
  return data;
};

module.exports = async (projectName) => {
  const spinner = ora('downloading template names');
  spinner.start();
  const data = await getTemplateNames();
  const templateNames = data.map((obj) => obj.name);
  spinner.succeed('download template names successfully')
  console.log(templateNames);
  // 2.让用户选择制定的模板名称
  const { currentTemplateName } = await inquirer.prompt({
    name: "currentTemplateName",
    type: "list",
    choices: templateNames,
    message: "请选择要使用哪个模板来创建项目"
  })
  console.log(currentTemplateName);

  // 3.获取用户指定模板的所有版本号
  spinner.start()
  const data2 = await getTemplateTags(currentTemplateName);
  const templateTags = data2.map((obj) => obj.name)
  spinner.succeed("download template tags successfully")
  console.log('templateTags', templateTags);
  // 4.让用户选择使用指定模板的哪一个版本来创建项目
  const { currentTemplateTagsName } = await inquirer.prompt({
    name: "currentTemplateTag",
    type: "list",
    choices: templateTags,
    message: "请选择要使用选择模板的哪一个版本来创建项目"
  })
  console.log(currentTemplateName, currentTemplateTagsName);
};

6.3 优化上面的代码

====》参考仿造

/*
function demo(a, b) {
  return a + b;
}
const res = demo(10, 20);
console.log(res);
 */
function demo(a) {
  return function (b) {
    return a + b;
  };
}
// const res = demo(10);
// const res2 = res(20);
// console.log(res2);
const res = demo(10)(20);
console.log(res);
// 给fn传参 async (...args) 参考上面
const waitLoading = (message, fn) => async (...args)=>{
  const spinner = ora(message);
  spinner.start();
  const data = await fn(...args);
  spinner.succeed(`${message}successful `)
  return data
}

全部代码

const axios = require('axios');
const inquirer = require('inquirer');
const ora = require('ora');


const getTemplateNames = async () => {
  const { data } = await axios.get('https://api.github.com/orgs/it666-com/repos');
  console.log('data', data);
  return data;
};

const getTemplateTags = async (currentTemplateName) => {
  const { data } = await axios.get(`https://api.github.com/repos/it666-com/${currentTemplateName}/tags`);
  return data;
};

const waitLoading = (message, fn) =>async (...args)=>{
  const spinner = ora(message);
  spinner.start();
  const data = await fn(...args);
  spinner.succeed(`${message}successful `)
  return data
}

module.exports = async (projectName) => {
  const data = await waitLoading('downloading template names', getTemplateNames)();
  const templateNames = data.map((obj) => obj.name);
  // console.log(templateNames);
  // // 2.让用户选择制定的模板名称
  const { currentTemplateName } = await inquirer.prompt({
    name: "currentTemplateName",
    type: "list",
    choices: templateNames,
    message: "请选择要使用哪个模板来创建项目"
  })
  console.log(currentTemplateName);

  // 3.获取用户指定模板的所有版本号
  const data2 = await waitLoading('downloading template tags', getTemplateTags)(currentTemplateName);
  const templateTags = data2.map((obj) => obj.name);

  // 4.让用户选择使用指定模板的哪一个版本来创建项目
  const { currentTemplateTag } = await inquirer.prompt({
    name: 'currentTemplateTag',
    type: 'list',
    choices: templateTags,
    message: '请选择要使用选择模板的哪一个版本来创建项目',
  });
  console.log(currentTemplateName, currentTemplateTag);
};

6.4 获取下载目录

  • 1.拿到模板名称版本号之后应该下载模板
  • 2.如何下载?下载到什么地方
    官方的Vue-CLI会先将模板下载到'用户目录'中, 然后再拷贝到’执行指令的目录'中
  • 3.为什么要先下载到用户目录中?
    因为下载好的模板可能还需要进行一些其它操作

process.env

在这里插入图片描述

USERPROFILE: ‘C:\Users\86182’ Windows当前电脑的用户目录

process.platform获取当前的平台

Windows和Mac的key不一样,需要做区分

const currentPlatformKey = process.platform === 'win32' ? 'USERPROFILE' : 'HOME';
const downloadDirPath = `${process.env[currentPlatformKey]}\\.nue-template` 

\\.nue-template是隐藏的文件夹路径,自定义放置create下载的模板的位置

6.5 如何从github下载模板?

可以借助 download-git-repo 这个库来下载我们指定的模板

download-git-repo可以下载git仓库

download-git-repo

在这里插入图片描述

const axios = require('axios');
const ora = require('ora');
const inquirer = require('inquirer');
const { downloadDirPath } = require('./const')
let DownloadGitRepo = require('download-git-repo')
//  通过NodeJS的util中的promisify方法, 可以快速的将回调函数的API转换成Promise的API
const {promisify} = require('util')
DownloadGitRepo = promisify(DownloadGitRepo);
// ........
const downloadTemplate = async (templateName, templateTag) => {
  let url = `it666-com/${templateName}`
  if(templateTag){
    url += `#${templateTag}`
  }
  let destPath = downloadDirPath + `\\${templateName}` // 指定下载放置的文件夹的名称与templateName一致
  // 转成了promise,第三个参数:回调 就不需要了
  await DownloadGitRepo(url, destPath)
  return destPath
}
// .......1,2,3,4

  // 5.下载用户选择的模板
  const destPath = await waitLoading('', downloadTemplate)(currentTemplateName, currentTemplateTag)
  console.log(destPath);
};

在这里插入图片描述
在这里插入图片描述

6.6 从用户目录拷贝到执行指令的目录当中

path.resolve() // 获取执行指令的目录

完整的目录:获取执行指令的目录 + 执行指令传递的项目名称(projectName)

path.resolve(projectName)

ncp:快速实现文件夹的拷贝

ncp

安装

npm install ncp

将回调转为promise处理

let ncp = require('ncp')
ncp = promisify(ncp)
// ...
let ncp = require('ncp')
ncp = promisify(ncp)
// ....
// 5.下载用户选择的模板
  const destPath = await waitLoading('', downloadTemplate)(currentTemplateName, currentTemplateTag)
  // 6.将用户目录中的模板拷贝到执行指令的路径中
  await waitLoading('copying template', ncp)(destPath, path.resolve(projectName)) 

projectName从创建create指令的action内传入

6.7 自动安装依赖

shellJS
cd:进入目录

cd([dir])

exec:执行指令

exec(command [, options] [, callback])
// ....
const shell = require('shelljs');
const exec = promisify(shell.exec); // 将shell.exec中的回调转为promise
 
ncp = promisify(ncp);
DownloadGitRepo = promisify(DownloadGitRepo);

// .....
const installDependencies = async (projectName) =>{
  // 1.进入创建的项目目录
  shell.cd(projectName);
  // 2.执行Npm install指令
  await exec('npm install');
}
module.exports = async (projectName) => {
// ....1,2,3,4
// 5.下载用户选择的模板
const destPath = await waitLoading('downloading template', downloadTemplate)(currentTemplateName, currentTemplateTag);
// 6.将用户目录中的模板拷贝到执行指令的路径中
await waitLoading('copying template', ncp)(destPath, path.resolve(projectName));
// 7.安装相关的依赖
await waitLoading('install dependencies', installDependencies)(projectName);
};

6.8 完善提示信息

修改提示信息的文字颜色

nodeJS中修改文字颜色:chalk(粉笔库)

chalk

安装

npm install chalk
const axios = require('axios');
const ora = require('ora');
const inquirer = require('inquirer');
let DownloadGitRepo = require('download-git-repo');
// 通过NodeJS的util中的promisify方法, 可以快速的将回调函数的API转换成Promise的API
const { promisify } = require('util');
const path = require('path');
let ncp = require('ncp');
const shell = require('shelljs');
const chalk = require('chalk');
const { downloadDirPath } = require('./const');

const exec = promisify(shell.exec);

ncp = promisify(ncp);
DownloadGitRepo = promisify(DownloadGitRepo);

const getTemplateNames = async () => {
  const { data } = await axios.get('https://api.github.com/orgs/it666-com/repos');
  return data;
};
const getTemplateTags = async (currentTemplateName) => {
  const { data } = await axios.get(`https://api.github.com/repos/it666-com/${currentTemplateName}/tags`);
  return data;
};
const waitLoading = (message, fn) => async (...args) => {
  const spinner = ora(message);
  spinner.start();
  const data = await fn(...args);
  spinner.succeed(`${message} successfully`);
  return data;
};
const downloadTemplate = async (templateName, templateTag) => {
  // 组织机构的名称/模板名称#模板版本号
  // 1.拼接模板在github上的路径
  let url = `it666-com/${templateName}`;
  if (templateTag) {
    url += `#${templateTag}`;
  }
  // 2.拼接存储下载好的模板的路径
  const destPath = `${downloadDirPath}\\${templateName}`;
  // 3.下载模板
  await DownloadGitRepo(url, destPath);
  // 4.将保存模板的路径返回给调用者
  return destPath;
};
const installDependencies = async (projectName) => {
  // 1.进入创建的项目目录
  shell.cd(projectName);
  // 2.执行Npm install指令
  await exec('npm install');
};

module.exports = async (projectName) => {
  const destPath = path.resolve(projectName); // 一进来就获取destPath用于下面的creating提示信息
  // 提示信息 ✨  Creating project in + 创建的目录
  console.log(chalk.green('✨  Creating project in ') + chalk.red(`${destPath}`));
  // 1.拉取所有模板名称
  const data = await waitLoading('downloading template names', getTemplateNames)();
  const templateNames = data.map((obj) => obj.name);

  // 2.让用户选择指定的模板名称
  const { currentTemplateName } = await inquirer.prompt({
    name: 'currentTemplateName',
    type: 'list',
    choices: templateNames,
    message: '请选择要使用哪个模板来创建项目',
  });

  // 3.获取用户指定模板的所有版本号
  const data2 = await waitLoading('downloading template tags', getTemplateTags)(currentTemplateName);
  const templateTags = data2.map((obj) => obj.name);

  // 4.让用户选择使用指定模板的哪一个版本来创建项目
  const { currentTemplateTag } = await inquirer.prompt({
    name: 'currentTemplateTag',
    type: 'list',
    choices: templateTags,
    message: '请选择要使用选择模板的哪一个版本来创建项目',
  });

  // 5.下载用户选择的模板
  //提示信息:✨  Initializing git repository...
  console.log(chalk.green('✨  Initializing git repository...'));
  const sourcePath = await waitLoading('downloading template', downloadTemplate)(currentTemplateName, currentTemplateTag);

  // 6.将用户目录中的模板拷贝到执行指令的路径中
  await waitLoading('copying template', ncp)(sourcePath, destPath);

  // 7.安装相关的依赖
  // 提示信息:✨  Initializing dependencies...
  console.log(chalk.green('✨  Initializing dependencies...'));
  await waitLoading('install dependencies', installDependencies)(projectName);

  // 8.显示创建成功之后的提示信息
  console.log(chalk.green(' Successfully created project ') + chalk.red(`${projectName}.`));
  console.log(chalk.green(' Get started with the following commands:'));
  console.log(chalk.magenta(`$ cd ${projectName}`));
  console.log(chalk.magenta('$ npm run serve'));
};

6.9 编译模板

// ask.js 配置文件
module.exports = [
    {
      type: 'input', // 项目的名称需要通过输入的方式配置
      name: 'name',
      message: 'project-name?',
    },
	{
      type: 'confirm', // 项目是否是私有的需要通过确认(yes or no)的方式配置
      name: 'private',
      message: 'ths resgistery is private?',
    },
    {
      type: 'input', // 项目的作者需要通过输入的方式配置
      name: 'author',
      message: 'author?',
    },
    {
      type: 'input', // 项目的描述需要通过输入的方式配置
      name: 'description',
      message: 'description?',
    }
  ]

通过配置文件告诉用户,哪些东西需要先配置,然后才能根据配置去编译模板

有了配置文件说明模板需要编译

1.判断是否存在ASK.JS文件

// ...
const fs = require('fs');
// ...
const askPath = path.join(sourcePath, 'ask.js');
if (!fs.existsSync(askPath)) {
	// 6.将用户目录中的模板拷贝到执行指令的路径中
    await waitLoading('copying template', ncp)(sourcePath, destPath);
}else {
	// 编译文件,使用第三方库:metalsmith
}

2.编译

metalsmith

安装
 npm install metalsmith
导入
const Metalsmith = require('metalsmith');
if(){}else{
	await new Promise((resolve, reject) => {
      Metalsmith(__dirname)
        .source(sourcePath) // 告诉Metalsmith真正需要遍历的目录是谁
        .destination(destPath) // 告诉Metalsmith编译完的文件放到什么地方
        .use((files, metal, done) => { // use方法可遍历所有方法:files被遍历目录下的文件路径,metal:多个use共享数据,done:表示执行完毕(前一个执行完,后一个use才可以执行)
          done();
        })
        .use((files, metal, done) => {
          done();
        })
        .build((err) => {
          if (err) {
            reject(err);
          } else {
            resolve();
          }
        });
    });
}

用户先填写配置信息,才能根据用户填写的配置信息编译,在use方法中编译的原因是:在use方法中能拿到所有的文件,就能对所有文件进行编译。写两个use的原因:为了保证方法的单一原则,一个方法只做一件事情。

编译模板:consolidate:利用用户填写的信息快速的填写模板(ejs)

consolidate

安装EJS

npm install ejs

update-notifier

update-notifier

检查脚手架有没有更新

npm install update-notifier

boxen

boxen

提示信息

7.全部代码

// create.js
const axios = require('axios');
const ora = require('ora');
const inquirer = require('inquirer');
let DownloadGitRepo = require('download-git-repo');
// 通过NodeJS的util中的promisify方法, 可以快速的将回调函数的API转换成Promise的API
const { promisify } = require('util');
const path = require('path');
let ncp = require('ncp');
const shell = require('shelljs');
const chalk = require('chalk');
const fs = require('fs');
let { render } = require('consolidate').ejs;
const updateNotifier = require('update-notifier');
const pkg = require('../package.json');
const boxen = require('boxen');

render = promisify(render);
const exec = promisify(shell.exec);
const Metalsmith = require('metalsmith');
const { downloadDirPath } = require('./const');

ncp = promisify(ncp);
DownloadGitRepo = promisify(DownloadGitRepo);

const getTemplateNames = async () => {
  const { data } = await axios.get('https://api.github.com/orgs/it666-com/repos');
  return data;
};
const getTemplateTags = async (currentTemplateName) => {
  const { data } = await axios.get(`https://api.github.com/repos/it666-com/${currentTemplateName}/tags`);
  return data;
};
const waitLoading = (message, fn) => async (...args) => {
  const spinner = ora(message);
  spinner.start();
  const data = await fn(...args);
  spinner.succeed(`${message} successfully`);
  return data;
};
const downloadTemplate = async (templateName, templateTag) => {
  // 组织机构的名称/模板名称#模板版本号
  // 1.拼接模板在github上的路径
  let url = `it666-com/${templateName}`;
  if (templateTag) {
    url += `#${templateTag}`;
  }
  // 2.拼接存储下载好的模板的路径
  const destPath = `${downloadDirPath}\\${templateName}`;
  // 3.下载模板
  await DownloadGitRepo(url, destPath);
  // 4.将保存模板的路径返回给调用者
  return destPath;
};
const installDependencies = async (projectName) => {
  // 1.进入创建的项目目录
  shell.cd(projectName);
  // 2.执行Npm install指令
  await exec('npm install');
};
const checkVersion = () => {
  const notifier = updateNotifier({
    pkg,
    updateCheckInterval: 0,
  });

  const { update } = notifier;
  if (update) {
    const messages = [];
    messages.push(
      `${chalk.bgYellow.black(' WARNI: ')}  Nue-Cli is not latest.\n`,
    );
    messages.push(
      chalk.grey('current ')
        + chalk.grey(update.current)
        + chalk.grey(' → ')
        + chalk.grey('latest ')
        + chalk.green(update.latest),
    );
    messages.push(
      `${chalk.grey('Up to date ')} npm i -g ${pkg.name}`,
    );

    console.log(boxen(messages.join('\n'), {
      padding: 2,
      margin: 2,
      align: 'center',
      borderColor: 'yellow',
      borderStyle: 'round',
    }));
  }
};

module.exports = async (projectName) => {
  // 0.检查脚手架更新
  checkVersion();
  const destPath = path.resolve(projectName);
  console.log(chalk.green('✨  Creating project in ') + chalk.red(`${destPath}`));
  // 1.拉取所有模板名称
  const data = await waitLoading('downloading template names', getTemplateNames)();
  const templateNames = data.map((obj) => obj.name);

  // 2.让用户选择指定的模板名称
  const { currentTemplateName } = await inquirer.prompt({
    name: 'currentTemplateName',
    type: 'list',
    choices: templateNames,
    message: '请选择要使用哪个模板来创建项目',
  });

  // 3.获取用户指定模板的所有版本号
  const data2 = await waitLoading('downloading template tags', getTemplateTags)(currentTemplateName);
  const templateTags = data2.map((obj) => obj.name);

  // 4.让用户选择使用指定模板的哪一个版本来创建项目
  const { currentTemplateTag } = await inquirer.prompt({
    name: 'currentTemplateTag',
    type: 'list',
    choices: templateTags,
    message: '请选择要使用选择模板的哪一个版本来创建项目',
  });

  // 5.下载用户选择的模板
  console.log(chalk.green('✨  Initializing git repository...'));
  const sourcePath = await waitLoading('downloading template', downloadTemplate)(currentTemplateName, currentTemplateTag);

  const askPath = path.join(sourcePath, 'ask.js');
  if (!fs.existsSync(askPath)) {
    // 6.将用户目录中的模板拷贝到执行指令的路径中
    await waitLoading('copying template', ncp)(sourcePath, destPath);
  } else {
    // 6.将用户目录中的模板编译后再拷贝到执行指令的路径中
    await new Promise((resolve, reject) => {
      Metalsmith(__dirname)
        .source(sourcePath) // 告诉Metalsmith真正需要遍历的目录是谁
        .destination(destPath) // 告诉Metalsmith编译完的文件放到什么地方
        .use(async (files, metal, done) => {
          // 1.让用户填写配置信息
          const config = require(askPath);
          const result = await inquirer.prompt(config);
          const meta = metal.metadata();
          Object.assign(meta, result);
          done();
        })
        .use((files, metal, done) => {
          const result = metal.metadata();
          // 2.根据用户填写的配置信息编译模板
          // 2.1遍历拿到所有文件路径
          Reflect.ownKeys(files).forEach(async (filePath) => {
            // 2.2提取我们需要处理的问题
            if (filePath.includes('.js') || filePath.includes('.json')) {
              // 2.3获取当前文件的内容
              const fileContent = files[filePath].contents.toString();
              // 2.4判断当前文件的内容是否需要编译
              if (fileContent.includes('<%')) {
                const resultContent = await render(fileContent, result);
                files[filePath].contents = Buffer.from(resultContent);
              }
            }
          });
          done();
        })
        .build((err) => {
          if (err) {
            reject(err);
          } else {
            resolve();
          }
        });
    });
  }

  // 7.安装相关的依赖
  console.log(chalk.green('✨  Initializing dependencies...'));
  await waitLoading('install dependencies', installDependencies)(projectName);

  // 8.显示创建成功之后的提示信息
  console.log(chalk.green(' Successfully created project ') + chalk.red(`${projectName}.`));
  console.log(chalk.green(' Get started with the following commands:'));
  console.log(chalk.magenta(`$ cd ${projectName}`));
  console.log(chalk.magenta('$ npm run serve'));
};


学习记录

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值