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

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

在进行上述操作的时候,我需要通过用到一些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. Windows系统设置开机自启动+分块压缩+文件共享

    开机自启动+分块压缩+文件共享 一.设置开机自启动 win+R 打开运行窗口,输入 shell:startup 此时桌面会弹出一个目录文件夹,只需要将需要启动的软件放入该文件夹即可开机自启. C:\U ...

  2. 在 .NET 中的 ConvertAll 和 Select 方法哪个性能好

    .NET 的 List 中提供了 ConvertAll 和 Select 两个方法,在开发中实际上应该使用哪一个? 接下来通过基准测试脚本来对比性能. 先编写基准测试脚本: [MemoryDiagno ...

  3. WPF初学者的一点迷思

    1.WPF只是前端!前端!前端!看了两天的视频,跟着敲了三个项目,自己写了一个小demo之后,从gitee上下了一个别的的框架之后才整明白,WPF只是前端.或者说只是把原本winfrom的界面+事件+ ...

  4. 60个 Linux C/C++ 实战小项目,挑战年薪30万+

    大家好啊!我是小康. 最近公众号后台收到好多小伙伴的私信: "小康哥,我已经把<C++ Primer>啃完了,Linux 环境也玩得差不多了,但就是不知道该做啥项目练手...&q ...

  5. AD 侦查-MSRPC

    本文通过 Google 翻译 AD Recon – MSRPC (135/539) 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充. 导航 0 前言 1 MSRPC(远 ...

  6. 编译报错出现原因以及处理方法之Error:(1, 1) java: 非法字符: ‘\ufeff‘

    问题 记录遇到的异常和总结处理的过程,希望能给到自己和别人帮助. Error:(1, 1) java: 非法字符: '\ufeff' 编译过程中出现报错Error:(1, 1) java: 非法字符: ...

  7. 鸿蒙NEXT(五):鸿蒙版React Native架构浅析

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...

  8. 用 Sidecar 容器为 .NET Core 应用做诊断和性能分析

    在微服务架构和云原生应用广泛采用的今天,.NET Core 应用被越来越多地部署在 Kubernetes 集群中.然而,一旦这些应用出现性能瓶颈,仅靠传统的日志和指标可能无法定位问题的根本原因. 从 ...

  9. Python实现http接口请求数据后,往RabbitMQ里面插入数据

    python实现http接口请求数据服务后,往RABBITmq里面插入数据 import time import requests import pika import datetime base_u ...

  10. Vue3 开发需要安装的工具

    Node 和Npm 扩展 node运行时和node包管理器 1.node.js.npm 处理TypeScript 2.tsc.tsc-node 创建vue3工程 3.create-preset 处理J ...