迁移项目到vue cli

因为部分项目用的自研构建封装,所以写了一个脚本批量转换项目构建工具。

import shell from 'shelljs';
import getAppDirs from './get-app-dirs';
import errors from '../errors';
const fg = require('fast-glob');
const fs = require('fs');
const path = require('path');

export default async function upgrade2lerna() {
  const repoPath = process.cwd();
  const { valid, invalid } = getAppDirs({ repoPath });
  const dirs = valid;
  invalid.length && console.log(` 这些文件夹被忽略(它们不是规范的应用):${invalid.join(', ')}`);
  if (!dirs.length) {
    console.log(errors.buildRepo.empty());
    return;
  }

  for (let i = 0; i < dirs.length; i++) {
    const appPath = path.join(process.cwd(), dirs[i]);
    const pkg = JSON.parse(fs.readFileSync(path.join(appPath, 'package.json')));
    if (!pkg.devDependencies['@w/XXXX']) {
      console.log(` ${appPath}不是XXXX应用,不做转换`);
      continue;
    }
    try {
      console.log(` ${dirs[i]} 去除 XXXX 依赖`);
      // 去除依赖
      await unInstallXXXX(appPath);
      console.log(` ${dirs[i]} 重写 packjson.json 引入vue cli`);
      // 重写pkg
      reWritePkg(appPath);
      console.log(` ${dirs[i]} 写入 tsconfig.json 配置`);
      // 写入tsconfig
      writeTsconfig(appPath);
      // 写入vueconfig
      console.log(` ${dirs[i]} 写入 vueconfig.js `);
      writeVueConfig(appPath);
      // 复制环境变量
      console.log(` ${dirs[i]} 写入项目目录下的环境变量`);
      copyEnv(appPath);
    } catch (error) {
      process.exit(1);
    }
  }
  console.log(` 进行 lerna 初始化`);
  await installByLerna(repoPath);
  process.exit();
}

/**
 * 卸载XXXX依赖
 * @param appPath 应用根路径
 * @returns Promise
 */
function unInstallXXXX(appPath: string) {
  return new Promise(function (resolve, reject) {
    const command = 'npm uni @w/fy-XXXX-plugin  @w/XXXX';
    const opts = { silent: true, cwd: appPath };
    shell.exec(command, opts, function (code: number, stdout: string, stderr: string) {
      if (code) {
        reject({ code, stdout, stderr });
        return;
      }
      resolve({ code, stdout });
    });
  });
}

/**
 * 修改packjson文件。 引入vue cli服务 改写 script 改写依赖  改写eslint配置
 * @param appPath 应用根路径
 * @returns void
 */
function reWritePkg(appPath: string) {
  const pkg = JSON.parse(fs.readFileSync(path.join(appPath, 'package.json')));
  const addDevDependencies = {
    '@typescript-eslint/eslint-plugin': '^4.18.0',
    '@typescript-eslint/parser': '^4.18.0',
    '@vue/cli-plugin-babel': '~4.5.0',
    '@vue/cli-plugin-eslint': '~4.5.0',
    '@vue/cli-plugin-router': '~4.5.0',
    '@vue/cli-plugin-typescript': '~4.5.0',
    '@vue/cli-plugin-vuex': '~4.5.0',
    '@vue/cli-service': '~4.5.0',
    '@vue/eslint-config-typescript': '^7.0.0',
    '@w/webpack-friday-manifest-plugin': '^0.0.8',
    dotenv: '^8.2.0',
    'dotenv-expand': '^5.1.0',
    eslint: '^6.7.2',
    'eslint-plugin-vue': '^6.2.2',
    less: '^3.0.4',
    'less-loader': '^5.0.0',
    typescript: '~4.1.5',
  };
  pkg.devDependencies = {
    ...pkg.devDependencies,
    ...addDevDependencies,
  };
  const pkgScript = {
    serve: 'vue-cli-service serve',
    build: 'vue-cli-service build',
    'build:test': 'vue-cli-service build --mode test',
    'build:preview': 'vue-cli-service build --mode preview',
    'build:production': 'vue-cli-service build --mode production',
  };
  pkg.scripts = {
    ...pkg.scripts,
    ...pkgScript,
  };
  const eslintConfig = {
    root: true,
    env: {
      node: true,
    },
    extends: ['plugin:vue/essential', 'eslint:recommended', '@vue/typescript/recommended'],
    parserOptions: {
      ecmaVersion: 2020,
    },
    rules: {},
  };
  pkg.eslintConfig = eslintConfig;
  delete pkg.XXXX;
  fs.writeFileSync(path.join(appPath, 'package.json'), JSON.stringify(pkg, null, 2));
}

