这是系列文章前端脚手架实现的第三篇,本文核心解决当我们通过模板来初始化项目时如何把选定版本的模板文件下载到对应文件夹中的问题。

当我们在拉取模板文件的时候,存在两种情况,一种是直接把初始化项目的模板文件下载下来保存到指定文件目录即可,另一种是模板文件需要先渲染,我们需要通过终端交互的方式来输入必要信息以完成模板文件的渲染操作,渲染完成后再下载到本地指定文件目录。模板文件是否需要渲染我们可以通过一个指定的文件来进行配置和说明。

在进行上述操作的时候,我需要通过用到一些node模块并提供两个模板仓库来供我们存放模板文件。

安装download-git-repo模块以实现下载文件的操作。

$ npm install download-git-repo

let downloadGitRepo = require('download-git-repo');

安装并引入util模块,该模块的promisify用来把异步 Api 转换为 Promise。

$ npm install util

const { promisify } = require('util');
downloadGitRepo = promisify(downloadGitRepo);

安装ncp模块用于拷贝文件到指定的目录。

$ npm install ncp

const ncp = require('ncp');
模板下载

我在自己的 github 帐号下面创建了Yong-Template组织,在该组织中创建了两个仓库,其中的仓库vue-simple-template中包含了用于创建 Vue项目的初始化项目(模板)文件,另一个仓库vue-render-template则需要通过终端询问的方式来先渲染再下载。

当我们通过 github 提供的 API: https://api.github.com/orgs/Yong-template/repos 来访问组织的时候可以获取仓库相关的 JSON 文件。下面利用安装的download-git-repo来下载项目文件。

/* constants.js 文件内容 */
const downloadDirectory =
`${process.env[process.platform === 'darwin' ? 'HOME' : 'USERPROFILE']}/.template`; module.exports = {
downloadDirectory,
}; /* create.js 文件内容 */
/* 1.安装和导入模板 */
const { promisify } = require('util');
let downloadGitRepo = require('download-git-repo');
downloadGitRepo = promisify(downloadGitRepo); /* 把异步 API 转换为 Promise */ const { downloadDirectory } = require('../util/constants.js'); /* 2.封装下载函数 */
const downloadTask = async(repo, tag) => {
const dest = `${downloadDirectory}/${repo}`; let url = `Yong-template/${repo}`;
if (tag) url += `#${tag}` console.log("dest", dest, "url", url);
/* dest:/Users/文顶顶/.template/vue-simple-template */
/* url :Yong-template/vue-simple-template#v1.0.0 */ await downloadGitRepo(url, dest);
return dest; // 下载的最终目录
}; /* 3.传入仓库名和版本号执行下载操作 */
/* 假设此处的仓库名repo:vue-simple-template 版本号:v2.0.0 */
const dest = await loading(downloadTask, "download template ...")(repo, tag);
console.log("template", dest); /* path.resolve(projectName) 在执行指令的当前目录下面创建projectName为名的文件夹 */
console.log("path.resolve(projectName)", path.resolve(projectName));
await ncp(dest, path.resolve(projectName));

当 create 文件中上面代码完成后,执行Yue-cli create app指令就能够下载文件到当前目录了。

wendingding:Yue-cli wendingding$ Yue-cli create app
执行 action-> create
[ '/usr/local/bin/node',
'/usr/local/bin/Yue-cli',
'create',
'app' ]
fetching template ...
? please choice a template to create project ! vue-simple-template
fetching tags ...
? please choices tags to create project v2.0.0
⠋ download template ...
dest /Users/文顶顶/.template/vue-simple-template url Yong-template/vue-simple-template#v2.0
download template ...
template /Users/文顶顶/.template/vue-simple-template
path.resolve(projectName) /Users/文顶顶/Documents/花田半亩 /Yue-cli/app
模板渲染

有时候用户在利用脚手架工具创建项目的时候,可能需要自己来输入描述信息、协议、和作者等信息,那么这种情况我们可以在项目的模板文件中提供一个文件用来保存这些信息,而 package.json文件中相关的字段用 ejs 模板的方式来处理,我们要做的就是在下载安装的时候,先询问用户,然后根据用户的选择来渲染并生成最终的package.json文件最后下载到本地。

我们先看下询问文件的信息和package.json文件的内容。

/* render.js 文件的内容 */
module.exports = [{
type: 'confirm',
name: 'private',
message: 'This register is private ?',
},
{
type: 'input',
name: 'author',
message: "Please set the author name ?",
},
{
type: 'input',
name: 'description',
message: 'Please enter description information ?',
},
{
type: 'input',
name: 'license',
message: 'Please enter license ?',
},
] /* package.json文件的内容 */
{
"name": "vue-template-simple",
"version": "0.1.2",
"private": "<%=private%>",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"vue": "^2.6.10"
},
"author": "<%=author%>",
"description": "<%=description%>",
"devDependencies": {
"@vue/cli-service": "^3.11.0",
"vue-template-compiler": "^2.6.10"
},
"license": "<%=license%>"
}

