node工具开发常用工具和代码
直接和 node 相关
从完整的物理路径名中获取文件名
包含文件类型后缀
js
import { basename } from 'path'
// 获取文件名
const newFileName = basename(myFile.filepath)
获取文件内容
js
import { readFileSync } from 'fs'
const filePath = 'D:/demo/test.txt'
const content = rf.readFileSync(filePath, 'utf-8')
判断是文件还是文件夹
js
import { readdirSync, statSync, accessSync } from 'fs'
const fileOrDirFullPath = '文件或目录的全路径名'
const stats = statSync(fileOrDirFullPath)
if (stats.isDirectory()) {
console.log('是目录')
} else if (stats.isFile()) {
console.log('是文件')
}
判断文件/目录是否存在
js
import { accessSync, constants } from 'fs'
const fileOrDirFullPath = '文件或目录的全路径'
try {
accessSync(fileOrDirFullPath, constants.F_OK)
console.log('文件或目录存在')
} catch (err) {
console.log('文件或目录不存在')
}
判断是否可读/可写
是否可写
js
import { accessSync, constants } from 'fs'
const fileOrDirFullPath = '文件或目录的全路径'
try {
accessSync(fileOrDirFullPath, constants.R_OK)
console.log('文件或目录可读')
} catch (err) {
console.log('文件或目录不可读')
}
是否可读
js
import { accessSync, constants } from 'fs'
const fileOrDirFullPath = '文件或目录的全路径'
try {
accessSync(fileOrDirFullPath, constants.W_OK)
console.log('文件或目录可写')
} catch (err) {
console.log('文件或目录不可写')
}
判断文件是否存在且写
js
import { accessSync, constants } from 'fs'
const fileOrDirFullPath = '文件全路径'
try {
accessSync(fileOrDirFullPath, constants.F_OK | constants.W_OK)
console.log('文件存在且可写')
} catch (err) {
console.log('文件不存在或不可写')
}
获取系统路径分隔符
ts
import { sep } from 'path'
console.log(sep)
系统路径组装
js
import { join } from 'path'
/*
* 文件下载
* __dirname: 表示开发者写 __dirname 这个语句所在文件的这个文件目录的绝对路径
*/
const root = join(__dirname, '../../upload')
各种路径
js
import { join } from 'path'
join('/foo', 'bar', 'baz/asdf', 'quux', '..')
// 返回: '/foo/bar/baz/asdf'
join('foo', {}, 'bar')
// 抛出 'TypeError: Path must be a string. Received {}'
获取当前路径的上级目录
js
import { join } from 'path'
const parentFullPath = join(__dirname, '../')
__dirname 和 ./ 的区别
__dirname总是指向被执行 js 文件的绝对路径
当在/react-admin-server/routers/file-upload.js 文件中写了 __dirname, 它的值就是/react-admin-server/routers
相反./会返回你执行 node 命令的路径,例如你的工作路径
假设有如下目录结构
/react-admin-server
/routers
file-upload.js
js
// file-upload.js
var path = require('path')
console.log(path.resolve('.'))
console.log(path.resolve(__dirname))
然后在file-upload.js 中,有如下代码,然后在终端执行了下面命令
cd /react-admin-server/routers
node file-upload.js
. 是你的当前工作目录,在这个例子中就是/react-admin-server/routers ,__dirname 是 file-upload.js 的文件路径,在这个例子中就是 /react-admin-server/routers
然而,如果我们的工作目录是/react-admin-server
cd /react-admin-server
node routers/file-upload.js
将会得到
/react-admin-server
/react-admin-server/routers
此时,.指向我们的工作目录,即/react-admin-server,__dirname 还是指向 /react-admin-server/routers 。
js
//__dirname表示当前文件所在的根路径,也就是/routers,然后..表示向上退一级,也就是到了项目根目录,然后public/upload表示到了这个目录
const dirPath = path.join(__dirname, '..', 'public/upload')
遍历指定目录的所有文件和文件夹
js
import { resolve, join, sep } from 'path'
import { readdirSync, statSync } from 'fs'
import { DefaultTheme } from 'vitepress'
/**
* 判断是否为markdown文件
*
* @param {string} fileName 文件名
*
* @return {[boolean]} 有返回值则表示是markdown文件,否则不是
*/
function isMarkdownFile(fileName: string) {
return fileName.match(/.+\.md$/)
}
// 获取docs目录的完整名称(从根目录一直到docs目录)
const docsDirFullPath = join(__dirname, '../')
// 获取docs目录的完整长度
const docsDirFullPathLen = docsDirFullPath.length
/**
* 获取dirOrFileFullName中第一个/docs/后的所有内容
*
* 如:
* /a-root/docs/test 则 获取到 /test
* /a-root-docs/docs/test 则 获取到 /test
* /a-root-docs/docs/docs/test 则 获取到 /docs/test
*
* @param {string} dirOrFileFullName 文件或者目录名
*
* @return {[type]} [return description]
*/
function getDocsDirNameAfterStr(dirOrFileFullName: string) {
// 使用docsDirFullPathLen采用字符串截取的方式,避免多层目录都叫docs的问题
return `${sep}${dirOrFileFullName.substring(docsDirFullPathLen)}`
}
interface SidebarGenerateConfig {
/**
* 需要遍历的目录. 默认:articles
*/
dirName?: string
/**
* 忽略的文件名. 默认: index.md
*/
ignoreFileName?: string
/**
* 忽略的文件夹名称. 默认: ['demo','asserts']
*/
ignoreDirNames?: string[]
}
export function getSidebarData(sidebarGenerateConfig: SidebarGenerateConfig = {}) {
const {
dirName = 'articles',
ignoreFileName = 'index.md',
ignoreDirNames = ['demo', 'asserts'],
} = sidebarGenerateConfig
// 获取目录的绝对路径
const dirFullPath = resolve(__dirname, `../${dirName}`)
const allDirAndFileNameArr = readdirSync(dirFullPath)
const obj = {}
allDirAndFileNameArr.map(dirName => {
let subDirFullName = join(dirFullPath, dirName)
const property = getDocsDirNameAfterStr(subDirFullName).replace(/\\/g, '/') + '/'
const arr = getSideBarItemTreeData(subDirFullName, 1, 2, ignoreFileName, ignoreDirNames)
obj[property] = arr
})
// console.log('sidebarData')
// console.log(obj)
return obj
}
interface SideBarItem {
text: string
collapsible?: boolean
collapsed?: boolean
items?: SideBarItem[]
link?: string
}
function getSideBarItemTreeData(
dirFullPath: string,
level: number,
maxLevel: number,
ignoreFileName: string,
ignoreDirNames: string[]
): SideBarItem[] {
// 获取所有文件名和目录名
const allDirAndFileNameArr = readdirSync(dirFullPath)
const result: SideBarItem[] = []
allDirAndFileNameArr.map((fileOrDirName: string, idx: number) => {
const fileOrDirFullPath = join(dirFullPath, fileOrDirName)
const stats = statSync(fileOrDirFullPath)
if (stats.isDirectory()) {
if (!ignoreDirNames.includes(fileOrDirName)) {
const text = fileOrDirName.match(/^[0-9]{2}-.+/) ? fileOrDirName.substring(3) : fileOrDirName
// 当前为文件夹
const dirData: SideBarItem = {
text,
collapsed: false,
}
if (level !== maxLevel) {
dirData.items = getSideBarItemTreeData(fileOrDirFullPath, level + 1, maxLevel, ignoreFileName, ignoreDirNames)
}
if (dirData.items) {
dirData.collapsible = true
}
result.push(dirData)
}
} else if (isMarkdownFile(fileOrDirName) && ignoreFileName !== fileOrDirName) {
// console.log(fileOrDirName)
// 当前为文件
const matchResult = fileOrDirName.match(/(.+)\.md/)
let text = matchResult ? matchResult[1] : fileOrDirName
text = text.match(/^[0-9]{2}-.+/) ? text.substring(3) : text
// console.log(text)
const fileData: SideBarItem = {
text,
link: getDocsDirNameAfterStr(fileOrDirFullPath).replace('.md', '').replace(/\\/g, '/'),
}
result.push(fileData)
}
})
return result
}
interface NavGenerateConfig {
/**
* 是否启用路由匹配显示激活状态. 默认:false
*/
enableDirActiveMatch: boolean
/**
* 需要遍历的目录. 默认:articles
*/
dirName?: string
/**
* 最大遍历层级. 默认:1
*/
maxLevel?: number
}
export function getNavData(navGenerateConfig: NavGenerateConfig) {
const { enableDirActiveMatch, dirName = 'articles', maxLevel = 1 } = navGenerateConfig
const dirFullPath = resolve(__dirname, `../${dirName}`)
const result = getNavDataArr(dirFullPath, 1, maxLevel, enableDirActiveMatch)
// console.log('navData')
// console.log(result)
return result
}
/**
* 获取顶部导航数据
*
* @param {string} dirFullPath 当前需要遍历的目录绝对路径
* @param {number} level 当前层级
* @param {number[]} maxLevel 允许遍历的最大层级
* @param {boolean} enableActiveMatch 是否启用路由匹配显示激活状态
*
* @return {NavItem[]} 导航数据数组
*/
function getNavDataArr(
dirFullPath: string,
level: number,
maxLevel: number,
enableActiveMatch: boolean
): DefaultTheme.NavItem[] {
// 获取所有文件名和目录名
const allDirAndFileNameArr = readdirSync(dirFullPath)
const result: DefaultTheme.NavItem[] = []
allDirAndFileNameArr.map((fileOrDirName: string, idx: number) => {
const fileOrDirFullPath = join(dirFullPath, fileOrDirName)
const stats = statSync(fileOrDirFullPath)
const link = getDocsDirNameAfterStr(fileOrDirFullPath).replace('.md', '').replace(/\\/g, '/')
// console.log(fileOrDirFullPath)
// console.log(link)
const text = fileOrDirName.match(/^[0-9]{2}-.+/) ? fileOrDirName.substring(3) : fileOrDirName
if (stats.isDirectory()) {
// 当前为文件夹
const dirData: DefaultTheme.NavItem = {
text,
link: `${link}/`,
}
if (level !== maxLevel) {
// @ts-ignore
dirData.items = getNavDataArr(fileOrDirFullPath, level + 1, maxLevel, enableActiveMatch)
}
if (enableActiveMatch) {
dirData.activeMatch = link + '/'
}
result.push(dirData)
} else if (isMarkdownFile(fileOrDirName)) {
// 当前为文件
const fileData: DefaultTheme.NavItem = {
text,
link: link,
}
if (enableActiveMatch) {
fileData.activeMatch = link + '/'
}
result.push(fileData)
}
})
return result
}
第三方工具
文件复制
shell
pnpm add cp
js
cp(src, dest, cb)
cp.sync(src, dest)
文件/目录删除
文件/目录删除
pnpm add rimraf
js
import rimraf from 'rimraf'
import { rimraf, rimrafSync, native, nativeSync } from 'rimraf'
提问与回答
shell
pnpm add inquirer
ts
import inquirer, { QuestionCollection } from 'inquirer'
interface TemplateInfo {
// 模板压缩文件下载地址
downloadUrl: string
// 模板描述
desc: string
}
const repositoryList: Record<string, TemplateInfo> = {
vue3: {
downloadUrl: 'https://gitcode.net/pzy_666/front-project-template/-/raw/master/zip/vue3.zip?inline=false',
desc: 'vue3项目基础模板',
},
'vue3-element-plus': {
downloadUrl:
'https://gitcode.net/pzy_666/front-project-template/-/raw/master/zip/vue3-element-plus.zip?inline=false',
desc: 'vue3项目基础模板上整合进element-plus',
},
}
const projectTemplateChoices = Object.keys(repositoryList).map(propertyName => ({
name: repositoryList[propertyName].desc,
value: propertyName,
}))
const PROMPT_LIST: QuestionCollection = [
{
type: 'input',
message: '请输入项目名',
name: 'projectName',
default: 'demo',
},
{
type: 'list',
message: '请选择需要的项目模板',
name: 'templateName',
choices: projectTemplateChoices,
},
]
inquirer.prompt<IPromptOption>(PROMPT_LIST).then(async answer => {
console.log(answer)
})
在 node 脚本中执行 shell 命令
pnpm add shelljs
js
import shell from 'shelljs'
// 检测当前环境是否有yalc命令
if (!shell.which('yalc')) {
shell.echo('抱歉执行该脚本需要 yalc')
shell.exit(1)
}
/**
* 执行yalc dir命令
*/
shell.exec('yalc dir', function (code: number, stdout: string, stderr: string) {
const yalcRepo = stdout.replace('\n', '').replace('\r\n', '')
if (!yalcRepo) {
shell.echo(`执行出错:未获取到yalc仓库地址`)
shell.exit(1)
}
if (code === 0) {
sucCall && sucCall(yalcRepo)
} else {
shell.echo(`执行出错:${stderr}`)
shell.exit(1)
}
})
帮助与版本与命令行参数获取
shell
pnpm add commander
ts
import { Command } from 'commander'
const program = new Command('yalc-clean')
program.usage(`your-package-name [-f]
e.g.
yalc-clean @pzy/my-button # 从yalc仓库删除 @pzy/my-button 包, 如果 @pzy/my-button 包被其他项目引用,则会删除失败
yalc-clean @pzy/my-button -f # 从yalc仓库中强制删除 @pzy/my-button 包, 无论 @pzy/my-button 包是否被其他项目引用
`)
program.description(`用于删除yalc仓库中的包.
1. 内部会先执行: yalc dir 用于获取yalc仓库目录.
2. 再执行: yalc installations show your-package-name 查看包的引用情况,如果存在引用则询问是否强制删除. 并告知强制删除命令. 然后终止程序执行.
3. 如果 your-package-name 没有被其他项目引用或你使用的是强制删除, 则先执行: yalc installations clean your-package-name 进行清除,
4. 最后再将 your-package-name 从yalc仓库中删除
`)
program.option('-f --force', '无论包是否被其他项目引用,都强制删除')
program.version('0.0.1', '-v, --version', '输出当前版本')
program.helpOption('-h --help', '显示命令帮助信息')
program.parse(process.argv)
const yalcPackageName = program.args[0]
const forceDel = program.opts().force
文件下载与解压
shell
pnpm add download-git-repo
js
import download from 'download-git-repo'
async function demo() {
const downloadUrl = `direct:${repositoryList[answer.templateName].downloadUrl}`
const targetPath = path.resolve(CURRENT_PATH, answer.projectName) // 目标路径
await download(downloadUrl, targetPath, {}, (err: any) => {
if (err) {
console.log(err)
} else {
cd ${answer.projectName}
pnpm install
pnpm run dev
`)
}
})
}
进度信息
js
import ora from 'ora'
const newOra = ora('开始创建项目...').start()
newOra.fail(`项目创建失败`)
newOra.succeed(`项目创建成功`)