/**
 * 写入tsconfig文件
 * @param appPath 应用根路径
 * @returns void
 */
function writeTsconfig(appPath: string) {
  const data = {
    compilerOptions: {
      target: 'esnext',
      module: 'esnext',
      strict: true,
      jsx: 'preserve',
      importHelpers: true,
      moduleResolution: 'node',
      experimentalDecorators: true,
      skipLibCheck: true,
      esModuleInterop: true,
      allowSyntheticDefaultImports: true,
      sourceMap: true,
      baseUrl: '.',
      types: ['webpack-env'],
      paths: {
        '@/*': ['src/*'],
      },
      lib: ['esnext', 'dom', 'dom.iterable', 'scripthost'],
    },
    include: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
    exclude: ['node_modules'],
  };
  fs.writeFileSync(path.join(appPath, 'tsconfig.json'), JSON.stringify(data, null, 2));
}

/**
 * 写入vueconfig.js
 * @param appPath 应用根路径
 * @returns void
 */
function writeVueConfig(appPath: string) {
  let ruilangConfig = '';
  try {
    const webpackconfig = require(path.join(appPath, 'webpack.config.js'));
    // 适配报表loader
    const hasRuiLang = webpackconfig.module.rules[0].test.test('x.grf');
    ruilangConfig = hasRuiLang
      ? `
    config.module
      .rule('grf')
      .test(/\.grf$/)
      .use('url-loader')
      .loader('url-loader')
      .end()
  `
      : '';
  } catch (error) {}
  const code = `
const path = require('path')
const manifestPlugin = require('@w/webpack-manifest-plugin')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')

const envList = [
  './env/.env',
  './env/.env.local' ,
  './env/.env.' + process.env.NODE_ENV,
]
envList.forEach((envPath) => {
  const myEnv = dotenv.config({ path: envPath })
  dotenvExpand(myEnv)
})
const { name } = require('./package')
module.exports = {
  lintOnSave: false,
  css: {
    extract: false,
  },
  configureWebpack: (config) => {
    const mode = process.env.NODE_ENV === 'development' ? 'dev' : 'prod'
    config.output.library = name + '-[name]'
    config.output.libraryTarget = 'umd'
    config.output.jsonpFunction = 'webpackJsonp_' + name
    config.output.globalObject = 'window'
    config.output.filename =
      mode === 'dev' ? 'static/js/app.js' : 'static/js/app.[hash].js'

    // 去掉chunk
    config.optimization.splitChunks = {
      cacheGroups: {
        default: false,
        vendors: false,
      },
    }

    config.optimization.runtimeChunk = false
    // 增加Friday mainfest
    config.plugins.push(new manifestPlugin({}))

    config.resolve = {
      extensions: ['.js', '.vue', '.json', '.ts'],
      alias: {
        '@': path.resolve(__dirname, 'src'),
      },
    }
  },
  chainWebpack: (config) => {
    ${ruilangConfig}
  },
  devServer: {
    headers: {
      // Friday: 配置允许跨域请求所有静态资源
      'Access-Control-Allow-Origin': '*',
    },
    // proxy: {
    //   '/api': {
    //     target: '<url>',
    //     changeOrigin: true
    //   }
    // }
  }
}
`;
  fs.writeFileSync(path.join(appPath, 'vue.config.js'), code);
}

/**
 * 复制env文件夹下的环境变量文件
 * @param appPath 应用根路径
 * @returns void
 */
async function copyEnv(appPath: string) {
  type fileName = 'dev.env.js' | 'pre.env.js' | 'prod.env.js' | 'test.env.js' | 'common.js';
  const nameMap = {
    'dev.env.js': '.env.development',
    'pre.env.js': '.env.preview',
    'prod.env.js': '.env.production',
    'test.env.js': '.env.test',
    'common.js': '.env',
  };
  const envPath = path.join(appPath, '/env');
  const files = await fg('**', {
    cwd: envPath,
    dot: true,
    onlyFiles: true,
    ignore: ['node_modules', '.DS_Store'],
  });
  const delFileTask = [];
  for (let i = 0; i < files.length; i++) {
    const fileName: fileName = files[i];
    let data;
    try {
      data = require(path.join(envPath, fileName));
    } catch (error) {
      console.log(error);
      console.log(`  没有读取到变量文件${fileName} 路径是${path.join(envPath, fileName)}`);
      continue;
    }
    if (typeof data === 'function') {
      data = data();
    }
    let fileStr = '';
    Object.entries(data).map(([k, v]) => {
      fileStr += `${k}=${v}\n`;
    });
    const vueEnvName = nameMap[fileName];
    if (!vueEnvName) {
      console.error(`  没有找到对应的环境变量文件 文件名为 ${fileName}`);
      continue;
    } else {
      delFileTask.push(() => {
        fs.unlinkSync(path.join(envPath, fileName));
      });
      fs.writeFileSync(path.join(envPath, vueEnvName), fileStr);
    }
  }
  // 删去原来的变量文件
  delFileTask.forEach((task) => task());
}

