React
-
介绍一下react
-
React单项数据流
-
react生命周期函数和react组件的生命周期
-
react和Vue的原理,区别,亮点,作用
-
reactJs的组件交流
-
有了解过react的虚拟DOM吗,虚拟DOM是怎么对比的呢
-
项目里用到了react,为什么要选择react,react有哪些好处
-
怎么获取真正的dom
-
选择react的原因
-
react的生命周期函数
-
setState之后的流程
-
react高阶组件知道吗?
-
React的jsx,函数式编程
-
react的组件是通过什么去判断是否刷新的
-
如何配置React-Router
-
路由的动态加载模块
-
Redux中间件是什么东西,接受几个参数
-
redux请求中间件如何处理并发
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

// 将上面的 updateChk() 方法导出
module.exports = updateChk
这里需要说明两点:updateCheckInterval 默认是 1 天,也就意味着今天检测更新了一次,下一次能进行检测更新的时间点应该为明天同这个时间点之后,否则周期内检测更新都会转到 No new version is available.。
举个栗子:我今天10点的时候检查更新了一次,提示有新版本可用,然后我下午4点再检查一次,此时将不会再提示有新版本可用,只能等到明天10点过后再检测更新才会重新提示新版本可用。因此,将 updateCheckInterval 设置为 1000 毫秒,就能使每次检测更新保持最新状态。
另外,update-notifier 检测更新机制是通过 package.json 文件的 name 字段值和 version 字段值来进行校验:它通过 name 字段值从 npmjs 获取库的最新版本号,然后再跟本地库的 version 字段值进行比对,如果本地库的版本号低于 npmjs 上最新版本号,则会有相关的更新提示。
当然,此时我们还需要把 upgrade 命令声明一下,打开 bin/index.js 文件,在合适的位置添加以下代码:
// 请求 lib/update.js
const updateChk = require(‘…/lib/update’)
// upgrade 检测更新
program
// 声明的命令
.command(‘upgrade’)
// 描述信息,在帮助信息时显示
.description(“Check the js-plugin-cli version.”)
.action(() => {
// 执行 lib/update.js 里面的操作
updateChk()
})
添加后的代码应该如图所示:

记得把 program.parse(process.argv) 放到最后就行。
添加好代码后,打开控制台,输入命令 js-plugin-cli upgrade 查看效果:

为了测试效果,我将本地库 js-plugin-cli 下 package.json 的 name 改为 vuepress-creator,version 默认为 1.0.0,而 npmjs 上 vuepress-creator 脚手架最新版本为 2.x,因此会有更新的提示。
mirror 切换镜像链接
我们通常会把模板放 Github 上,但是在国内从 Github 下载模板不是一般的慢,所以我考虑将模板放 Vercel 上,但是为了避免一些地区的用户因网络问题不能正常下载模板的问题,我们需要将模板链接变成可定义的,然后用户就可以自定义模板链接,更改为他们自己觉得稳定的镜像托管平台上,甚至还可以把模板下载下来,放到他们自己服务器上维护。
为了能够记录切换后的镜像链接,我们需要在本地创建 config.json 文件来保存相关信息,当然不是由我们手动创建,而是让脚手架来创建,整个逻辑过程如下:

