'a'
mh-two-thousand-and-two
2024-04-12 44d2c92345cd156a59fc327b3060292a282d2893
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
const path = require('path');
const Analyze = require('./analyze');
const { wxComponentsStr, outerComponents, weuiComponentStr } = require('./constant');
const { generateAsset } = require('./util');
const {
    collectAllOutSideComponentsMap,
    getIndependentPkgRoots,
    getIndependentEntryPages,
    getGlobalComponentKeyByGlobalComponentPath,
    copyAllWxComponentsFiles,
    collectPkgCopyFiles,
    getNewComponentPathInIndependentPkg,
    getJsonByPageOrComponentPath
} = require('./util');
 
// 原则:原生组件不允许使用全局组件
 
// 忽略原生组件(wxComponents)使用全局组件的情况
 
function recurIndependentJson (independentRoot, independentPages, sourceRepo, handler, cacheSet = new Set()) {
    independentPages.forEach(independentPage => {
        // 避免无限递归
        const recured = cacheSet.has(independentPage);
        if (recured) return;
 
        cacheSet.add(independentPage);
 
        // 关键:映射到独立分包下面的组件路径
        const newComponentPath = getNewComponentPathInIndependentPkg(independentRoot, independentPage);
        const {
            content: jsonObj, fromAssetsFlag
        } = getJsonByPageOrComponentPath(newComponentPath, sourceRepo);
        if (!jsonObj) {
            // console.log('independent.recurIndependentJson', newComponentPath);
            return;
        }
 
        // 处理 newComponentPath.json 中的包外组件路径
 
        const usingComponents = jsonObj.usingComponents || {};
        for (let componentKey in usingComponents) {
            const componentPath = usingComponents[componentKey];
            if (componentPath.indexOf(weuiComponentStr) >= 0) {
                continue;
            }
            handler(usingComponents, componentKey);
            recurIndependentJson(independentRoot, [componentPath], sourceRepo, handler, cacheSet);
        }
 
        if (fromAssetsFlag) {
            sourceRepo.compilationAssets[`${newComponentPath}.json`] = generateAsset(JSON.stringify(jsonObj));
        }
    });
}
 
// TODO watch 只针对发生变化的文件
class Index extends Analyze {
    init () {
        const emitFileMap = this.emitFileMap;
        const independentRoots = getIndependentPkgRoots();
        const outSideComponentsMap = {}; // 引用的包外组件(vue组件和原生组件)
        independentRoots.forEach(independentRoot => {
            const independentPages = getIndependentEntryPages(independentRoot);
            let cacheSet = new Set();
            let cacheGlobalUsageMap = new Map();
            // 收集包外组件
            const collectOuterCompos = independentPage => collectAllOutSideComponentsMap(independentRoot, emitFileMap, independentPage, cacheSet, cacheGlobalUsageMap);
            independentPages.forEach(collectOuterCompos);
 
            // 如果是原生组件,则忽略wxComponents以外的组件
            cacheSet = [...cacheSet].filter(componentPath => {
                if (componentPath.startsWith('/')) {
                    componentPath = componentPath.substring(1);
                }
                const isVueComponent = emitFileMap.get(`${componentPath}.json`);
                const isWxComponent = componentPath.startsWith(`${wxComponentsStr}`);
                // TODO weui组件
                return !!(isVueComponent || isWxComponent);
            });
            // 暂时只收集包外的vue组件和原生组件(wxComponents)
            outSideComponentsMap[independentRoot] = {
                outerComponentSet: new Set(cacheSet),
                globalComponentsMap: cacheGlobalUsageMap
            };
        });
 
        // 独立分包使用到[全局组件]和[入口页面]作为[文件依赖分析]的入口
        const componentFileCache = {};
        for (let independentRoot in outSideComponentsMap) {
            const info = outSideComponentsMap[independentRoot];
            this.copyAndUpdateJson(independentRoot, info, componentFileCache);
        }
    }
 
    copyAndUpdateJson (independentRoot, info, componentFileCache) {
        const { outerComponentSet, globalComponentsMap } = info;
        this.getDependFiles({ [independentRoot]: outerComponentSet }, componentFileCache, true);
        // 1. 先复制
        this.copyOuterComponents(independentRoot, outerComponentSet, componentFileCache);
        // 2. 更新组件json中包外组件引用路径
        this.updateIndependentJson(independentRoot, globalComponentsMap);
    }
 
    updateIndependentJson (independentRoot, globalComponentsMap) {
        // 1. 先添加全局组件依赖
        this.addGlobalComponentReference(independentRoot, globalComponentsMap);
        // 2. 更新显示引用包外组件路径
        this.updateOuterComponentReference(independentRoot);
    }
 
