【探讨】关于 `vite` 和 `webpack` 的分包优化的探讨
引语
-
以下是uniapp2, 也就是基于webpack打包的vue2版本的分包优化策略的详细实现 https://github.com/dcloudio/uni-app/blob/fb00949fa49dd7d5d68a43700b7be62e6c0b4af3/packages/vue-cli-plugin-uni/lib/split-chunks.js#L114
-
以下是基于vite打包的vue3版本的rollup的打包配置
TODO: 此处代码是有待优化的地方
https://github.com/dcloudio/uni-app/blob/1c19012e38fb2049ad7775009d9bb0157965910b/packages/uni-mp-vite/src/plugin/build.ts#L118
问题
本人的项目(uniapp-vue3版本)在小程序这边的包大小已经到了一个瓶颈阶段,该分包的都分包了,公共组件也是尽可能的少了;
甚至魔改了uniapp的源码实现了小程序那边支持的异步分包功能,实现了主包异步引用分包组件的能力;
但是随着业务功能的增多,以上分包优化策略已不满足,小程序发版受到严重的影响,急需实现与vue2版本的分包优化一致功能。
探讨
本人研究了uniapp2版本的webpack的分包优化的具体实现,正在尝试对vite的分包优化进行实现(此工作正在进行中,突发奇想发个issue想知道官方有没有考虑这一块)。
不知官方对此处rollup的打包配置有没有什么指导性的建议,以实现与vue2版本的一样的分包优化的效果。
问题整理
https://github.com/Vanisper/uniapp-bundle-optimizer 我做的分包优化解决方案的一个整理,请阅读readme指引操作。
已实现vite下的分包优化,下面贴出patch相关部分:
基于
@[email protected]版本的补丁
diff --git a/dist/plugin/build.js b/dist/plugin/build.js
index 47598739e43a1f8049b54fcd756411b42a5dc0af..5c77f79dfd9be6df4ec18517c833034bc55b393b 100644
--- a/dist/plugin/build.js
+++ b/dist/plugin/build.js
@@ -91,9 +91,86 @@ 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;
return (id, { getModuleInfo }) => {
const normalizedId = (0, uni_cli_shared_1.normalizePath)(id);
const filename = normalizedId.split('?')[0];
@@ -114,6 +191,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';
diff --git a/dist/plugins/manifestJson.js b/dist/plugins/manifestJson.js
index 95af5ee04442d5407cb1a1d8c8d27a769aae7d06..ab65b0608c8bd35479dc7c3a50ac3a582dd29f92 100644
--- a/dist/plugins/manifestJson.js
+++ b/dist/plugins/manifestJson.js
@@ -27,6 +27,12 @@ function uniManifestJsonPlugin(options) {
if (options.project) {
userProjectFilename = findUserProjectConfigFile(inputDir, options.project.config);
}
+ // #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;
+ // #endregion
return {
name: 'uni:mp-manifest-json',
enforce: 'pre',
diff --git a/dist/plugins/pagesJson.js b/dist/plugins/pagesJson.js
index 4aad1ad03113710a2f63dbc61f977d148aa194e0..691eac2ac414fd1d271db3050756238644087a37 100644
--- a/dist/plugins/pagesJson.js
+++ b/dist/plugins/pagesJson.js
@@ -61,6 +61,8 @@ function uniPagesJsonPlugin(options) {
(0, uni_cli_shared_1.addMiniProgramPageJson)(name, pageJsons[name]);
}
});
+ // 将 pages.json 的内容挂载到 process.UNI_SUBPACKAGES,方便后续插件使用
+ process.UNI_SUBPACKAGES = appJson.subPackages || {};
return {
code: `import './${uni_cli_shared_1.MANIFEST_JSON_JS}'\n` + importPagesCode(appJson),
map: { mappings: '' },
下一步需要做的是将这部分抽离成 vite 的插件。
插件化实现进行中,见此项目此处(说明见此处),插件化todos:
- [x] 分包优化 (补丁化已实现,插件化已实现)
- [x] 组件异步(跨包)引用 (补丁化已实现,插件化已实现)
- [x] js 插件异步(跨包)调用 (补丁化未实现,并不打算实现补丁化,~~计划插件化实现~~,插件化已实现)
补丁更新: 先前的补丁通过在两个内置的json解析、处理的插件中, 获取需要的子包数据、manifest分包优化是否开启, 将这两个数据分别挂载到
process.UNI_SUBPACKAGES、process.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';
我原本写了一个简单的,参考博主的做了一些修改,已经用上了,感谢。
m
@Otto-J @fxy060608 @zhenyuWang 大佬们!打扰了,我这个话题的进度快完结了,分包优化插件化快完成了,见https://github.com/Vanisper/uniapp-bundle-optimizer
现在遇到一个问题,就是在vite插件的generateBundle阶段,bundle中拿到的code内容中,异步调用的import关键字全部被剔除了,我尝试了拉uniapp源码执行,看是哪个环节剔除的,无奈调试毫无头绪,肯定是generateBundle生命周期之前的一个环节去除的,找了好久没找到,希望大佬能指点一下具体是哪个部分。
我编写的插件卡在这一步了,generateBundle阶段把所有的import异步调用的关键字都剔除了,我的插件命中不了一些情况,无法进行下一步。
再次劳烦大佬指点一下是哪个关键剔除了import关键字的
以下附上剔除之后的编译结果图:
import
在这里:https://github.com/dcloudio/uni-app/blob/next/packages/uni-mp-vite/src/plugin/build.ts#L86
import
在这里:https://github.com/dcloudio/uni-app/blob/next/packages/uni-mp-vite/src/plugin/build.ts#L86
感谢大佬,确实是这里,这个地方我之前关注到过,不知道为啥没定位到这里。 总之非常感谢 : )
此贴可以做一个终结了: 目前已经将研究成果整理成vite插件并发布到了npm市场,详情见 https://github.com/uni-ku/bundle-optimizer。
目前vue3版本的分包优化是空白的,我努力了很久填补了分包优化的三个方向的空白。 不知官方大大对此的意向如何?
太棒了 目前的 ‘如果这个资源只属于一个子包,并且其调用组件的不存在跨包调用的情况’ 这个情况在vue3目前看也会被加进去主包真是太糟糕了
提一个建议,vendor空间优化的方案中,vendor.js的优化,将主包有引用的js就打进vendor,多个分包引用的js,copy到各个分包,单独引用,这样主包大小压力就会小很多。 @Vanisper
@GeeFib 我考虑过这个问题,这就意味着一个 js 模块需要允许有多个输出的目录。
这需要构建、打包工具的支持,vite 使用的是 rollup,其中相关的插件配置是 manualChunks 。 也就是说需要 manualChunks 支持一个依赖允许有多个 vendor chunk 的输出。
我没有深入调研,在我对这个 manualChunks 使用过程中的理解来看,实现不了:
manualChunks 的返回值决定了模块最终打包到哪个 chunk(要知道一个函数只能存在一个返回值,何况 manualChunks 只允许返回 string | undefined),且每个模块只会被分配到一个 chunk 中。这意味着,一个模块无法直接同时出现在多个子包的 vendor 中。
可能有路子,因为我想到了构建工具应该可以配置 多入口 (entry) 的,如果可以的话,每个子包都是一个 entry,然后(可能)可以实现一个依赖有多个 chunk 的拷贝。
这个方案有待探索,需要构建工具的支持。
顺便提一句,我很烦恼语言障碍对方案调研的阻力,就比如说上面这个方案,通过英文检索应该是可以获得不少相关话题的讨论结果的。
当然,更多是我的懒惰导致的,没有动手去检索它(咬咬牙,用翻译软件搞一些个关键词检索基本是没问题的)。
不得不承认,英文检索得到的信息会多一些。
我的项目没用vite,还是vue2,webpack的方案,我对webpack了解尚浅,还请指教webpack有可能实现吗?
正在翻阅资料中....
webpack 使用到的配置是 splitChunks,我询问了 AI,上面说是可以通过 splitChunks.cacheGroups 配置实现。
我对此也了解不多,需要验证一下。
待验证的配置(类似)如下:
module.exports = {
entry: {
entry1: './src/entry1.js',
entry2: './src/entry2.js',
},
optimization: {
splitChunks: {
cacheGroups: {
entry1Chunk: {
test: /[\\/]node_modules[\\/]lodash/, // 只匹配 lodash
name: 'entry1', // 将 lodash 拆分到 entry1.js 的 chunk 中
chunks: 'entry1', // 仅对 entry1 进行拆分
},
entry2Chunk: {
test: /[\\/]node_modules[\\/]lodash/, // 只匹配 lodash
name: 'entry2', // 将 lodash 拆分到 entry2.js 的 chunk 中
chunks: 'entry2', // 仅对 entry2 进行拆分
},
},
},
},
};
如果可行,那么在两个文件中都将会出现 lodash。
社区里有人做过 “多个分包引用的js,copy到各个分包” 这种方案,很久之前我参考了一下用unplugin写了一下。可用,但是在分部分包的时候会导致程序跑不起来,具体原因我不熟悉webpack没去做过多了解,可以做一下参考
webpack(compiler) {
const pluginName = 'DispatchScriptPlugin'
const handledModules: Array<{
module: string
subPackages: Array<string>
}> = []
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
compilation.hooks.optimizeChunks.tap(pluginName, () => {
const waitDisposeModules = [...compilation.modules].filter(module => baseTest(module, options))
waitDisposeModules.forEach((module) => {
const chunks = module.getChunks()
// console.log('chunks', chunks, module)
const matchSubPackages = findSubPackages(chunks)
const isMain = hasMainPackage(chunks)
// 有分包在使用,但是主包没有使用,包含了上面的情况2
if (matchSubPackages.size > 0 && !isMain) {
moveFiles.set(module, {
// @ts-expect-error 未知错误
name: module.resource,
pkgSet: matchSubPackages,
})
}
})
saveJsonToLog([...moveFiles.values()].map(item => ({
...item,
pkgSet: [...item.pkgSet],
})), 'dispatch-script.can-move-files.json')
})
compilation.hooks.optimizeChunkModules.tap(pluginName, (chunks, modules) => {
[...modules].forEach((module) => {
if (moveFiles.has(module)) {
const mainChunks = module.getChunks()
// 如果不存在主包的common/vendor中,说明已经被uni放入分包了,就不需要做任何处理
// console.log(mainChunks.length, mainChunks[0].name, VENDER_PATH)
if (mainChunks.length === 1 && mainChunks[0].name === VENDER_PATH) {
const mainChunk = mainChunks[0]
const moveFileInfo = moveFiles.get(module)
const chunkNames: string[] = []
const pkgSet = [...moveFileInfo.pkgSet]
pkgSet.forEach((value: string) => {
// console.log(value)
chunkNames.push(path.normalize(path.join(value, VENDER_PATH)))
chunkNames.forEach((chunkName) => {
let pkgChunk = findNameChunk(Array.from(chunks), chunkName)
if (!pkgChunk) {
// 新建一个chunk
const group = compilation.addChunkInGroup(chunkName)
pkgChunk = group.chunks[0]
}
// 添加到分包chunk中
compilation.chunkGraph.connectChunkAndModule(pkgChunk, module)
})
handledModules.push({
// @ts-expect-error 未知错误
module: getRelativePath(module.resource),
subPackages: Array.from(moveFileInfo.pkgSet),
})
// 从之前chunk移除,也就是common/vendor
compilation.chunkGraph.disconnectChunkAndModule(mainChunk, module)
})
}
}
})
// console.log('save log', handledModules)
saveJsonToLog(handledModules, 'dispatch-script.handled-modules.json')
})
})
},
vue.config.ts
UnpluginJsDistribute.webpack({
whiteList: [
'evaluate_car',
'ticketFollowManageMixin',
'chinese-simple2traditional',
'useIdentifyClipboard',
// -----
'commonCanvas',
'show-card.png',
'submit-success.png',
'back-to-carlist.png',
'ocrPics',
// --
'carDetail/config.ts',
]
})
我尝试一下,感谢支持
@Kanade-Lu 可以的 只要是有路子就可以,至于子包无法运行,我觉得的问题应该就是该子包,其自身的依赖关系索引的问题,可能需要在插件中细节处理一下。
APP分包支持吗
APP分包支持吗
研究发现,app 平台的编译构建本身对业务核心相关的东西就是一个大的js文件,所以猜测,uniapp 本身在 app 平台下的构建就是没有分包的”传统“的,可能有些什么原因,自己去实现分包可能有些阻力?
只开发小程序,没研究过app侧的情况
看你们这些大佬的讨论受益匪浅。
我做了一个使用简单,无侵入式的 跨分包自定义组件引用解决方案 PR-5822 ,使用时只需要一行注释:
基础用法:
/* @uni-async-component */
import EcCanvas from '@/subPackageB/components/ec-canvas'
export default {
components: {
EcCanvas
}
}
高级配置:
/* @uni-async-component {"placeholder": "view", "platform": "mp-weixin,mp-alipay"} */
import EcCanvas from '@/subPackageB/components/ec-canvas'