所以我们还需要在 lib 文件夹下创建 config.js 文件,用于生成默认配置文件。
打开 lib/config.js 文件,添加以下代码:
// 请求 fs-extra 库
const fse = require(‘fs-extra’)
const path = require(‘path’)
// 声明配置文件内容
const jsonConfig = {
“name”: “js-plugin-cli”,
“mirror”: “https://zpfz.vercel.app/download/files/frontend/tpl/js-plugin-cli/”
}
// 拼接 config.json 完整路径
const configPath = path.resolve(__dirname,‘…/config.json’)
async function defConfig() {
try {
// 利用 fs-extra 封装的方法,将 jsonConfig 内容保存成 json 文件
await fse.outputJson(configPath, jsonConfig)
} catch (err) {
console.error(err)
process.exit()
}
}
// 将上面的 defConfig() 方法导出
module.exports = defConfig
这里需要注意的是,我们不要再直接去用内置的 fs 库,推荐使用增强库 fs-extra,fs-extra 除了封装原有基础文件操作方法外,还有方便的 json 文件读写方法。
打开 lib/mirror.js 文件,添加以下代码:
// 请求 log-symbols 库
const symbols = require(‘log-symbols’)
// 请求 fs-extra 库
const fse = require(‘fs-extra’)
const path = require(‘path’)
// 请求 config.js 文件
const defConfig = require(‘./config’)
// 拼接 config.json 完整路径
const cfgPath = path.resolve(__dirname,‘…/config.json’)
async function setMirror(link) {
// 判断 config.json 文件是否存在
const exists = await fse.pathExists(cfgPath)
if (exists){
// 存在时直接写入配置
mirrorAction(link)
}else{
// 不存在时先初始化配置,然后再写入配置
await defConfig()
mirrorAction(link)
}
}
async function mirrorAction(link){
try {
// 读取 config.json 文件
const jsonConfig = await fse.readJson(cfgPath)
// 将传进来的参数 link 写入 config.json 文件
jsonConfig.mirror = link
// 再写入 config.json 文件
await fse.writeJson(cfgPath, jsonConfig)
// 等待写入后再提示配置成功
console.log(symbols.success, ‘Set the mirror successful.’)
} catch (err) {
// 如果出错,提示报错信息
console.log(symbols.error, chalk.red(Set the mirror failed. ${err}))
process.exit()
}
}
// 将上面的 setMirror(link) 方法导出
module.exports = setMirror
需要注意的是 async 和 await,这里用的是 Async/Await 的写法,其他相关写法可参照 fs-extra[10] 。async 一般默认放函数前面,而 await 看情况添加,举个例子:
…
const jsonConfig = await fse.readJson(cfgPath)
jsonConfig.mirror = link
await fse.writeJson(cfgPath, jsonConfig)
console.log(symbols.success, ‘Set the mirror successful.’)
…
我们需要等待 fs-extra 读取完,才可以进行下一步,如果不等待,就会继续执行 jsonConfig.mirror = link 语句,就会导致传入的 json 结构发生变化。再比如 await fse.writeJson(cfgPath, jsonConfig) 这句,如果去掉 await,将意味着还在写入 json 数据(假设写入数据需要花 1 分钟)时,就已经继续执行下一个语句,也就是提示 Set the mirror successful.,但实际上写入文件不会那么久,就算去掉 await,也不能明显看出先后执行关系。
老规矩,我们还需要把 mirror 命令声明一下,打开 bin/index.js 文件,在合适的位置添加以下代码:
// 请求 lib/mirror.js
const setMirror = require(‘…/lib/mirror’)
// mirror 切换镜像链接
program
.command(‘mirror <template_mirror>’)
.description(“Set the template mirror.”)
.action((tplMirror) => {
setMirror(tplMirror)
})
打开控制台,输入命令 js-plugin-cli mirror 你的镜像链接 查看效果:

此时,在项目下应该已经生成 config.json 文件,里面相关内容应该为:
{
“name”: “js-plugin-cli”,
“mirror”: “https://zpfz.vercel.app/download/files/frontend/tpl/js-plugin-cli/”
}
download 下载/更新模板
网络上很多教程在谈及脚手架下载模板时都会选择 download-git-repo 库,但是这里我选择 download 库,因为利用它可以实现更自由的下载方式,毕竟 download-git-repo 库主要还是针对 Github 等平台的下载,而 download 库可以下载任何链接的资源,甚至还有强大的解压功能(无需再安装其他解压库)。
在此之前,我们得先明白 lib/download.js 需要执行哪些逻辑:下载/更新模板应属于强制机制,也就是说,不管用户本地是否有模板存在,lib/download.js 都会下载并覆盖原有文件,以保持模板的最新状态,相关逻辑图示如下:

打开 lib/download.js 文件,添加以下代码:
// 请求 download 库,用于下载模板
const download = require(‘download’)
// 请求 ora 库,用于实现等待动画
const ora = require(‘ora’)
// 请求 chalk 库,用于实现控制台字符样式
const chalk = require(‘chalk’)
// 请求 fs-extra 库,用于文件操作
const fse = require(‘fs-extra’)
const path = require(‘path’)
// 请求 config.js 文件
const defConfig = require(‘./config’)
// 拼接 config.json 完整路径
const cfgPath = path.resolve(__dirname,‘…/config.json’)
// 拼接 template 模板文件夹完整路径
const tplPath = path.resolve(__dirname,‘…/template’)
async function dlTemplate() {
// 参考上方 mirror.js 主代码注释
const exists = await fse.pathExists(cfgPath)
if (exists){
// 这里记得加 await,在 init.js 调用时使用 async/await 生效
await dlAction()
}else{
await defConfig()
// 同上
await dlAction()
}
}
async function dlAction(){
// 清空模板文件夹的相关内容,用法见 fs-extra 的 README.md
try {
await fse.remove(tplPath)
} catch (err) {
console.error(err)
process.exit()
}
// 读取配置,用于获取镜像链接
const jsonConfig = await fse.readJson(cfgPath)
// Spinner 初始设置
const dlSpinner = ora(chalk.cyan(‘Downloading template…’))
// 开始执行等待动画
dlSpinner.start()
try {
// 下载模板后解压
await download(jsonConfig.mirror + ‘template.zip’, path.resolve(__dirname,‘…/template/’),{extract:true});
} catch (err) {
// 下载失败时提示
dlSpinner.text = chalk.red(Download template failed. ${err})
// 终止等待动画并显示 X 标志
dlSpinner.fail()
process.exit()
}
// 下载成功时提示
dlSpinner.text = ‘Download template successful.’
// 终止等待动画并显示 ✔ 标志
dlSpinner.succeed()
}
// 将上面的 dlTemplate() 方法导出
module.exports = dlTemplate
我们先用 fse.remove() 清空模板文件夹的内容(不考虑模板文件夹存在与否,因为文件夹不存在不会报错),然后执行等待动画并请求下载,模板文件名固定为 template.zip,download 语句里的 extract:true 表示开启解压。
上述代码有两处加了 process.exit(),意味着将强制进程尽快退出(有点类似 return 的作用,只不过 process.exit() 结束的是整个进程),哪怕还有未完全完成的异步操作。
就比如说第二个 process.exit() 吧,当你镜像链接处于 404 或者其他状态,它会返回你相应的报错信息并退出进程,就不会继续执行下面 dlSpinner.text 语句了。
我们还需要把 template 命令声明一下,打开 bin/index.js 文件,在合适的位置添加以下代码:
// 请求 lib/download.js
const dlTemplate = require(‘…/lib/download’)
// template 下载/更新模板
program
.command(‘template’)
.description(“Download template from mirror.”)
.action(() => {
dlTemplate()
})
打开控制台,输入命令 js-plugin-cli template 查看效果:

上图直接报错返回,提示 404 Not Found,那是因为我还没把模板文件上传到服务器上。等把模板上传后就能正确显示了。
关于学源码可以看看:【前车之鉴】Vue,你真的熟练了么?
init 初始化项目
接下来是咱们最主要的 init 命令,init 初始化项目涉及的逻辑比其他模板相对较多,所以放在最后解析。
初始化项目的命令是 js-plugin-cli init 项目名,所以我们需要把 项目名 作为文件夹的名称,也是项目内 package.json 的 name 名称(只能小写,所以需要转换)。由于模板是用于开发 js 插件,也就需要抛出全局函数名称(比如 import Antd from 'ant-design-vue' 的 Antd),所以我们还需要把模板的全局函数名称抛给用户来定义,通过控制台之间的交互来实现。完成交互后,脚手架会把用户输入的内容替换到模板内容内,整个完整的逻辑导图如下:

打开 lib/init.js 文件,添加以下代码:
// 请求 fs-extra 库,用于文件操作
const fse = require(‘fs-extra’)
// 请求 ora 库,用于初始化项目时等待动画
const ora = require(‘ora’)
// 请求 chalk 库
const chalk = require(‘chalk’)
// 请求 log-symbols 库
const symbols = require(‘log-symbols’)
// 请求 inquirer 库,用于控制台交互
const inquirer = require(‘inquirer’)
// 请求 handlebars 库,用于替换模板字符
const handlebars = require(‘handlebars’)
const path = require(‘path’)
// 请求 download.js 文件,模板不在本地时执行该操作
const dlTemplate = require(‘./download’)
async function initProject(projectName) {
try {
const exists = await fse.pathExists(projectName)
if (exists) {
// 项目重名时提醒用户
console.log(symbols.error, chalk.red(‘The project already exists.’))
} else {
// 执行控制台交互
inquirer
.prompt([
{
type: ‘input’, // 类型,其他类型看官方文档
name: ‘name’, // 名称,用来索引当前 name 的值
message: ‘Set a global name for javascript plugin?’,
default: ‘Default’, // 默认值,用户不输入时用此值
},
])
.then(async (answers) => {
// Spinner 初始设置
const initSpinner = ora(chalk.cyan(‘Initializing project…’))
// 开始执行等待动画
initSpinner.start()
// 拼接 template 文件夹路径
const templatePath = path.resolve(__dirname, ‘…/template/’)
// 返回 Node.js 进程的当前工作目录
const processPath = process.cwd()
// 把项目名转小写
const LCProjectName = projectName.toLowerCase()
// 拼接项目完整路径
const targetPath = ${processPath}/${LCProjectName}
// 先判断模板路径是否存在
const exists = await fse.pathExists(templatePath)
if (!exists) {
// 不存在时,就先等待下载模板,下载完再执行下面的语句
await dlTemplate()
}
// 等待复制好模板文件到对应路径去
try {
await fse.copy(templatePath, targetPath)
} catch (err) {
console.log(symbols.error, chalk.red(Copy template failed. ${err}))
process.exit()
}
// 把要替换的模板字符准备好
const multiMeta = {
project_name: LCProjectName,
global_name: answers.name
}
// 把要替换的文件准备好
const multiFiles = [
${targetPath}/package.json,
${targetPath}/gulpfile.js,
${targetPath}/test/index.html,
${targetPath}/src/index.js
]
// 用条件循环把模板字符替换到文件去
for (var i = 0;i < multiFiles.length;i++){
// 这里记得 try {} catch {} 哦,以便出错时可以终止掉 Spinner
try {
// 等待读取文件
const multiFilesContent = await fse.readFile(multiFiles[i], ‘utf8’)
// 等待替换文件,handlebars.compile(原文件内容)(模板字符)
const multiFilesResult = await handlebars.compile(multiFilesContent)(multiMeta)
// 等待输出文件
await fse.outputFile(multiFiles[i], multiFilesResult)
} catch (err) {
// 如果出错,Spinner 就改变文字信息
initSpinner.text = chalk.red(Initialize project failed. ${err})
// 终止等待动画并显示 X 标志
initSpinner.fail()
// 退出进程
process.exit()
}
}
// 如果成功,Spinner 就改变文字信息
initSpinner.text = ‘Initialize project successful.’
// 终止等待动画并显示 ✔ 标志
React
-
介绍一下react
-
React单项数据流
-
react生命周期函数和react组件的生命周期
-
react和Vue的原理,区别,亮点,作用
-
reactJs的组件交流
-
有了解过react的虚拟DOM吗,虚拟DOM是怎么对比的呢
-
项目里用到了react,为什么要选择react,react有哪些好处
-
怎么获取真正的dom
-
选择react的原因
-
react的生命周期函数
-
setState之后的流程
-
react高阶组件知道吗?
-
React的jsx,函数式编程
-
react的组件是通过什么去判断是否刷新的
-
如何配置React-Router
-
路由的动态加载模块
-
Redux中间件是什么东西,接受几个参数
-
redux请求中间件如何处理并发
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】


被折叠的 条评论
为什么被折叠?



