uniapp、vite、vue3开发的微信小程序进行分包,主包过大优化

1,224 阅读2分钟

因为我文笔不是很好,所以我只能贴下代码,其实里面很多代码都是在uni的源码里面复制过来的,主要的地方就是manualChunks这个方法,思路是参考的uni源码的vue3分支uni-app\packages\uni-mp-vite\src\plugin\build.ts文件的代码

新建一个文件,把下面代码复制进去

import fs from 'node:fs';
import path from 'node:path';
import type { Plugin } from 'vite';

import {
    EXTNAME_JS_RE,
    normalizePath,
    hasJsonFile,
    removeExt,
    isCSSRequest,
    DEFAULT_ASSETS_RE
} from '@dcloudio/uni-cli-shared';

const chunkFileNameBlackList = ['main', 'pages.json', 'manifest.json'];

function isVueJs(id: string) {
    return id.includes('\0plugin-vue:export-helper');
}

function staticImportedByEntry(
    id: string,
    getModuleInfo: any,
    cache: Map<string, boolean>,
    importStack: string[] = []
): boolean {
    if (cache.has(id)) {
        return cache.get(id) as boolean;
    }
    if (importStack.includes(id)) {
        // circular deps!
        cache.set(id, false);
        return false;
    }
    const mod = getModuleInfo(id);
    if (!mod) {
        cache.set(id, false);
        return false;
    }

    if (mod.isEntry) {
        cache.set(id, true);
        return true;
    }
    const someImporterIs = mod.importers.some((importer) =>
        staticImportedByEntry(importer, getModuleInfo, cache, importStack.concat(id))
    );
    cache.set(id, someImporterIs);
    return someImporterIs;
}

