Migrate project to vue cli

Because some projects use self-developed build packages, I wrote a script to batch convert project build tools.

 

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 environment variable copy

The original environment variables are placed in the env folder and are js files. For reuse, they are converted into .env files and a name mapping is done.

Read the original environment variable object, and then write it to the new file in the form of key=value. After the writing is completed, delete the original variable file.

code show as below

/**
 * 复制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());
}

requires attention

  • The newline operation is \r\n on Windows and \n on Linux. We usually use the editor in LF mode and use \n to mark a newline.
  • The fast-glob package is used to quickly traverse file names in a folder

 

In order to solve the problem that the location of the .env file cannot be changed , it can only be placed in the project root directory. Use dotenv to introduce environment variable files, and dotenv-expand to automatically inject variables. In order to combine the variable descriptions introduced by vue cli, multiple variable files need to be loaded, such as .env, .env.local, and .env.environment. code show as below

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)
})

the clays 

lerna facilitates the management of monorepo's common dependencies and can effectively reduce the size of the project's node_modules.

Guess you like

Origin blog.csdn.net/weixin_48408736/article/details/116302351