我们把用户的选择融入到package.json模板文件中最后生成完整的package.json文件需要用到ejs模板,此外还需要安装metalsmith等模块来遍历文件夹,下面给出安装的关键指令和最终的代码。

$ npm install metalsmith
$ npm install ejs
$ npm install consolidate
/* 列出 create.js 文件的内容 */
/* 导入模块 */
const axios = require('axios');
const ora = require('ora');
const fs = require('fs');
const ncp = require('ncp');
const path = require('path');
const inquirer = require('inquirer');
const { promisify } = require('util');
const MetalSmith = require('metalsmith');
let { render } = require('consolidate').ejs; /* 模板引擎 */
render = promisify(render); let downloadGitRepo = require('download-git-repo');
downloadGitRepo = promisify(downloadGitRepo); /* 把异步 API 转换为 Promise */
const { downloadDirectory } = require('../util/constants.js'); /* 封装函数获取存放模板信息的数据 */
async function getRepositoryList() {
const { data } = await axios.get("https://api.github.com/orgs/Yong-template/repos");
return data;
} const getTagList = async(repo) => {
// https: //api.github.com/repos/Yong-template/vue-simple-template/tags
const { data } = await axios.get(`https://api.github.com/repos/Yong-template/${repo}/tags`);
return data;
}; const loading = (fn, message) => async(...args) => {
const spinner = ora(message);
spinner.start();
const result = await fn(...args);
spinner.succeed();
return result;
}; const downloadTask = async(repo, tag) => {
let url = `Yong-template/${repo}`;
if (tag) url += `#${tag}`
const dest = `${downloadDirectory}/${repo}`;
console.log("dest", dest, "url", url);
await downloadGitRepo(url, dest);
return dest; // 下载的最终目录
}; module.exports = async(projectName) => { let repoList = await loading(getRepositoryList, "fetching template ...")();
const { repo } = await inquirer.prompt({
name: "repo",
type: "list",
message: "please choice a template to create project !",
choices: repoList.map(item => item.name)
}) let tagList = await loading(getTagList, "fetching tags ...")(repo); const { tag } = await inquirer.prompt({
name: 'tag',
type: 'list',
message: 'please choices tags to create project',
choices: tagList.map(item => item.name),
}); const dest = await loading(downloadTask, "download template ...")(repo, tag);
console.log("template", dest); // console.log("tag ->", tag)
/* 根据选择的仓库 + 版本号,下载模板文件到当前项目中指定的文件夹 */ /* dest:/Users/文顶顶/.template/vue-simple-template */
/* url :Yong-template/vue-simple-template#v1.0.0 */ /* path.resolve(projectName) 表示在执行指令的当前目录下面创建projectName为名的文件夹 */
console.log("path.resolve(projectName)", path.resolve(projectName));
await ncp(dest, path.resolve(projectName)); if (!fs.existsSync(path.join(dest, 'render.js'))) {
await ncp(dest, path.resolve(projectName));
} else {
await new Promise((resolve, reject) => {
MetalSmith(__dirname) // 如果你传入路径 他默认会遍历当前路径下的src文件夹
.source(dest)
.destination(path.resolve(projectName))
.use(async(files, metal, done) => {
const args = require(path.join(dest, 'render.js'));
const obj = await inquirer.prompt(args);
const meta = metal.metadata();
Object.assign(meta, obj);
delete files['render.js'];
done();
})
.use((files, metal, done) => {
const obj = metal.metadata();
Reflect.ownKeys(files).forEach(async(file) => {
if (file.includes('js') || file.includes('json')) {
let content = files[file].contents.toString(); // 文件的内容
if (content.includes('<%')) {
content = await render(content, obj);
files[file].contents = Buffer.from(content); // 渲染
}
}
});
done();
})
.build((err) => {
if (err) {
reject();
} else {
resolve();
}
});
});
}
};

测试指令的执行情况。

wendingding$ Yue-cli c myApp
执行 action-> create
[ '/usr/local/bin/node', '/usr/local/bin/Yue-cli', 'c', 'myApp' ]
fetching template ...
? please choice a template to create project ! vue-render-template
fetching tags ...
? please choices tags to create project v1.0.0
⠋ download template ...
dest /Users/文顶顶/.template/vue-render-template url Yong-template/vue-render-template#v1.0
download template ...
template /Users/文顶顶/.template/vue-render-template
path.resolve(projectName) /Users/文顶顶/Documents/花田半亩 /Yue-cli/myApp
? this register is private ? No
? author? Yong
? description ? 测试
? license ? MIT wendingding$ Tree -L 2
.
├── LICENSE
├── README.md
├── bin
│ └── www
├── dist
├── myApp
│ ├── README.md
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── render.js
│ ├── src
│ └── yarn.lock
├── node_modules
│ ├── ...
│ └── yauzl
├── note.md
├── package-lock.json
├── package.json
├── src
│ ├── create.js
│ ├── dist
│ └── main.js
└── util
├── constants.js
└── dist

前端开发系列134-进阶篇之脚手架Yue-cli的实现03-download功能的更多相关文章

  1. openlayers5-webpack 入门开发系列一初探篇(附源码下载)

    前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...

  2. leaflet-webpack 入门开发系列一初探篇(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...

  3. 【Windows10 IoT开发系列】配置篇

    原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry ...

  4. ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

  5. 【webpack 系列】进阶篇

    本文将继续引入更多的 webpack 配置,建议先阅读[webpack 系列]基础篇的内容.如果发现文中有任何错误,请在评论区指正.本文所有代码都可在 github 找到. 打包多页应用 之前我们配置 ...

  6. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  7. 旨在脱离后端环境的前端开发套件 - IDT Server篇

    IDT,一个基于Nodejs的,旨在脱离后端环境的前端开发套件,目的就是能让前端开发完全脱离后端的环境,无论后端是什么模板引擎(主流),都能应付自如. IDT主要包括两大部分:Server + Bui ...

  8. 前端开发【第2篇:CSS】

    鸡血 样式的属性多达几千个,但别担心,按照80-20原则,常用的也就几十个,你完全可以掌握它. Css初识 HTML的诞生 早期只有HTML的时候为了让HTML更美观一点,当时页面的开发者会把颜色写到 ...

  9. [置顶]【实用 .NET Core开发系列】- 导航篇

    前言 此系列从出发点来看,是 上个系列的续篇, 上个系列因为后面工作的原因,后面几篇没有写完,后来.NET Core出来之后,注意力就转移到了.NET Core上,所以再也就没有继续下去,此是原因之一 ...

  10. openlayers4 入门开发系列之风场图篇

    前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...

随机推荐

  1. 详细介绍Dubbo的SPI机制

    一.定义 Dubbo 的 SPI (Service Provider Interface) 机制是对 Java 原生 SPI 机制的增强和扩展,提供了更强大的扩展能力 二.Dubbo SPI 核心实现 ...

  2. AnnotationAwareAspectJAutoProxyCreator后置处理器的BeanDefinition定义信息导入和其对象实例创建过程

    步骤1 我们从配置类上的@EnableAspectJAutoProxy 注解入手,进入发现这个注解上又有一个@Import(AspectJAutoProxyRegistrar.class)注解, 了解 ...

  3. wpf 代码判断当前是否在设计模式,兼容没有UI线程的限制

    /// <summary> /// 当前是否处于设计模式 /// </summary> bool IsInDesignMode { get { return (bool)Des ...

  4. python-docx styles(样式)的用法

    doc=Document()#创建一个空白文档 p=doc.add_paragraph()#给文档增加一个段落 p.paragraph_format.space_before=Pt(0)#设置段落 段 ...

  5. K8s Service 示例详解

    Kubernetes 官方文档:Services-Networking Service介绍 在kubernetes中,pod是应用程序的载体,我们可以通过pod的ip来访问应用程序,但是pod的ip地 ...

  6. 基于ThinkPHP5知识付费系统AntPayCMS

    历时6个月开发基于ThinkPHP5.1知识付费系统AntPayCMS,自己作IT开发已经10年,一直想自己开发自己的系统,虽然看网上也有很多知识付费类的网站的,但基于TP基本很少,而且自己也一直想做 ...

  7. 中国版 Cursor:CodeBuddy

    我正在参加CodeBuddy「首席试玩官」内容创作大赛,本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 一句话即可让 AI 助手生成网 ...

  8. JS中的Uncaught TypeError: Cannot set property 'innerHTML' of null

    问题: 这是JS中常见的一种错误,其错误在于在页面载入之前,JS中有代码提前调用了页面的元素,如以下就是今天碰到的问题,查找了半天才发现了这个简单的错误,做个记录: 可见页面上并没有任何内容,提示是U ...

  9. The Eclipse executable launcher was unable to locate its companion shared library

    win10,笔者是安装eclipse2018.03的情况下,想安装java2019EE遇到的路径问题 1.解决方法 找到配置文件 打开,用记事本打开的话会糊成一行,建议用其他方式打开,例如笔者所用的N ...

  10. Java并发基础之多线程

    文章也发布在我的个人博客上:https://blog.ysboke.cn/archives/129.html 概述 每个Thread类的示例都代表一个线程,而进程是操作系统级别的多任务,JVM就是运行 ...