提升前端开发效率:利用 Babel 实现 JavaScript 文件的定制化修改

因为公司前端架构,在一些情况下需要对vue的路由的meta字段新增或删除很多属性,随着项目规模的扩大,如果手动加配置就会变得很麻烦。 为了简化这一过程,我们需要对路由进行一些自动化的处理,以提高开发效率。本文将介绍一个基于 Babel 的 nodejs 脚本,用于自动化修改 JavaScript 文件。

准备工作

检查当前仓库是否已经提交了,因为该脚本会直接操作 Git 仓库中的文件,如果有未提交的更改,可能会导致覆盖本地更改。然后获取 router 目录下所有的路由文件。

js 复制代码
async function getAllIndexJsFiles() {
  const result = []
  const routerPath = resolve(__dirname, '../src/router')
  const appDirList = fs.readdirSync(routerPath)
  for (const appName of appDirList) {
    const appDirPath = join(routerPath, appName)
    result.push(...(await foo(appDirPath)))
  }
  return result
}
async function foo(appDirPath, result = []) {
  const appDir = fs.readdirSync(appDirPath)
  for (const file of appDir) {
    const filePath = resolve(appDirPath, file)
    if (file === 'index.js') {
      result.push(resolve(filePath))
    }
    const fileStat = await fs.stat(filePath)
    const isDirectory = fileStat.isDirectory()
    if (isDirectory) {
      await foo(filePath, result)
    }
  }
  return result
}

async function checkGitStatus() {
  const { execa } = await import('execa')
  const { stdout } = await execa('git', ['status', '--short'])
  if (stdout.length === 0) {
    return true
  } else {
    throw (
      '建议在执行此脚本之前,先将本地的更改提交git仓库中。\n' +
      '因为该脚本会直接操作 Git 仓库中的文件,如果有未提交的更改,可能会导致覆盖本地更改。\n\n'
    )
  }
}

Babel处理

tip:如果不了解babel等编译工具的原理,建议看看the-super-tiny-compiler

通过parse的 ast 结果,可以知道 meta 是一个ObjectProperty,并且其中的 value 字段是一个ObjectExpression里面的properties就是 meta 的各个属性。

js 复制代码
const file = path.resolve('./', '对应的路由文件.js')
const fileContext = await fs.readFile(file, 'utf8')
const ast = parse(fileContext, {
  filePath: file,
  sourceType: 'module',
})

得到这个 ast 后,我们就可以利用traverse遍历这个 ast,并进行对应的转换(新增或删除对应的属性)。

js 复制代码
// 新增的属性(route 的 mete 对象)
const addMetaPropertiesMap = {
  isLayoutHide: true,
}
// 删除的属性(route 的 mete 对象)
const deleteMetaPropertiesMap = {
  isSideHide: true,
  isNavHide: true,
  isNavMenuHide: true,
  isMenuHide: true,
}
traverse(ast, {
  ObjectProperty(path) {
    if (path.node.key.name === 'meta') {
      const properties = path.node.value.properties
      Object.keys(addMetaPropertiesMap).forEach((metaKey) => {
        if (!properties.find((i) => i.key.name === metaKey)) {
          isUpdata = true
          // 添加属性
          properties.push({
            type: 'ObjectProperty',
            key: { type: 'Identifier', name: metaKey },
            value: {
              type: 'StringLiteral',
              value: addMetaPropertiesMap[metaKey],
            },
          })
        }
      })
      Object.keys(deleteMetaPropertiesMap).forEach((metaKey) => {
        const index = properties.findIndex((i) => i.key.name === metaKey)
        if (index > -1) {
          const targetItem = properties[index]
          // 删除属性
          if (targetItem.value.value === deleteMetaPropertiesMap[metaKey]) {
            isUpdata = true
            properties.splice(index, 1)
          }
        }
      })
    }
  },
})

转换完成后,通过generate生成新的代码,并使用prettier进行格式化。

js 复制代码
const { code } = generate(ast, {}, '')
fs.writeFileSync(file, code)
prettierFile(file)

async function prettierFile(file) {
  const { execa } = await import('execa')
  await execa('npx', ['prettier', '--write', file])
}

完整代码