export default function uniSubpackagePlugin(options?: {
    entryFile?: string[];
    exclude?: (string | RegExp)[];
}): Plugin {
    const { entryFile, exclude } = options || {};
    return {
        name: 'uni-subpackage-plugin',
        enforce: 'post',
        apply: 'build',
        config: function (config) {
            try {
                // 判断是否是小程序
                if (
                    process.env.UNI_PLATFORM === 'mp-weixin' ||
                    process.env.UNI_PLATFORM === 'mp-alipay'
                ) {
                    const cache = new Map<string, boolean>();
                    // 获取项目路径
                    const inputDir = normalizePath(process.env.UNI_INPUT_DIR || '');

                    // 实例化pages.json
                    const pages: Record<string, any> = JSON.parse(
                        fs
                            .readFileSync(path.resolve(inputDir, 'pages.json'))
                            .toString()
                            .replace(/\s\/\/.*|\/\*[\s\S]*?\*\//g, '')
                    );
                    // 获取分包信息
                    const subPackages = (pages?.subPackages || []).map((item) => {
                        return {
                            name: item.root,
                            subPath: normalizePath(path.resolve(inputDir, item.root))
                        };
                    });

                    const getSubPackageInfo = (moduleInfo) => {
                        return subPackages.find(({ subPath }) => {
                            if (moduleInfo.importers) {
                                return moduleInfo.importers.every(
                                    (item) => !!item.startsWith(subPath)
                                );
                            }
                            return moduleInfo.every((item) => !!item.startsWith(subPath));
                        });
                    };
                    if (!Array.isArray(config.build?.rollupOptions?.output)) {
                        if (!config.build) config.build = {};
                        if (!config.build.rollupOptions) config.build.rollupOptions = {};
                        if (!config.build.rollupOptions.output)
                            config.build.rollupOptions.output = {};
                        // 为了支持小程序的跨分包 JS 代码引用,避免文件名修改
                        config.build.rollupOptions.output['entryFileNames'] = function (chunk) {
                            if (chunk.name === 'main') {
                                return 'app.js';
                            }
                            if (entryFile?.includes(chunk.name)) {
                                const normalizedId = normalizePath(chunk.facadeModuleId);
                                const filename = removeExt(
                                    normalizePath(
                                        path.relative(inputDir, normalizedId.split('?')[0])
                                    )
                                );
                                return filename + '.js';
                            }
                            return chunk.name + '.js';
                        };

                        // 为了支持小程序的 vendor.js 文件分包
                        config.build.rollupOptions.output['manualChunks'] = function (
                            id: string,
                            { getModuleInfo }
                        ) {
                            const normalizedId = normalizePath(id);
                            const filename = normalizedId.split('?')[0];
                            const isExclude = exclude?.some((item) => {
                                if (typeof item === 'string') {
                                    return normalizedId.includes(item);
                                } else if (item instanceof RegExp) {
                                    return item.test(normalizedId);
                                }
                                return false;
                            });
                            // 处理资源文件
                            if (DEFAULT_ASSETS_RE.test(filename)) {
                                const moduleInfo = getModuleInfo(id);
                                const subPackageInfo = getSubPackageInfo(moduleInfo);
                                if (!isExclude && subPackageInfo?.name) {
                                    return subPackageInfo?.name + '/common/assets';
                                } else {
                                    return 'common/assets';
                                }
                            }

                            // 处理项目内的js,ts文件
                            if (EXTNAME_JS_RE.test(filename)) {
                                const moduleInfo = getModuleInfo(id);
                                const subPackageInfo = getSubPackageInfo(moduleInfo);
                                const chunkFileName = removeExt(
                                    normalizePath(path.relative(inputDir, filename))
                                );

                                if (
                                    filename.startsWith(inputDir) &&
                                    !filename.includes('node_modules')
                                ) {
                                    if (
                                        !chunkFileNameBlackList.includes(chunkFileName) &&
                                        !hasJsonFile(chunkFileName) // 无同名的page,component
                                    ) {
                                        // 为了支持小程序的跨分包 JS 文件,这是控制输出的路径
                                        if (
                                            !isExclude &&
                                            subPackageInfo?.name &&
                                            !filename.startsWith(subPackageInfo?.subPath)
                                        ) {
                                            return `${subPackageInfo?.name}/${chunkFileName}`;
                                        }
                                        return chunkFileName;
                                    }
                                    return;
                                } else if (filename.includes('node_modules')) {
                                    if (
                                        subPackageInfo?.name &&
                                        moduleInfo.importers.every(
                                            (item) => !item.includes('node_modules')
                                        )
                                    ) {
                                        // const directory =
                                        //     filename.match(/node_modules\/(.+)\.(.+)/);
                                        // if (directory && directory[1]) {
                                        //     return (
                                        //         subPackageInfo?.name +
                                        //         '/node-modules/' +
                                        //         directory[1]
                                        //     );
                                        // }
                                        return subPackageInfo?.name + '/common/vendor';
                                    } else if (
                                        moduleInfo.importers.length > 1 &&
                                        moduleInfo.importers.every((item) => {
                                            const importerModuleInfo = getModuleInfo(item);
                                            return importerModuleInfo.importers.every(
                                                (item) => !item.includes('node_modules')
                                            );
                                        })
                                    ) {
                                        return 'common/vendor';
                                    }
                                }

                                if (isExclude) {
                                    // 注释掉uni的分包规则,使用vite默认的分包规则,来支持vendor.js 文件分包,不然会分包不彻底
                                    return 'common/vendor';
                                }

                                // vendor.js 文件分包
                                if (subPackageInfo?.name) {
                                    return subPackageInfo?.name + '/common/vendor';
                                }
                            }

                            // 这里是uni的默认逻辑,不太清楚是干嘛的
                            if (
                                isVueJs(normalizedId) ||
                                (normalizedId.includes('node_modules') &&
                                    !isCSSRequest(normalizedId) &&
                                    // 使用原始路径,格式化的可能找不到模块信息 https://github.com/dcloudio/uni-app/issues/3425
                                    staticImportedByEntry(id, getModuleInfo, cache))
                            ) {
                                return 'common/vendor';
                            }
                        };
                    }
                }
            } catch (error) {
                console.error('[uni-subpackage-plugin] error:', error);
            }
            return config;
        }
    };
}