    // pages/chat-im/vueOuterComponents/components/navigation-bar.json mini-icon引用出错?
    // 先处理全局组件:将全局组件引用添加到json文件中
    // 整体思路:
    // 1. 在init函数中,收集了独立分包用到的所有全局组件(包括包外组件用到的全局组件),
    // 2. 保存全局组件被那些页面或者组件使用
    // 3. 复制包外组件(全局组件只是包外组件的一部分)
    // 4. 由于独立分包不能使用全局组件,所以该方法将全局组件路径添加到独立分包下的组件或页面关联的json文件中,确保可以访问到。
    addGlobalComponentReference (independentRoot, globalComponentsMap) {
        const globalComponentInfoMap = getGlobalComponentKeyByGlobalComponentPath();
        for (let [globalComponentPath, componentSetWhoUsedGlobalCompo] of globalComponentsMap) {
            // weui 暂时先不处理
            if (globalComponentPath.indexOf(weuiComponentStr) >= 0) {
                continue;
            }
 
            if (globalComponentPath.indexOf(weuiComponentStr) >= 0) {
                continue;
            }
            const componentKey = globalComponentInfoMap[globalComponentPath];
 
            if (globalComponentPath.startsWith('/')) {
                globalComponentPath = globalComponentPath.substring(1);
            }
 
            const globalComponentReplacePath = getNewComponentPathInIndependentPkg(independentRoot, globalComponentPath);
 
            if (globalComponentReplacePath === globalComponentPath) return; // 理论上不会走
 
            const compilationAssets = this.compilation.assets;
            // pages均为vue文件
            [...componentSetWhoUsedGlobalCompo].forEach(componentWhoUsedGlobalCompo => {
                // 获取在 independentRoot 目录下的新路径(独立分包内引用的包外组件也有可能用到全局组件,获取该包外组件在独立分包内的新路径)
                componentWhoUsedGlobalCompo = getNewComponentPathInIndependentPkg(independentRoot, componentWhoUsedGlobalCompo);
 
                // 获取该组件json文件内容
                // 分包内的vue组件对应json存储在emitFileMap中
                // 分包外vue组件由于前面的复制,内容保存在assets中
                const {
                    content: pageObj,
                    fromAssetsFlag // json内容是否来自assets(还可能来自emiFileMap)
                } = getJsonByPageOrComponentPath(componentWhoUsedGlobalCompo, {
                    emitFileMap: this.emitFileMap, compilationAssets
                });
 
                const usingComponents = pageObj.usingComponents || {};
                // 如果没有同名标签,则使用全局组件(优先使用显示声明的标签-针对同名标签)
                if (!usingComponents[componentKey]) {
                    usingComponents[componentKey] = `/${globalComponentReplacePath}`;
                }
 
                // 如果json内容来自emiFileMap(可能还没同步到assets上
                // emitFileMap 后面会统一挂到assets上
                if (!fromAssetsFlag) return;
 
                delete pageObj.usingGlobalComponents
                compilationAssets[`${componentWhoUsedGlobalCompo}.json`] = generateAsset(JSON.stringify(pageObj));
            });
        }
    }
 
    updateOuterComponentReference (independentRoot) {
        const sourceRepo = {
            emitFileMap: this.emitFileMap,
            compilationAssets: this.compilation.assets
        };
        const independentPages = getIndependentEntryPages(independentRoot);
        recurIndependentJson(independentRoot, independentPages, sourceRepo, (usingComponents, componentKey) => {
            const componentPath = usingComponents[componentKey];
            const newComponentPath = getNewComponentPathInIndependentPkg(independentRoot, componentPath);
            if (newComponentPath && newComponentPath !== componentPath) {
                usingComponents[componentKey] = `/${newComponentPath}`;
            }
        });
    }
 
    copyOuterComponents (independentRoot, outerComponentSet, componentFileCache) {
        let copyFiles = collectPkgCopyFiles(outerComponentSet, componentFileCache);
        const thisCompilationAssets = this.compilation.assets;
        // TODO 组件依赖分许的时候需要记录 绝对路径(js/css/wxml) 进行模块引用的文件,输出后需要更改为相对路径,
        copyAllWxComponentsFiles(independentRoot, copyFiles, (originalFilePath, targetPath, relativePath) => {
            // 原生组件
            if (relativePath.indexOf(wxComponentsStr) >= 0) {
                return this.copyWxComponent(independentRoot, originalFilePath, targetPath);
            }
            // vue组件
            const assetInfo = thisCompilationAssets[relativePath];
            let assetSource = assetInfo && assetInfo.source();
 
            // json文件此时还没有同步到 assets 上
            if (!assetSource && relativePath.endsWith('.json')) {
                assetSource = JSON.stringify(this.emitFileMap.get(relativePath));
            }
 
            if (!assetSource) {
                console.error('independent.error', 'invalid assetSource');
            }
 
            const targetPrefix = `${independentRoot}/${outerComponents}`;
            const targetJsAssetName = `${targetPrefix}/${relativePath}`;
 
            if (relativePath.endsWith('.js')) {
                const originalAsset = thisCompilationAssets[relativePath];
                const originalSource = originalAsset && originalAsset.source;
                // 见 generate-component
                const __$wrappered = originalSource && originalSource.__$wrappered;
                if (__$wrappered) {
                    return;
                }
 
                const relativeToDist = path.relative(path.dirname(targetJsAssetName), `${independentRoot}/common/index.js`);
                assetSource = `require('${relativeToDist}');${assetSource}`;
            }
 
            thisCompilationAssets[targetJsAssetName] = generateAsset(assetSource);
        });
    }
}
 
module.exports = Index;