Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 122 additions & 135 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,188 +1,175 @@
import { promisify } from 'util';
import { exec } from 'child_process';
import fs, { mkdirSync } from 'fs';
import { cp } from 'fs/promises';
import path from 'path';
import fse from 'fs-extra';

/** 异步 exec 命令 */
const execAsync = promisify(exec);
/** 异步 cp 命令 */
const cpAsync = promisify(cp);

/**
* 匹配文件路径 vite.config.js/ts farm.config.js/ts
* @param root 根目录
* @param reg 匹配规则
* @return 返回路径
*/
const filePathReg = (root: string, reg: RegExp) => {
// 获取根目录下的文件夹名称
const dirs = fs.readdirSync(root, { withFileTypes: true })
.map(dirent => dirent.name);
return dirs.filter(dir => reg.test(dir)).map(dir => path.join(root, dir));
/** 模板配置类型 */
interface TemplateConfig {
name: string;
description: string;
type: string;
buildToolType: string;
}
/**
* 匹配文件名称 vite.config.js/ts farm.config.js/ts

/** 获取目录下的文件夹列表
* @param root 根目录
* @param reg 匹配规则
* @return 返回文件名
* @param fullPath 是否返回完整路径
* @return 返回文件夹名或路径数组
*/
const fileNameReg = (root: string, reg: RegExp, isPath = false) => {
// 获取根目录下的文件夹名称
const getMatchedDirs = (root: string, reg: RegExp, fullPath = false): string[] => {
const dirs = fs.readdirSync(root, { withFileTypes: true })
.map(dirent => dirent.name);
return dirs.filter(dir => reg.test(dir));
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
.filter(dirName => reg.test(dirName));
return fullPath ? dirs.map(dir => path.join(root, dir)) : dirs;
}

type GetNewConfigFileFn = (readConfigFile: string, template: string) => string;

/**
* 处理重写文件(添加path) vite.config.js/ts farm.config.js/ts
/** 模板类型配置 */
const TEMPLATE_CONFIGS: { vite: RegExp; farm: RegExp; webpack: RegExp } = {
vite: /^template-vite/,
farm: /^template-farm/,
webpack: /^template-webpack/,
};

/** 工具函数:统一替换配置 */
const applyReplacementRules = (content: string, rules: { mate: string; sub: string }[]): string => {
let result = content;
for (const rule of rules) {
if (result.includes(rule.mate)) {
result = result.replace(rule.mate, rule.sub);
}
}
return result;
};

/** 处理重写文件(添加path) vite.config.js/ts farm.config.js/ts
* @param configReg 匹配规则
* @param reg 匹配规则
* @param getNewConfigFile 获取新的config.* 文件
*/
const configFilesReg = async (configReg: RegExp, reg: RegExp, getNewConfigFile: GetNewConfigFileFn) => {
const cwd = process.cwd();
const templates = fileNameReg(cwd, configReg);
const templates = getMatchedDirs(cwd, configReg);
console.log(`找到 ${templates.length} 个模板: ${templates.join(', ')}`);

templates.forEach(async (template) => {
// 特殊处理 template-webpack-react 使用craco重写 create-react-app 配置
for (const template of templates) {
console.log(`\n========== 开始处理模板: ${template} ==========`);

// 特殊处理 template-webpack-react 使用 webpack 配置
if (template === 'template-webpack-react') {
const cracoConfig = `
const path = require('path');
const fs = require('fs');

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
webpack: {
configure: (webpackConfig, { env, paths }) => {
paths.appBuild = webpackConfig.output.path = path.resolve(__dirname, 'dist');
webpackConfig.output.publicPath = resolveApp('/template-webpack-react')+ '/';
return webpackConfig;
},
},
};
`
const cracoConfigPath = path.join(cwd, template, 'craco.config.js');
fs.writeFileSync(cracoConfigPath, cracoConfig);
await execAsync(`pnpm i -D @types/node && pnpm i -D @craco/craco`, { cwd: path.join(process.cwd(), template) });
const packageJsonPath = path.join(cwd, template, 'package.json');
fs.writeFileSync(packageJsonPath, fs.readFileSync(packageJsonPath, 'utf-8').replace('react-scripts build', 'craco build'));

// 处理 PUBLIC_URL 在构建的过程中因实际的项目路径在 /template-webpack-react/ 下,所以需要修改 PUBLIC_URL 的值
const envPath = path.join(cwd, template, ".env");
fs.writeFileSync(envPath, `PUBLIC_URL="/template-webpack-react/"`);


// 处理 create-react-app需要 .eslintrc.cjs的问题 删除 .eslintrc.js
const eslintrcPath = path.join(cwd, '.eslintrc.js');
if (fs.existsSync(eslintrcPath)) {
fs.unlinkSync(eslintrcPath);
const webpackConfigPath = path.join(cwd, template, 'webpack.config.js');
if (fs.existsSync(webpackConfigPath)) {
let webpackConfig = fs.readFileSync(webpackConfigPath, 'utf-8');
// 添加 publicPath 配置
webpackConfig = webpackConfig.replace(
/publicPath:\s*['"]\/['"]/,
`publicPath: '/${template}/'`
);
fs.writeFileSync(webpackConfigPath, webpackConfig);
console.log(`webpack.config.js 已更新`);
}
}

// 匹配config.* 文件
const configFilePath = filePathReg(path.join(cwd, template), reg)?.[0];
const templateDir = path.join(cwd, template);
const configFilePath = getMatchedDirs(templateDir, reg, true)?.[0];
console.log(`配置文件路径: ${configFilePath || '未找到'}`);

if (configFilePath) {
// 重写config.* 文件
const readConfigFile = fs.readFileSync(configFilePath, 'utf-8');
const newConfigFile = getNewConfigFile(readConfigFile, template);
fs.writeFileSync(configFilePath, newConfigFile);
console.log(`配置文件已更新`);
}

await execAsync(`pnpm install && pnpm run build`, { cwd: path.join(process.cwd(), template) });
console.log(`开始安装依赖并构建...`);
try {
await execAsync(`pnpm install && pnpm run build`, { cwd: templateDir });
console.log(`构建完成`);
} catch (buildError) {
console.error(`构建失败: ${buildError}`);
continue;
}

// 拷贝dist文件夹到根目录并且重命名
const distFilePath = path.join(process.cwd(), template, 'dist');
const newDistFilePath = path.join(process.cwd(), 'dist', template);
await cpAsync(distFilePath, newDistFilePath, { recursive: true });
});
// webpack-react 使用 build 目录
const outputDir = template === 'template-webpack-react' ? 'build' : 'dist';
const distFilePath = path.join(cwd, template, outputDir);
const newDistFilePath = path.join(cwd, 'dist', template);
console.log(`准备拷贝 ${outputDir}: ${distFilePath} -> ${newDistFilePath}`);

if (!fs.existsSync(distFilePath)) {
console.error(`${outputDir} 目录不存在: ${distFilePath}`);
continue;
}

try {
await fse.copy(distFilePath, newDistFilePath);
console.log(`dist 目录已拷贝到 ${newDistFilePath}`);
} catch (copyError) {
console.error(`拷贝 dist 目录失败: ${copyError}`);
continue;
}
console.log(`========== 模板 ${template} 处理完成 ==========\n`);
}
console.log(`configFilesReg 完成`);
}

const initTemplates = async (templates: { name: string, description: string, type: string, buildToolType: string }[]) => {
const initTemplates = async (templates: TemplateConfig[]) => {
for (const template of templates) {
await execAsync(`pnpm run dev init ${template.name} --description ${template.description} --type ${template.type} --template lite --buildToolType ${template.buildToolType}`);
await execAsync(
`node ./bin/index.js init ${template.name} --description "${template.description}" --type ${template.type} --template lite --buildToolType ${template.buildToolType}`
);
}
}
};

/** 预定义模板列表 */
const TEMPLATES: TemplateConfig[] = [
{ name: 'template-vite-vue3', description: '这是一个vite构建的vue3项目', type: 'vue3', buildToolType: 'vite' },
{ name: 'template-vite-vue2', description: '这是一个vite构建的vue2项目', type: 'vue2', buildToolType: 'vite' },
{ name: 'template-vite-react', description: '这是一个vite构建的react项目', type: 'react', buildToolType: 'vite' },
{ name: 'template-farm-vue3', description: '这是一个farm构建的vue3项目', type: 'vue3', buildToolType: 'farm' },
{ name: 'template-farm-vue2', description: '这是一个farm构建的vue2项目', type: 'vue2', buildToolType: 'farm' },
{ name: 'template-farm-react', description: '这是一个farm构建的react项目', type: 'react', buildToolType: 'farm' },
{ name: 'template-webpack-vue3', description: '这是一个webpack构建的vue3项目', type: 'vue3', buildToolType: 'webpack' },
{ name: 'template-webpack-vue2', description: '这是一个webpack构建的vue2项目', type: 'vue2', buildToolType: 'webpack' },
{ name: 'template-webpack-react', description: '这是一个webpack构建的react项目', type: 'react', buildToolType: 'webpack' },
];

const preview = async () => {
try {
const templates = [
{ name: 'template-vite-vue3', description: '这是一个vite构建的vue3项目', type: 'vue3', buildToolType: 'vite' },
{ name: 'template-vite-vue2', description: '这是一个vite构建的vue2项目', type: 'vue2', buildToolType: 'vite' },
{ name: 'template-vite-react', description: '这是一个vite构建的react项目', type: 'react', buildToolType: 'vite' },
{ name: 'template-farm-vue3', description: '这是一个farm构建的vue3项目', type: 'vue3', buildToolType: 'farm' },
{ name: 'template-farm-vue2', description: '这是一个farm构建的vue2项目', type: 'vue2', buildToolType: 'farm' },
{ name: 'template-farm-react', description: '这是一个farm构建的react项目', type: 'react', buildToolType: 'farm' },
{ name: 'template-webpack-vue3', description: '这是一个webpack构建的vue3项目', type: 'vue3', buildToolType: 'webpack' },
{ name: 'template-webpack-vue2', description: '这是一个webpack构建的vue2项目', type: 'vue2', buildToolType: 'webpack' },
{ name: 'template-webpack-react', description: '这是一个webpack构建的react项目', type: 'react', buildToolType: 'webpack' },
];

await initTemplates(templates);

// vite 模版重写
const generateViteConfig = (readConfigFile: string, template: string) => {
const replacementRules = [
{
mate: 'defineConfig({',
sub: `defineConfig({\n base: '/${template}',`
},
{
mate: 'export default {',
sub: `export default {\n base: '/${template}',`
}
]

replacementRules.map(replacementRule => {
if (readConfigFile.includes(replacementRule.mate)) {
readConfigFile = readConfigFile.replace(replacementRule.mate, replacementRule.sub);
}
});
return readConfigFile;
}
// 创建 dist 目录
mkdirSync('dist', { recursive: true });

// farm 模版重写
const generateFarmConfig = (readConfigFile: string, template: string) => {
const replacementRules = [
{
mate: 'defineConfig({',
sub: `defineConfig({ \n compilation: {\n output: {\n publicPath: '/${template}',\n },\n },\n`
}
]

replacementRules.map(replacementRule => {
if (readConfigFile.includes(replacementRule.mate)) {
readConfigFile = readConfigFile.replace(replacementRule.mate, replacementRule.sub);
}
});
return readConfigFile;
}
await initTemplates(TEMPLATES);

// webpack 模版重写
const generateWebpackConfig = (readConfigFile: string, template: string) => {
const replacementRules = [
{
mate: 'module.exports = {',
sub: `module.exports = {\n publicPath: '/${template}',\n`
}
]

replacementRules.map(replacementRule => {
if (readConfigFile.includes(replacementRule.mate)) {
readConfigFile = readConfigFile.replace(replacementRule.mate, replacementRule.sub);
}
});
return readConfigFile;
}
// vite 模版重写 - 使用统一替换函数
const generateViteConfig = (readConfigFile: string, template: string) => applyReplacementRules(readConfigFile, [
{ mate: 'defineConfig({', sub: `defineConfig({\n base: '/${template}',` },
{ mate: 'export default {', sub: `export default {\n base: '/${template}',` },
]);

await configFilesReg(/^template-vite/, /^vite.config.*/, generateViteConfig);
await configFilesReg(/^template-farm/, /^farm.config.*/, generateFarmConfig);
await configFilesReg(/^template-webpack/, /^vue.config.*/, generateWebpackConfig);
// farm 模版重写
const generateFarmConfig = (readConfigFile: string, template: string) => applyReplacementRules(readConfigFile, [
{ mate: 'defineConfig({', sub: `defineConfig({ \n compilation: {\n output: {\n publicPath: '/${template}/',\n },\n },\n` },
]);

// webpack 模版重写
const generateWebpackConfig = (readConfigFile: string, template: string) => applyReplacementRules(readConfigFile, [
{ mate: 'module.exports = {', sub: `module.exports = {\n publicPath: '/${template}',\n` },
]);

await configFilesReg(TEMPLATE_CONFIGS.vite, /^vite.config.*/, generateViteConfig);
await configFilesReg(TEMPLATE_CONFIGS.farm, /^farm.config.*/, generateFarmConfig);
await configFilesReg(TEMPLATE_CONFIGS.webpack, /^vue.config.*/, generateWebpackConfig);
} catch (e) {
console.error(e);
}
Expand Down
18 changes: 9 additions & 9 deletions templates/farm/react-lite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
"dependencies": {
"tdesign-icons-react": "latest",
"tdesign-react": "latest",
"react": "18",
"react-dom": "18"
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@farmfe/cli": "^1.0.1",
"@farmfe/core": "^1.1.1",
"@farmfe/plugin-react": "^1.0.1",
"@types/react": "18",
"@types/react-dom": "18",
"react-refresh": "^0.14.0"
"@farmfe/cli": "^1.0.5",
"@farmfe/core": "^1.7.11",
"@farmfe/plugin-react": "^1.2.6",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"react-refresh": "^0.18.0"
}
}
}
10 changes: 5 additions & 5 deletions templates/farm/vue-lite/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "vue-next-lite",
"name": "vue-lite",
"version": "1.0.0",
"type": "module",
"dependencies": {
Expand All @@ -8,9 +8,9 @@
"vue": "~2.6.14"
},
"devDependencies": {
"@farmfe/cli": "^1.0.2",
"@farmfe/core": "^1.1.4",
"@tangllty/vite-plugin-svg": "^1.0.0",
"@farmfe/cli": "^1.0.5",
"@farmfe/core": "^1.7.11",
"@tangllty/vite-plugin-svg": "^1.0.5",
"vite-plugin-vue2": "^2.0.3",
"vue-template-compiler": "~2.6.14"
},
Expand All @@ -21,4 +21,4 @@
"preview": "farm preview",
"clean": "farm clean"
}
}
}
6 changes: 2 additions & 4 deletions templates/farm/vue-next-lite/farm.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { defineConfig } from '@farmfe/core';
import vue from '@vitejs/plugin-vue';
import vue from '@farmfe/js-plugin-vue';

export default defineConfig({
vitePlugins: [
vue(),
]
plugins: [vue()]
});
14 changes: 8 additions & 6 deletions templates/farm/vue-next-lite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@
"clean": "farm clean"
},
"dependencies": {
"core-js": "^3.48.0",
"tdesign-icons-vue-next": "latest",
"tdesign-vue-next": "latest",
"vite-svg-loader": "^5.1.0",
"vue": "^3.4.21"
"vue": "^3.5.29"
},
"devDependencies": {
"@farmfe/cli": "^1.0.2",
"@farmfe/core": "^1.1.4",
"@vitejs/plugin-vue": "^5.0.4",
"typescript": "^5.4.4",
"vue-tsc": "^2.0.10"
"@farmfe/cli": "^1.0.5",
"@farmfe/core": "^1.7.11",
"@farmfe/js-plugin-vue": "^3.13.0",
"@jridgewell/gen-mapping": "^0.3.2",
"typescript": "^5.9.3",
"vue-tsc": "^3.2.5"
}
}
Loading