一部のプロジェクトでは自社開発のビルド パッケージを使用するため、プロジェクトのビルド ツールを一括変換するスクリプトを作成しました。
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());
}
注意が必要です
- 改行操作は Windows では \r\n、Linux では \n です。通常、エディターを LF モードで使用し、改行をマークするために \n を使用します。
- fast-glob パッケージは、フォルダー内のファイル名をすばやく検索するために使用されます。
.env ファイルの場所を変更できないという問題を解決するには、プロジェクトのルート ディレクトリにのみ配置できます。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 は、モノリポジトリの共通の依存関係の管理を容易にし、プロジェクトのノードモジュールのサイズを効果的に削減できます。