首先,获得 init 后面输入的参数,作为项目名称,当然判断这个项目名称是否存在,然后进行对应的逻辑操作,通过download-git-repo工具,下载仓库的模板,然后通过inquirer.js 处理命令行交互,获得输入的名称,版本号能信息,最后在根据这些信息,处理模板文件。
用download-git-repo下载模板文件
在lib下创建download.js文件
const download = require('download-git-repo') const path = require("path") const ora = require('ora') module.exports = function (target) { target = path.join(target || '.', '.download-temp'); return new Promise(function (res, rej) { // 这里可以根据具体的模板地址设置下载的url,注意,如果是git,url后面的branch不能忽略 let url='github:ZoeLeee/BaseLearnCli#bash'; const spinner = ora(`正在下载项目模板,源地址:${url}`) spinner.start(); download(url, target, { clone: true }, function (err) { if (err) { download(url, target, { clone: false }, function (err) { if (err) { spinner.fail(); rej(err) } else { // 下载的模板存放在一个临时路径中,下载完成后,可以向下通知这个临时路径,以便后续处理 spinner.succeed() res(target) } }) } else { // 下载的模板存放在一个临时路径中,下载完成后,可以向下通知这个临时路径,以便后续处理 spinner.succeed() res(target) } }) }) }
这里注意下下载地址的url,注意url的格式,不是git clone 的那个地址。其中有个clone:false这个参数,如果只是个人用,可以为true,这样就相当于执行的git clone的操作,如果给其他人,可能会出错,用false的话,那个就是直接用http协议去下载这个模板,具体可以去看官网的文档.
inquirer.js 处理命令交互
比较简单,可以看init.js
这里把获取到的输入信息在往下传递去处理。
metalsmith
接着,要根据获取到的信息,渲染模板。
首先,未不影响原来的模板运行,我们在git仓库上创建一个package_temp.json,对应上我们要交互的变量名
{ "name": "{{projectName}}", "version": "{{projectVersion}}", "description": "{{projectDescription}}", "main": "./src/index.js", "scripts": { "dev": "webpack-dev-server --config ./config/webpack.config.js", "build": "webpack --config ./config/webpack.config.js --mode production" }, "author": "{{author}}", "license": "ISC", "devDependencies": { "@babel/core": "^7.3.3", "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.5", "clean-webpack-plugin": "^1.0.1", "css-loader": "^2.1.0", "html-webpack-plugin": "^3.2.0", "style-loader": "^0.23.1", "webpack": "^4.28.2", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.2.0" }, "dependencies": { "react": "^16.8.2", "react-dom": "^16.8.2" } }
在lib下创建generator.js文件,用来处理模板
const Metalsmith = require('metalsmith') const Handlebars = require('handlebars') const remove = require("../lib/remove") const fs = require("fs") const path = require("path") module.exports = function (context) { let metadata = context.metadata; let src = context.downloadTemp; let dest = './' + context.root; if (!src) { return Promise.reject(new Error(`无效的source:${src}`)) } return new Promise((resolve, reject) => { const metalsmith = Metalsmith(process.cwd()) .metadata(metadata) .clean(false) .source(src) .destination(dest); // 判断下载的项目模板中是否有templates.ignore const ignoreFile = path.resolve(process.cwd(), path.join(src, 'templates.ignore')); const packjsonTemp = path.resolve(process.cwd(), path.join(src, 'package_temp.json')); let package_temp_content; if (fs.existsSync(ignoreFile)) { // 定义一个用于移除模板中被忽略文件的metalsmith插件 metalsmith.use((files, metalsmith, done) => { const meta = metalsmith.metadata() // 先对ignore文件进行渲染,然后按行切割ignore文件的内容,拿到被忽略清单 const ignores = Handlebars .compile(fs.readFileSync(ignoreFile).toString())(meta) .split('\n').map(s => s.trim().replace(/\//g, "\\")).filter(item => item.length); //删除被忽略的文件 for (let ignorePattern of ignores) { if (files.hasOwnProperty(ignorePattern)) { delete files[ignorePattern]; } } done() }) } metalsmith.use((files, metalsmith, done) => { const meta = metalsmith.metadata(); package_temp_content = Handlebars.compile(fs.readFileSync(packjsonTemp).toString())(meta); done(); }) metalsmith.use((files, metalsmith, done) => { const meta = metalsmith.metadata() Object.keys(files).forEach(fileName => { const t = files[fileName].contents.toString() if (fileName === "package.json") files[fileName].contents = new Buffer(package_temp_content); else files[fileName].contents = new Buffer(Handlebars.compile(t)(meta)); }) done() }).build(err => { remove(src); err ? reject(err) : resolve(context); }) }) }
通过Handlebars给我们的package_temp.json进行插值渲染,然后把渲染好的文件内容替换掉原先的package.json的内容