1.6.0 • Published 1 year ago

@caolinss/replace v1.6.0

Weekly downloads
-
License
ISC
Repository
-
Last release
1 year ago

@caolinss/replace

一个替换关键词的node工具

多包架构的一个子包,这里做成子包时想如果后面有时间做成vscode插件则子包功能独立,一次实现多路共用。

目的

用node+ts+eslint写一个工具cl-cli 去全局替换其他前端项目中存在的关键词,用tsdx搭建项目。

当想在项目A中替换关键词时,只需要先全局安装npm i -g @caolinss/cli,在A项目根目录运行cl-cli rep 则开始遍历rootDir(默认".")下的文件,并替换满足条件的关键词。

默认配置文件config.ts如下,可以通过配置根目录的.replace.config.json覆盖:

如何使用

1. 全局安装插件

pnpm i -g @caolinss/cli

2. 打开需要替换的项目

cd [project]

3. 创建配置文件.replace.config.json

{
  "white": {
    "你好": "hello",
    "测试": "test",
    "联系人": "i18n('contact')",
    "企业": "i18n('business')",
    "销售机会": "i18n('opportunity')"
  }, 
  "black": ["不替换的联系人", "不替换的企业", "不替换的销售机会"]
}

内部的默认配置(config.base.ts)如下:

type IConfig = {
  rootDir: string, // 以此目录为跟目录替换
  ignores: string[],
  fileTypes: string[],
  importConfig: IImportConfig,
  white: Record<string, any>,
  black: string[],
  createBakFile?: boolean, // 替换是是否生成备份文件
}

// 默认配置:
const baseConfig: IConfig = {
  rootDir: '.',
  ignores: [
    'node_modules/**',
    'dist/**',
    'build/**',
    '.history/**',
    '.husky/**',
    '.vscode/**',
    ".replaceReport/**",
    "**/__test__/**",
    "**/*.test.ts",
    "**/*.spec.ts",
    'src/api/**',
    'src/webapi/**'
  ],
  fileTypes: ['.js', '.ts', '.tsx', '.jsx', '.vue'],
  importConfig: {
    moduleName: '@caolinss/i18n',
    importName: 'i18n',
    url: `import { i18n } from '@caolinss/i18n';`
  },
  white: {
    "你好": 'hello',
    "联系人": "i18n('contact')",
  },
  black: ['不替换的联系人'],
  createBakFile: false,
}

4. 执行

在需要替换的项目根目录执行脚手架指令:

cl-cli rep

实现

1. 先实现一个脚手架工具,子包:@caolinss/cli

脚手架工具,参考:packages/cli/src/index.ts

目的提供全局指令:cl-cli

则替换指令:

cl-cli replace
# 或者
cl-cli rep

2. 实现替换工具的核心,子包:@caolinss/replace

替换工具,参考:packages/replace/src/core/index

import getFiles from './libs/getFiles';

async function main() {
  const { rootDir, ignores, fileTypes, ...args } = replaceConfig;
  const files = await getFiles(rootDir, ignores, fileTypes);

  for (const filePath of files) {
    let content = await fsPromise.readFile(filePath, 'utf-8');
    const ext = path.extname(filePath);

    if (!ext) {
      console.log(`Oops! Just Folder:${filePath}`)
    }
    if (!content) {
      console.log('Not content')
    }

    if(ext === '.vue') {
      // 替换方案试试转 AST 树
      const { 
        templateList, 
        scriptList 
      } = replaceVueFile(content, {filePath, args});
    } else if(['.js', '.ts'].includes(ext)){
      // js和ts的替换方案用正则
      const {repContent, repList} = replaceJSFile(content, filePath, args)
      chalk.cyan(`${filePath} 成功写入`)
      reportData.replaceList = [...reportData.replaceList, ...repList]
      fsPromise.writeFile(filePath, repContent, 'utf-8')
    } else {
      chalk.red(`文件 ${filePath} 不支持转化`)
    }
  }

  const root = process.cwd(); // 获取项目根目录路径
  const outputPath = path.join(root, 'report'); // 拼接报告输出目录路径

  // 如果报告输出目录不存在,则创建它
  if (!fs.existsSync(outputPath)) {
    fs.mkdirSync(outputPath);
  }

  // await generateReport(reportData, '/replace');
}

3. @caolinss/cli 安装依赖 @caolinss/replace

@caolinss/cli 的 package.json:

"dependencies": {
    "@caolinss/replace": "workspace:*",
}

替换的Demo

demo-ts.ts

/**
 * 不替换多行注释的联系人
 */
const demo1 = '普普通通的文本';
const demo2 = '不替换的联系人';
const demo3 = '不替换的企业';

const rep1 = '你好啊';
const rep2 = '你好啊,这是测联系人';
const rep3 = '这是联系人&测试企业';

const bool1 = true;
const rep4 = `字符串${bool1}有联系人替换项和企业替换项`;
const rep5 = `字符串${bool1}无替换项`;
const rep6 = bool1 ? '啊你好': '企业';

// 不替换 单行注释的联系人
const obj = {
  n1: '这是联系人',
  n2: '不替换的联系人'
}
// 不替换 对象的key
const obj2 = {
  "你好": "这种不替换key了,你好",
  "联系人": "这种不替换key2了,你好",
  "企业": "这种不替换key3了, 你好",
}

// 不替换 console.log
console.log('打印里面的联系人啊')

demo-vue2.vue

<template>
  <div class="vue2-test">
    <div>你好</div>
    <div>这是联系人:test1</div>
    <div>这是{{ true ? '联系人': "vue2" }}</div>
  </div>
</template>

<script lang="ts">
import { useRoute } from 'vue-router';
import { ref, reactive, defineComponent } from "vue";

export default defineComponent({
  setup() {
    // vue2:你好,字符串
    const s1 = 'vue3联系人';
    const s2 = ref('不替换的联系人');
    const s3 = '不替换的联系人'; // 黑名单
    const s4 = `你好,我是${s1}`;

    /**
     * vue2:你好,对象
     */ 
    const obj1 = {
      test1: 'obj的联系人',
      test2: '不替换的联系人',
      "1你好": "这种不替换key了,你好",
      "2联系人": "这种不替换key2了,联系人",
      "3企业": "这种不替换key3了, 企业",
    }
    const obj2 = reactive({
      test1: 'obj的联系人',
      test2: '不替换的联系人',
      "11你好": "这种不替换了,你好",
      "22联系人": "这种不替换了,联系人",
      "33企业": "这种不替换了, 企业",
    })

    // 其他
    const demo1 = true ? '你好' : "联系人";
    const demo2 = `测试${s1+'vue2'}模版字符串的联系人`;

    console.log('log你好');
    console.log(s1,s2,s3,s4);
    console.log(obj1, obj2);
    console.log(demo1, demo2);
  },
});
</script>

demo-vue3.vue

<template>
  <div class="vue3-test">
    <h1>你好,</h1>
    <div>test1: 这是联系人</div>
    <div>你好,{{ true ? '联系人': "vue3" }}</div>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive } from 'vue';

// 你好,字符串
const s1 = 'vue3联系人';
const s2 = ref('不替换的联系人');
const s3 = '不替换的联系人'; // 黑名单
const s4 = `你好,我是${s1}`;
/**
 * 你好,对象
 */
const obj1 = {
  test1: 'vue3/obj的联系人',
  test2: '不替换的联系人',
  "1你好": "这种不替换key了,你好",
  "2联系人": "这种不替换key2了,联系人",
  "3企业": "这种不替换key3了, 企业",
}
const obj2 = reactive({
  test1: 'vue3/obj的联系人',
  test2: '不替换的联系人',
  "11你好": "这种不替换了,你好",
  "22联系人": "这种不替换了,联系人",
  "33企业": "这种不替换了, 企业",
})

// 三目
const demo1 = true ? '你好' : "联系人";
const demo2 = `测试${s1+'vue2'}模版字符串的联系人`;

console.log('log你好');
console.log(s1,s2,s3,s4);
console.log(obj1, obj2);
console.log(demo1, demo2);
</script>