/* eslint-disable no-eval */ /* eslint-disable import/extensions */ /* eslint-disable @typescript-eslint/no-var-requires */ import { PageContext, SubPageMetaDatum, PageMetaDatum } from '@uni-helper/vite-plugin-uni-pages' import { Plugin } from 'vite' import enums from './src/enums/index' const common = require('./scripts/common.js') const { files, cl } = common const config = { /** 打包路径 */ mpBuildPath: '', /** 原始路径 */ rootPath: '', env: process.env, } const convert = { /** 已整体瘦身 */ slimed: false, /** 瘦身过的文件信息 */ slimeds: {}, /** 引用接口所有文件信息 */ alleics: [] as any[], /** 瘦身接口配置信息 */ slimInterfaceConfig({ filePath = '' } = {}) { // 路径获取失败 跳出 if (!(config.mpBuildPath && config.rootPath)) return /** 打包路径 */ const mpBuildPath = config.mpBuildPath /** 原始路径 */ const rootPath = config.rootPath // 接口配置目录 const interfacePath = 'utils/config/interFaces' // const path = 'D:\\cl\\work\\pro\\mobile\\dist\\build\\mp-weixin' // console.log(path, pfs) // 所有接口配置信息文件路径 const ifs = files.path.resolve(rootPath, interfacePath) const iffs = files.getAllFile(ifs) // .filter((f) => f.includes(`utils\\config\\interFaces`)) // 接口信息内容 const ifcontents = iffs .filter((f) => !['index.ts'].some((s) => f.includes(s))) .map((m) => { let fsc = files.read(m) fsc = fsc // 删除realUrl后面的配置 // .replace(/(realUrl.+),(?:(?!realUrl)[\s\S])+},/g, '$1},') // 删除reqType、resType配置信息 .replace(/reqType(?:(?!,\n)[\s\S])+,\n/g, '') .replace(/resType(?:(?!,\n)[\s\S])+,\n/g, '') .replace(/\n/g, '') .replaceAll(' as const', '') // const fscm = fsc.match(/^(.+\.)(.+)=(.+)(;+\s*)*$/) // 反序列化最外层的对象 const fscm = fsc.match(/\{[\s\S]+\}/) const groupName = files.basename(m, false) let gifs = {} if (fscm && fscm.length === 1) { try { gifs = (0, eval)(`(${fscm[0]})`) } catch (e) { cl.loge(m, e) } } return { /** 原始文件路径 */ filePath: m, /** 打包后的文件路径 */ buildFilePath: m.replace(rootPath, mpBuildPath).replace('.ts', '.js'), /** 分组名称 */ groupName, /** 分组内的接口信息 */ gifs, } }) // 排除接口配置信息之外的所有js文件内容 /** 引用接口所有文件内容 */ let alleiConts = '' // 指定某个文件变化,替换公共存储内的本文件内容 if (filePath) { cl.logs(`变化来源:${filePath}`) const filePathEnd = filePath.replace(rootPath, '') const curEic = convert.alleics.find( (f) => f.filePath.replace(mpBuildPath, '').replace(/\..*$/, '') === filePathEnd.replace(/\..*$/, ''), ) if (curEic) { curEic.content = files.read(filePath) } // 整体瘦身时 } else { const pfs = files.getAllFile(mpBuildPath) convert.alleics = pfs .filter( (f) => f.endsWith('.js') && !f.includes('node-modules') && !f.includes(interfacePath), ) .map((m) => { return { filePath: m, content: files.read(m), } }) } alleiConts = convert.alleics.map((m) => m.content).join('') alleiConts = alleiConts.replaceAll(' ', '').replaceAll('\n', '').replaceAll('\r', '') // 删除未用到的接口配置信息 ifcontents.forEach((f) => { const curGifs = f.gifs Object.keys(f.gifs).forEach((fk) => { // 使用痕迹 const UsageTraces = [ `${f.groupName}.${fk}.`, `${f.groupName}.${fk}(`, `${f.groupName}.${fk}<`, ] // 删除 未使用的接口信息 if (!UsageTraces.some((s) => alleiConts.includes(s))) { delete curGifs[fk] } else { // 删除类型信息对象 delete curGifs[fk].reqType delete curGifs[fk].resType } }) const curSD = convert.slimeds[f.groupName] if (curSD) { if (JSON.stringify(curSD.gifs) !== JSON.stringify(f.gifs)) { curSD.gifs = f.gifs cl.logs('变化的文件:' + curSD.buildFilePath) } } else { // 记录瘦身信息 convert.slimeds[f.groupName] = f } }) // 瘦身目标目录的接口配置信息 if (!convert.slimed) { cl.logw('整体瘦身中...') ifcontents.forEach((f) => { files.write( f.buildFilePath, `"use strict";exports.${f.groupName}=${JSON.stringify(f.gifs)};`, ) cl.logs('瘦身文件:' + f.buildFilePath) }) cl.logs('整体瘦身结束') } }, /** 生成页面配置文件 */ generateFileOper(type, callBack) { const pagesPath = files.getPath(`src/utils/config/${type === 0 ? 'pages' : 'subPages'}.ts`) let pagesContent = files.read(pagesPath) pagesContent = pagesContent.replace('export default', '') pagesContent = pagesContent.replaceAll(' as ayPage', '') /** 读取的旧的配置信息-可手动修改 */ let pagesContentObj try { pagesContentObj = (0, eval)(`(${pagesContent})`) } catch (e) { pagesContentObj = {} } callBack(pagesContentObj) let pageMetaDataStr = JSON.stringify(pagesContentObj, null, 2) // 去掉" pageMetaDataStr = pageMetaDataStr.replace(/\"(.+)\":/g, '$1:').replaceAll('"', `'`) // pageMetaDataStr = pageMetaDataStr.replaceAll('},', `} as ayPage,`) // 追加 ayPage 类型 // pageMetaDataStr = pageMetaDataStr.replace(/(isPager:.+\s+\})([\n,])/g, '$1 as ayPage$2') pageMetaDataStr = pageMetaDataStr.replace(/(\})(\n\}|,)/g, '$1 as ayPage$2') files.write(pagesPath, `export default ${pageMetaDataStr}`) }, /** 生成页面配置信息 */ generatePageConfig({ ctx, spmd, pagesContentObj, }: { ctx?: PageContext /** 子包页面 */ spmd?: SubPageMetaDatum /** 最终的配置信息-可手动修改,对应utils/config/pages.ts 或 utils/config/subPages.ts */ pagesContentObj: AnyObject }) { /** 新生成的配置 */ const pageMetaDataObj = {} const getPageType = (f) => { let rv = enums.PageType.page if (ctx?.pagesGlobConfig?.tabBar?.list.some((s) => s?.pagePath === f.path)) { rv = enums.PageType.tabPage } // if (ctx.options.homePage.includes(f.path)) { // rv = 'home' // } return rv } let cusPages = [] as PageMetaDatum[] if (spmd?.pages) { cusPages = spmd.pages.map((m) => { return { ...m, path: `${spmd.root}/${m.path}`, } }) } const curPages = ctx?.pageMetaData || cusPages curPages.forEach((f) => { let key = f.path.replaceAll('/', '_') if (!/^[$A-Z_][0-9A-Z_$]*$/i.test(key)) { cl.loge(`${f.path}名称不合法,只能包含字母数字下划线`) return } // key值移除包名前缀 // 主包页面 if (ctx) { key = key.replace('pages_', '') } // 分包页面 if (spmd?.root) { key = key.replace(spmd.root + '_', '') } pageMetaDataObj[key] = { // 为了方便小程序跳转,这里拼上/ _url: '/' + f.path, _type: getPageType(f), title: key, // 登录不需要身份 identity: !f.path.includes('login/index'), isPager: false, } // 手动设置了style,赋值给PageMetaDatum if (pagesContentObj[key]?.style) { f.style = pagesContentObj[key].style } }) // 删除配置-因:删掉的目录 Object.keys(pagesContentObj).forEach((k) => { if (!pageMetaDataObj[k]) { cl.logw(`删除的页面:${pagesContentObj[k]._url}`) delete pagesContentObj[k] } }) /** 增量生成页面配置信息 */ for (const k in pageMetaDataObj) { // 页面已存在 if (pagesContentObj[k]) { const cur = pagesContentObj[k] // 重置只读属性(_开头的)值 Object.keys(cur).forEach((f) => { if (f.startsWith('_')) delete cur[f] }) pagesContentObj[k] = { ...pageMetaDataObj[k], ...pagesContentObj[k], } } else { // 页面不存在 pagesContentObj[k] = pageMetaDataObj[k] cl.logs(`新增的页面:${pageMetaDataObj[k]._url}`) } } }, } /** 对 uno.config.ts 功能的补充 */ function UnoCssMPPlugin(): Plugin { return { name: 'UnoCssMPPlugin', /** 拦截app.wxss写入时修改 uno中的样式名称增加.container 前缀,提高样式权重 */ writeBundle(options, bundle) { for (const filename in bundle) { if (filename.includes('app.wxss')) { const chunk = bundle[filename] if (chunk.type === 'asset') { const tempFilePath = `${options.dir}/${filename}` const code = chunk.source as string // const allCss = code.split('/* stylelint-disable comment-empty-line-before */') const startIndex = code.indexOf('.h-100_a_') const endIndex = code.indexOf('.chunk') if (startIndex > -1 && endIndex > -1 && startIndex < endIndex) { const unoCssBef = code.substring(0, endIndex) let unoCss = code.substring(startIndex, endIndex) const unoCssAft = code.substring(endIndex) unoCss = unoCss .replaceAll('}', '}\n') .replace(/(\.(?:(?!container).+))\{/g, '.container$1,.container $1{') // files.write(tempFilePath, unoCss + allCss[1]) files.write(tempFilePath, unoCssBef + unoCss + unoCssAft) } // bundle[filename] = { // fileName: 'app.wxss', // needsCodeReference: false, // source: '6666', // type: 'asset', // } } } } }, } } /** 小程序瘦身插件 */ function slimMPPlugin(): Plugin { if (config.env.UNI_PLATFORM === 'h5') { return { name: 'slimMP-plugin', } } else { return { name: 'slimMP-plugin', // apply: 'build', // renderStart(outputOptions, inputOptions) { // }, // renderChunk() { // }, writeBundle1(options, bundle) { for (const filename in bundle) { // 获取文件内容 const chunk = bundle[filename] delete bundle[filename] // try { // // 内容未变化的文件,不进行磁盘写入 // const fileContent = files.read(options.dir + '/' + filename) // let codekn = 'code' // if (chunk.type === 'asset') { // codekn = 'source' // } // if (chunk[codekn] === fileContent) { // console.log(filename) // delete bundle[filename] // } // } catch {} } }, // 输出生成阶段的第一个钩子是 outputOptions, /** 输出生成阶段最后一个钩子 */ generateBundle(options, bundle) { // 开发环境减少每次编译后的文件写入 if (config.env.NODE_ENV !== 'development') return // files.write(files.getPath('temp/generateBundle.json'), JSON.stringify(arguments)) // 遍历所有的打包文件 for (const filename in bundle) { // 获取文件内容 const chunk = bundle[filename] if (chunk.type === 'chunk') { // 写入瘦身后的 接口配置信息 if (filename.includes('config/interFaces')) { const groupName = files.basename(filename, false) const curSD = convert.slimeds[groupName] if (curSD) { chunk.code = `"use strict";exports.${groupName}=${JSON.stringify(curSD.gifs)};` } } } try { // 内容未变化的文件,不进行磁盘写入 const fileContent = files.read(options.dir + '/' + filename) let codekn = 'code' if (chunk.type === 'asset') { codekn = 'source' } if (chunk[codekn] === fileContent) { delete bundle[filename] } } catch {} } }, /** 监听文件修改 */ watchChange(filePath) { // h5环境 跳出 // if (config.env.UNI_PLATFORM === 'h5') return // 已整体瘦身 & 有路径 & 非\utils\config目录下的 & filePath包含\\(小程序路径) if ( convert.slimed && filePath && !filePath.includes('\\utils\\config') && filePath.includes('\\') ) { convert.slimInterfaceConfig({ filePath }) } }, buildStart(options) { // files.write(files.getPath('temp/buildStart.json'), JSON.stringify(options)) // return if (options.plugins) { const unimp: any = options.plugins.find((f) => ['uni:mp'].includes(f.name)) if (unimp) { config.mpBuildPath = unimp.uni.copyOptions.targets[0].dest config.rootPath = unimp.uni.compilerOptions.root } } }, closeBundle() { if (convert.slimed) { return } convert.slimInterfaceConfig() convert.slimed = true }, } } } /** 生成页面配置信息 */ function generatePageConfig(ctx: PageContext) { const pagesContentObj = {} convert.generateFileOper(0, (pagesContentObj) => { convert.generatePageConfig({ ctx, pagesContentObj }) }) if (ctx.subPageMetaData.length) { const pagesContentObj = {} convert.generateFileOper(1, (pagesContentObj) => { ctx.subPageMetaData.forEach((f) => { if (!pagesContentObj[f.root]) { pagesContentObj[f.root] = {} } convert.generatePageConfig({ spmd: f, pagesContentObj: pagesContentObj[f.root], }) }) }) } } export { slimMPPlugin, UnoCssMPPlugin, generatePageConfig }