介绍
什么是脚手架?
建筑工程领域的定义:脚手架是为了保证各施工过程顺利进行而搭设的工作平台。
前端中的脚手架也是同样是思想,是一种辅助工具,它有不仅限于以下几点的诸多特点:
- 减少重复机械的工作(栗子:一键生成项目结构,再也不需要手动逐个创建了)
- 提供项目规范和约定(栗子:帮助你生成 eslint 配置)
常用的脚手架工具
- 专用型:vue-cli、create-react-app、angular-cli
- 通用型:Yeoman
- 其它:Plop
脚手架工作原理
- 通过命令行交互询问用户问题
- 根据用户回答的结果生成文件
实现步骤
创建 Node CLI 应用
前端脚手架工具其实是一个 Node CLI 应用,CLI 即 Command Line Interface 命令行界面
- 指定 CLI 应用的入口文件
创建一个文件夹(我这里叫做 simple-scaffold) ,用 npm 初始化(npm init
)为 npm 模块,然后在 package.json
中添加 bin
字段:
{
"name": "simple-scaffold",
"version": "0.0.1",
"description": "a simple scaffold demo",
"main": "index.js",
"bin": "cli.js",
}
- 创建入口文件 cli.js 并添加头注释
注意要添加在第一行:
#!/usr/bin/env node
// 以上注释告诉操作系统用 node 来运行这个文件
如果是在 Linux 或 macOS 下运行,还需要修改此文件的读写权限为 755:chmod 755 cli.js
(相关知识点自行搜索)
- 将该 npm 模块 link 到全局
cd
进入 simple-scaffold 目录后执行 npm link
:
> cd ./simple-scaffold
> npm link
这样模块就链接完成了,可以在 cli.js 中用 console.log
打印一些内容测试下效果:
// cli.js
console.log("success")
然后执行命令:
> simple-scaffold
> success
实现简单的命令行交互
这里借助第三方 npm 模块 inquirer 来实现,官方对于 inquirer 的描述是“常用的交互式命令行用户界面集合”,可以用来发起命令行交互:
// cli.js
const path = require("path")
const inquirer = require("inquirer")
// 获取 Node.js 进程当前工作目录文件夹的名字(作为项目默认名称)
const getCwdName = () => {
// 当前工作目录去掉上一级目录, 留下的部分就是文件夹的名字了
const cwd = process.cwd()
const parentDir = path.resolve(cwd, "../")
return cwd.replace(parentDir, "").substring(1)
}
inquirer
.prompt([
{
name: "projectName",
message: "Project name?",
default: getCwdName, // getCwdName 的返回结果作为默认值
filter(res) {
// 过滤空字符串
if (!res.trim()) return getCwdName()
return res
},
},
])
.then((answers) => {
console.log(answers)
})
.catch((err) => {
console.log("inquirer error", err)
})
效果如下:
> cd ./demo
> simple-scaffold
? Project name? demo
{ projectName: 'demo' }
使用模板生成文件
在 simple-scaffold 目录下创建 templates 文件夹,然后随便创建一些文件或文件夹:
├─templates
├─code
├─notes
├─.gitignore
└─README.md
README.md 中的内容:
# <%= readmeHeadline %>
解析 README.md 中的模板语法,需要安装一下 ejs 模块,然后修改 cli.js,完整的代码如下:
#!/usr/bin/env node
const fs = require("fs")
const path = require("path")
const inquirer = require("inquirer")
const ejs = require("ejs")
// 获取 Node.js 进程当前工作目录文件夹的名字(作为项目默认名称)
const getCwdName = () => {
const cwd = process.cwd()
const parentDir = path.resolve(cwd, "../")
return cwd.replace(parentDir, "").substring(1)
}
const isFile = (path) => {
const stats = fs.statSync(path)
return stats.isFile()
}
inquirer
.prompt([
{
name: "projectName",
message: "Project name?",
default: getCwdName,
filter(res) {
// 过滤空字符串
if (!res.trim()) return getCwdName()
return res
},
},
{
name: "readmeHeadline",
message: "readme headline:",
default: "Headline",
},
])
.then((answers) => {
console.log(answers)
// 模板目录
const tmpDir = path.resolve(__dirname, "templates")
// 目标目录(Node.js 进程当前工作目录)
const destDir = process.cwd()
fs.readdir(tmpDir, (err, files) => {
if (err) throw err
files.forEach((file) => {
const tmpFilePath = path.resolve(tmpDir, file)
const destFilePath = path.resolve(destDir, file)
// 如果是文件, 则通过 ejs 渲染后写入目标文件夹
// answers { projectName: xxx, readmeHeadline: xxx }
if (isFile(tmpFilePath)) {
ejs.renderFile(tmpFilePath, answers, (err, result) => {
if (err) throw err
fs.writeFileSync(destFilePath, result)
})
} else {
// 如果是文件夹, 则创建文件夹
if (!fs.existsSync(destFilePath)) fs.mkdirSync(destFilePath)
}
})
})
})
.catch((err) => {
console.log("inquirer error", err)
})
效果如下:
> cd ./demo
> simple-scaffold
? Project name? test
? readme headline: 测试测试
{ projectName: 'test', readmeHeadline: '测试测试' }
按照模板生成了 demo 的目录结构
├─demo
├─code
├─notes
├─.gitignore
└─README.md
README.md 中的内容也被正常替换
# 测试测试
结语
至此,一个简易的前端脚手架就完成啦!
Comments | NOTHING