uniapp多环境多客户构建
·
目录
继微信原生小程序的一次gulp构建后,需要来个uniapp迁移,于是搞它。
一、项目目录介绍
源码目录
├── build
│ ├── pagesJsonConfigs
│ ├── pageUrlConfigs
│ └── env.config.json
├── config
│ └── constants.js
├── .gitignore
├── envPlugin.js
├── package.json
└── tsconfig.json
- build:用于存放构建相关的配置文件,是前端工程化的重要部分,里面的子目录和文件会在项目构建过程中发挥作用,以生成符合小程序要求的代码或资源。
- pagesJsonConfigs:存放微信小程序pages.json文件,这里可以根据环境量选择不同的pages.json。
- pageUrlConfigs:这个目录用于存放小程序路由map文件
{key:路径},这个文件不是必须的,但有了它,便于管理页面跳转地址。 - env.config.json:环境配置文件,可根据不同的运行环境(如开发环境、生产环境)配置不同的参数,像接口地址、调试开关等,实现项目在不同环境下的灵活切换。
- envPlugin.js:vite环境量插件。
- package.json:项目的配置文件,包含了项目的基本信息(如名称、版本)、依赖包列表、脚本命令等,是 npm 管理项目的核心文件。
- vite.config.js: vite构建配置
二、目录文件介绍
2.1 build/pagesJsonConfigs
- index.json 配置索引文件
{
"customer1": "customer1_pages.json" // 环境:配置文件名
}
- default_pages.json 默认的小程序pages.json文件配置
- customer1_pages.json 环境量customer1对应的pages.json配置文件,当环境量是customer1时,选取此文件作为pages.json文件
2.2 build/pageUrlConfigs
- index.json配置索引文件
{
"customer1": "customer1_pageUrl.js" // 环境:配置文件名
}
- default_pageUrl.js 默认的小程序pageUrl.js文件配置
export default {
HOME: '/pages/index/index',
LOG: '/pages/logs/logs'
}
- customer1_pageUrl.js环境量customer1对应的pageUrl.js配置文件,当环境量是customer1时,选取此文件作为pageUrl.js文件
注意
以上index.json索引文件的key还可以加上运行平台标志,比如alipay、weixin,然后在envPlugin中修改读取key的逻辑
{
"alipay:customer1": "alipay_customer1_***.js", // 环境:配置文件名
"weixin:customer1": "weixin_customer1_***.js"
}
2.3 build/env.config.json
环境量配置文件
{
"common": {
"PAGE_TITLE": "测试项目"
},
"env": {
"customer1": {
"dev": {
"API_URL": "http://test.com.cn/customer1"
},
"prod": {
"API_URL": "http://prod.com.cn/customer1"
}
},
"customer2": {
"dev": {
"API_URL": "http://test.com.cn/customer2"
},
"prod": {
"API_URL": "http://prod.com.cn/customer2"
}
}
}
}
环境量customer1对应的dev环境量就是
{
"API_URL": "http://test.com.cn/customer1"
}
2.6 package.json
{
"name": "uniapp-vue3",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"uni-app": {
"scripts": {
"weixin:dev:customer1": { "title": "customer1(wx:dev)", "env": { "UNI_PLATFORM": "mp-weixin" }},
"alipay:dev:customer1": { "title": "customer1(alipay:dev)", "env": { "UNI_PLATFORM": "mp-alipay" }}
}
}
}
启动的时候在运行在菜单中,找到title对应的命令就行
三、envPlugin.js vite插件介绍
const fs = require('fs')
const fsPromises = fs.promises;
const path = require('path')
function envPlugin(options = {}) {
// 配置文件路径
const pathConfig = {
pagesJsonConfigPath: options.pagesJsonConfigPath || './build/pagesJsonConfigs',
pageUrlConfigPath: options.pageUrlConfigPath || './build/pageUrlConfigs',
envConfigPath: options.envConfigPath || './build/env.config.json',
outputPagesJsonPath: options.outputPagesJsonPath || './pages.json',
outputPageUrlPath: options.outputPageUrlPath || './config/pageUrl.js',
outputEnvPath: options.outputEnvPath || './config/envConfig.js',
}
// 定位项目根目录(通过 package.json 确保准确性)
function getProjectRoot() {
let currentDir = __dirname;
while (currentDir !== path.dirname(currentDir)) {
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
return currentDir;
}
currentDir = path.dirname(currentDir);
}
return __dirname; // 兜底
}
const projectRoot = getProjectRoot();
// 解析环境标识
function parseEnvName(envName) {
const [platform, envType, client] = envName.split(':')
return { platform, envType, client }
}
// 检查输出目录是否存在,不存在就创建
function ensureOutputDirs(outputPaths) {
outputPaths.forEach(pathStr => {
// 基于项目根目录解析路径
const outputDir = path.dirname(path.resolve(projectRoot, pathStr));
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
console.log(`创建目录: ${outputDir}`);
}
});
}
// 基于项目根目录解析路径
function resolvePath(filePath) {
const resolvedPath = path.resolve(projectRoot, filePath);
return resolvedPath;
}
// 读取文件(改进:使用fsPromises并确保读取最新内容)
async function readFile(filePath, needParse = false) {
try {
const fullPath = resolvePath(filePath);
// 检查文件是否存在
await fsPromises.access(fullPath);
// 读取文件内容,确保获取最新内容
const content = await fsPromises.readFile(fullPath, 'utf-8');
return needParse ? JSON.parse(content) : content;
} catch (error) {
throw new Error(`解析文件失败 ${filePath}: ${error.message}`);
}
}
// 生成envConfig配置文件
async function generateEnvConfig(platform, envType, client) {
try {
ensureOutputDirs([pathConfig.outputEnvPath]);
// ------------读取env配置源文件------------
const envConfig = await readFile(resolvePath(pathConfig.envConfigPath), true);
const envData = envConfig.env?.[client]?.[envType];
const commonEnvData = envConfig['common'] || {};
if (!envData) {
throw new Error(`${pathConfig.envConfigPath} 中未找到 env:${client} -- ${envType} 配置`);
}
// 生成envConfig.js
const envContent = `
// 构建时自动生成,请勿修改!
export default ${JSON.stringify({
...envData,
CUSTOMER_CODE: client,
ENV: envType,
...commonEnvData
}, null, 2)}
`.trim();
await fsPromises.writeFile(resolvePath(pathConfig.outputEnvPath), envContent, 'utf-8');
console.log(`${pathConfig.outputEnvPath}配置已更新: ${platform}:${envType}:${client}`);
return true;
} catch (error) {
console.error('配置文件生成失败:', error.message);
return false;
}
}
// 生成pages.json配置文件
async function generatePagesJson(platform, envType, client) {
try {
ensureOutputDirs([pathConfig.outputPagesJsonPath]);
// -------------读取pages.json配置源文件-------------------------
const pageConfigPath = resolvePath(`${pathConfig.pagesJsonConfigPath}/index.json`);
const pageConfig = await readFile(pageConfigPath, true);
const pagesDataFileName = pageConfig[`${platform}:${client}`] || 'default_pages.json';
const pagesDataPath = resolvePath(`${pathConfig.pagesJsonConfigPath}/${pagesDataFileName}`);
// 增加日志确认使用的pages配置文件
console.log(`使用页面配置文件: ${pagesDataPath}`);
const pagesContent = await readFile(pagesDataPath);
// 生成pages.json
const outputPagesPath = resolvePath(pathConfig.outputPagesJsonPath);
await fsPromises.writeFile(outputPagesPath, pagesContent, 'utf-8');
// 验证写入内容
const writtenContent = await fsPromises.readFile(outputPagesPath, 'utf-8');
if (writtenContent !== pagesContent) {
console.warn(`页面配置写入不一致: ${outputPagesPath}`);
}
console.log(`${pathConfig.outputPagesJsonPath}配置已更新: ${platform}:${envType}:${client}`);
return true;
} catch (error) {
console.error('配置文件生成失败:', error.message);
return false;
}
}
// 生成pageUrl配置文件
async function generatePageUrl(platform, envType, client) {
try {
ensureOutputDirs([pathConfig.outputPageUrlPath]);
// -------------读取pageUrl.js配置源文件-------------
const pageUrlConfig = await readFile(resolvePath(`${pathConfig.pageUrlConfigPath}/index.json`), true);
const pageUrlFileName = pageUrlConfig[`${platform}:${client}`] || 'default_pageUrl.js';
const pageUrlData = await readFile(`${pathConfig.pageUrlConfigPath}/${pageUrlFileName}`);
// 生成pageUrl.js
const pageUrlContent = `
// 构建时自动生成,请勿修改!
${pageUrlData}
`.trim();
await fsPromises.writeFile(resolvePath(pathConfig.outputPageUrlPath), pageUrlContent, 'utf-8');
console.log(`${pathConfig.outputPageUrlPath}配置已更新: ${platform}:${envType}:${client}`);
return true;
} catch (error) {
console.error('配置文件生成失败:', error.message);
return false;
}
}
// 记录已监听的目录,避免重复监听
const watchedDirs = new Set();
// 平台判断
const isWindows = process.platform === 'win32';
const isMac = process.platform === 'darwin';
function watchDirectory(targetPath) {
// 标准化路径,解决不同平台路径格式问题
const normalizedPath = path.normalize(targetPath);
// 避免重复监听同一目录
if (watchedDirs.has(normalizedPath)) {
return;
}
watchedDirs.add(normalizedPath);
// 监听当前目录
const watcher = fs.watch(normalizedPath, {
recursive: false,
encoding: 'utf8'
}, async (eventType, filename) => {
if (!filename) {
return;
}
// 处理不同平台的事件类型差异
let actualEventType = eventType;
// Windows下的rename事件可能包含change
if (isWindows && eventType === 'rename') {
// 检查文件是否存在来判断是新增/删除还是重命名
try {
await fs.promises.access(path.join(normalizedPath, filename));
actualEventType = 'change';
} catch {
actualEventType = 'unlink';
}
}
// 处理macOS的特殊情况
if (isMac && eventType === 'rename') {
// macOS下rename事件可能需要额外处理
actualEventType = 'change';
}
const fullPath = path.join(normalizedPath, filename);
try {
// 获取文件状态判断是文件还是目录
const stats = await fs.promises.stat(fullPath);
if (stats.isDirectory()) {
// 如果是目录且有变化,重新监听子目录
watchDirectory(fullPath);
} else if (actualEventType === 'change') {
await updateFile(fullPath);
}
} catch (err) {
// 文件可能已被删除,忽略错误
if (err.code !== 'ENOENT') {
console.error('Watch error:', err);
}
}
});
// 监听错误事件
watcher.on('error', (err) => {
console.error(`Watcher error for ${normalizedPath}:`, err);
// 出错时移除监听记录,允许重新监听
watchedDirs.delete(normalizedPath);
});
// 初始递归监听子目录
try {
const dirents = fs.readdirSync(normalizedPath, { withFileTypes: true });
dirents.forEach(dirent => {
if (dirent.isDirectory()) {
const subDir = path.join(normalizedPath, dirent.name);
watchDirectory(subDir);
}
});
} catch (err) {
console.error(`Failed to read directory ${normalizedPath}:`, err);
watchedDirs.delete(normalizedPath);
}
return watcher;
}
let debounceTimer = null; // 定时器存储到状态对象中
async function updateFile(target) {
// 防抖计时器ID(使用闭包存储)
if (debounceTimer) {
clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(async () => {
const envName = process.env.UNI_SCRIPT;
const { platform, envType, client } = parseEnvName(envName);
// 检查文件属于哪种配置类型,并执行对应生成方法
if (target.includes('env.config.json')) {
await generateEnvConfig(platform, envType, client);
} else if (target.includes('pagesJsonConfigs')) {
await generatePagesJson(platform, envType, client);
} else if (target.includes('pageUrlConfigs')) {
await generatePageUrl(platform, envType, client);
}
// 执行完毕后清除计时器
debounceTimer = null;
}, 300); // 防抖延迟时间,单位ms
}
let hasWatcher = false;
return {
name: 'vite-env-plugin',
// 在配置解析前执行
async config() {
const envName = process.env.UNI_SCRIPT;
if (envName) {
console.log('开始生成配置(UniApp编译前)...');
const { platform, envType, client } = parseEnvName(envName);
await generateEnvConfig(platform, envType, client);
await generatePagesJson(platform, envType, client);
await generatePageUrl(platform, envType, client);
console.log('配置生成完成,即将启动UniApp编译流程');
} else {
console.warn('未设置UNI_SCRIPT环境变量,跳过配置生成');
}
},
// 在构建开始时启动监听(小程序运行时会触发)
async buildStart() {
const envName = process.env.UNI_SCRIPT;
if (!envName) return;
if (!hasWatcher) {
hasWatcher = true
const target = resolvePath('./build');
if (fs.existsSync(target)) {
console.log('开始监听./build');
watchDirectory(target);
}
}
},
}
}
module.exports = envPlugin;
四、vite.config.js介绍
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import envPlugin from './envPlugin.js'
export default defineConfig(() => {
return {
plugins: [ // 注意envPlugin一定放在uni前面,先执行环境量相关配置,再执行原本的uniapp运行步骤
envPlugin(), uni(),
],
define: {
'process.env': process.env
},
};
});
更多推荐
所有评论(0)