@arkxio/ark-dev-utils 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc.js +11 -11
- package/LICENSE +170 -0
- package/README.md +17 -17
- package/dist/{ark-dev-utils.js → index.js} +1114 -1114
- package/dist/{ark-dev-utils.min.js → index.min.js} +1114 -1114
- package/jsconfig.json +5 -5
- package/lib/index.js +1113 -1113
- package/package.json +10 -10
- package/src/base-utils/index.js +119 -119
- package/src/check.js +102 -102
- package/src/configs/consts.js +24 -24
- package/src/configs/externalsDefault.js +25 -25
- package/src/configs/presetExternals.js +18 -18
- package/src/index.js +33 -33
- package/src/inner-utils/arr.js +8 -8
- package/src/inner-utils/index.js +124 -124
- package/src/inner-utils/obj.js +45 -45
- package/src/inner-utils/str.js +75 -75
- package/src/meta-extractor/fillAssetList.js +428 -428
- package/src/meta-extractor/index.js +84 -84
- package/src/meta-extractor/parse.js +56 -56
- package/src/meta-extractor/utils.js +141 -141
- package/src/share/createSubApp.js +72 -72
- package/src/sub-app/createLibSubApp.js +12 -12
- package/src/sub-app/createReactSubApp.js +12 -12
- package/src/sub-app/createVueSubApp.js +12 -12
- package/typings.d.ts +295 -295
|
@@ -1,428 +1,428 @@
|
|
|
1
|
-
/** @typedef {import('../../typings').SrcMap} SrcMap*/
|
|
2
|
-
/** @typedef {import('../../typings').IAssetOptions} IAssetOptions*/
|
|
3
|
-
/** @typedef {import('../../typings').IAssetInfo} IAssetInfo */
|
|
4
|
-
/** @typedef {import('../../typings').IInnerFillAssetListOptions} IInnerFillAssetListOptions */
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import util from 'util';
|
|
7
|
-
import { slash } from '../base-utils/index';
|
|
8
|
-
import { noDupPush } from '../inner-utils/arr';
|
|
9
|
-
import { verbose } from '../inner-utils/index';
|
|
10
|
-
import { isNull } from '../inner-utils/obj';
|
|
11
|
-
import { pfstr } from '../inner-utils/str';
|
|
12
|
-
import { getAllFilePath } from './utils';
|
|
13
|
-
|
|
14
|
-
const writeFile = util.promisify(fs.writeFile);
|
|
15
|
-
|
|
16
|
-
/** jsdom 15 里去内联脚本 innerText 取不到,这里用此函数辅助 */
|
|
17
|
-
function getInnerText(/** @type {HTMLElement}} */ dom) {
|
|
18
|
-
return dom.innerText || dom.innerHTML || '';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getDatasetVal(dataset, key, defaultValIfOnlyKey) {
|
|
22
|
-
const hasKey = Object.prototype.hasOwnProperty.call(dataset, key);
|
|
23
|
-
if (hasKey) {
|
|
24
|
-
return dataset[key] || defaultValIfOnlyKey;
|
|
25
|
-
}
|
|
26
|
-
return '';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isRelativePath(path) {
|
|
30
|
-
if (path.startsWith('//')) return false;
|
|
31
|
-
return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function buildAssetItem(tag, /** @type {IAssetInfo} */ assetInfo) {
|
|
35
|
-
const isLink = tag === 'link';
|
|
36
|
-
const { isBuildUrl, isNonBuildAndRelative, canAppend, el, url, innerText } = assetInfo;
|
|
37
|
-
let attrs = {};
|
|
38
|
-
if (url) {
|
|
39
|
-
attrs = isLink ? { href: url } : { src: url };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let tagVar = '';
|
|
43
|
-
if (isBuildUrl) {
|
|
44
|
-
tagVar = tag;
|
|
45
|
-
} else if (isNonBuildAndRelative) {
|
|
46
|
-
tagVar = isLink ? 'relativeLink' : 'relativeScript';
|
|
47
|
-
} else {
|
|
48
|
-
tagVar = isLink ? 'staticLink' : 'staticScript';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const assetItem = { tag: tagVar, append: canAppend, attrs };
|
|
52
|
-
if (innerText) {
|
|
53
|
-
assetItem.innerText = innerText;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const attrNames = el.getAttributeNames();
|
|
57
|
-
attrNames.forEach((name) => {
|
|
58
|
-
// src href 上面已记录真正的目标值,故移除
|
|
59
|
-
// data-helappend 只在提取元数据辅助计算 append 值时用到,故此处移除
|
|
60
|
-
if (['src', 'href', 'data-helappend'].includes(name)) return;
|
|
61
|
-
attrs[name] = el.getAttribute(name);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return assetItem;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 注意:这个变量需要在每次编译时重置
|
|
68
|
-
// 不在这里维护,而是通过 options 传递
|
|
69
|
-
let custScriptIdx = 0;
|
|
70
|
-
|
|
71
|
-
export function resetScriptIdx() {
|
|
72
|
-
custScriptIdx = 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param {HTMLScriptElement | HTMLStyleElement} childDom
|
|
77
|
-
* @param {string} fileType
|
|
78
|
-
* @param {IInnerFillAssetListOptions} options
|
|
79
|
-
*/
|
|
80
|
-
async function writeInnerText(childDom, fileType, options) {
|
|
81
|
-
const { homePage, buildDirFullPath, compilation, customAssets = [] } = options;
|
|
82
|
-
let innerText = getInnerText(childDom);
|
|
83
|
-
if (!innerText) return '';
|
|
84
|
-
|
|
85
|
-
verbose(`found a user customized ${fileType} tag node in html, try extract its content and write them to local fs`);
|
|
86
|
-
custScriptIdx += 1;
|
|
87
|
-
const scriptName = `ark_userChunk_${custScriptIdx}.${fileType}`;
|
|
88
|
-
const fileAbsolutePath = `${buildDirFullPath}/${scriptName}`;
|
|
89
|
-
const fileWebPath = `${slash.noEnd(homePage)}/${scriptName}`;
|
|
90
|
-
|
|
91
|
-
// 对于 JS 文件,移除或替换 document.write() 调用
|
|
92
|
-
// 因为异步加载的脚本中不能使用 document.write()
|
|
93
|
-
if (fileType === 'js') {
|
|
94
|
-
// 方法1:完全移除 document.write() 调用(推荐)
|
|
95
|
-
// innerText = innerText.replace(/document\.write\s*\([^)]*\)/g, '');
|
|
96
|
-
|
|
97
|
-
// 方法2:用 console.warn 替换 document.write(),保留代码逻辑但避免错误
|
|
98
|
-
innerText = innerText.replace(
|
|
99
|
-
/document\.write\s*\(([^)]*)\)/g,
|
|
100
|
-
'console.warn("[ark-micro] document.write is not allowed in asynchronously loaded scripts:", $1)'
|
|
101
|
-
);
|
|
102
|
-
verbose(`Replaced document.write calls in ${scriptName}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 在开发模式下,使用 compilation.emitAsset 确保文件能被 dev server 访问
|
|
106
|
-
if (compilation && compilation.emitAsset) {
|
|
107
|
-
const { sources } = compilation.compiler.webpack;
|
|
108
|
-
compilation.emitAsset(
|
|
109
|
-
scriptName,
|
|
110
|
-
new sources.RawSource(innerText)
|
|
111
|
-
);
|
|
112
|
-
verbose(`emit asset ${scriptName} via webpack compilation`);
|
|
113
|
-
} else {
|
|
114
|
-
// 生产模式或没有 compilation 对象时,直接写入文件系统
|
|
115
|
-
await writeFile(fileAbsolutePath, innerText);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// 将自定义资产信息保存到 customAssets 数组,供后续使用
|
|
119
|
-
if (customAssets) {
|
|
120
|
-
customAssets.push({ scriptName, fileWebPath, content: innerText });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
verbose(`write done, the web file will be ${fileWebPath} later`);
|
|
124
|
-
return fileWebPath;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @param {string} url
|
|
129
|
-
* @param {IAssetOptions} options
|
|
130
|
-
*/
|
|
131
|
-
function getAssetInfo(url, options) {
|
|
132
|
-
const { homePage, extractMode, enableRelativePath, enableAssetInnerText, el, innerText } = options;
|
|
133
|
-
const { dataset = {} } = el;
|
|
134
|
-
// 是构建生成的产物路径,无 url 的当做是内联的处理,isBuildUrl 强制为 true
|
|
135
|
-
const isBuildUrl = url ? url.startsWith(homePage) : true;
|
|
136
|
-
const isRelative = isRelativePath(url);
|
|
137
|
-
// 是 homePage 之外相对路径导入的产物路径
|
|
138
|
-
const isNonBuildAndRelative = !isBuildUrl && isRelative;
|
|
139
|
-
const isStatic = !isBuildUrl && !isRelative;
|
|
140
|
-
const isIcoAsset = url.endsWith('.ico');
|
|
141
|
-
// 设置了 extractMode 为 build 和 build_no_html 时,当前产物路径是非构建生成的,则直接忽略,不记录到 assetList 数据里
|
|
142
|
-
const ignoreAddToAssetList = !isBuildUrl && (extractMode === 'build' || extractMode === 'build_no_html');
|
|
143
|
-
let allowAddToAssetList = !ignoreAddToAssetList;
|
|
144
|
-
if (!allowAddToAssetList) {
|
|
145
|
-
verbose(` >>> ignore add asset [${url}] to assetList by extractMode=${extractMode}`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 未显式设置 data-helappend 时,helappend 默认值会走内部逻辑来决定如何赋值
|
|
149
|
-
let helAppendValOfDataset = getDatasetVal(dataset, 'helappend', '1');
|
|
150
|
-
let helAppendValOfInnerLogic = '';
|
|
151
|
-
if (helAppendValOfDataset && !['1', '0'].includes(helAppendValOfDataset)) {
|
|
152
|
-
throw new Error(`found invalid helappend value [${helAppendValOfDataset}], only accpet 1 or 0 currently`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const ex = dataset.helex || '';
|
|
156
|
-
if (isNonBuildAndRelative) {
|
|
157
|
-
if (isIcoAsset && !helAppendValOfDataset) {
|
|
158
|
-
helAppendValOfDataset = '0'; // ico 文件特殊处理,默认是不加载的
|
|
159
|
-
} else if (ex && !helAppendValOfDataset) {
|
|
160
|
-
helAppendValOfDataset = '1'; // 标记了 ex 的文件特殊处理,默认需要加载,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 如下面错误描述所示,在既没有设置 enableRelativePath=true,又没有显式的标记 data-helappend 的情况下
|
|
164
|
-
// 不允许 homePage 之外的相对路径导入的资源存在
|
|
165
|
-
// 所以对于此类 homePage 之外的相对路径导入的资源,要么用户设置 enableRelativePath=true,要么显式的标记 data-helappend
|
|
166
|
-
// 设置 enableRelativePath=true 后,优先读可能已存在的 data-helappend 值,没有则默认为 0,表示不加载
|
|
167
|
-
// 不设置 enableRelativePath=true 的话,则需要用户一定标记 data-helappend 值
|
|
168
|
-
if (!enableRelativePath && !helAppendValOfDataset) {
|
|
169
|
-
throw new Error(
|
|
170
|
-
pfstr(`
|
|
171
|
-
found asset url [${url}] is a relative path, it is obviously not a valid url for cdn architecture deploy!
|
|
172
|
-
but if you are sure this url is valid, there are 2 ways to skip this error occured, you can choose any one of them:
|
|
173
|
-
1. pass enableRelativePath true to ark-dev-utils.extractArkMetaJson method options.
|
|
174
|
-
2. add data-helappend="0" on the asset dom attribute to tell ark-dev-utils ignore this asset.
|
|
175
|
-
|
|
176
|
-
ark-dev-utils will mark this url as relativeLink or relativeScript, and set append as false,
|
|
177
|
-
if you want sdk append this asset, you can explicitly add data-helappend="1" on the asset dom attribute.
|
|
178
|
-
a demo will be like:<script src="./a/b.js" data-helappend="1"></script>,<br/>
|
|
179
|
-
note that the asset will depend on your host site seriously under this situation.
|
|
180
|
-
`),
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 构建时设置 enableRelativePath=true,则允许此类【homePage之外相对路径导入的产物】加入到资源清单列表里
|
|
185
|
-
allowAddToAssetList = true;
|
|
186
|
-
helAppendValOfInnerLogic = helAppendValOfDataset || '0'; // 没有显示设定 data-helappend 时默认标记为不加载
|
|
187
|
-
} else if (isIcoAsset) {
|
|
188
|
-
helAppendValOfInnerLogic = '0'; // ico 文件特殊处理,默认是不加载的
|
|
189
|
-
} else if (isStatic) {
|
|
190
|
-
if (ex) {
|
|
191
|
-
// 对于标记了 helex 的元素,默认是 append 的,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
192
|
-
// 重复则不追加,不重复则追加
|
|
193
|
-
helAppendValOfInnerLogic = helAppendValOfDataset || '1';
|
|
194
|
-
} else {
|
|
195
|
-
helAppendValOfInnerLogic = '0';
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
// isBuild
|
|
199
|
-
helAppendValOfInnerLogic = '1'; // 标记为可加载
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const helAppendVal = helAppendValOfDataset || helAppendValOfInnerLogic;
|
|
203
|
-
const helAppend = helAppendVal === '1';
|
|
204
|
-
if (ex && !helAppend) {
|
|
205
|
-
// 设置了 helex 但同时设置了不加载,会造成歧义,当前版本是不允许的
|
|
206
|
-
throw new Error(
|
|
207
|
-
pfstr(`
|
|
208
|
-
found conflict setting for helex: [data-helex="${ex}"]、[data-helappend="${helAppendVal}"],
|
|
209
|
-
remove data-helappend( append is true for helex by default ) or set data-helappend="1"!
|
|
210
|
-
`),
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
url,
|
|
216
|
-
el,
|
|
217
|
-
isBuildUrl,
|
|
218
|
-
isNonBuildAndRelative,
|
|
219
|
-
canAppend: helAppend,
|
|
220
|
-
allowAddToAssetList,
|
|
221
|
-
innerText: enableAssetInnerText ? innerText : '',
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* 提取link、script标签数据并填充到目标assetList
|
|
227
|
-
* @param {HTMLCollectionOf<HTMLScriptElement>} doms
|
|
228
|
-
* @param {IInnerFillAssetListOptions} options
|
|
229
|
-
*/
|
|
230
|
-
export async function fillAssetList(doms, options) {
|
|
231
|
-
const {
|
|
232
|
-
homePage,
|
|
233
|
-
enableReplaceDevJs = true,
|
|
234
|
-
enableRelativePath = false,
|
|
235
|
-
enableAssetInnerText = false,
|
|
236
|
-
enablePrefixHomePage = false,
|
|
237
|
-
srcMap,
|
|
238
|
-
isHead,
|
|
239
|
-
} = options;
|
|
240
|
-
const {
|
|
241
|
-
headAssetList,
|
|
242
|
-
bodyAssetList,
|
|
243
|
-
extractMode,
|
|
244
|
-
chunkCssSrcList,
|
|
245
|
-
staticCssSrcList,
|
|
246
|
-
relativeCssSrcList,
|
|
247
|
-
chunkJsSrcList,
|
|
248
|
-
staticJsSrcList,
|
|
249
|
-
relativeJsSrcList,
|
|
250
|
-
} = srcMap;
|
|
251
|
-
const assetList = isHead ? headAssetList : bodyAssetList;
|
|
252
|
-
|
|
253
|
-
const len = doms.length;
|
|
254
|
-
const replaceContentList = [];
|
|
255
|
-
verbose(`extractMode is ${extractMode}`);
|
|
256
|
-
verbose(`enableAssetInnerText is ${enableAssetInnerText}`);
|
|
257
|
-
|
|
258
|
-
const pushToSrcList = (assetType, /** @type {IAssetInfo} */ assetInfo) => {
|
|
259
|
-
const { isBuildUrl, isNonBuildAndRelative, url } = assetInfo;
|
|
260
|
-
const isAllExtractMode = extractMode === 'all' || extractMode === 'all_no_html';
|
|
261
|
-
|
|
262
|
-
if (assetType === 'css') {
|
|
263
|
-
if (isBuildUrl) {
|
|
264
|
-
return noDupPush(chunkCssSrcList, url);
|
|
265
|
-
}
|
|
266
|
-
if (isAllExtractMode) {
|
|
267
|
-
const list = isNonBuildAndRelative ? relativeCssSrcList : staticCssSrcList;
|
|
268
|
-
return noDupPush(list, url);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (isBuildUrl) {
|
|
273
|
-
return noDupPush(chunkJsSrcList, url);
|
|
274
|
-
}
|
|
275
|
-
if (isAllExtractMode) {
|
|
276
|
-
const list = isNonBuildAndRelative ? relativeJsSrcList : staticJsSrcList;
|
|
277
|
-
return noDupPush(list, url);
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const mayPrefixHomePage = (url) => {
|
|
282
|
-
if (enablePrefixHomePage) {
|
|
283
|
-
if (url.startsWith('/') && !url.startsWith('//')) {
|
|
284
|
-
verbose(` >>> prefix homePage [${homePage}] for url [${url}]`);
|
|
285
|
-
const finalUrl = `${slash.noEnd(homePage)}${url}`;
|
|
286
|
-
replaceContentList.push({ toMatch: `="${url}"`, toReplace: `="${finalUrl}"` });
|
|
287
|
-
return finalUrl;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return url;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
for (let i = 0; i < len; i++) {
|
|
294
|
-
const childDom = doms[i];
|
|
295
|
-
const { tagName, dataset = {} } = childDom;
|
|
296
|
-
const innerText = getInnerText(childDom);
|
|
297
|
-
let toPushAsset = null;
|
|
298
|
-
let allowAddToAssetList = false;
|
|
299
|
-
const assetOptions = { el: childDom, innerText, homePage, extractMode, enableRelativePath, enableAssetInnerText };
|
|
300
|
-
|
|
301
|
-
if (!['LINK', 'SCRIPT', 'STYLE'].includes(tagName)) {
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
if (!isNull(dataset)) {
|
|
305
|
-
verbose(`found ${tagName} dataset`, dataset);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (tagName === 'LINK') {
|
|
309
|
-
const { hreflang = '', href: oriHref } = childDom;
|
|
310
|
-
verbose(`analyze link href:[${oriHref}]`);
|
|
311
|
-
if (!oriHref) continue;
|
|
312
|
-
|
|
313
|
-
let href = mayPrefixHomePage(oriHref);
|
|
314
|
-
verbose(`analyze link [${href}]`);
|
|
315
|
-
// 一些使用了老版本cra的项目,这两个href 在修改了 publicPath 后也不被添加前缀,这里做一下修正
|
|
316
|
-
const legacyHrefs = ['/manifest.json', '/favicon.ico'];
|
|
317
|
-
if (legacyHrefs.includes(href)) {
|
|
318
|
-
const oldHref = href;
|
|
319
|
-
href = `${homePage}${href}`;
|
|
320
|
-
replaceContentList.push({ toMatch: href, toReplace: href });
|
|
321
|
-
verbose(`replace link [${oldHref}] to [href]`);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const assetInfo = getAssetInfo(href, assetOptions);
|
|
325
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
326
|
-
// 供 shadow-dom 或其他需要知道当前应用所有样式列表的场景用
|
|
327
|
-
if (href.endsWith('.css')) {
|
|
328
|
-
pushToSrcList('css', assetInfo);
|
|
329
|
-
}
|
|
330
|
-
toPushAsset = buildAssetItem('link', assetInfo);
|
|
331
|
-
} else if (tagName === 'SCRIPT') {
|
|
332
|
-
const { src } = childDom;
|
|
333
|
-
verbose(`analyze script src:[${src}], innerText:[${innerText}]`);
|
|
334
|
-
|
|
335
|
-
if (!src && !innerText) {
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (!src && innerText && enableAssetInnerText) {
|
|
340
|
-
verbose('user set enableAssetInnerText = true and found SCRIPT node innerText');
|
|
341
|
-
const assetInfo = getAssetInfo('', assetOptions);
|
|
342
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
343
|
-
toPushAsset = buildAssetItem('script', assetInfo);
|
|
344
|
-
} else {
|
|
345
|
-
let targetSrc = src;
|
|
346
|
-
if (!targetSrc) {
|
|
347
|
-
targetSrc = await writeInnerText(childDom, 'js', options);
|
|
348
|
-
}
|
|
349
|
-
if (!targetSrc) continue;
|
|
350
|
-
|
|
351
|
-
targetSrc = mayPrefixHomePage(targetSrc);
|
|
352
|
-
if (enableReplaceDevJs) {
|
|
353
|
-
// 替换用户的 development 模式的 react 相关包体
|
|
354
|
-
if (targetSrc.endsWith('react.dev.js')) {
|
|
355
|
-
const toReplace = targetSrc.replace('react.dev.js', 'react.js');
|
|
356
|
-
replaceContentList.push({ toMatch: targetSrc, toReplace });
|
|
357
|
-
targetSrc = toReplace;
|
|
358
|
-
}
|
|
359
|
-
// 替换用户的 development 模式的 vue 相关包体
|
|
360
|
-
if (targetSrc.endsWith('vue.dev.js')) {
|
|
361
|
-
const toReplace = targetSrc.replace('vue.dev.js', 'vue.js');
|
|
362
|
-
replaceContentList.push({ toMatch: targetSrc, toReplace });
|
|
363
|
-
targetSrc = toReplace;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const assetInfo = getAssetInfo(targetSrc, assetOptions);
|
|
368
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
369
|
-
|
|
370
|
-
pushToSrcList('js', assetInfo);
|
|
371
|
-
toPushAsset = buildAssetItem('script', assetInfo);
|
|
372
|
-
}
|
|
373
|
-
} else if (tagName === 'STYLE') {
|
|
374
|
-
verbose(`analyze style innerText:[${innerText}]`);
|
|
375
|
-
|
|
376
|
-
if (innerText && enableAssetInnerText) {
|
|
377
|
-
verbose('user set enableAssetInnerText = true and found STYLE node innerText');
|
|
378
|
-
const assetInfo = getAssetInfo('', assetOptions);
|
|
379
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
380
|
-
toPushAsset = buildAssetItem('style', assetInfo);
|
|
381
|
-
} else {
|
|
382
|
-
// style 标签转换为 css 文件存起来,以便让 @arkxio/ark-micro 用 link 标签加载
|
|
383
|
-
let href = await writeInnerText(childDom, 'css', options);
|
|
384
|
-
if (!href) continue;
|
|
385
|
-
|
|
386
|
-
href = mayPrefixHomePage(href);
|
|
387
|
-
const assetInfo = getAssetInfo(href, assetOptions);
|
|
388
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
389
|
-
|
|
390
|
-
pushToSrcList('css', assetInfo);
|
|
391
|
-
toPushAsset = buildAssetItem('link', assetInfo);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (toPushAsset && allowAddToAssetList) {
|
|
396
|
-
assetList.push(toPushAsset);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return replaceContentList;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* @param {IInnerFillAssetListOptions} options
|
|
405
|
-
*/
|
|
406
|
-
export async function fillAssetListByDist(options) {
|
|
407
|
-
const { homePage, srcMap, buildDirFullPath } = options;
|
|
408
|
-
const fileFullPathList = getAllFilePath(buildDirFullPath);
|
|
409
|
-
verbose('filePathList', fileFullPathList);
|
|
410
|
-
|
|
411
|
-
fileFullPathList.forEach((fileAbsolutePath) => {
|
|
412
|
-
// 获取文件处于build目录下的相对路径,形如:
|
|
413
|
-
// /static/js/runtime-main.66c45929.js
|
|
414
|
-
// /asset-manifest.json
|
|
415
|
-
const filePathUnderBuild = fileAbsolutePath.split(buildDirFullPath)[1];
|
|
416
|
-
// 拼出 web 路径
|
|
417
|
-
const fileWebPath = `${slash.noEnd(homePage)}${slash.start(filePathUnderBuild)}`;
|
|
418
|
-
|
|
419
|
-
// 补上剩余的 css 文件路径
|
|
420
|
-
if (fileWebPath.endsWith('.css')) {
|
|
421
|
-
noDupPush(srcMap.chunkCssSrcList, fileWebPath);
|
|
422
|
-
} else if (fileWebPath.endsWith('.js')) {
|
|
423
|
-
noDupPush(srcMap.chunkJsSrcList, fileWebPath);
|
|
424
|
-
} else {
|
|
425
|
-
noDupPush(srcMap.otherSrcList, fileWebPath);
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
}
|
|
1
|
+
/** @typedef {import('../../typings').SrcMap} SrcMap*/
|
|
2
|
+
/** @typedef {import('../../typings').IAssetOptions} IAssetOptions*/
|
|
3
|
+
/** @typedef {import('../../typings').IAssetInfo} IAssetInfo */
|
|
4
|
+
/** @typedef {import('../../typings').IInnerFillAssetListOptions} IInnerFillAssetListOptions */
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import util from 'util';
|
|
7
|
+
import { slash } from '../base-utils/index';
|
|
8
|
+
import { noDupPush } from '../inner-utils/arr';
|
|
9
|
+
import { verbose } from '../inner-utils/index';
|
|
10
|
+
import { isNull } from '../inner-utils/obj';
|
|
11
|
+
import { pfstr } from '../inner-utils/str';
|
|
12
|
+
import { getAllFilePath } from './utils';
|
|
13
|
+
|
|
14
|
+
const writeFile = util.promisify(fs.writeFile);
|
|
15
|
+
|
|
16
|
+
/** jsdom 15 里去内联脚本 innerText 取不到,这里用此函数辅助 */
|
|
17
|
+
function getInnerText(/** @type {HTMLElement}} */ dom) {
|
|
18
|
+
return dom.innerText || dom.innerHTML || '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getDatasetVal(dataset, key, defaultValIfOnlyKey) {
|
|
22
|
+
const hasKey = Object.prototype.hasOwnProperty.call(dataset, key);
|
|
23
|
+
if (hasKey) {
|
|
24
|
+
return dataset[key] || defaultValIfOnlyKey;
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isRelativePath(path) {
|
|
30
|
+
if (path.startsWith('//')) return false;
|
|
31
|
+
return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildAssetItem(tag, /** @type {IAssetInfo} */ assetInfo) {
|
|
35
|
+
const isLink = tag === 'link';
|
|
36
|
+
const { isBuildUrl, isNonBuildAndRelative, canAppend, el, url, innerText } = assetInfo;
|
|
37
|
+
let attrs = {};
|
|
38
|
+
if (url) {
|
|
39
|
+
attrs = isLink ? { href: url } : { src: url };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let tagVar = '';
|
|
43
|
+
if (isBuildUrl) {
|
|
44
|
+
tagVar = tag;
|
|
45
|
+
} else if (isNonBuildAndRelative) {
|
|
46
|
+
tagVar = isLink ? 'relativeLink' : 'relativeScript';
|
|
47
|
+
} else {
|
|
48
|
+
tagVar = isLink ? 'staticLink' : 'staticScript';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const assetItem = { tag: tagVar, append: canAppend, attrs };
|
|
52
|
+
if (innerText) {
|
|
53
|
+
assetItem.innerText = innerText;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const attrNames = el.getAttributeNames();
|
|
57
|
+
attrNames.forEach((name) => {
|
|
58
|
+
// src href 上面已记录真正的目标值,故移除
|
|
59
|
+
// data-helappend 只在提取元数据辅助计算 append 值时用到,故此处移除
|
|
60
|
+
if (['src', 'href', 'data-helappend'].includes(name)) return;
|
|
61
|
+
attrs[name] = el.getAttribute(name);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return assetItem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 注意:这个变量需要在每次编译时重置
|
|
68
|
+
// 不在这里维护,而是通过 options 传递
|
|
69
|
+
let custScriptIdx = 0;
|
|
70
|
+
|
|
71
|
+
export function resetScriptIdx() {
|
|
72
|
+
custScriptIdx = 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {HTMLScriptElement | HTMLStyleElement} childDom
|
|
77
|
+
* @param {string} fileType
|
|
78
|
+
* @param {IInnerFillAssetListOptions} options
|
|
79
|
+
*/
|
|
80
|
+
async function writeInnerText(childDom, fileType, options) {
|
|
81
|
+
const { homePage, buildDirFullPath, compilation, customAssets = [] } = options;
|
|
82
|
+
let innerText = getInnerText(childDom);
|
|
83
|
+
if (!innerText) return '';
|
|
84
|
+
|
|
85
|
+
verbose(`found a user customized ${fileType} tag node in html, try extract its content and write them to local fs`);
|
|
86
|
+
custScriptIdx += 1;
|
|
87
|
+
const scriptName = `ark_userChunk_${custScriptIdx}.${fileType}`;
|
|
88
|
+
const fileAbsolutePath = `${buildDirFullPath}/${scriptName}`;
|
|
89
|
+
const fileWebPath = `${slash.noEnd(homePage)}/${scriptName}`;
|
|
90
|
+
|
|
91
|
+
// 对于 JS 文件,移除或替换 document.write() 调用
|
|
92
|
+
// 因为异步加载的脚本中不能使用 document.write()
|
|
93
|
+
if (fileType === 'js') {
|
|
94
|
+
// 方法1:完全移除 document.write() 调用(推荐)
|
|
95
|
+
// innerText = innerText.replace(/document\.write\s*\([^)]*\)/g, '');
|
|
96
|
+
|
|
97
|
+
// 方法2:用 console.warn 替换 document.write(),保留代码逻辑但避免错误
|
|
98
|
+
innerText = innerText.replace(
|
|
99
|
+
/document\.write\s*\(([^)]*)\)/g,
|
|
100
|
+
'console.warn("[ark-micro] document.write is not allowed in asynchronously loaded scripts:", $1)'
|
|
101
|
+
);
|
|
102
|
+
verbose(`Replaced document.write calls in ${scriptName}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 在开发模式下,使用 compilation.emitAsset 确保文件能被 dev server 访问
|
|
106
|
+
if (compilation && compilation.emitAsset) {
|
|
107
|
+
const { sources } = compilation.compiler.webpack;
|
|
108
|
+
compilation.emitAsset(
|
|
109
|
+
scriptName,
|
|
110
|
+
new sources.RawSource(innerText)
|
|
111
|
+
);
|
|
112
|
+
verbose(`emit asset ${scriptName} via webpack compilation`);
|
|
113
|
+
} else {
|
|
114
|
+
// 生产模式或没有 compilation 对象时,直接写入文件系统
|
|
115
|
+
await writeFile(fileAbsolutePath, innerText);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 将自定义资产信息保存到 customAssets 数组,供后续使用
|
|
119
|
+
if (customAssets) {
|
|
120
|
+
customAssets.push({ scriptName, fileWebPath, content: innerText });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
verbose(`write done, the web file will be ${fileWebPath} later`);
|
|
124
|
+
return fileWebPath;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {string} url
|
|
129
|
+
* @param {IAssetOptions} options
|
|
130
|
+
*/
|
|
131
|
+
function getAssetInfo(url, options) {
|
|
132
|
+
const { homePage, extractMode, enableRelativePath, enableAssetInnerText, el, innerText } = options;
|
|
133
|
+
const { dataset = {} } = el;
|
|
134
|
+
// 是构建生成的产物路径,无 url 的当做是内联的处理,isBuildUrl 强制为 true
|
|
135
|
+
const isBuildUrl = url ? url.startsWith(homePage) : true;
|
|
136
|
+
const isRelative = isRelativePath(url);
|
|
137
|
+
// 是 homePage 之外相对路径导入的产物路径
|
|
138
|
+
const isNonBuildAndRelative = !isBuildUrl && isRelative;
|
|
139
|
+
const isStatic = !isBuildUrl && !isRelative;
|
|
140
|
+
const isIcoAsset = url.endsWith('.ico');
|
|
141
|
+
// 设置了 extractMode 为 build 和 build_no_html 时,当前产物路径是非构建生成的,则直接忽略,不记录到 assetList 数据里
|
|
142
|
+
const ignoreAddToAssetList = !isBuildUrl && (extractMode === 'build' || extractMode === 'build_no_html');
|
|
143
|
+
let allowAddToAssetList = !ignoreAddToAssetList;
|
|
144
|
+
if (!allowAddToAssetList) {
|
|
145
|
+
verbose(` >>> ignore add asset [${url}] to assetList by extractMode=${extractMode}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 未显式设置 data-helappend 时,helappend 默认值会走内部逻辑来决定如何赋值
|
|
149
|
+
let helAppendValOfDataset = getDatasetVal(dataset, 'helappend', '1');
|
|
150
|
+
let helAppendValOfInnerLogic = '';
|
|
151
|
+
if (helAppendValOfDataset && !['1', '0'].includes(helAppendValOfDataset)) {
|
|
152
|
+
throw new Error(`found invalid helappend value [${helAppendValOfDataset}], only accpet 1 or 0 currently`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const ex = dataset.helex || '';
|
|
156
|
+
if (isNonBuildAndRelative) {
|
|
157
|
+
if (isIcoAsset && !helAppendValOfDataset) {
|
|
158
|
+
helAppendValOfDataset = '0'; // ico 文件特殊处理,默认是不加载的
|
|
159
|
+
} else if (ex && !helAppendValOfDataset) {
|
|
160
|
+
helAppendValOfDataset = '1'; // 标记了 ex 的文件特殊处理,默认需要加载,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 如下面错误描述所示,在既没有设置 enableRelativePath=true,又没有显式的标记 data-helappend 的情况下
|
|
164
|
+
// 不允许 homePage 之外的相对路径导入的资源存在
|
|
165
|
+
// 所以对于此类 homePage 之外的相对路径导入的资源,要么用户设置 enableRelativePath=true,要么显式的标记 data-helappend
|
|
166
|
+
// 设置 enableRelativePath=true 后,优先读可能已存在的 data-helappend 值,没有则默认为 0,表示不加载
|
|
167
|
+
// 不设置 enableRelativePath=true 的话,则需要用户一定标记 data-helappend 值
|
|
168
|
+
if (!enableRelativePath && !helAppendValOfDataset) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
pfstr(`
|
|
171
|
+
found asset url [${url}] is a relative path, it is obviously not a valid url for cdn architecture deploy!
|
|
172
|
+
but if you are sure this url is valid, there are 2 ways to skip this error occured, you can choose any one of them:
|
|
173
|
+
1. pass enableRelativePath true to ark-dev-utils.extractArkMetaJson method options.
|
|
174
|
+
2. add data-helappend="0" on the asset dom attribute to tell ark-dev-utils ignore this asset.
|
|
175
|
+
|
|
176
|
+
ark-dev-utils will mark this url as relativeLink or relativeScript, and set append as false,
|
|
177
|
+
if you want sdk append this asset, you can explicitly add data-helappend="1" on the asset dom attribute.
|
|
178
|
+
a demo will be like:<script src="./a/b.js" data-helappend="1"></script>,<br/>
|
|
179
|
+
note that the asset will depend on your host site seriously under this situation.
|
|
180
|
+
`),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 构建时设置 enableRelativePath=true,则允许此类【homePage之外相对路径导入的产物】加入到资源清单列表里
|
|
185
|
+
allowAddToAssetList = true;
|
|
186
|
+
helAppendValOfInnerLogic = helAppendValOfDataset || '0'; // 没有显示设定 data-helappend 时默认标记为不加载
|
|
187
|
+
} else if (isIcoAsset) {
|
|
188
|
+
helAppendValOfInnerLogic = '0'; // ico 文件特殊处理,默认是不加载的
|
|
189
|
+
} else if (isStatic) {
|
|
190
|
+
if (ex) {
|
|
191
|
+
// 对于标记了 helex 的元素,默认是 append 的,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
192
|
+
// 重复则不追加,不重复则追加
|
|
193
|
+
helAppendValOfInnerLogic = helAppendValOfDataset || '1';
|
|
194
|
+
} else {
|
|
195
|
+
helAppendValOfInnerLogic = '0';
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
// isBuild
|
|
199
|
+
helAppendValOfInnerLogic = '1'; // 标记为可加载
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const helAppendVal = helAppendValOfDataset || helAppendValOfInnerLogic;
|
|
203
|
+
const helAppend = helAppendVal === '1';
|
|
204
|
+
if (ex && !helAppend) {
|
|
205
|
+
// 设置了 helex 但同时设置了不加载,会造成歧义,当前版本是不允许的
|
|
206
|
+
throw new Error(
|
|
207
|
+
pfstr(`
|
|
208
|
+
found conflict setting for helex: [data-helex="${ex}"]、[data-helappend="${helAppendVal}"],
|
|
209
|
+
remove data-helappend( append is true for helex by default ) or set data-helappend="1"!
|
|
210
|
+
`),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
url,
|
|
216
|
+
el,
|
|
217
|
+
isBuildUrl,
|
|
218
|
+
isNonBuildAndRelative,
|
|
219
|
+
canAppend: helAppend,
|
|
220
|
+
allowAddToAssetList,
|
|
221
|
+
innerText: enableAssetInnerText ? innerText : '',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 提取link、script标签数据并填充到目标assetList
|
|
227
|
+
* @param {HTMLCollectionOf<HTMLScriptElement>} doms
|
|
228
|
+
* @param {IInnerFillAssetListOptions} options
|
|
229
|
+
*/
|
|
230
|
+
export async function fillAssetList(doms, options) {
|
|
231
|
+
const {
|
|
232
|
+
homePage,
|
|
233
|
+
enableReplaceDevJs = true,
|
|
234
|
+
enableRelativePath = false,
|
|
235
|
+
enableAssetInnerText = false,
|
|
236
|
+
enablePrefixHomePage = false,
|
|
237
|
+
srcMap,
|
|
238
|
+
isHead,
|
|
239
|
+
} = options;
|
|
240
|
+
const {
|
|
241
|
+
headAssetList,
|
|
242
|
+
bodyAssetList,
|
|
243
|
+
extractMode,
|
|
244
|
+
chunkCssSrcList,
|
|
245
|
+
staticCssSrcList,
|
|
246
|
+
relativeCssSrcList,
|
|
247
|
+
chunkJsSrcList,
|
|
248
|
+
staticJsSrcList,
|
|
249
|
+
relativeJsSrcList,
|
|
250
|
+
} = srcMap;
|
|
251
|
+
const assetList = isHead ? headAssetList : bodyAssetList;
|
|
252
|
+
|
|
253
|
+
const len = doms.length;
|
|
254
|
+
const replaceContentList = [];
|
|
255
|
+
verbose(`extractMode is ${extractMode}`);
|
|
256
|
+
verbose(`enableAssetInnerText is ${enableAssetInnerText}`);
|
|
257
|
+
|
|
258
|
+
const pushToSrcList = (assetType, /** @type {IAssetInfo} */ assetInfo) => {
|
|
259
|
+
const { isBuildUrl, isNonBuildAndRelative, url } = assetInfo;
|
|
260
|
+
const isAllExtractMode = extractMode === 'all' || extractMode === 'all_no_html';
|
|
261
|
+
|
|
262
|
+
if (assetType === 'css') {
|
|
263
|
+
if (isBuildUrl) {
|
|
264
|
+
return noDupPush(chunkCssSrcList, url);
|
|
265
|
+
}
|
|
266
|
+
if (isAllExtractMode) {
|
|
267
|
+
const list = isNonBuildAndRelative ? relativeCssSrcList : staticCssSrcList;
|
|
268
|
+
return noDupPush(list, url);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (isBuildUrl) {
|
|
273
|
+
return noDupPush(chunkJsSrcList, url);
|
|
274
|
+
}
|
|
275
|
+
if (isAllExtractMode) {
|
|
276
|
+
const list = isNonBuildAndRelative ? relativeJsSrcList : staticJsSrcList;
|
|
277
|
+
return noDupPush(list, url);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const mayPrefixHomePage = (url) => {
|
|
282
|
+
if (enablePrefixHomePage) {
|
|
283
|
+
if (url.startsWith('/') && !url.startsWith('//')) {
|
|
284
|
+
verbose(` >>> prefix homePage [${homePage}] for url [${url}]`);
|
|
285
|
+
const finalUrl = `${slash.noEnd(homePage)}${url}`;
|
|
286
|
+
replaceContentList.push({ toMatch: `="${url}"`, toReplace: `="${finalUrl}"` });
|
|
287
|
+
return finalUrl;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return url;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
for (let i = 0; i < len; i++) {
|
|
294
|
+
const childDom = doms[i];
|
|
295
|
+
const { tagName, dataset = {} } = childDom;
|
|
296
|
+
const innerText = getInnerText(childDom);
|
|
297
|
+
let toPushAsset = null;
|
|
298
|
+
let allowAddToAssetList = false;
|
|
299
|
+
const assetOptions = { el: childDom, innerText, homePage, extractMode, enableRelativePath, enableAssetInnerText };
|
|
300
|
+
|
|
301
|
+
if (!['LINK', 'SCRIPT', 'STYLE'].includes(tagName)) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (!isNull(dataset)) {
|
|
305
|
+
verbose(`found ${tagName} dataset`, dataset);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (tagName === 'LINK') {
|
|
309
|
+
const { hreflang = '', href: oriHref } = childDom;
|
|
310
|
+
verbose(`analyze link href:[${oriHref}]`);
|
|
311
|
+
if (!oriHref) continue;
|
|
312
|
+
|
|
313
|
+
let href = mayPrefixHomePage(oriHref);
|
|
314
|
+
verbose(`analyze link [${href}]`);
|
|
315
|
+
// 一些使用了老版本cra的项目,这两个href 在修改了 publicPath 后也不被添加前缀,这里做一下修正
|
|
316
|
+
const legacyHrefs = ['/manifest.json', '/favicon.ico'];
|
|
317
|
+
if (legacyHrefs.includes(href)) {
|
|
318
|
+
const oldHref = href;
|
|
319
|
+
href = `${homePage}${href}`;
|
|
320
|
+
replaceContentList.push({ toMatch: href, toReplace: href });
|
|
321
|
+
verbose(`replace link [${oldHref}] to [href]`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const assetInfo = getAssetInfo(href, assetOptions);
|
|
325
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
326
|
+
// 供 shadow-dom 或其他需要知道当前应用所有样式列表的场景用
|
|
327
|
+
if (href.endsWith('.css')) {
|
|
328
|
+
pushToSrcList('css', assetInfo);
|
|
329
|
+
}
|
|
330
|
+
toPushAsset = buildAssetItem('link', assetInfo);
|
|
331
|
+
} else if (tagName === 'SCRIPT') {
|
|
332
|
+
const { src } = childDom;
|
|
333
|
+
verbose(`analyze script src:[${src}], innerText:[${innerText}]`);
|
|
334
|
+
|
|
335
|
+
if (!src && !innerText) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!src && innerText && enableAssetInnerText) {
|
|
340
|
+
verbose('user set enableAssetInnerText = true and found SCRIPT node innerText');
|
|
341
|
+
const assetInfo = getAssetInfo('', assetOptions);
|
|
342
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
343
|
+
toPushAsset = buildAssetItem('script', assetInfo);
|
|
344
|
+
} else {
|
|
345
|
+
let targetSrc = src;
|
|
346
|
+
if (!targetSrc) {
|
|
347
|
+
targetSrc = await writeInnerText(childDom, 'js', options);
|
|
348
|
+
}
|
|
349
|
+
if (!targetSrc) continue;
|
|
350
|
+
|
|
351
|
+
targetSrc = mayPrefixHomePage(targetSrc);
|
|
352
|
+
if (enableReplaceDevJs) {
|
|
353
|
+
// 替换用户的 development 模式的 react 相关包体
|
|
354
|
+
if (targetSrc.endsWith('react.dev.js')) {
|
|
355
|
+
const toReplace = targetSrc.replace('react.dev.js', 'react.js');
|
|
356
|
+
replaceContentList.push({ toMatch: targetSrc, toReplace });
|
|
357
|
+
targetSrc = toReplace;
|
|
358
|
+
}
|
|
359
|
+
// 替换用户的 development 模式的 vue 相关包体
|
|
360
|
+
if (targetSrc.endsWith('vue.dev.js')) {
|
|
361
|
+
const toReplace = targetSrc.replace('vue.dev.js', 'vue.js');
|
|
362
|
+
replaceContentList.push({ toMatch: targetSrc, toReplace });
|
|
363
|
+
targetSrc = toReplace;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const assetInfo = getAssetInfo(targetSrc, assetOptions);
|
|
368
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
369
|
+
|
|
370
|
+
pushToSrcList('js', assetInfo);
|
|
371
|
+
toPushAsset = buildAssetItem('script', assetInfo);
|
|
372
|
+
}
|
|
373
|
+
} else if (tagName === 'STYLE') {
|
|
374
|
+
verbose(`analyze style innerText:[${innerText}]`);
|
|
375
|
+
|
|
376
|
+
if (innerText && enableAssetInnerText) {
|
|
377
|
+
verbose('user set enableAssetInnerText = true and found STYLE node innerText');
|
|
378
|
+
const assetInfo = getAssetInfo('', assetOptions);
|
|
379
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
380
|
+
toPushAsset = buildAssetItem('style', assetInfo);
|
|
381
|
+
} else {
|
|
382
|
+
// style 标签转换为 css 文件存起来,以便让 @arkxio/ark-micro 用 link 标签加载
|
|
383
|
+
let href = await writeInnerText(childDom, 'css', options);
|
|
384
|
+
if (!href) continue;
|
|
385
|
+
|
|
386
|
+
href = mayPrefixHomePage(href);
|
|
387
|
+
const assetInfo = getAssetInfo(href, assetOptions);
|
|
388
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
389
|
+
|
|
390
|
+
pushToSrcList('css', assetInfo);
|
|
391
|
+
toPushAsset = buildAssetItem('link', assetInfo);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (toPushAsset && allowAddToAssetList) {
|
|
396
|
+
assetList.push(toPushAsset);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return replaceContentList;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @param {IInnerFillAssetListOptions} options
|
|
405
|
+
*/
|
|
406
|
+
export async function fillAssetListByDist(options) {
|
|
407
|
+
const { homePage, srcMap, buildDirFullPath } = options;
|
|
408
|
+
const fileFullPathList = getAllFilePath(buildDirFullPath);
|
|
409
|
+
verbose('filePathList', fileFullPathList);
|
|
410
|
+
|
|
411
|
+
fileFullPathList.forEach((fileAbsolutePath) => {
|
|
412
|
+
// 获取文件处于build目录下的相对路径,形如:
|
|
413
|
+
// /static/js/runtime-main.66c45929.js
|
|
414
|
+
// /asset-manifest.json
|
|
415
|
+
const filePathUnderBuild = fileAbsolutePath.split(buildDirFullPath)[1];
|
|
416
|
+
// 拼出 web 路径
|
|
417
|
+
const fileWebPath = `${slash.noEnd(homePage)}${slash.start(filePathUnderBuild)}`;
|
|
418
|
+
|
|
419
|
+
// 补上剩余的 css 文件路径
|
|
420
|
+
if (fileWebPath.endsWith('.css')) {
|
|
421
|
+
noDupPush(srcMap.chunkCssSrcList, fileWebPath);
|
|
422
|
+
} else if (fileWebPath.endsWith('.js')) {
|
|
423
|
+
noDupPush(srcMap.chunkJsSrcList, fileWebPath);
|
|
424
|
+
} else {
|
|
425
|
+
noDupPush(srcMap.otherSrcList, fileWebPath);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|