js 复制代码
const fs = require('fs-extra')
const { resolve, join } = require('path')
const { parse } = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
const { createSpinner } = require('nanospinner')
// 新增的属性(route 的 mete 对象)
const addMetaPropertiesMap = {
  isLayoutHide: true,
}
// 删除的属性(route 的 mete 对象)
const deleteMetaPropertiesMap = {
  isSideHide: true,
  isNavHide: true,
  isNavMenuHide: true,
  isMenuHide: true,
}
const log = console.log
let chalk = null
let spinner = null
async function main() {
  chalk = (await import('chalk')).default
  spinner = createSpinner('formating route meta').start()
  try {
    await checkGitStatus()
    const allIndexJsFiles = await getAllIndexJsFiles()
    await Promise.all(allIndexJsFiles.map((i) => formatFile(i)))
    spinner.success()
  } catch (e) {
    spinner.error()
    log(chalk.red('\n' + e))
    process.exit(1)
  }
}

async function getAllIndexJsFiles() {
  const result = []
  const routerPath = resolve(__dirname, '../src/router')
  const appDirList = fs.readdirSync(routerPath)
  for (const appName of appDirList) {
    const appDirPath = join(routerPath, appName)
    result.push(...(await foo(appDirPath)))
  }
  return result
}
async function foo(appDirPath, result = []) {
  const appDir = fs.readdirSync(appDirPath)
  for (const file of appDir) {
    const filePath = resolve(appDirPath, file)
    if (file === 'index.js') {
      result.push(resolve(filePath))
    }
    const fileStat = await fs.stat(filePath)
    const isDirectory = fileStat.isDirectory()
    if (isDirectory) {
      await foo(filePath, result)
    }
  }
  return result
}

async function formatFile(file) {
  const fileContext = await fs.readFile(file, 'utf8')
  // 确认是 route的注册函数?
  if (fileContext.includes('meta: {')) {
    // const result = require(fileContext);
    const ast = parse(fileContext, {
      filePath: file,
      sourceType: 'module',
    })
    let isUpdata = false
    traverse(ast, {
      ObjectProperty(path) {
        if (path.node.key.name === 'meta') {
          const properties = path.node.value.properties
          Object.keys(addMetaPropertiesMap).forEach((metaKey) => {
            if (!properties.find((i) => i.key.name === metaKey)) {
              isUpdata = true
              // 添加新的属性
              properties.push({
                type: 'ObjectProperty',
                key: { type: 'Identifier', name: metaKey },
                value: {
                  type: 'StringLiteral',
                  value: addMetaPropertiesMap[metaKey],
                },
              })
            }
          })
          Object.keys(deleteMetaPropertiesMap).forEach((metaKey) => {
            const index = properties.findIndex((i) => i.key.name === metaKey)
            if (index > -1) {
              const targetItem = properties[index]
              if (targetItem.value.value === deleteMetaPropertiesMap[metaKey]) {
                isUpdata = true
                properties.splice(index, 1)
              }
            }
          })
        }
      },
    })
    const { code } = generate(ast, {}, '')
    if (isUpdata) {
      fs.writeFileSync(file, code)
      await prettierFile(file)
      spinner.clear()
      log(chalk.green('prettier format ', file, '\n'))
    }
  }
}

async function prettierFile(file) {
  const { execa } = await import('execa')
  await execa('npx', ['prettier', '--write', file])
}
async function checkGitStatus() {
  const { execa } = await import('execa')
  const { stdout } = await execa('git', ['status', '--short'])
  if (stdout.length === 0) {
    return true
  } else {
    throw (
      '建议在执行此脚本之前,先将本地的更改提交git仓库中。\n' +
      '因为该脚本会直接操作 Git 仓库中的文件,如果有未提交的更改,可能会导致覆盖本地更改。\n'
    )
  }
}
main()
相关推荐
dot.Net安全矩阵7 分钟前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
Hellc00718 分钟前
MacOS升级ruby版本
前端·macos·ruby
前端西瓜哥27 分钟前
贝塞尔曲线算法:求贝塞尔曲线和直线的交点
前端·算法
又写了一天BUG28 分钟前
npm install安装缓慢及npm更换源
前端·npm·node.js
cc蒲公英41 分钟前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel
Java开发追求者42 分钟前
在CSS中换行word-break: break-word和 word-break: break-all区别
前端·css·word
好名字08211 小时前
monorepo基础搭建教程(从0到1 pnpm+monorepo+vue)
前端·javascript
pink大呲花1 小时前
css鼠标常用样式
前端·css·计算机外设
Flying_Fish_roe1 小时前
浏览器的内存回收机制&监控内存泄漏
java·前端·ecmascript·es6
小小竹子1 小时前
前端vue-实现富文本组件
前端·vue.js·富文本