/**
 *
 * @param repoPath 项目根路径
 */
async function installByLerna(repoPath: string) {
  await execCmd('npx lerna init');
  // 重写packages 配置
  fs.writeFileSync(
    path.join(repoPath, 'lerna.json'),
    JSON.stringify(
      {
        packages: ['apps/*', 'widgets/*'],
        version: '1.0.0',
      },
      null,
      2,
    ),
  );
  console.log(` 执行 lerna 依赖安装`);
  await execCmd('npx lerna bootstrap --hoist', false);
  /**
   *
   * @param cmd 需要执行的命令
   * @param silent 是否静默执行
   * @returns Promise
   */
  async function execCmd(cmd: string, silent = true) {
    return new Promise(function (resolve, reject) {
      const command = cmd;
      const opts = { silent, cwd: repoPath };
      shell.exec(command, opts, function (code: number, stdout: string, stderr: string) {
        if (code) {
          reject({ code, stdout, stderr });
          return;
        }
        resolve({ code, stdout });
      });
    });
  }
}

Vue cli 环境变量复制

原来的环境变量放在env文件夹,且为js文件, 为了复用,转化为 .env 文件,做了一个名称映射。

读取原有的环境变量对象,然后在按照 key=value的形式,写入新文件,写入完成后再删除原来的变量文件。

代码如下

/**
 * 复制env文件夹下的环境变量文件
 * @param appPath 应用根路径
 * @returns void
 */
async function copyEnv(appPath: string) {
  type fileName = 'dev.env.js' | 'pre.env.js' | 'prod.env.js' | 'test.env.js' | 'common.js';
  const nameMap = {
    'dev.env.js': '.env.development',
    'pre.env.js': '.env.preview',
    'prod.env.js': '.env.production',
    'test.env.js': '.env.test',
    'common.js': '.env',
  };
  const envPath = path.join(appPath, '/env');
  const files = await fg('**', {
    cwd: envPath,
    dot: true,
    onlyFiles: true,
    ignore: ['node_modules', '.DS_Store'],
  });
  const delFileTask = [];
  for (let i = 0; i < files.length; i++) {
    const fileName: fileName = files[i];
    let data;
    try {
      data = require(path.join(envPath, fileName));
    } catch (error) {
      console.log(error);
      console.log(`  没有读取到变量文件${fileName} 路径是${path.join(envPath, fileName)}`);
      continue;
    }
    if (typeof data === 'function') {
      data = data();
    }
    let fileStr = '';
    Object.entries(data).map(([k, v]) => {
      fileStr += `${k}=${v}\n`;
    });
    const vueEnvName = nameMap[fileName];
    if (!vueEnvName) {
      console.error(`  没有找到对应的环境变量文件 文件名为 ${fileName}`);
      continue;
    } else {
      delFileTask.push(() => {
        fs.unlinkSync(path.join(envPath, fileName));
      });
      fs.writeFileSync(path.join(envPath, vueEnvName), fileStr);
    }
  }
  // 删去原来的变量文件
  delFileTask.forEach((task) => task());
}

需要注意

  • 换行的操作,在window上是 \r\n ,在Linux 上是 \n。 我们平常使用编辑器一般都是 采用LF 模式,使用 \n标识换行。
  • fast-glob包用与快速遍历文件夹下的文件名

为了解决 .env 文件位置无法改变的问题 issues,只能放在 项目根目录的情况。 使用 dotenv 引入环境变量文件,dotenv-expand 自动注入变量。 为了复合vue cli 的引入变量描述,还需要加载多个变量文件,如 .env 、 .env.local 、 .env.environment。 代码如下

const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')

const envList = [
  './env/.env',
  './env/.env.local' ,
  './env/.env.' + process.env.NODE_ENV,
]
envList.forEach((envPath) => {
  const myEnv = dotenv.config({ path: envPath })
  dotenvExpand(myEnv)
})

lerna 

lerna 方便管理 monorepo 的共同依赖, 可以有效减小项目的 node_modules 体积。

猜你喜欢

转载自blog.csdn.net/weixin_48408736/article/details/116302351
今日推荐