Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【探讨】关于 vitewebpack 的分包优化的探讨 #5025

Open
Vanisper opened this issue Jul 9, 2024 · 6 comments
Open

【探讨】关于 vitewebpack 的分包优化的探讨 #5025

Vanisper opened this issue Jul 9, 2024 · 6 comments
Labels
Good!完整的提问 完整的提问 full info mp mini program question Further information is requested

Comments

@Vanisper
Copy link

Vanisper commented Jul 9, 2024

引语

  1. 以下是uniapp2, 也就是基于webpack打包的vue2版本的分包优化策略的详细实现

    Object.keys(process.UNI_SUBPACKAGES).forEach(root => {

  2. 以下是基于vite打包的vue3版本的rollup的打包配置

TODO: 此处代码是有待优化的地方

function createMoveToVendorChunkFn(): GetManualChunk {

问题

本人的项目(uniapp-vue3版本)在小程序这边的包大小已经到了一个瓶颈阶段,该分包的都分包了,公共组件也是尽可能的少了;

甚至魔改了uniapp的源码实现了小程序那边支持的异步分包功能,实现了主包异步引用分包组件的能力;

但是随着业务功能的增多,以上分包优化策略已不满足,小程序发版受到严重的影响,急需实现与vue2版本的分包优化一致功能。

探讨

本人研究了uniapp2版本的webpack的分包优化的具体实现,正在尝试对vite的分包优化进行实现(此工作正在进行中,突发奇想发个issue想知道官方有没有考虑这一块)。

不知官方对此处rollup的打包配置有没有什么指导性的建议,以实现与vue2版本的一样的分包优化的效果。

问题整理

https://github.com/Vanisper/uniapp-bundle-optimizer 我做的分包优化解决方案的一个整理,请阅读readme指引操作。

@Vanisper

This comment was marked as outdated.

@Vanisper
Copy link
Author

Vanisper commented Jul 10, 2024

下一步需要做的是将这部分抽离成 vite 的插件。

相关链接:rollupOptions.output.manualChunks


插件化实现进行中,见此项目此处(说明见此处),插件化todos:

  • 分包优化 (补丁化实现,插件化已实现)
  • 组件异步(跨包)引用 (补丁化实现,插件化已实现)
  • js 插件异步(跨包)调用 (补丁化实现,并不打算实现补丁化,计划插件化实现,插件化已实现)

@Vanisper
Copy link
Author

补丁更新:
先前的补丁通过在两个内置的json解析、处理的插件中,
获取需要的子包数据、manifest分包优化是否开启,
将这两个数据分别挂载到process.UNI_SUBPACKAGESprocess.env.UNI_OPT_TRACE,供后续插件的使用
但是由于这两个插件的执行顺序可能在 build.js 之后,导致环境变量无法生效,
故单独建立 env.js 脚本,在 @dcloudio__uni-mp-vite 项目入口,引入此脚本以初始化相关环境变量。


基于 @[email protected] 版本的补丁(优化版本)

diff --git a/dist/env.js b/dist/env.js
new file mode 100644
index 0000000000000000000000000000000000000000..364a5c722dd0774027e550899bc6eee9c8a04c4f
--- /dev/null
+++ b/dist/env.js
@@ -0,0 +1,22 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+    return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const path_1 = __importDefault(require("path"));
+const fs_1 = require("fs");
+const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
+const platform = process.env.UNI_PLATFORM;
+const inputDir = process.env.UNI_INPUT_DIR;
+
+// #region 分包优化参数获取
+const manifestJson = (0, uni_cli_shared_1.parseManifestJsonOnce)(inputDir);
+const platformOptions = manifestJson[platform] || {};
+const optimization = platformOptions.optimization || {};
+process.env.UNI_OPT_TRACE = !!optimization.subPackages;
+
+const pagesJsonPath = path_1.default.resolve(inputDir, 'pages.json');
+const jsonStr = fs_1.readFileSync(pagesJsonPath, 'utf8');
+const { appJson } = (0, uni_cli_shared_1.parseMiniProgramPagesJson)(jsonStr, platform, { subpackages: true });
+process.UNI_SUBPACKAGES = appJson.subPackages || {};
+// #endregion
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
index f343787fe9d9aa1ac97f15a91078814ce747f9ed..6795d4f25cedc4ca7408cbe530b5ac4aa5c0ca2e 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1,5 +1,7 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
+// 引入一个初始化环境变量的脚本
+require("./env");
 const shared_1 = require("@vue/shared");
 const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
 const plugin_1 = require("./plugin");
diff --git a/dist/plugin/build.js b/dist/plugin/build.js
index 47598739e43a1f8049b54fcd756411b42a5dc0af..61ac0227f4587def6bb28baae63ddc789341fe3d 100644
--- a/dist/plugin/build.js
+++ b/dist/plugin/build.js
@@ -91,9 +91,87 @@ function isVueJs(id) {
     return id.includes('\0plugin-vue:export-helper');
 }
 const chunkFileNameBlackList = ['main', 'pages.json', 'manifest.json'];
+
+// #region subpackage
+const UNI_SUBPACKAGES = process.UNI_SUBPACKAGES || {};
+const subPkgsInfo = Object.values(UNI_SUBPACKAGES);
+const normalFilter = ({ independent }) => !independent;
+const independentFilter = ({ independent }) => independent;
+const map2Root = ({ root }) => root + '/';
+const subPackageRoots = subPkgsInfo.map(map2Root);
+const normalSubPackageRoots = subPkgsInfo.filter(normalFilter).map(map2Root);
+const independentSubpackageRoots = subPkgsInfo.filter(independentFilter).map(map2Root);
+
+// id处理器:将id中的moduleId转换为相对于inputDir的路径并去除查询参数后缀
+function moduleIdProcessor(id) {
+    let inputDir = (0, uni_cli_shared_1.normalizePath)(process.env.UNI_INPUT_DIR);
+    // 确保inputDir以斜杠结尾
+    if (!inputDir.endsWith('/')) {
+        inputDir += '/';
+    }
+
+    const normalized = (0, uni_cli_shared_1.normalizePath)(id);
+    const name = normalized.split('?')[0];
+    // 从name中剔除inputDir前缀
+    const updatedName = name.replace(inputDir, '');
+
+    return updatedName;
+}
+// 查找模块列表中是否有属于子包的模块
+const findSubPackages = function (importers) {
+    return importers.reduce((pkgs, item) => {
+        const pkgRoot = normalSubPackageRoots.find(root => moduleIdProcessor(item).indexOf(root) === 0);
+        pkgRoot && pkgs.add(pkgRoot);
+        return pkgs;
+    }, new Set())
+}
+// 判断是否有主包(是否被主包引用)
+const hasMainPackage = function (importers) {
+    return importers.some(item => {
+        return !subPackageRoots.some(root => moduleIdProcessor(item).indexOf(root) === 0);
+    })
+}
+// 判断该模块引用的模块是否有跨包引用的组件
+const hasMainPackageComponent = function (moduleInfo, subPackageRoot) {
+    if (moduleInfo.id && moduleInfo.importedIdResolutions) {
+        for (let index = 0; index < moduleInfo.importedIdResolutions.length; index++) {
+            const m = moduleInfo.importedIdResolutions[index];
+            
+            if (m && m.id) {
+                const name = moduleIdProcessor(m.id);
+                // 判断是否为组件
+                if (
+                    name.indexOf('.vue') !== -1 ||
+                    name.indexOf('.nvue') !== -1
+                ) {
+                    // 判断存在跨包引用的情况(该组件的引用路径不包含子包路径,就说明跨包引用了)
+                    if (name.indexOf(subPackageRoot) === -1) {
+                        if (process.env.UNI_OPT_TRACE) {
+                            console.log('move module to main chunk:', moduleInfo.id,
+                                'from', subPackageRoot, 'for component in main package:', name)
+                        }
+
+                        // 独立分包除外
+                        const independentRoot = independentSubpackageRoots.find(root => name.indexOf(root) >= 0)
+                        if (!independentRoot) {
+                            return true
+                        }
+                    }
+                } else {
+                    return hasMainPackageComponent(m, subPackageRoot)
+                }
+            }
+        }
+    }
+    return false;
+}
+// #endregion
+
 function createMoveToVendorChunkFn() {
     const cache = new Map();
     const inputDir = (0, uni_cli_shared_1.normalizePath)(process.env.UNI_INPUT_DIR);
+    const UNI_OPT_TRACE = process.env.UNI_OPT_TRACE === 'true' ? true : false;
+    console.log('分包优化开启状态:', UNI_OPT_TRACE);
     return (id, { getModuleInfo }) => {
         const normalizedId = (0, uni_cli_shared_1.normalizePath)(id);
         const filename = normalizedId.split('?')[0];
@@ -114,6 +192,20 @@ function createMoveToVendorChunkFn() {
                 }
                 return;
             }
+            if (UNI_OPT_TRACE) {
+                // 如果这个资源只属于一个子包,并且其调用组件的不存在跨包调用的情况,那么这个模块就会被加入到对应的子包中。
+                const moduleInfo = getModuleInfo(id) || {};
+                const importers = moduleInfo.importers || []; // 依赖当前模块的模块id
+                const matchSubPackages = findSubPackages(importers);
+                if (
+                    matchSubPackages.size === 1 &&
+                    !hasMainPackage(importers) &&
+                    !hasMainPackageComponent(moduleInfo, matchSubPackages.values().next().value)
+                ) {
+                    debugChunk(`${matchSubPackages.values().next().value}common/vendor`, normalizedId);
+                    return `${matchSubPackages.values().next().value}common/vendor`;
+                }
+            }
             // 非项目内的 js 资源,均打包到 vendor
             debugChunk('common/vendor', normalizedId);
             return 'common/vendor';

@Otto-J Otto-J added question Further information is requested mp mini program Good!完整的提问 完整的提问 full info labels Jul 12, 2024
@zlinggnilz
Copy link

我原本写了一个简单的,参考博主的做了一些修改,已经用上了,感谢。

@Vanisper
Copy link
Author

我原本写了一个简单的,参考博主的做了一些修改,已经用上了,感谢。

实现了插件化了哈,见这里的说明

@aymonYU
Copy link

aymonYU commented Oct 24, 2024

m

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Good!完整的提问 完整的提问 full info mp mini program question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants