@arkxio/ark-dev-utils 0.1.6 → 0.1.9
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/README.md +17 -17
- package/dist/{ark-dev-utils.js → index.js} +1219 -1229
- package/dist/{ark-dev-utils.min.js → index.min.js} +1219 -1229
- package/jsconfig.json +5 -5
- package/lib/index.js +1218 -1228
- package/package.json +51 -51
- package/src/ArkWebpackPlugin.js +125 -135
- 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,7 +1,7 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
2
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('os'), require('path'), require('util'), require('jsdom'), require('webpack')) :
|
|
3
3
|
typeof define === 'function' && define.amd ? define(['exports', 'fs', 'os', 'path', 'util', 'jsdom', 'webpack'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.os, global.path, global.util, global.jsdom, global.webpack));
|
|
5
5
|
})(this, (function (exports, fs, os, path, util, jsdom, webpack) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -32,168 +32,168 @@
|
|
|
32
32
|
var util__namespace = /*#__PURE__*/_interopNamespace(util);
|
|
33
33
|
var jsdom__default = /*#__PURE__*/_interopDefaultLegacy(jsdom);
|
|
34
34
|
|
|
35
|
-
/**
|
|
36
|
-
* @type {{
|
|
37
|
-
* ARK_DIST_DIR: 'ark_dist',
|
|
38
|
-
* ARK_PROXY_DIR: 'ark_proxy',
|
|
39
|
-
* HEL_BUNDLE_DIR: 'ark_bundle',
|
|
40
|
-
* DEFAULT_GUESS_SUB_APP_CONF_PATH:string,
|
|
41
|
-
* DEFAULT_PLAT: 'unpkg',
|
|
42
|
-
* DEFAULT_NPM_CDN_TYPE: 'unpkg',
|
|
43
|
-
* DEFAULT_SEMVER_API: true,
|
|
44
|
-
* DEFAULT_HTML_INDEX_NAME: 'index.html',
|
|
45
|
-
* PLUGIN_VER:string,
|
|
46
|
-
* }}
|
|
47
|
-
*/
|
|
48
|
-
var cst = {
|
|
49
|
-
ARK_DIST_DIR: 'ark_dist',
|
|
50
|
-
ARK_PROXY_DIR: 'ark_proxy',
|
|
51
|
-
HEL_BUNDLE_DIR: 'ark_bundle',
|
|
52
|
-
DEFAULT_GUESS_SUB_APP_CONF_PATH: '../../../src/configs/subApp',
|
|
53
|
-
DEFAULT_PLAT: 'unpkg',
|
|
54
|
-
DEFAULT_NPM_CDN_TYPE: 'unpkg',
|
|
55
|
-
DEFAULT_HTML_INDEX_NAME: 'index.html',
|
|
56
|
-
DEFAULT_SEMVER_API: true,
|
|
57
|
-
PLUGIN_VER: '4.3.20',
|
|
35
|
+
/**
|
|
36
|
+
* @type {{
|
|
37
|
+
* ARK_DIST_DIR: 'ark_dist',
|
|
38
|
+
* ARK_PROXY_DIR: 'ark_proxy',
|
|
39
|
+
* HEL_BUNDLE_DIR: 'ark_bundle',
|
|
40
|
+
* DEFAULT_GUESS_SUB_APP_CONF_PATH:string,
|
|
41
|
+
* DEFAULT_PLAT: 'unpkg',
|
|
42
|
+
* DEFAULT_NPM_CDN_TYPE: 'unpkg',
|
|
43
|
+
* DEFAULT_SEMVER_API: true,
|
|
44
|
+
* DEFAULT_HTML_INDEX_NAME: 'index.html',
|
|
45
|
+
* PLUGIN_VER:string,
|
|
46
|
+
* }}
|
|
47
|
+
*/
|
|
48
|
+
var cst = {
|
|
49
|
+
ARK_DIST_DIR: 'ark_dist',
|
|
50
|
+
ARK_PROXY_DIR: 'ark_proxy',
|
|
51
|
+
HEL_BUNDLE_DIR: 'ark_bundle',
|
|
52
|
+
DEFAULT_GUESS_SUB_APP_CONF_PATH: '../../../src/configs/subApp',
|
|
53
|
+
DEFAULT_PLAT: 'unpkg',
|
|
54
|
+
DEFAULT_NPM_CDN_TYPE: 'unpkg',
|
|
55
|
+
DEFAULT_HTML_INDEX_NAME: 'index.html',
|
|
56
|
+
DEFAULT_SEMVER_API: true,
|
|
57
|
+
PLUGIN_VER: '4.3.20',
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
/** @typedef {import('../../typings').FileDesc} FileDesc*/
|
|
61
|
-
|
|
62
|
-
function verbose(...args) {
|
|
63
|
-
console.log('[Ark-verbose:] ', ...args);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const cdnType2host = {
|
|
67
|
-
unpkg: 'https://unpkg.com',
|
|
68
|
-
jsdelivr: 'https://cdn.jsdelivr.net/npm',
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function getNpmCdnHomePage(packageJson, options) {
|
|
72
|
-
const { npmCdnType = cst.DEFAULT_NPM_CDN_TYPE, distDir = cst.ARK_DIST_DIR, homePage } = options;
|
|
73
|
-
const { name, version } = packageJson;
|
|
74
|
-
// 优先考虑用户透传的 homePage,表示用户部署了 unpkg 私服
|
|
75
|
-
const unpkgHost = homePage || cdnType2host[npmCdnType] || '';
|
|
76
|
-
// TODO,未来考虑更多类型的 cdn,如:jsdelivr
|
|
77
|
-
return `${unpkgHost}/${name}@${version}/${distDir}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* @param {string} inputPath
|
|
84
|
-
* @param {Object} options
|
|
85
|
-
* @param {'end' | 'start'} [options.loc='end']
|
|
86
|
-
* @param {boolean} [options.need=false]
|
|
87
|
-
*/
|
|
88
|
-
function ensureSlash(inputPath, options) {
|
|
89
|
-
const { need, loc = 'end' } = options;
|
|
90
|
-
const isEnd = loc === 'end';
|
|
91
|
-
const hasSlash = isEnd ? inputPath.endsWith('/') : inputPath.startsWith('/');
|
|
92
|
-
|
|
93
|
-
const shouldDelSlash = hasSlash && !need;
|
|
94
|
-
const shouldAddSlash = !hasSlash && need;
|
|
95
|
-
if (isEnd) {
|
|
96
|
-
if (shouldDelSlash) {
|
|
97
|
-
return inputPath.substring(0, inputPath.length - 1);
|
|
98
|
-
}
|
|
99
|
-
if (shouldAddSlash) {
|
|
100
|
-
return `${inputPath}/`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (!isEnd) {
|
|
104
|
-
if (shouldDelSlash) {
|
|
105
|
-
// del start slash
|
|
106
|
-
return inputPath.substring(1);
|
|
107
|
-
}
|
|
108
|
-
if (shouldAddSlash) {
|
|
109
|
-
return `/${inputPath}`;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return inputPath;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** 语义化 slash 相关操作,方便上层理解和使用 */
|
|
117
|
-
const slash = {
|
|
118
|
-
start: (path) => ensureSlash(path, { loc: 'start', need: true }),
|
|
119
|
-
noStart: (path) => ensureSlash(path, { loc: 'start', need: false }),
|
|
120
|
-
end: (path) => ensureSlash(path, { loc: 'end', need: true }),
|
|
121
|
-
noEnd: (path) => ensureSlash(path, { loc: 'end', need: false }),
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
function getHelProcessEnvParams() {
|
|
125
|
-
// 以下常量由蓝盾流水线注入(由流水线变量或bash脚本注入)
|
|
126
|
-
const {
|
|
127
|
-
HOST,
|
|
128
|
-
PORT,
|
|
129
|
-
// appHomePage, 形如 http://xxx.cdn.com/hel/app1_2020121201011666
|
|
130
|
-
HEL_APP_HOME_PAGE,
|
|
131
|
-
/** 在构建机环境时,会注入真正对应的应用名 */
|
|
132
|
-
HEL_APP_GROUP_NAME,
|
|
133
|
-
HEL_APP_NAME,
|
|
134
|
-
} = process.env;
|
|
135
|
-
|
|
136
|
-
let appHomePage = HEL_APP_HOME_PAGE;
|
|
137
|
-
// 未传递 HEL_APP_HOME_PAGE 的话,根据 HOST 和 PORT 推导
|
|
138
|
-
if (!appHomePage && (HOST || PORT)) {
|
|
139
|
-
const host = HOST || 'localhost';
|
|
140
|
-
const port = PORT || '80';
|
|
141
|
-
const hotsStr = host.startsWith('http') ? host : `http://${host}`;
|
|
142
|
-
appHomePage = `${hotsStr}:${port}`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
appHomePage,
|
|
147
|
-
appGroupName: HEL_APP_GROUP_NAME,
|
|
148
|
-
appName: HEL_APP_NAME,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* @param {Record<string, any>} pkg
|
|
154
|
-
* @param {ICreateSubAppOptions} options
|
|
155
|
-
* @returns
|
|
156
|
-
*/
|
|
157
|
-
function getHelEnvParams(pkg, options = {}) {
|
|
158
|
-
const { platform, distDir, homePage: userCustomHomePage, handleHomePage = true, npmCdnType } = options;
|
|
159
|
-
let cdnHomePage = '';
|
|
160
|
-
// 计算 unpkg 平台 的 homePage 值,此时如果透传了 homePage,表示 unpkg 为私服
|
|
161
|
-
if (platform === 'unpkg' && handleHomePage) {
|
|
162
|
-
cdnHomePage = getNpmCdnHomePage(pkg, { distDir, npmCdnType, homePage: userCustomHomePage });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 来自 process.env 的值优先级最高
|
|
166
|
-
const p0EnvParams = getHelProcessEnvParams();
|
|
167
|
-
const appName = p0EnvParams.appName || pkg.appGroupName || '';
|
|
168
|
-
return {
|
|
169
|
-
appHomePage: p0EnvParams.appHomePage || cdnHomePage || userCustomHomePage || pkg.homepage || '/',
|
|
170
|
-
appGroupName: p0EnvParams.appGroupName || pkg.appGroupName || appName,
|
|
171
|
-
appName,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* @param {string} appName - hel 管理台注册的应用名 或 package.name
|
|
177
|
-
* @param {boolean} [useTimestampSuffix=true] - default: true, 是否设置时间戳后缀
|
|
178
|
-
* 设置为 true 时,支持同一个模块正确执行多版本js文件
|
|
179
|
-
* 如不需要同屏加载同一个模块的多个版本功能,且需要自己定制一些其他逻辑,可以设置为false
|
|
180
|
-
*/
|
|
181
|
-
function getJsonpFnName(appName, useTimestampSuffix = true) {
|
|
182
|
-
if (useTimestampSuffix) {
|
|
183
|
-
return `helJsonp_${appName}_${Date.now()}`;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return `helJsonp_${appName}`;
|
|
60
|
+
/** @typedef {import('../../typings').FileDesc} FileDesc*/
|
|
61
|
+
|
|
62
|
+
function verbose(...args) {
|
|
63
|
+
console.log('[Ark-verbose:] ', ...args);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cdnType2host = {
|
|
67
|
+
unpkg: 'https://unpkg.com',
|
|
68
|
+
jsdelivr: 'https://cdn.jsdelivr.net/npm',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function getNpmCdnHomePage(packageJson, options) {
|
|
72
|
+
const { npmCdnType = cst.DEFAULT_NPM_CDN_TYPE, distDir = cst.ARK_DIST_DIR, homePage } = options;
|
|
73
|
+
const { name, version } = packageJson;
|
|
74
|
+
// 优先考虑用户透传的 homePage,表示用户部署了 unpkg 私服
|
|
75
|
+
const unpkgHost = homePage || cdnType2host[npmCdnType] || '';
|
|
76
|
+
// TODO,未来考虑更多类型的 cdn,如:jsdelivr
|
|
77
|
+
return `${unpkgHost}/${name}@${version}/${distDir}`;
|
|
187
78
|
}
|
|
188
79
|
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
* @param {
|
|
193
|
-
* @
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
80
|
+
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {string} inputPath
|
|
84
|
+
* @param {Object} options
|
|
85
|
+
* @param {'end' | 'start'} [options.loc='end']
|
|
86
|
+
* @param {boolean} [options.need=false]
|
|
87
|
+
*/
|
|
88
|
+
function ensureSlash(inputPath, options) {
|
|
89
|
+
const { need, loc = 'end' } = options;
|
|
90
|
+
const isEnd = loc === 'end';
|
|
91
|
+
const hasSlash = isEnd ? inputPath.endsWith('/') : inputPath.startsWith('/');
|
|
92
|
+
|
|
93
|
+
const shouldDelSlash = hasSlash && !need;
|
|
94
|
+
const shouldAddSlash = !hasSlash && need;
|
|
95
|
+
if (isEnd) {
|
|
96
|
+
if (shouldDelSlash) {
|
|
97
|
+
return inputPath.substring(0, inputPath.length - 1);
|
|
98
|
+
}
|
|
99
|
+
if (shouldAddSlash) {
|
|
100
|
+
return `${inputPath}/`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!isEnd) {
|
|
104
|
+
if (shouldDelSlash) {
|
|
105
|
+
// del start slash
|
|
106
|
+
return inputPath.substring(1);
|
|
107
|
+
}
|
|
108
|
+
if (shouldAddSlash) {
|
|
109
|
+
return `/${inputPath}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return inputPath;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 语义化 slash 相关操作,方便上层理解和使用 */
|
|
117
|
+
const slash = {
|
|
118
|
+
start: (path) => ensureSlash(path, { loc: 'start', need: true }),
|
|
119
|
+
noStart: (path) => ensureSlash(path, { loc: 'start', need: false }),
|
|
120
|
+
end: (path) => ensureSlash(path, { loc: 'end', need: true }),
|
|
121
|
+
noEnd: (path) => ensureSlash(path, { loc: 'end', need: false }),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function getHelProcessEnvParams() {
|
|
125
|
+
// 以下常量由蓝盾流水线注入(由流水线变量或bash脚本注入)
|
|
126
|
+
const {
|
|
127
|
+
HOST,
|
|
128
|
+
PORT,
|
|
129
|
+
// appHomePage, 形如 http://xxx.cdn.com/hel/app1_2020121201011666
|
|
130
|
+
HEL_APP_HOME_PAGE,
|
|
131
|
+
/** 在构建机环境时,会注入真正对应的应用名 */
|
|
132
|
+
HEL_APP_GROUP_NAME,
|
|
133
|
+
HEL_APP_NAME,
|
|
134
|
+
} = process.env;
|
|
135
|
+
|
|
136
|
+
let appHomePage = HEL_APP_HOME_PAGE;
|
|
137
|
+
// 未传递 HEL_APP_HOME_PAGE 的话,根据 HOST 和 PORT 推导
|
|
138
|
+
if (!appHomePage && (HOST || PORT)) {
|
|
139
|
+
const host = HOST || 'localhost';
|
|
140
|
+
const port = PORT || '80';
|
|
141
|
+
const hotsStr = host.startsWith('http') ? host : `http://${host}`;
|
|
142
|
+
appHomePage = `${hotsStr}:${port}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
appHomePage,
|
|
147
|
+
appGroupName: HEL_APP_GROUP_NAME,
|
|
148
|
+
appName: HEL_APP_NAME,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Record<string, any>} pkg
|
|
154
|
+
* @param {ICreateSubAppOptions} options
|
|
155
|
+
* @returns
|
|
156
|
+
*/
|
|
157
|
+
function getHelEnvParams(pkg, options = {}) {
|
|
158
|
+
const { platform, distDir, homePage: userCustomHomePage, handleHomePage = true, npmCdnType } = options;
|
|
159
|
+
let cdnHomePage = '';
|
|
160
|
+
// 计算 unpkg 平台 的 homePage 值,此时如果透传了 homePage,表示 unpkg 为私服
|
|
161
|
+
if (platform === 'unpkg' && handleHomePage) {
|
|
162
|
+
cdnHomePage = getNpmCdnHomePage(pkg, { distDir, npmCdnType, homePage: userCustomHomePage });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 来自 process.env 的值优先级最高
|
|
166
|
+
const p0EnvParams = getHelProcessEnvParams();
|
|
167
|
+
const appName = p0EnvParams.appName || pkg.appGroupName || '';
|
|
168
|
+
return {
|
|
169
|
+
appHomePage: p0EnvParams.appHomePage || cdnHomePage || userCustomHomePage || pkg.homepage || '/',
|
|
170
|
+
appGroupName: p0EnvParams.appGroupName || pkg.appGroupName || appName,
|
|
171
|
+
appName,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {string} appName - hel 管理台注册的应用名 或 package.name
|
|
177
|
+
* @param {boolean} [useTimestampSuffix=true] - default: true, 是否设置时间戳后缀
|
|
178
|
+
* 设置为 true 时,支持同一个模块正确执行多版本js文件
|
|
179
|
+
* 如不需要同屏加载同一个模块的多个版本功能,且需要自己定制一些其他逻辑,可以设置为false
|
|
180
|
+
*/
|
|
181
|
+
function getJsonpFnName(appName, useTimestampSuffix = true) {
|
|
182
|
+
if (useTimestampSuffix) {
|
|
183
|
+
return `helJsonp_${appName}_${Date.now()}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return `helJsonp_${appName}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @param {string} homePage - 应用homePage
|
|
192
|
+
* @param {boolean} [needSlash]
|
|
193
|
+
* @returns
|
|
194
|
+
*/
|
|
195
|
+
function getPublicPathOrUrl(homePage, needSlash = true) {
|
|
196
|
+
return ensureSlash(homePage, { loc: 'end', need: needSlash });
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
var baseUtils = /*#__PURE__*/Object.freeze({
|
|
@@ -206,1119 +206,1109 @@
|
|
|
206
206
|
getPublicPathOrUrl: getPublicPathOrUrl
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
/**
|
|
210
|
-
* for getting pretty format multi line string when use \`...\`
|
|
211
|
-
* this function will remove indent of every line automatically
|
|
212
|
-
* @param {string} mayLineBreakStr
|
|
213
|
-
* @param {'MULTI' | 'ONE'} [mode='MULTI']
|
|
214
|
-
* @returns
|
|
215
|
-
* ```
|
|
216
|
-
* // usage 1, process multi lines with mode='MULTI' by default
|
|
217
|
-
* pfstr(`
|
|
218
|
-
* line1 line1 line1,
|
|
219
|
-
* line2 line2 line2.
|
|
220
|
-
* `);
|
|
221
|
-
* // pass to console.log will print:
|
|
222
|
-
* line1 line1 line1,
|
|
223
|
-
* line2 line2 line2.
|
|
224
|
-
* // attention: end <br/> will be removed automatically in MULTI mode
|
|
225
|
-
* pfstr(`
|
|
226
|
-
* line1 line1 line1,<br/>
|
|
227
|
-
* line2 line2 line2.
|
|
228
|
-
* `);
|
|
229
|
-
* // pass to console.log will print:
|
|
230
|
-
* line1 line1 line1,
|
|
231
|
-
* line2 line2 line2.
|
|
232
|
-
*
|
|
233
|
-
* // usage 2, set mode='ONE' to get no line break string
|
|
234
|
-
* pfstr(`
|
|
235
|
-
* line1 line1 line1,
|
|
236
|
-
* line2 line2 line2.
|
|
237
|
-
* `, 'ONE');
|
|
238
|
-
* // pass to console.log will print:
|
|
239
|
-
* line1 line1 line1, line2 line2 line2.
|
|
240
|
-
*
|
|
241
|
-
* // usage 3, add <br/> to control line break
|
|
242
|
-
* pfstr(`
|
|
243
|
-
* line1 line1 line1,
|
|
244
|
-
* line2 line2 line2,<br/>
|
|
245
|
-
* line3 line3 line3.
|
|
246
|
-
* `, 'ONE');
|
|
247
|
-
* // pass to console.log will print:
|
|
248
|
-
* line1 line1 line1, line2 line2 line2,
|
|
249
|
-
* line3 line3 line3.
|
|
250
|
-
* ```
|
|
251
|
-
*/
|
|
252
|
-
function pfstr(/** @type string */ mayLineBreakStr, mode = 'MULTI') {
|
|
253
|
-
// MULTI ONE
|
|
254
|
-
const lines = mayLineBreakStr.split('\n');
|
|
255
|
-
const lastIdx = lines.length - 1;
|
|
256
|
-
|
|
257
|
-
const formatLine = lines
|
|
258
|
-
.map((line, idx) => {
|
|
259
|
-
if (!line && (idx === 0 || idx === lastIdx)) {
|
|
260
|
-
return '';
|
|
261
|
-
}
|
|
262
|
-
const replaceBr = (/** @type string */ line, hasBrStr, noBrStr = '') => {
|
|
263
|
-
let result = line;
|
|
264
|
-
if (line.endsWith('<br/>')) {
|
|
265
|
-
result = line.substring(0, result.length - 5);
|
|
266
|
-
return `${result}${hasBrStr}`;
|
|
267
|
-
}
|
|
268
|
-
return `${result}${noBrStr}`;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// 此处暂时规避可选链写法,因 rollup 对此未处理,编译后上层使用会报错
|
|
272
|
-
// SyntaxError: Unexpected token '.'
|
|
273
|
-
const { trimStart } = line;
|
|
274
|
-
let result = trimStart ? line.trimStart() : line; // 去头部所有空格
|
|
275
|
-
if (mode === 'MULTI') {
|
|
276
|
-
return `${replaceBr(result, '')}\n`;
|
|
277
|
-
}
|
|
278
|
-
result = replaceBr(result, '\n', ' ');
|
|
279
|
-
return result;
|
|
280
|
-
})
|
|
281
|
-
.join('');
|
|
282
|
-
return formatLine;
|
|
209
|
+
/**
|
|
210
|
+
* for getting pretty format multi line string when use \`...\`
|
|
211
|
+
* this function will remove indent of every line automatically
|
|
212
|
+
* @param {string} mayLineBreakStr
|
|
213
|
+
* @param {'MULTI' | 'ONE'} [mode='MULTI']
|
|
214
|
+
* @returns
|
|
215
|
+
* ```
|
|
216
|
+
* // usage 1, process multi lines with mode='MULTI' by default
|
|
217
|
+
* pfstr(`
|
|
218
|
+
* line1 line1 line1,
|
|
219
|
+
* line2 line2 line2.
|
|
220
|
+
* `);
|
|
221
|
+
* // pass to console.log will print:
|
|
222
|
+
* line1 line1 line1,
|
|
223
|
+
* line2 line2 line2.
|
|
224
|
+
* // attention: end <br/> will be removed automatically in MULTI mode
|
|
225
|
+
* pfstr(`
|
|
226
|
+
* line1 line1 line1,<br/>
|
|
227
|
+
* line2 line2 line2.
|
|
228
|
+
* `);
|
|
229
|
+
* // pass to console.log will print:
|
|
230
|
+
* line1 line1 line1,
|
|
231
|
+
* line2 line2 line2.
|
|
232
|
+
*
|
|
233
|
+
* // usage 2, set mode='ONE' to get no line break string
|
|
234
|
+
* pfstr(`
|
|
235
|
+
* line1 line1 line1,
|
|
236
|
+
* line2 line2 line2.
|
|
237
|
+
* `, 'ONE');
|
|
238
|
+
* // pass to console.log will print:
|
|
239
|
+
* line1 line1 line1, line2 line2 line2.
|
|
240
|
+
*
|
|
241
|
+
* // usage 3, add <br/> to control line break
|
|
242
|
+
* pfstr(`
|
|
243
|
+
* line1 line1 line1,
|
|
244
|
+
* line2 line2 line2,<br/>
|
|
245
|
+
* line3 line3 line3.
|
|
246
|
+
* `, 'ONE');
|
|
247
|
+
* // pass to console.log will print:
|
|
248
|
+
* line1 line1 line1, line2 line2 line2,
|
|
249
|
+
* line3 line3 line3.
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
function pfstr(/** @type string */ mayLineBreakStr, mode = 'MULTI') {
|
|
253
|
+
// MULTI ONE
|
|
254
|
+
const lines = mayLineBreakStr.split('\n');
|
|
255
|
+
const lastIdx = lines.length - 1;
|
|
256
|
+
|
|
257
|
+
const formatLine = lines
|
|
258
|
+
.map((line, idx) => {
|
|
259
|
+
if (!line && (idx === 0 || idx === lastIdx)) {
|
|
260
|
+
return '';
|
|
261
|
+
}
|
|
262
|
+
const replaceBr = (/** @type string */ line, hasBrStr, noBrStr = '') => {
|
|
263
|
+
let result = line;
|
|
264
|
+
if (line.endsWith('<br/>')) {
|
|
265
|
+
result = line.substring(0, result.length - 5);
|
|
266
|
+
return `${result}${hasBrStr}`;
|
|
267
|
+
}
|
|
268
|
+
return `${result}${noBrStr}`;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// 此处暂时规避可选链写法,因 rollup 对此未处理,编译后上层使用会报错
|
|
272
|
+
// SyntaxError: Unexpected token '.'
|
|
273
|
+
const { trimStart } = line;
|
|
274
|
+
let result = trimStart ? line.trimStart() : line; // 去头部所有空格
|
|
275
|
+
if (mode === 'MULTI') {
|
|
276
|
+
return `${replaceBr(result, '')}\n`;
|
|
277
|
+
}
|
|
278
|
+
result = replaceBr(result, '\n', ' ');
|
|
279
|
+
return result;
|
|
280
|
+
})
|
|
281
|
+
.join('');
|
|
282
|
+
return formatLine;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
/*
|
|
286
|
-
|--------------------------------------------------------------------------
|
|
287
|
-
|
|
|
288
|
-
| 此脚本在流水线上会被触发,用于校验组名是否和应用里的组名保持一致
|
|
289
|
-
|
|
|
290
|
-
|--------------------------------------------------------------------------
|
|
291
|
-
*/
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* @param {Record<string, any>} pkg - 用户模块的 package.json 文件
|
|
295
|
-
* @param {ICheckOptions['fileFullPath'] | ICheckOptions} [subAppFilePathOrOptions] - 文件全路径名字,可带或不带后缀(.ts, .js)
|
|
296
|
-
*/
|
|
297
|
-
function check (pkg, subAppFilePathOrOptions) {
|
|
298
|
-
let fileFullPath = path__namespace.join(__dirname, cst.DEFAULT_GUESS_SUB_APP_CONF_PATH);
|
|
299
|
-
let checkEnv = true;
|
|
300
|
-
if (subAppFilePathOrOptions) {
|
|
301
|
-
if (typeof subAppFilePathOrOptions === 'string') {
|
|
302
|
-
fileFullPath = subAppFilePathOrOptions;
|
|
303
|
-
} else {
|
|
304
|
-
fileFullPath = subAppFilePathOrOptions.fileFullPath;
|
|
305
|
-
if (subAppFilePathOrOptions.checkEnv !== undefined) {
|
|
306
|
-
checkEnv = subAppFilePathOrOptions.checkEnv;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/** 子应用组名 */
|
|
312
|
-
let srcAppGroupName = '';
|
|
313
|
-
let content = '';
|
|
314
|
-
if (fileFullPath.endsWith('.js') || fileFullPath.endsWith('.ts')) {
|
|
315
|
-
content = fs__namespace.readFileSync(fileFullPath);
|
|
316
|
-
} else {
|
|
317
|
-
const fileFullPathJs = `${fileFullPath}.js`;
|
|
318
|
-
const fileFullPathTs = `${fileFullPath}.ts`;
|
|
319
|
-
const isJsExist = fs__namespace.existsSync(fileFullPathJs);
|
|
320
|
-
if (isJsExist) {
|
|
321
|
-
verbose(`guess js subApp file: ${fileFullPathJs}`);
|
|
322
|
-
content = fs__namespace.readFileSync(fileFullPathJs);
|
|
323
|
-
} else {
|
|
324
|
-
verbose(`guess ts subApp file: ${fileFullPathTs}`);
|
|
325
|
-
const isTsExist = fs__namespace.existsSync(fileFullPathTs);
|
|
326
|
-
if (isTsExist) {
|
|
327
|
-
content = fs__namespace.readFileSync(fileFullPathTs);
|
|
328
|
-
} else {
|
|
329
|
-
throw new Error('no src/configs/subApp.(js|ts) file found');
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const fileStr = content.toString();
|
|
335
|
-
const strList = fileStr.split(os__namespace.EOL);
|
|
336
|
-
for (const item of strList) {
|
|
337
|
-
// export const HEL_APP_GROUP_NAME = 'your-app';
|
|
338
|
-
// or
|
|
339
|
-
// export const LIB_NAME = 'your-app';
|
|
340
|
-
if (item.includes('export') && item.includes('const') && (item.includes('LIB_NAME') || item.includes('HEL_APP_GROUP_NAME'))) {
|
|
341
|
-
const noBlankStr = item.replace(/ /gi, '');
|
|
342
|
-
let [, appNameStr] = noBlankStr.split('=');
|
|
343
|
-
appNameStr = appNameStr.replace(/'/gi, '');
|
|
344
|
-
appNameStr = appNameStr.replace(/"/gi, '');
|
|
345
|
-
appNameStr = appNameStr.replace(/;/gi, '');
|
|
346
|
-
appNameStr = appNameStr.replace(/\r/gi, '');
|
|
347
|
-
appNameStr = appNameStr.replace(/\n/gi, '');
|
|
348
|
-
srcAppGroupName = appNameStr;
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (!srcAppGroupName) {
|
|
353
|
-
throw new Error(
|
|
354
|
-
pfstr(`
|
|
355
|
-
HEL_APP_GROUP_NAME or LIB_NAME not found in src/configs/subApp.(js|ts) file,
|
|
356
|
-
your should expose it like below:
|
|
357
|
-
export const HEL_APP_GROUP_NAME = 'your-app';
|
|
358
|
-
or
|
|
359
|
-
export const LIB_NAME = 'your-app';
|
|
360
|
-
`),
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const pgkAppGroupName = pkg.appGroupName || pkg.name;
|
|
365
|
-
if (pgkAppGroupName !== srcAppGroupName) {
|
|
366
|
-
throw new Error(
|
|
367
|
-
`package.json name [${pgkAppGroupName}] should be equal to src/configs/subApp LIB_NAME(or HEL_APP_GROUP_NAME) [${srcAppGroupName}]`,
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (checkEnv) {
|
|
372
|
-
// 以下常量由 ci&cd 系统注入(通常由流水线变量或 bash 脚本注入)
|
|
373
|
-
const { HEL_APP_GROUP_NAME } = process.env;
|
|
374
|
-
|
|
375
|
-
if (pgkAppGroupName !== HEL_APP_GROUP_NAME) {
|
|
376
|
-
throw new Error(`package.json name [${pgkAppGroupName}] should be equal to pipeline var HEL_APP_GROUP_NAME [${HEL_APP_GROUP_NAME}]`);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
285
|
+
/*
|
|
286
|
+
|--------------------------------------------------------------------------
|
|
287
|
+
|
|
|
288
|
+
| 此脚本在流水线上会被触发,用于校验组名是否和应用里的组名保持一致
|
|
289
|
+
|
|
|
290
|
+
|--------------------------------------------------------------------------
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* @param {Record<string, any>} pkg - 用户模块的 package.json 文件
|
|
295
|
+
* @param {ICheckOptions['fileFullPath'] | ICheckOptions} [subAppFilePathOrOptions] - 文件全路径名字,可带或不带后缀(.ts, .js)
|
|
296
|
+
*/
|
|
297
|
+
function check (pkg, subAppFilePathOrOptions) {
|
|
298
|
+
let fileFullPath = path__namespace.join(__dirname, cst.DEFAULT_GUESS_SUB_APP_CONF_PATH);
|
|
299
|
+
let checkEnv = true;
|
|
300
|
+
if (subAppFilePathOrOptions) {
|
|
301
|
+
if (typeof subAppFilePathOrOptions === 'string') {
|
|
302
|
+
fileFullPath = subAppFilePathOrOptions;
|
|
303
|
+
} else {
|
|
304
|
+
fileFullPath = subAppFilePathOrOptions.fileFullPath;
|
|
305
|
+
if (subAppFilePathOrOptions.checkEnv !== undefined) {
|
|
306
|
+
checkEnv = subAppFilePathOrOptions.checkEnv;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** 子应用组名 */
|
|
312
|
+
let srcAppGroupName = '';
|
|
313
|
+
let content = '';
|
|
314
|
+
if (fileFullPath.endsWith('.js') || fileFullPath.endsWith('.ts')) {
|
|
315
|
+
content = fs__namespace.readFileSync(fileFullPath);
|
|
316
|
+
} else {
|
|
317
|
+
const fileFullPathJs = `${fileFullPath}.js`;
|
|
318
|
+
const fileFullPathTs = `${fileFullPath}.ts`;
|
|
319
|
+
const isJsExist = fs__namespace.existsSync(fileFullPathJs);
|
|
320
|
+
if (isJsExist) {
|
|
321
|
+
verbose(`guess js subApp file: ${fileFullPathJs}`);
|
|
322
|
+
content = fs__namespace.readFileSync(fileFullPathJs);
|
|
323
|
+
} else {
|
|
324
|
+
verbose(`guess ts subApp file: ${fileFullPathTs}`);
|
|
325
|
+
const isTsExist = fs__namespace.existsSync(fileFullPathTs);
|
|
326
|
+
if (isTsExist) {
|
|
327
|
+
content = fs__namespace.readFileSync(fileFullPathTs);
|
|
328
|
+
} else {
|
|
329
|
+
throw new Error('no src/configs/subApp.(js|ts) file found');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const fileStr = content.toString();
|
|
335
|
+
const strList = fileStr.split(os__namespace.EOL);
|
|
336
|
+
for (const item of strList) {
|
|
337
|
+
// export const HEL_APP_GROUP_NAME = 'your-app';
|
|
338
|
+
// or
|
|
339
|
+
// export const LIB_NAME = 'your-app';
|
|
340
|
+
if (item.includes('export') && item.includes('const') && (item.includes('LIB_NAME') || item.includes('HEL_APP_GROUP_NAME'))) {
|
|
341
|
+
const noBlankStr = item.replace(/ /gi, '');
|
|
342
|
+
let [, appNameStr] = noBlankStr.split('=');
|
|
343
|
+
appNameStr = appNameStr.replace(/'/gi, '');
|
|
344
|
+
appNameStr = appNameStr.replace(/"/gi, '');
|
|
345
|
+
appNameStr = appNameStr.replace(/;/gi, '');
|
|
346
|
+
appNameStr = appNameStr.replace(/\r/gi, '');
|
|
347
|
+
appNameStr = appNameStr.replace(/\n/gi, '');
|
|
348
|
+
srcAppGroupName = appNameStr;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!srcAppGroupName) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
pfstr(`
|
|
355
|
+
HEL_APP_GROUP_NAME or LIB_NAME not found in src/configs/subApp.(js|ts) file,
|
|
356
|
+
your should expose it like below:
|
|
357
|
+
export const HEL_APP_GROUP_NAME = 'your-app';
|
|
358
|
+
or
|
|
359
|
+
export const LIB_NAME = 'your-app';
|
|
360
|
+
`),
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const pgkAppGroupName = pkg.appGroupName || pkg.name;
|
|
365
|
+
if (pgkAppGroupName !== srcAppGroupName) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`package.json name [${pgkAppGroupName}] should be equal to src/configs/subApp LIB_NAME(or HEL_APP_GROUP_NAME) [${srcAppGroupName}]`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (checkEnv) {
|
|
372
|
+
// 以下常量由 ci&cd 系统注入(通常由流水线变量或 bash 脚本注入)
|
|
373
|
+
const { HEL_APP_GROUP_NAME } = process.env;
|
|
374
|
+
|
|
375
|
+
if (pgkAppGroupName !== HEL_APP_GROUP_NAME) {
|
|
376
|
+
throw new Error(`package.json name [${pgkAppGroupName}] should be equal to pipeline var HEL_APP_GROUP_NAME [${HEL_APP_GROUP_NAME}]`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
/**
|
|
382
|
-
*
|
|
383
|
-
* @param {Array<string>} arr
|
|
384
|
-
* @param {string} item
|
|
385
|
-
*/
|
|
386
|
-
function noDupPush(arr, item) {
|
|
387
|
-
if (!arr.includes(item)) arr.push(item);
|
|
381
|
+
/**
|
|
382
|
+
*
|
|
383
|
+
* @param {Array<string>} arr
|
|
384
|
+
* @param {string} item
|
|
385
|
+
*/
|
|
386
|
+
function noDupPush(arr, item) {
|
|
387
|
+
if (!arr.includes(item)) arr.push(item);
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
function okeys(obj) {
|
|
391
|
-
return Object.keys(obj);
|
|
390
|
+
function okeys(obj) {
|
|
391
|
+
return Object.keys(obj);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
*
|
|
396
|
+
* @param value
|
|
397
|
+
* @param {{nullValues: any[], isEmptyObjNull: boolean, isEmptyArrNull: boolean}} nullDef - 空定义
|
|
398
|
+
*/
|
|
399
|
+
function isNull(value, nullDef = {}) {
|
|
400
|
+
const { nullValues = [null, undefined, ''], isEmptyObjNull = true, isEmptyArrNull = true } = nullDef;
|
|
401
|
+
|
|
402
|
+
const inNullValues = nullValues.includes(value);
|
|
403
|
+
if (inNullValues) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (Array.isArray(value)) {
|
|
408
|
+
if (isEmptyArrNull) return value.length === 0;
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (typeof value === 'object') {
|
|
413
|
+
if (isEmptyObjNull) return okeys(value).length === 0;
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return false;
|
|
392
418
|
}
|
|
393
419
|
|
|
394
|
-
/**
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
* @param {
|
|
398
|
-
*/
|
|
399
|
-
function
|
|
400
|
-
const {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
420
|
+
/** @typedef {import('../../typings').SrcMap} SrcMap*/
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @param {import('../../typings').IUserExtractOptions} extractOptions
|
|
424
|
+
*/
|
|
425
|
+
function makeAppVersionSrcMap(extractOptions) {
|
|
426
|
+
const { appInfo, indexHtmlName = cst.DEFAULT_HTML_INDEX_NAME, extractMode = 'all' } = extractOptions;
|
|
427
|
+
const { homePage } = appInfo;
|
|
428
|
+
// 用于更新到数据库的app信息,通常来说在构建机器上触发
|
|
429
|
+
// 从上往下的key顺序也是在html创建的顺序
|
|
430
|
+
return {
|
|
431
|
+
webDirPath: homePage,
|
|
432
|
+
htmlIndexSrc: `${slash.end(homePage)}${indexHtmlName}`,
|
|
433
|
+
extractMode,
|
|
434
|
+
iframeSrc: '',
|
|
435
|
+
chunkCssSrcList: [], // all build css files
|
|
436
|
+
chunkJsSrcList: [], // all build js files
|
|
437
|
+
staticCssSrcList: [], // all static css files
|
|
438
|
+
staticJsSrcList: [], // all static js files
|
|
439
|
+
relativeCssSrcList: [], // all relative js files
|
|
440
|
+
relativeJsSrcList: [], // all relative js files
|
|
441
|
+
headAssetList: [],
|
|
442
|
+
bodyAssetList: [],
|
|
443
|
+
otherSrcList: [],
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* 从 index.html 提取资源的描述数据,包含 htmlContent、srcMap
|
|
449
|
+
* @param {import('../../typings').IUserExtractOptions} userExtractOptions
|
|
450
|
+
*/
|
|
451
|
+
function makeArkMetaJson(userExtractOptions, parsedRet) {
|
|
452
|
+
const { packageJson, extractMode = 'build', subApp } = userExtractOptions;
|
|
453
|
+
const { homePage, groupName, name: appName, semverApi } = subApp;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* 构建版本号,当指定了 homePage 且不想采用默认的版本号生成规则时,才需要透传 buildVer 值
|
|
457
|
+
* 默认生成规则:
|
|
458
|
+
* 内网包:裁出 homePage ${cdnHost}/${appZone}/${appName}_${dateStr} 里的 ${appName}_${dateStr} 作为版本号
|
|
459
|
+
* 外网包:pkg.version
|
|
460
|
+
*/
|
|
461
|
+
let version = userExtractOptions.buildVer;
|
|
462
|
+
const packVer = packageJson.version;
|
|
463
|
+
if (!version) {
|
|
464
|
+
if (semverApi) {
|
|
465
|
+
version = packVer;
|
|
466
|
+
} else {
|
|
467
|
+
try {
|
|
468
|
+
// ${cdnHost}/${appZone}/${appName}_${dateStr}
|
|
469
|
+
const [, restStr] = homePage.split('//');
|
|
470
|
+
const [, , versionMakeOnPipeline] = restStr.split('/');
|
|
471
|
+
if (versionMakeOnPipeline) {
|
|
472
|
+
const arr = versionMakeOnPipeline.split('_');
|
|
473
|
+
const lastItem = arr[arr.length - 1];
|
|
474
|
+
// 特征符合 arkpack 的版本号
|
|
475
|
+
if (lastItem && lastItem.length === 14 && new RegExp('^[1-9]+[0-9]*$').test(lastItem)) {
|
|
476
|
+
version = versionMakeOnPipeline;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
} catch (err) {}
|
|
480
|
+
|
|
481
|
+
if (!version) {
|
|
482
|
+
// 自定义 homePage 后,版本未必能推导出来,降级为使用 package.json 版本号
|
|
483
|
+
version = packVer;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const repo = packageJson.repository || {};
|
|
488
|
+
|
|
489
|
+
// 新增依赖处理逻辑
|
|
490
|
+
const { dependencies = {}, arkDependencies = {} } = packageJson;
|
|
491
|
+
const arklibDeps = Object.entries(dependencies)
|
|
492
|
+
.filter(([name]) => name.indexOf('arklib-') !== -1)
|
|
493
|
+
.reduce((acc, [name, version]) => ({ ...acc, [name]: version }), {});
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
app: {
|
|
497
|
+
name: appName,
|
|
498
|
+
app_group_name: groupName,
|
|
499
|
+
git_repo_url: repo.url || packageJson.homepage || '',
|
|
500
|
+
online_version: version,
|
|
501
|
+
build_version: version,
|
|
502
|
+
},
|
|
503
|
+
version: {
|
|
504
|
+
plugin_ver: cst.PLUGIN_VER,
|
|
505
|
+
extract_mode: extractMode,
|
|
506
|
+
sub_app_name: appName,
|
|
507
|
+
sub_app_version: version,
|
|
508
|
+
// ... existing version fields ...
|
|
509
|
+
arkDependencies: { ...arklibDeps, ...arkDependencies },
|
|
510
|
+
src_map: parsedRet.srcMap,
|
|
511
|
+
html_content: parsedRet.htmlContent,
|
|
512
|
+
},
|
|
513
|
+
};
|
|
418
514
|
}
|
|
419
515
|
|
|
420
|
-
/** @typedef {import('../../typings').SrcMap} SrcMap*/
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
};
|
|
516
|
+
/** @typedef {import('../../typings').SrcMap} SrcMap*/
|
|
517
|
+
|
|
518
|
+
const writeFile = util__default["default"].promisify(fs__default["default"].writeFile);
|
|
519
|
+
|
|
520
|
+
/** jsdom 15 里去内联脚本 innerText 取不到,这里用此函数辅助 */
|
|
521
|
+
function getInnerText(/** @type {HTMLElement}} */ dom) {
|
|
522
|
+
return dom.innerText || dom.innerHTML || '';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function getDatasetVal(dataset, key, defaultValIfOnlyKey) {
|
|
526
|
+
const hasKey = Object.prototype.hasOwnProperty.call(dataset, key);
|
|
527
|
+
if (hasKey) {
|
|
528
|
+
return dataset[key] || defaultValIfOnlyKey;
|
|
529
|
+
}
|
|
530
|
+
return '';
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function isRelativePath(path) {
|
|
534
|
+
if (path.startsWith('//')) return false;
|
|
535
|
+
return path.startsWith('/') || path.startsWith('./') || path.startsWith('../');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function buildAssetItem(tag, /** @type {IAssetInfo} */ assetInfo) {
|
|
539
|
+
const isLink = tag === 'link';
|
|
540
|
+
const { isBuildUrl, isNonBuildAndRelative, canAppend, el, url, innerText } = assetInfo;
|
|
541
|
+
let attrs = {};
|
|
542
|
+
if (url) {
|
|
543
|
+
attrs = isLink ? { href: url } : { src: url };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
let tagVar = '';
|
|
547
|
+
if (isBuildUrl) {
|
|
548
|
+
tagVar = tag;
|
|
549
|
+
} else if (isNonBuildAndRelative) {
|
|
550
|
+
tagVar = isLink ? 'relativeLink' : 'relativeScript';
|
|
551
|
+
} else {
|
|
552
|
+
tagVar = isLink ? 'staticLink' : 'staticScript';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const assetItem = { tag: tagVar, append: canAppend, attrs };
|
|
556
|
+
if (innerText) {
|
|
557
|
+
assetItem.innerText = innerText;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const attrNames = el.getAttributeNames();
|
|
561
|
+
attrNames.forEach((name) => {
|
|
562
|
+
// src href 上面已记录真正的目标值,故移除
|
|
563
|
+
// data-helappend 只在提取元数据辅助计算 append 值时用到,故此处移除
|
|
564
|
+
if (['src', 'href', 'data-helappend'].includes(name)) return;
|
|
565
|
+
attrs[name] = el.getAttribute(name);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
return assetItem;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// 注意:这个变量需要在每次编译时重置
|
|
572
|
+
// 不在这里维护,而是通过 options 传递
|
|
573
|
+
let custScriptIdx = 0;
|
|
574
|
+
|
|
575
|
+
function resetScriptIdx() {
|
|
576
|
+
custScriptIdx = 0;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* @param {HTMLScriptElement | HTMLStyleElement} childDom
|
|
581
|
+
* @param {string} fileType
|
|
582
|
+
* @param {IInnerFillAssetListOptions} options
|
|
583
|
+
*/
|
|
584
|
+
async function writeInnerText(childDom, fileType, options) {
|
|
585
|
+
const { homePage, buildDirFullPath, compilation, customAssets = [] } = options;
|
|
586
|
+
let innerText = getInnerText(childDom);
|
|
587
|
+
if (!innerText) return '';
|
|
588
|
+
|
|
589
|
+
verbose(`found a user customized ${fileType} tag node in html, try extract its content and write them to local fs`);
|
|
590
|
+
custScriptIdx += 1;
|
|
591
|
+
const scriptName = `ark_userChunk_${custScriptIdx}.${fileType}`;
|
|
592
|
+
const fileAbsolutePath = `${buildDirFullPath}/${scriptName}`;
|
|
593
|
+
const fileWebPath = `${slash.noEnd(homePage)}/${scriptName}`;
|
|
594
|
+
|
|
595
|
+
// 对于 JS 文件,移除或替换 document.write() 调用
|
|
596
|
+
// 因为异步加载的脚本中不能使用 document.write()
|
|
597
|
+
if (fileType === 'js') {
|
|
598
|
+
// 方法1:完全移除 document.write() 调用(推荐)
|
|
599
|
+
// innerText = innerText.replace(/document\.write\s*\([^)]*\)/g, '');
|
|
600
|
+
|
|
601
|
+
// 方法2:用 console.warn 替换 document.write(),保留代码逻辑但避免错误
|
|
602
|
+
innerText = innerText.replace(
|
|
603
|
+
/document\.write\s*\(([^)]*)\)/g,
|
|
604
|
+
'console.warn("[ark-micro] document.write is not allowed in asynchronously loaded scripts:", $1)'
|
|
605
|
+
);
|
|
606
|
+
verbose(`Replaced document.write calls in ${scriptName}`);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// 在开发模式下,使用 compilation.emitAsset 确保文件能被 dev server 访问
|
|
610
|
+
if (compilation && compilation.emitAsset) {
|
|
611
|
+
const { sources } = compilation.compiler.webpack;
|
|
612
|
+
compilation.emitAsset(
|
|
613
|
+
scriptName,
|
|
614
|
+
new sources.RawSource(innerText)
|
|
615
|
+
);
|
|
616
|
+
verbose(`emit asset ${scriptName} via webpack compilation`);
|
|
617
|
+
} else {
|
|
618
|
+
// 生产模式或没有 compilation 对象时,直接写入文件系统
|
|
619
|
+
await writeFile(fileAbsolutePath, innerText);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// 将自定义资产信息保存到 customAssets 数组,供后续使用
|
|
623
|
+
if (customAssets) {
|
|
624
|
+
customAssets.push({ scriptName, fileWebPath, content: innerText });
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
verbose(`write done, the web file will be ${fileWebPath} later`);
|
|
628
|
+
return fileWebPath;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @param {string} url
|
|
633
|
+
* @param {IAssetOptions} options
|
|
634
|
+
*/
|
|
635
|
+
function getAssetInfo(url, options) {
|
|
636
|
+
const { homePage, extractMode, enableRelativePath, enableAssetInnerText, el, innerText } = options;
|
|
637
|
+
const { dataset = {} } = el;
|
|
638
|
+
// 是构建生成的产物路径,无 url 的当做是内联的处理,isBuildUrl 强制为 true
|
|
639
|
+
const isBuildUrl = url ? url.startsWith(homePage) : true;
|
|
640
|
+
const isRelative = isRelativePath(url);
|
|
641
|
+
// 是 homePage 之外相对路径导入的产物路径
|
|
642
|
+
const isNonBuildAndRelative = !isBuildUrl && isRelative;
|
|
643
|
+
const isStatic = !isBuildUrl && !isRelative;
|
|
644
|
+
const isIcoAsset = url.endsWith('.ico');
|
|
645
|
+
// 设置了 extractMode 为 build 和 build_no_html 时,当前产物路径是非构建生成的,则直接忽略,不记录到 assetList 数据里
|
|
646
|
+
const ignoreAddToAssetList = !isBuildUrl && (extractMode === 'build' || extractMode === 'build_no_html');
|
|
647
|
+
let allowAddToAssetList = !ignoreAddToAssetList;
|
|
648
|
+
if (!allowAddToAssetList) {
|
|
649
|
+
verbose(` >>> ignore add asset [${url}] to assetList by extractMode=${extractMode}`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// 未显式设置 data-helappend 时,helappend 默认值会走内部逻辑来决定如何赋值
|
|
653
|
+
let helAppendValOfDataset = getDatasetVal(dataset, 'helappend', '1');
|
|
654
|
+
let helAppendValOfInnerLogic = '';
|
|
655
|
+
if (helAppendValOfDataset && !['1', '0'].includes(helAppendValOfDataset)) {
|
|
656
|
+
throw new Error(`found invalid helappend value [${helAppendValOfDataset}], only accpet 1 or 0 currently`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const ex = dataset.helex || '';
|
|
660
|
+
if (isNonBuildAndRelative) {
|
|
661
|
+
if (isIcoAsset && !helAppendValOfDataset) {
|
|
662
|
+
helAppendValOfDataset = '0'; // ico 文件特殊处理,默认是不加载的
|
|
663
|
+
} else if (ex && !helAppendValOfDataset) {
|
|
664
|
+
helAppendValOfDataset = '1'; // 标记了 ex 的文件特殊处理,默认需要加载,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// 如下面错误描述所示,在既没有设置 enableRelativePath=true,又没有显式的标记 data-helappend 的情况下
|
|
668
|
+
// 不允许 homePage 之外的相对路径导入的资源存在
|
|
669
|
+
// 所以对于此类 homePage 之外的相对路径导入的资源,要么用户设置 enableRelativePath=true,要么显式的标记 data-helappend
|
|
670
|
+
// 设置 enableRelativePath=true 后,优先读可能已存在的 data-helappend 值,没有则默认为 0,表示不加载
|
|
671
|
+
// 不设置 enableRelativePath=true 的话,则需要用户一定标记 data-helappend 值
|
|
672
|
+
if (!enableRelativePath && !helAppendValOfDataset) {
|
|
673
|
+
throw new Error(
|
|
674
|
+
pfstr(`
|
|
675
|
+
found asset url [${url}] is a relative path, it is obviously not a valid url for cdn architecture deploy!
|
|
676
|
+
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:
|
|
677
|
+
1. pass enableRelativePath true to ark-dev-utils.extractArkMetaJson method options.
|
|
678
|
+
2. add data-helappend="0" on the asset dom attribute to tell ark-dev-utils ignore this asset.
|
|
679
|
+
|
|
680
|
+
ark-dev-utils will mark this url as relativeLink or relativeScript, and set append as false,
|
|
681
|
+
if you want sdk append this asset, you can explicitly add data-helappend="1" on the asset dom attribute.
|
|
682
|
+
a demo will be like:<script src="./a/b.js" data-helappend="1"></script>,<br/>
|
|
683
|
+
note that the asset will depend on your host site seriously under this situation.
|
|
684
|
+
`),
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// 构建时设置 enableRelativePath=true,则允许此类【homePage之外相对路径导入的产物】加入到资源清单列表里
|
|
689
|
+
allowAddToAssetList = true;
|
|
690
|
+
helAppendValOfInnerLogic = helAppendValOfDataset || '0'; // 没有显示设定 data-helappend 时默认标记为不加载
|
|
691
|
+
} else if (isIcoAsset) {
|
|
692
|
+
helAppendValOfInnerLogic = '0'; // ico 文件特殊处理,默认是不加载的
|
|
693
|
+
} else if (isStatic) {
|
|
694
|
+
if (ex) {
|
|
695
|
+
// 对于标记了 helex 的元素,默认是 append 的,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
696
|
+
// 重复则不追加,不重复则追加
|
|
697
|
+
helAppendValOfInnerLogic = helAppendValOfDataset || '1';
|
|
698
|
+
} else {
|
|
699
|
+
helAppendValOfInnerLogic = '0';
|
|
700
|
+
}
|
|
701
|
+
} else {
|
|
702
|
+
// isBuild
|
|
703
|
+
helAppendValOfInnerLogic = '1'; // 标记为可加载
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const helAppendVal = helAppendValOfDataset || helAppendValOfInnerLogic;
|
|
707
|
+
const helAppend = helAppendVal === '1';
|
|
708
|
+
if (ex && !helAppend) {
|
|
709
|
+
// 设置了 helex 但同时设置了不加载,会造成歧义,当前版本是不允许的
|
|
710
|
+
throw new Error(
|
|
711
|
+
pfstr(`
|
|
712
|
+
found conflict setting for helex: [data-helex="${ex}"]、[data-helappend="${helAppendVal}"],
|
|
713
|
+
remove data-helappend( append is true for helex by default ) or set data-helappend="1"!
|
|
714
|
+
`),
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
url,
|
|
720
|
+
el,
|
|
721
|
+
isBuildUrl,
|
|
722
|
+
isNonBuildAndRelative,
|
|
723
|
+
canAppend: helAppend,
|
|
724
|
+
allowAddToAssetList,
|
|
725
|
+
innerText: enableAssetInnerText ? innerText : '',
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* 提取link、script标签数据并填充到目标assetList
|
|
731
|
+
* @param {HTMLCollectionOf<HTMLScriptElement>} doms
|
|
732
|
+
* @param {IInnerFillAssetListOptions} options
|
|
733
|
+
*/
|
|
734
|
+
async function fillAssetList(doms, options) {
|
|
735
|
+
const {
|
|
736
|
+
homePage,
|
|
737
|
+
enableReplaceDevJs = true,
|
|
738
|
+
enableRelativePath = false,
|
|
739
|
+
enableAssetInnerText = false,
|
|
740
|
+
enablePrefixHomePage = false,
|
|
741
|
+
srcMap,
|
|
742
|
+
isHead,
|
|
743
|
+
} = options;
|
|
744
|
+
const {
|
|
745
|
+
headAssetList,
|
|
746
|
+
bodyAssetList,
|
|
747
|
+
extractMode,
|
|
748
|
+
chunkCssSrcList,
|
|
749
|
+
staticCssSrcList,
|
|
750
|
+
relativeCssSrcList,
|
|
751
|
+
chunkJsSrcList,
|
|
752
|
+
staticJsSrcList,
|
|
753
|
+
relativeJsSrcList,
|
|
754
|
+
} = srcMap;
|
|
755
|
+
const assetList = isHead ? headAssetList : bodyAssetList;
|
|
756
|
+
|
|
757
|
+
const len = doms.length;
|
|
758
|
+
const replaceContentList = [];
|
|
759
|
+
verbose(`extractMode is ${extractMode}`);
|
|
760
|
+
verbose(`enableAssetInnerText is ${enableAssetInnerText}`);
|
|
761
|
+
|
|
762
|
+
const pushToSrcList = (assetType, /** @type {IAssetInfo} */ assetInfo) => {
|
|
763
|
+
const { isBuildUrl, isNonBuildAndRelative, url } = assetInfo;
|
|
764
|
+
const isAllExtractMode = extractMode === 'all' || extractMode === 'all_no_html';
|
|
765
|
+
|
|
766
|
+
if (assetType === 'css') {
|
|
767
|
+
if (isBuildUrl) {
|
|
768
|
+
return noDupPush(chunkCssSrcList, url);
|
|
769
|
+
}
|
|
770
|
+
if (isAllExtractMode) {
|
|
771
|
+
const list = isNonBuildAndRelative ? relativeCssSrcList : staticCssSrcList;
|
|
772
|
+
return noDupPush(list, url);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (isBuildUrl) {
|
|
777
|
+
return noDupPush(chunkJsSrcList, url);
|
|
778
|
+
}
|
|
779
|
+
if (isAllExtractMode) {
|
|
780
|
+
const list = isNonBuildAndRelative ? relativeJsSrcList : staticJsSrcList;
|
|
781
|
+
return noDupPush(list, url);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
const mayPrefixHomePage = (url) => {
|
|
786
|
+
if (enablePrefixHomePage) {
|
|
787
|
+
if (url.startsWith('/') && !url.startsWith('//')) {
|
|
788
|
+
verbose(` >>> prefix homePage [${homePage}] for url [${url}]`);
|
|
789
|
+
const finalUrl = `${slash.noEnd(homePage)}${url}`;
|
|
790
|
+
replaceContentList.push({ toMatch: `="${url}"`, toReplace: `="${finalUrl}"` });
|
|
791
|
+
return finalUrl;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return url;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
for (let i = 0; i < len; i++) {
|
|
798
|
+
const childDom = doms[i];
|
|
799
|
+
const { tagName, dataset = {} } = childDom;
|
|
800
|
+
const innerText = getInnerText(childDom);
|
|
801
|
+
let toPushAsset = null;
|
|
802
|
+
let allowAddToAssetList = false;
|
|
803
|
+
const assetOptions = { el: childDom, innerText, homePage, extractMode, enableRelativePath, enableAssetInnerText };
|
|
804
|
+
|
|
805
|
+
if (!['LINK', 'SCRIPT', 'STYLE'].includes(tagName)) {
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (!isNull(dataset)) {
|
|
809
|
+
verbose(`found ${tagName} dataset`, dataset);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (tagName === 'LINK') {
|
|
813
|
+
const { hreflang = '', href: oriHref } = childDom;
|
|
814
|
+
verbose(`analyze link href:[${oriHref}]`);
|
|
815
|
+
if (!oriHref) continue;
|
|
816
|
+
|
|
817
|
+
let href = mayPrefixHomePage(oriHref);
|
|
818
|
+
verbose(`analyze link [${href}]`);
|
|
819
|
+
// 一些使用了老版本cra的项目,这两个href 在修改了 publicPath 后也不被添加前缀,这里做一下修正
|
|
820
|
+
const legacyHrefs = ['/manifest.json', '/favicon.ico'];
|
|
821
|
+
if (legacyHrefs.includes(href)) {
|
|
822
|
+
const oldHref = href;
|
|
823
|
+
href = `${homePage}${href}`;
|
|
824
|
+
replaceContentList.push({ toMatch: href, toReplace: href });
|
|
825
|
+
verbose(`replace link [${oldHref}] to [href]`);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const assetInfo = getAssetInfo(href, assetOptions);
|
|
829
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
830
|
+
// 供 shadow-dom 或其他需要知道当前应用所有样式列表的场景用
|
|
831
|
+
if (href.endsWith('.css')) {
|
|
832
|
+
pushToSrcList('css', assetInfo);
|
|
833
|
+
}
|
|
834
|
+
toPushAsset = buildAssetItem('link', assetInfo);
|
|
835
|
+
} else if (tagName === 'SCRIPT') {
|
|
836
|
+
const { src } = childDom;
|
|
837
|
+
verbose(`analyze script src:[${src}], innerText:[${innerText}]`);
|
|
838
|
+
|
|
839
|
+
if (!src && !innerText) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (!src && innerText && enableAssetInnerText) {
|
|
844
|
+
verbose('user set enableAssetInnerText = true and found SCRIPT node innerText');
|
|
845
|
+
const assetInfo = getAssetInfo('', assetOptions);
|
|
846
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
847
|
+
toPushAsset = buildAssetItem('script', assetInfo);
|
|
848
|
+
} else {
|
|
849
|
+
let targetSrc = src;
|
|
850
|
+
if (!targetSrc) {
|
|
851
|
+
targetSrc = await writeInnerText(childDom, 'js', options);
|
|
852
|
+
}
|
|
853
|
+
if (!targetSrc) continue;
|
|
854
|
+
|
|
855
|
+
targetSrc = mayPrefixHomePage(targetSrc);
|
|
856
|
+
if (enableReplaceDevJs) {
|
|
857
|
+
// 替换用户的 development 模式的 react 相关包体
|
|
858
|
+
if (targetSrc.endsWith('react.dev.js')) {
|
|
859
|
+
const toReplace = targetSrc.replace('react.dev.js', 'react.js');
|
|
860
|
+
replaceContentList.push({ toMatch: targetSrc, toReplace });
|
|
861
|
+
targetSrc = toReplace;
|
|
862
|
+
}
|
|
863
|
+
// 替换用户的 development 模式的 vue 相关包体
|
|
864
|
+
if (targetSrc.endsWith('vue.dev.js')) {
|
|
865
|
+
const toReplace = targetSrc.replace('vue.dev.js', 'vue.js');
|
|
866
|
+
replaceContentList.push({ toMatch: targetSrc, toReplace });
|
|
867
|
+
targetSrc = toReplace;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const assetInfo = getAssetInfo(targetSrc, assetOptions);
|
|
872
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
873
|
+
|
|
874
|
+
pushToSrcList('js', assetInfo);
|
|
875
|
+
toPushAsset = buildAssetItem('script', assetInfo);
|
|
876
|
+
}
|
|
877
|
+
} else if (tagName === 'STYLE') {
|
|
878
|
+
verbose(`analyze style innerText:[${innerText}]`);
|
|
879
|
+
|
|
880
|
+
if (innerText && enableAssetInnerText) {
|
|
881
|
+
verbose('user set enableAssetInnerText = true and found STYLE node innerText');
|
|
882
|
+
const assetInfo = getAssetInfo('', assetOptions);
|
|
883
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
884
|
+
toPushAsset = buildAssetItem('style', assetInfo);
|
|
885
|
+
} else {
|
|
886
|
+
// style 标签转换为 css 文件存起来,以便让 @arkxio/ark-micro 用 link 标签加载
|
|
887
|
+
let href = await writeInnerText(childDom, 'css', options);
|
|
888
|
+
if (!href) continue;
|
|
889
|
+
|
|
890
|
+
href = mayPrefixHomePage(href);
|
|
891
|
+
const assetInfo = getAssetInfo(href, assetOptions);
|
|
892
|
+
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
893
|
+
|
|
894
|
+
pushToSrcList('css', assetInfo);
|
|
895
|
+
toPushAsset = buildAssetItem('link', assetInfo);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (toPushAsset && allowAddToAssetList) {
|
|
900
|
+
assetList.push(toPushAsset);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return replaceContentList;
|
|
445
905
|
}
|
|
446
906
|
|
|
447
|
-
/**
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
let
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
.reduce((acc, [name, version]) => ({ ...acc, [name]: version }), {});
|
|
494
|
-
|
|
495
|
-
return {
|
|
496
|
-
app: {
|
|
497
|
-
name: appName,
|
|
498
|
-
app_group_name: groupName,
|
|
499
|
-
git_repo_url: repo.url || packageJson.homepage || '',
|
|
500
|
-
online_version: version,
|
|
501
|
-
build_version: version,
|
|
502
|
-
},
|
|
503
|
-
version: {
|
|
504
|
-
plugin_ver: cst.PLUGIN_VER,
|
|
505
|
-
extract_mode: extractMode,
|
|
506
|
-
sub_app_name: appName,
|
|
507
|
-
sub_app_version: version,
|
|
508
|
-
// ... existing version fields ...
|
|
509
|
-
arkDependencies: { ...arklibDeps, ...arkDependencies },
|
|
510
|
-
src_map: parsedRet.srcMap,
|
|
511
|
-
html_content: parsedRet.htmlContent,
|
|
512
|
-
},
|
|
513
|
-
};
|
|
907
|
+
/** @typedef {import('../../typings').SrcMap} SrcMap */
|
|
908
|
+
|
|
909
|
+
const readFile = util__namespace.promisify(fs__namespace.readFile);
|
|
910
|
+
const { JSDOM } = jsdom__default["default"];
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* @param {IUserExtractOptions} extractOptions
|
|
914
|
+
*/
|
|
915
|
+
async function parseIndexHtml(extractOptions) {
|
|
916
|
+
const { appInfo, indexHtmlPath, extractMode = 'all', indexHtmlName = cst.DEFAULT_HTML_INDEX_NAME } = extractOptions;
|
|
917
|
+
const { name, homePage } = appInfo;
|
|
918
|
+
const htmlFilePath = `${indexHtmlPath}`;
|
|
919
|
+
verbose(`start to parse ${name} index.html file [${indexHtmlPath}]`);
|
|
920
|
+
|
|
921
|
+
let htmlContent = await readFile(htmlFilePath, { encoding: 'UTF-8' });
|
|
922
|
+
const srcMap = makeAppVersionSrcMap(extractOptions);
|
|
923
|
+
|
|
924
|
+
const dom = new JSDOM(htmlContent);
|
|
925
|
+
const { head, body } = dom.window.document;
|
|
926
|
+
/** @type {IInnerFillAssetListOptions} */
|
|
927
|
+
const fillAssetListOptions = { srcMap, homePage, ...extractOptions };
|
|
928
|
+
const [replaceContentListOfHead, replaceContentLisOfBody] = await Promise.all([
|
|
929
|
+
fillAssetList(head.children, { ...fillAssetListOptions, isHead: true }),
|
|
930
|
+
fillAssetList(body.children, fillAssetListOptions),
|
|
931
|
+
]);
|
|
932
|
+
|
|
933
|
+
replaceContentListOfHead.forEach((item) => {
|
|
934
|
+
htmlContent = htmlContent.replace(item.toMatch, item.toReplace);
|
|
935
|
+
});
|
|
936
|
+
replaceContentLisOfBody.forEach((item) => {
|
|
937
|
+
htmlContent = htmlContent.replace(item.toMatch, item.toReplace);
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
const shouldRecordHtmlContent = extractMode === 'all' || extractMode === 'build';
|
|
941
|
+
const htmlContentVar = shouldRecordHtmlContent ? htmlContent : '';
|
|
942
|
+
if (!shouldRecordHtmlContent) {
|
|
943
|
+
verbose(`user set extractMode='${extractMode}', dev-utils will ignore write version.html_content`);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const hasReplacedContent = replaceContentListOfHead.length || replaceContentLisOfBody.length;
|
|
947
|
+
const parsedRet = { srcMap, htmlContent: htmlContentVar, hasReplacedContent };
|
|
948
|
+
verbose(`parse app [${name}] index.html file done!`);
|
|
949
|
+
verbose('replaceContentListOfHead: ', replaceContentListOfHead);
|
|
950
|
+
verbose('replaceContentLisOfBody: ', replaceContentLisOfBody);
|
|
951
|
+
verbose('parsedRet: ', parsedRet);
|
|
952
|
+
return parsedRet;
|
|
514
953
|
}
|
|
515
954
|
|
|
516
|
-
/**
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
955
|
+
/**
|
|
956
|
+
* 从 index.html 提取资源的描述数据,包含 htmlContent、srcMap
|
|
957
|
+
* @param {import('../../typings').IUserExtractOptions} userExtractOptions
|
|
958
|
+
*/
|
|
959
|
+
async function extractArkMetaJson(userExtractOptions) {
|
|
960
|
+
const { buildDirFullPath, writeMetaJsonToDist = true } = userExtractOptions;
|
|
961
|
+
const appInfo = userExtractOptions.appInfo || userExtractOptions.subApp;
|
|
962
|
+
const indexHtmlName = userExtractOptions.indexHtmlName || 'index.html';// getIndexHtmlFileName(buildDirFullPath);
|
|
963
|
+
|
|
964
|
+
if (!appInfo) {
|
|
965
|
+
throw new Error('appInfo should be supplied in ver 3.0+ ark-dev-utils: extractArkMetaJson({appInfo, ...})');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const { homePage } = appInfo;
|
|
969
|
+
const options = { ...userExtractOptions, appInfo, subApp: appInfo, indexHtmlName };
|
|
970
|
+
|
|
971
|
+
verbose(`start extractArkMetaJson, appHomePage is [${homePage}]`);
|
|
972
|
+
// 分析 html 入口,提取 sdk 加载需要的资源清单
|
|
973
|
+
const parsedRet = await parseIndexHtml(options);
|
|
974
|
+
// 分析构建产物目录,提取剩余的资源清单补充到 chunkJsSrcList chunkCssSrcList 下,以便描述出应用的所有构建产物的资源路径
|
|
975
|
+
// fillAssetListByDist({ ...userExtractOptions, srcMap: parsedRet.srcMap, homePage, buildDirFullPath });
|
|
976
|
+
if (userExtractOptions.chunkJsFiles) {
|
|
977
|
+
for (const chunkJsFile of userExtractOptions.chunkJsFiles) {
|
|
978
|
+
noDupPush(parsedRet.srcMap.chunkJsSrcList, chunkJsFile);
|
|
979
|
+
|
|
980
|
+
noDupPush(parsedRet.srcMap.bodyAssetList, {
|
|
981
|
+
tag: 'script',
|
|
982
|
+
append: true,
|
|
983
|
+
attrs: {
|
|
984
|
+
src: chunkJsFile,
|
|
985
|
+
type: 'text/javascript',
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// 有替换内容生成,则将 index.html 内容重写,让后续上传 cdn 步骤上传的是替换后的文件内容
|
|
992
|
+
if (parsedRet.hasReplacedContent) {
|
|
993
|
+
const htmlFilePath = `${buildDirFullPath}/${indexHtmlName}`;
|
|
994
|
+
fs__default["default"].writeFileSync(htmlFilePath, parsedRet.htmlContent, { encoding: 'utf-8' });
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const arkMeta = makeArkMetaJson(options, parsedRet);
|
|
998
|
+
verbose('userExtractOptions.isDev', userExtractOptions.isDev);
|
|
999
|
+
if(userExtractOptions.isDev) {
|
|
1000
|
+
// 开发模式:写入内存文件系统
|
|
1001
|
+
const metaFilename = 'ark-meta.json';
|
|
1002
|
+
verbose('[begin] write memory ark meta ' + metaFilename);
|
|
1003
|
+
|
|
1004
|
+
const metaContent = JSON.stringify(arkMeta, null, 2);
|
|
1005
|
+
const compilation = userExtractOptions.compilation;
|
|
1006
|
+
|
|
1007
|
+
// Webpack 5 正确的 API:使用 emitAsset
|
|
1008
|
+
if (compilation.emitAsset) {
|
|
1009
|
+
const { sources } = compilation.compiler.webpack;
|
|
1010
|
+
compilation.emitAsset(
|
|
1011
|
+
metaFilename,
|
|
1012
|
+
new sources.RawSource(metaContent)
|
|
1013
|
+
);
|
|
1014
|
+
} else {
|
|
1015
|
+
// Webpack 4 兼容
|
|
1016
|
+
compilation.assets[metaFilename] = {
|
|
1017
|
+
source: () => metaContent,
|
|
1018
|
+
size: () => metaContent.length
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
verbose('[finish] write memory ark meta ' + metaFilename);
|
|
1022
|
+
}else if (writeMetaJsonToDist) {
|
|
1023
|
+
// 生产模式:写入物理文件
|
|
1024
|
+
const arkMetaJsonFile = `${buildDirFullPath}/ark-meta.json`;
|
|
1025
|
+
verbose('finish write ark meta');
|
|
1026
|
+
fs__default["default"].writeFileSync(arkMetaJsonFile, JSON.stringify(arkMeta, null, 2));
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
return arkMeta;
|
|
523
1030
|
}
|
|
524
1031
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
1032
|
+
var presetExternals = {
|
|
1033
|
+
react: {
|
|
1034
|
+
react: 'React',
|
|
1035
|
+
'react-dom': 'ReactDOM',
|
|
1036
|
+
'react-is': 'ReactIs',
|
|
1037
|
+
},
|
|
1038
|
+
/** 历史原因,暂留 vue2 */
|
|
1039
|
+
vue2: {
|
|
1040
|
+
vue: 'Vue',
|
|
1041
|
+
},
|
|
1042
|
+
/** 历史原因,暂留 vue3 */
|
|
1043
|
+
vue3: {
|
|
1044
|
+
vue: 'Vue',
|
|
1045
|
+
},
|
|
1046
|
+
vue: {
|
|
1047
|
+
vue: 'Vue',
|
|
1048
|
+
},
|
|
1049
|
+
};
|
|
532
1050
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1051
|
+
/** @typedef {import('../../typings').IInnerSubAppOptions} IInnerSubAppOptions */
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
*
|
|
1055
|
+
* @param {Record<string, any>} pkg
|
|
1056
|
+
* @param {IInnerSubAppOptions} innerOptions
|
|
1057
|
+
* @param {ICreateSubAppOptions} [userOptions]
|
|
1058
|
+
* @returns
|
|
1059
|
+
*/
|
|
1060
|
+
function createSubApp(pkg, innerOptions, userOptions) {
|
|
1061
|
+
const { frameworkType } = innerOptions;
|
|
1062
|
+
const optionsVar = Object.assign(
|
|
1063
|
+
{
|
|
1064
|
+
platform: cst.DEFAULT_PLAT,
|
|
1065
|
+
npmCdnType: cst.DEFAULT_NPM_CDN_TYPE,
|
|
1066
|
+
handleHomePage: true,
|
|
1067
|
+
semverApi: cst.DEFAULT_SEMVER_API,
|
|
1068
|
+
distDir: cst.ARK_DIST_DIR,
|
|
1069
|
+
},
|
|
1070
|
+
userOptions || {},
|
|
1071
|
+
);
|
|
1072
|
+
const envParams = getHelEnvParams(pkg, optionsVar);
|
|
1073
|
+
const externals = Object.assign({}, optionsVar.externals || {}, presetExternals[frameworkType] || {});
|
|
1074
|
+
const jsonpFnName = getJsonpFnName(envParams.appName || pkg.name);
|
|
1075
|
+
|
|
1076
|
+
return {
|
|
1077
|
+
platform: optionsVar.platform,
|
|
1078
|
+
/**
|
|
1079
|
+
* 资源的网络根目录
|
|
1080
|
+
* 形如:
|
|
1081
|
+
* 1 /web-app/sub-apps/ticket
|
|
1082
|
+
* 2 http://www.cdn.com/xxx/yyy
|
|
1083
|
+
*/
|
|
1084
|
+
homePage: envParams.appHomePage,
|
|
1085
|
+
npmCdnType: optionsVar.npmCdnType,
|
|
1086
|
+
groupName: envParams.appGroupName,
|
|
1087
|
+
/** 构建时可注入到应用的APP_NAME下 */
|
|
1088
|
+
name: envParams.appName,
|
|
1089
|
+
externals,
|
|
1090
|
+
/**
|
|
1091
|
+
* @param {Record<string, any>} userExternals
|
|
1092
|
+
* @returns
|
|
1093
|
+
*/
|
|
1094
|
+
getExternals: (userExternals) => {
|
|
1095
|
+
if (userExternals && !Array.isArray(userExternals)) {
|
|
1096
|
+
return { ...userExternals, externals };
|
|
1097
|
+
}
|
|
1098
|
+
return externals;
|
|
1099
|
+
},
|
|
1100
|
+
jsonpFnName,
|
|
1101
|
+
/**
|
|
1102
|
+
* @param {string} [fallbackPathOrUrl] 兜底用的 publicPathOrUrl
|
|
1103
|
+
* @param {boolean} [needEndSlash]
|
|
1104
|
+
* @returns
|
|
1105
|
+
*/
|
|
1106
|
+
getPublicPathOrUrl: (fallbackPathOrUrl = '/', needEndSlash = true) => {
|
|
1107
|
+
let pathOrUrl = envParams.appHomePage;
|
|
1108
|
+
// 用户传递了非 / 的值时,优先采用用户传递的值
|
|
1109
|
+
if (pathOrUrl === '/' && fallbackPathOrUrl !== '/') {
|
|
1110
|
+
pathOrUrl = fallbackPathOrUrl;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const finalPathOrUrl = getPublicPathOrUrl(pathOrUrl, needEndSlash);
|
|
1114
|
+
return finalPathOrUrl;
|
|
1115
|
+
},
|
|
1116
|
+
distDir: optionsVar.distDir,
|
|
1117
|
+
};
|
|
536
1118
|
}
|
|
537
1119
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
tagVar = tag;
|
|
549
|
-
} else if (isNonBuildAndRelative) {
|
|
550
|
-
tagVar = isLink ? 'relativeLink' : 'relativeScript';
|
|
551
|
-
} else {
|
|
552
|
-
tagVar = isLink ? 'staticLink' : 'staticScript';
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const assetItem = { tag: tagVar, append: canAppend, attrs };
|
|
556
|
-
if (innerText) {
|
|
557
|
-
assetItem.innerText = innerText;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const attrNames = el.getAttributeNames();
|
|
561
|
-
attrNames.forEach((name) => {
|
|
562
|
-
// src href 上面已记录真正的目标值,故移除
|
|
563
|
-
// data-helappend 只在提取元数据辅助计算 append 值时用到,故此处移除
|
|
564
|
-
if (['src', 'href', 'data-helappend'].includes(name)) return;
|
|
565
|
-
attrs[name] = el.getAttribute(name);
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
return assetItem;
|
|
1120
|
+
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
*
|
|
1124
|
+
* @param {Record<string, any>} pkg
|
|
1125
|
+
* @param {ICreateSubAppOptions} [options]
|
|
1126
|
+
* @returns
|
|
1127
|
+
*/
|
|
1128
|
+
function createLibSubApp(pkg, options) {
|
|
1129
|
+
return createSubApp(pkg, { frameworkType: 'lib' }, options);
|
|
569
1130
|
}
|
|
570
1131
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1132
|
+
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
*
|
|
1136
|
+
* @param {Record<string, any>} pkg
|
|
1137
|
+
* @param {ICreateSubAppOptions} [options]
|
|
1138
|
+
* @returns
|
|
1139
|
+
*/
|
|
1140
|
+
function createReactSubApp(pkg, options) {
|
|
1141
|
+
return createSubApp(pkg, { frameworkType: 'react' }, options);
|
|
577
1142
|
}
|
|
578
1143
|
|
|
579
|
-
/**
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
*
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
verbose(`found a user customized ${fileType} tag node in html, try extract its content and write them to local fs`);
|
|
590
|
-
custScriptIdx += 1;
|
|
591
|
-
const scriptName = `ark_userChunk_${custScriptIdx}.${fileType}`;
|
|
592
|
-
const fileAbsolutePath = `${buildDirFullPath}/${scriptName}`;
|
|
593
|
-
const fileWebPath = `${slash.noEnd(homePage)}/${scriptName}`;
|
|
594
|
-
|
|
595
|
-
// 对于 JS 文件,移除或替换 document.write() 调用
|
|
596
|
-
// 因为异步加载的脚本中不能使用 document.write()
|
|
597
|
-
if (fileType === 'js') {
|
|
598
|
-
// 方法1:完全移除 document.write() 调用(推荐)
|
|
599
|
-
// innerText = innerText.replace(/document\.write\s*\([^)]*\)/g, '');
|
|
600
|
-
|
|
601
|
-
// 方法2:用 console.warn 替换 document.write(),保留代码逻辑但避免错误
|
|
602
|
-
innerText = innerText.replace(
|
|
603
|
-
/document\.write\s*\(([^)]*)\)/g,
|
|
604
|
-
'console.warn("[ark-micro] document.write is not allowed in asynchronously loaded scripts:", $1)'
|
|
605
|
-
);
|
|
606
|
-
verbose(`Replaced document.write calls in ${scriptName}`);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// 在开发模式下,使用 compilation.emitAsset 确保文件能被 dev server 访问
|
|
610
|
-
if (compilation && compilation.emitAsset) {
|
|
611
|
-
const { sources } = compilation.compiler.webpack;
|
|
612
|
-
compilation.emitAsset(
|
|
613
|
-
scriptName,
|
|
614
|
-
new sources.RawSource(innerText)
|
|
615
|
-
);
|
|
616
|
-
verbose(`emit asset ${scriptName} via webpack compilation`);
|
|
617
|
-
} else {
|
|
618
|
-
// 生产模式或没有 compilation 对象时,直接写入文件系统
|
|
619
|
-
await writeFile(fileAbsolutePath, innerText);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// 将自定义资产信息保存到 customAssets 数组,供后续使用
|
|
623
|
-
if (customAssets) {
|
|
624
|
-
customAssets.push({ scriptName, fileWebPath, content: innerText });
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
verbose(`write done, the web file will be ${fileWebPath} later`);
|
|
628
|
-
return fileWebPath;
|
|
1144
|
+
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* 创建 vue 应用的描述对象
|
|
1148
|
+
* @param {Record<string, any>} pkg
|
|
1149
|
+
* @param {ICreateSubAppOptions} [options]
|
|
1150
|
+
* @returns
|
|
1151
|
+
*/
|
|
1152
|
+
function createVueSubApp(pkg, options) {
|
|
1153
|
+
return createSubApp(pkg, { frameworkType: 'vue' }, options);
|
|
629
1154
|
}
|
|
630
1155
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
//
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
throw new Error(`found invalid helappend value [${helAppendValOfDataset}], only accpet 1 or 0 currently`);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const ex = dataset.helex || '';
|
|
660
|
-
if (isNonBuildAndRelative) {
|
|
661
|
-
if (isIcoAsset && !helAppendValOfDataset) {
|
|
662
|
-
helAppendValOfDataset = '0'; // ico 文件特殊处理,默认是不加载的
|
|
663
|
-
} else if (ex && !helAppendValOfDataset) {
|
|
664
|
-
helAppendValOfDataset = '1'; // 标记了 ex 的文件特殊处理,默认需要加载,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// 如下面错误描述所示,在既没有设置 enableRelativePath=true,又没有显式的标记 data-helappend 的情况下
|
|
668
|
-
// 不允许 homePage 之外的相对路径导入的资源存在
|
|
669
|
-
// 所以对于此类 homePage 之外的相对路径导入的资源,要么用户设置 enableRelativePath=true,要么显式的标记 data-helappend
|
|
670
|
-
// 设置 enableRelativePath=true 后,优先读可能已存在的 data-helappend 值,没有则默认为 0,表示不加载
|
|
671
|
-
// 不设置 enableRelativePath=true 的话,则需要用户一定标记 data-helappend 值
|
|
672
|
-
if (!enableRelativePath && !helAppendValOfDataset) {
|
|
673
|
-
throw new Error(
|
|
674
|
-
pfstr(`
|
|
675
|
-
found asset url [${url}] is a relative path, it is obviously not a valid url for cdn architecture deploy!
|
|
676
|
-
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:
|
|
677
|
-
1. pass enableRelativePath true to ark-dev-utils.extractArkMetaJson method options.
|
|
678
|
-
2. add data-helappend="0" on the asset dom attribute to tell ark-dev-utils ignore this asset.
|
|
679
|
-
|
|
680
|
-
ark-dev-utils will mark this url as relativeLink or relativeScript, and set append as false,
|
|
681
|
-
if you want sdk append this asset, you can explicitly add data-helappend="1" on the asset dom attribute.
|
|
682
|
-
a demo will be like:<script src="./a/b.js" data-helappend="1"></script>,<br/>
|
|
683
|
-
note that the asset will depend on your host site seriously under this situation.
|
|
684
|
-
`),
|
|
685
|
-
);
|
|
686
|
-
}
|
|
1156
|
+
var ExternalsDefault = {
|
|
1157
|
+
|
|
1158
|
+
vue: 'Vue',
|
|
1159
|
+
axios: 'axios',
|
|
1160
|
+
// vuex: 'Vuex',
|
|
1161
|
+
// 'vue-dim'
|
|
1162
|
+
pinia: 'Pinia',
|
|
1163
|
+
'pinia-plugin-persistedstate': 'piniaPluginPersistedstate',
|
|
1164
|
+
'vue-i18n': 'VueI18n',
|
|
1165
|
+
'vue-router': 'VueRouter',
|
|
1166
|
+
'element-plus': 'ElementPlus',
|
|
1167
|
+
'@arkxio/ark-micro-core': 'ArkMicroCore',
|
|
1168
|
+
'@arkxio/ark-lib-proxy': 'ArkLibProxy',
|
|
1169
|
+
'@arkxio/ark-micro': 'ArkMicro',
|
|
1170
|
+
'@arkxio/ark-plugin': 'ArkPlugin'
|
|
1171
|
+
// 'echarts': 'echarts',
|
|
1172
|
+
// 'tinymce': 'tinymce'
|
|
1173
|
+
// "loadjs": { //将vue依赖 "外部化",不打包进组件库
|
|
1174
|
+
// root: 'loadjs',
|
|
1175
|
+
// commonjs: 'loadjs',
|
|
1176
|
+
// commonjs2: 'loadjs',
|
|
1177
|
+
// amd: 'loadjs'
|
|
1178
|
+
// }
|
|
1179
|
+
|
|
1180
|
+
};
|
|
687
1181
|
|
|
688
|
-
|
|
689
|
-
allowAddToAssetList = true;
|
|
690
|
-
helAppendValOfInnerLogic = helAppendValOfDataset || '0'; // 没有显示设定 data-helappend 时默认标记为不加载
|
|
691
|
-
} else if (isIcoAsset) {
|
|
692
|
-
helAppendValOfInnerLogic = '0'; // ico 文件特殊处理,默认是不加载的
|
|
693
|
-
} else if (isStatic) {
|
|
694
|
-
if (ex) {
|
|
695
|
-
// 对于标记了 helex 的元素,默认是 append 的,能不能真的追加到文档上,取决于 @arkxio/ark-micro 的重复检测结果
|
|
696
|
-
// 重复则不追加,不重复则追加
|
|
697
|
-
helAppendValOfInnerLogic = helAppendValOfDataset || '1';
|
|
698
|
-
} else {
|
|
699
|
-
helAppendValOfInnerLogic = '0';
|
|
700
|
-
}
|
|
701
|
-
} else {
|
|
702
|
-
// isBuild
|
|
703
|
-
helAppendValOfInnerLogic = '1'; // 标记为可加载
|
|
704
|
-
}
|
|
1182
|
+
class ArkWebpackPlugin {
|
|
705
1183
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (ex && !helAppend) {
|
|
709
|
-
// 设置了 helex 但同时设置了不加载,会造成歧义,当前版本是不允许的
|
|
710
|
-
throw new Error(
|
|
711
|
-
pfstr(`
|
|
712
|
-
found conflict setting for helex: [data-helex="${ex}"]、[data-helappend="${helAppendVal}"],
|
|
713
|
-
remove data-helappend( append is true for helex by default ) or set data-helappend="1"!
|
|
714
|
-
`),
|
|
715
|
-
);
|
|
1184
|
+
constructor(options) {
|
|
1185
|
+
this.metaOptions = options;
|
|
716
1186
|
}
|
|
717
1187
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
isNonBuildAndRelative,
|
|
723
|
-
canAppend: helAppend,
|
|
724
|
-
allowAddToAssetList,
|
|
725
|
-
innerText: enableAssetInnerText ? innerText : '',
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* 提取link、script标签数据并填充到目标assetList
|
|
731
|
-
* @param {HTMLCollectionOf<HTMLScriptElement>} doms
|
|
732
|
-
* @param {IInnerFillAssetListOptions} options
|
|
733
|
-
*/
|
|
734
|
-
async function fillAssetList(doms, options) {
|
|
735
|
-
const {
|
|
736
|
-
homePage,
|
|
737
|
-
enableReplaceDevJs = true,
|
|
738
|
-
enableRelativePath = false,
|
|
739
|
-
enableAssetInnerText = false,
|
|
740
|
-
enablePrefixHomePage = false,
|
|
741
|
-
srcMap,
|
|
742
|
-
isHead,
|
|
743
|
-
} = options;
|
|
744
|
-
const {
|
|
745
|
-
headAssetList,
|
|
746
|
-
bodyAssetList,
|
|
747
|
-
extractMode,
|
|
748
|
-
chunkCssSrcList,
|
|
749
|
-
staticCssSrcList,
|
|
750
|
-
relativeCssSrcList,
|
|
751
|
-
chunkJsSrcList,
|
|
752
|
-
staticJsSrcList,
|
|
753
|
-
relativeJsSrcList,
|
|
754
|
-
} = srcMap;
|
|
755
|
-
const assetList = isHead ? headAssetList : bodyAssetList;
|
|
756
|
-
|
|
757
|
-
const len = doms.length;
|
|
758
|
-
const replaceContentList = [];
|
|
759
|
-
verbose(`extractMode is ${extractMode}`);
|
|
760
|
-
verbose(`enableAssetInnerText is ${enableAssetInnerText}`);
|
|
761
|
-
|
|
762
|
-
const pushToSrcList = (assetType, /** @type {IAssetInfo} */ assetInfo) => {
|
|
763
|
-
const { isBuildUrl, isNonBuildAndRelative, url } = assetInfo;
|
|
764
|
-
const isAllExtractMode = extractMode === 'all' || extractMode === 'all_no_html';
|
|
765
|
-
|
|
766
|
-
if (assetType === 'css') {
|
|
767
|
-
if (isBuildUrl) {
|
|
768
|
-
return noDupPush(chunkCssSrcList, url);
|
|
769
|
-
}
|
|
770
|
-
if (isAllExtractMode) {
|
|
771
|
-
const list = isNonBuildAndRelative ? relativeCssSrcList : staticCssSrcList;
|
|
772
|
-
return noDupPush(list, url);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
if (isBuildUrl) {
|
|
777
|
-
return noDupPush(chunkJsSrcList, url);
|
|
778
|
-
}
|
|
779
|
-
if (isAllExtractMode) {
|
|
780
|
-
const list = isNonBuildAndRelative ? relativeJsSrcList : staticJsSrcList;
|
|
781
|
-
return noDupPush(list, url);
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
const mayPrefixHomePage = (url) => {
|
|
786
|
-
if (enablePrefixHomePage) {
|
|
787
|
-
if (url.startsWith('/') && !url.startsWith('//')) {
|
|
788
|
-
verbose(` >>> prefix homePage [${homePage}] for url [${url}]`);
|
|
789
|
-
const finalUrl = `${slash.noEnd(homePage)}${url}`;
|
|
790
|
-
replaceContentList.push({ toMatch: `="${url}"`, toReplace: `="${finalUrl}"` });
|
|
791
|
-
return finalUrl;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
return url;
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
for (let i = 0; i < len; i++) {
|
|
798
|
-
const childDom = doms[i];
|
|
799
|
-
const { tagName, dataset = {} } = childDom;
|
|
800
|
-
const innerText = getInnerText(childDom);
|
|
801
|
-
let toPushAsset = null;
|
|
802
|
-
let allowAddToAssetList = false;
|
|
803
|
-
const assetOptions = { el: childDom, innerText, homePage, extractMode, enableRelativePath, enableAssetInnerText };
|
|
804
|
-
|
|
805
|
-
if (!['LINK', 'SCRIPT', 'STYLE'].includes(tagName)) {
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
if (!isNull(dataset)) {
|
|
809
|
-
verbose(`found ${tagName} dataset`, dataset);
|
|
810
|
-
}
|
|
1188
|
+
apply(compiler) {
|
|
1189
|
+
// 获取项目根目录路径
|
|
1190
|
+
compiler.context;
|
|
1191
|
+
const useExtractOptions = this.metaOptions;
|
|
811
1192
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
let href = mayPrefixHomePage(oriHref);
|
|
818
|
-
verbose(`analyze link [${href}]`);
|
|
819
|
-
// 一些使用了老版本cra的项目,这两个href 在修改了 publicPath 后也不被添加前缀,这里做一下修正
|
|
820
|
-
const legacyHrefs = ['/manifest.json', '/favicon.ico'];
|
|
821
|
-
if (legacyHrefs.includes(href)) {
|
|
822
|
-
const oldHref = href;
|
|
823
|
-
href = `${homePage}${href}`;
|
|
824
|
-
replaceContentList.push({ toMatch: href, toReplace: href });
|
|
825
|
-
verbose(`replace link [${oldHref}] to [href]`);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
const assetInfo = getAssetInfo(href, assetOptions);
|
|
829
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
830
|
-
// 供 shadow-dom 或其他需要知道当前应用所有样式列表的场景用
|
|
831
|
-
if (href.endsWith('.css')) {
|
|
832
|
-
pushToSrcList('css', assetInfo);
|
|
833
|
-
}
|
|
834
|
-
toPushAsset = buildAssetItem('link', assetInfo);
|
|
835
|
-
} else if (tagName === 'SCRIPT') {
|
|
836
|
-
const { src } = childDom;
|
|
837
|
-
verbose(`analyze script src:[${src}], innerText:[${innerText}]`);
|
|
838
|
-
|
|
839
|
-
if (!src && !innerText) {
|
|
840
|
-
continue;
|
|
841
|
-
}
|
|
1193
|
+
// 在每次编译开始时重置脚本索引计数器,确保文件名一致性
|
|
1194
|
+
compiler.hooks.thisCompilation.tap('ArkResetScriptIdx', () => {
|
|
1195
|
+
resetScriptIdx();
|
|
1196
|
+
});
|
|
842
1197
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1198
|
+
compiler.hooks.thisCompilation.tap('ArkDynamicCdnChunkLoaderPlugin', (compilation) => {
|
|
1199
|
+
compilation.hooks.processAssets.tap(
|
|
1200
|
+
{
|
|
1201
|
+
name: 'ArkDynamicCdnChunkLoaderPlugin',
|
|
1202
|
+
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
|
|
1203
|
+
},
|
|
1204
|
+
(assets) => {
|
|
1205
|
+
// 新增分块文件过滤逻辑
|
|
1206
|
+
const chunkFiles = new Set();
|
|
1207
|
+
compilation.chunks.forEach(chunk => {
|
|
1208
|
+
chunk.files.forEach(file => {
|
|
1209
|
+
if (file.endsWith('.js')) chunkFiles.add(file);
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
// 调试日志
|
|
1213
|
+
verbose('[分块文件列表]', Array.from(chunkFiles));
|
|
1214
|
+
|
|
1215
|
+
Object.entries(assets).forEach(([filename, source]) => {
|
|
1216
|
+
// 仅处理分块JS文件
|
|
1217
|
+
if (!chunkFiles.has(filename)) return;
|
|
1218
|
+
|
|
1219
|
+
// 检查资源内容有效性
|
|
1220
|
+
if (!source || !source.source || typeof source.source() !== 'string' || source.source().trim() === '') {
|
|
1221
|
+
console.warn(`资源 ${filename} 内容为空或无效,跳过处理`);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
let content = source.source().toString();
|
|
1226
|
+
|
|
1227
|
+
// 替换逻辑
|
|
1228
|
+
// /(__webpack_require__\.l)\(url,/g)
|
|
1229
|
+
// const webpackRequireLPattern = /(__webpack_require__\.l)\(url,/g;
|
|
1230
|
+
// if (webpackRequireLPattern.test(content)) {
|
|
1231
|
+
// verbose('检测到 __webpack_require__.l 模式');
|
|
1232
|
+
// content = content.replace(
|
|
1233
|
+
// webpackRequireLPattern,
|
|
1234
|
+
// `(function(url, done, key, chunkId) {
|
|
1235
|
+
// if (typeof window.ArkConfig !== 'undefined' && window.ArkConfig.cdnHost) {
|
|
1236
|
+
// if (!url.startsWith(window.ArkConfig.cdnHost) && url.startsWith('https://unpkg.com')) {
|
|
1237
|
+
// url = window.ArkConfig.cdnHost + url.slice('https://unpkg.com'.length);
|
|
1238
|
+
// }
|
|
1239
|
+
// }
|
|
1240
|
+
// return $1(url, done, key, chunkId);
|
|
1241
|
+
// })(url,`
|
|
1242
|
+
// );
|
|
1243
|
+
// const webpackRequireLPattern = /"(https:\/\/unpkg\.com)/g;
|
|
1244
|
+
// 新:捕获前面可选的 \(eval 模式下会有 \")
|
|
1245
|
+
const webpackRequireLPattern = /(\\?")https:\/\/unpkg\.com/g;
|
|
1246
|
+
if (webpackRequireLPattern.test(content)) {
|
|
1247
|
+
verbose('检测到 unpkg.com 模式');
|
|
1248
|
+
content = content.replace(webpackRequireLPattern, `window.ArkConfig.cdnHost + "`);
|
|
1249
|
+
|
|
1250
|
+
// 更新资源
|
|
1251
|
+
compilation.updateAsset(filename, new compiler.webpack.sources.RawSource(content));
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
852
1254
|
}
|
|
853
|
-
|
|
1255
|
+
);
|
|
1256
|
+
});
|
|
854
1257
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1258
|
+
compiler.hooks.emit.tapPromise('ArkMetaJsonGeneratorPlugin', async (compilation) => {
|
|
1259
|
+
// 注入环境判断脚本
|
|
1260
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
1261
|
+
|
|
1262
|
+
// 收集所有生成的JS文件
|
|
1263
|
+
const outputPath = compilation.options.output.publicPath || '';
|
|
1264
|
+
const jsFiles = [];
|
|
1265
|
+
|
|
1266
|
+
// Webpack 5 正确的API:使用 getAssets()
|
|
1267
|
+
const assets = compilation.getAssets ? compilation.getAssets() : compilation.assets;
|
|
1268
|
+
|
|
1269
|
+
// 遍历所有资产,收集JS文件
|
|
1270
|
+
assets.forEach((asset) => {
|
|
1271
|
+
const assetName = asset.name || asset;
|
|
1272
|
+
if (assetName.endsWith('.js')) {
|
|
1273
|
+
console.log('Found JS file:', assetName);
|
|
1274
|
+
// 排除 ark-meta.json 和 ark_userChunk 文件
|
|
1275
|
+
if (assetName.indexOf('js/app.') !== -1 ||
|
|
1276
|
+
assetName.indexOf('js/runtime.') !== -1 ||
|
|
1277
|
+
assetName.indexOf('js/vendors.') !== -1 ||
|
|
1278
|
+
assetName.indexOf('js/main.') !== -1) {
|
|
1279
|
+
jsFiles.push(outputPath + assetName);
|
|
868
1280
|
}
|
|
869
1281
|
}
|
|
870
|
-
|
|
871
|
-
const assetInfo = getAssetInfo(targetSrc, assetOptions);
|
|
872
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
873
|
-
|
|
874
|
-
pushToSrcList('js', assetInfo);
|
|
875
|
-
toPushAsset = buildAssetItem('script', assetInfo);
|
|
876
|
-
}
|
|
877
|
-
} else if (tagName === 'STYLE') {
|
|
878
|
-
verbose(`analyze style innerText:[${innerText}]`);
|
|
879
|
-
|
|
880
|
-
if (innerText && enableAssetInnerText) {
|
|
881
|
-
verbose('user set enableAssetInnerText = true and found STYLE node innerText');
|
|
882
|
-
const assetInfo = getAssetInfo('', assetOptions);
|
|
883
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
884
|
-
toPushAsset = buildAssetItem('style', assetInfo);
|
|
885
|
-
} else {
|
|
886
|
-
// style 标签转换为 css 文件存起来,以便让 @arkxio/ark-micro 用 link 标签加载
|
|
887
|
-
let href = await writeInnerText(childDom, 'css', options);
|
|
888
|
-
if (!href) continue;
|
|
889
|
-
|
|
890
|
-
href = mayPrefixHomePage(href);
|
|
891
|
-
const assetInfo = getAssetInfo(href, assetOptions);
|
|
892
|
-
allowAddToAssetList = assetInfo.allowAddToAssetList;
|
|
893
|
-
|
|
894
|
-
pushToSrcList('css', assetInfo);
|
|
895
|
-
toPushAsset = buildAssetItem('link', assetInfo);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
if (toPushAsset && allowAddToAssetList) {
|
|
900
|
-
assetList.push(toPushAsset);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
return replaceContentList;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/** @typedef {import('../../typings').SrcMap} SrcMap */
|
|
908
|
-
|
|
909
|
-
const readFile = util__namespace.promisify(fs__namespace.readFile);
|
|
910
|
-
const { JSDOM } = jsdom__default["default"];
|
|
911
|
-
|
|
912
|
-
/**
|
|
913
|
-
* @param {IUserExtractOptions} extractOptions
|
|
914
|
-
*/
|
|
915
|
-
async function parseIndexHtml(extractOptions) {
|
|
916
|
-
const { appInfo, indexHtmlPath, extractMode = 'all', indexHtmlName = cst.DEFAULT_HTML_INDEX_NAME } = extractOptions;
|
|
917
|
-
const { name, homePage } = appInfo;
|
|
918
|
-
const htmlFilePath = `${indexHtmlPath}`;
|
|
919
|
-
verbose(`start to parse ${name} index.html file [${indexHtmlPath}]`);
|
|
920
|
-
|
|
921
|
-
let htmlContent = await readFile(htmlFilePath, { encoding: 'UTF-8' });
|
|
922
|
-
const srcMap = makeAppVersionSrcMap(extractOptions);
|
|
923
|
-
|
|
924
|
-
const dom = new JSDOM(htmlContent);
|
|
925
|
-
const { head, body } = dom.window.document;
|
|
926
|
-
/** @type {IInnerFillAssetListOptions} */
|
|
927
|
-
const fillAssetListOptions = { srcMap, homePage, ...extractOptions };
|
|
928
|
-
const [replaceContentListOfHead, replaceContentLisOfBody] = await Promise.all([
|
|
929
|
-
fillAssetList(head.children, { ...fillAssetListOptions, isHead: true }),
|
|
930
|
-
fillAssetList(body.children, fillAssetListOptions),
|
|
931
|
-
]);
|
|
932
|
-
|
|
933
|
-
replaceContentListOfHead.forEach((item) => {
|
|
934
|
-
htmlContent = htmlContent.replace(item.toMatch, item.toReplace);
|
|
935
|
-
});
|
|
936
|
-
replaceContentLisOfBody.forEach((item) => {
|
|
937
|
-
htmlContent = htmlContent.replace(item.toMatch, item.toReplace);
|
|
938
|
-
});
|
|
939
|
-
|
|
940
|
-
const shouldRecordHtmlContent = extractMode === 'all' || extractMode === 'build';
|
|
941
|
-
const htmlContentVar = shouldRecordHtmlContent ? htmlContent : '';
|
|
942
|
-
if (!shouldRecordHtmlContent) {
|
|
943
|
-
verbose(`user set extractMode='${extractMode}', dev-utils will ignore write version.html_content`);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
const hasReplacedContent = replaceContentListOfHead.length || replaceContentLisOfBody.length;
|
|
947
|
-
const parsedRet = { srcMap, htmlContent: htmlContentVar, hasReplacedContent };
|
|
948
|
-
verbose(`parse app [${name}] index.html file done!`);
|
|
949
|
-
verbose('replaceContentListOfHead: ', replaceContentListOfHead);
|
|
950
|
-
verbose('replaceContentLisOfBody: ', replaceContentLisOfBody);
|
|
951
|
-
verbose('parsedRet: ', parsedRet);
|
|
952
|
-
return parsedRet;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
/**
|
|
956
|
-
* 从 index.html 提取资源的描述数据,包含 htmlContent、srcMap
|
|
957
|
-
* @param {import('../../typings').IUserExtractOptions} userExtractOptions
|
|
958
|
-
*/
|
|
959
|
-
async function extractArkMetaJson(userExtractOptions) {
|
|
960
|
-
const { buildDirFullPath, writeMetaJsonToDist = true } = userExtractOptions;
|
|
961
|
-
const appInfo = userExtractOptions.appInfo || userExtractOptions.subApp;
|
|
962
|
-
const indexHtmlName = userExtractOptions.indexHtmlName || 'index.html';// getIndexHtmlFileName(buildDirFullPath);
|
|
963
|
-
|
|
964
|
-
if (!appInfo) {
|
|
965
|
-
throw new Error('appInfo should be supplied in ver 3.0+ ark-dev-utils: extractArkMetaJson({appInfo, ...})');
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
const { homePage } = appInfo;
|
|
969
|
-
const options = { ...userExtractOptions, appInfo, subApp: appInfo, indexHtmlName };
|
|
970
|
-
|
|
971
|
-
verbose(`start extractArkMetaJson, appHomePage is [${homePage}]`);
|
|
972
|
-
// 分析 html 入口,提取 sdk 加载需要的资源清单
|
|
973
|
-
const parsedRet = await parseIndexHtml(options);
|
|
974
|
-
// 分析构建产物目录,提取剩余的资源清单补充到 chunkJsSrcList chunkCssSrcList 下,以便描述出应用的所有构建产物的资源路径
|
|
975
|
-
// fillAssetListByDist({ ...userExtractOptions, srcMap: parsedRet.srcMap, homePage, buildDirFullPath });
|
|
976
|
-
if (userExtractOptions.chunkJsFiles) {
|
|
977
|
-
for (const chunkJsFile of userExtractOptions.chunkJsFiles) {
|
|
978
|
-
noDupPush(parsedRet.srcMap.chunkJsSrcList, chunkJsFile);
|
|
979
|
-
|
|
980
|
-
noDupPush(parsedRet.srcMap.bodyAssetList, {
|
|
981
|
-
tag: 'script',
|
|
982
|
-
append: true,
|
|
983
|
-
attrs: {
|
|
984
|
-
src: chunkJsFile,
|
|
985
|
-
type: 'text/javascript',
|
|
986
|
-
},
|
|
987
1282
|
});
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
1283
|
|
|
991
|
-
|
|
992
|
-
if (parsedRet.hasReplacedContent) {
|
|
993
|
-
const htmlFilePath = `${buildDirFullPath}/${indexHtmlName}`;
|
|
994
|
-
fs__default["default"].writeFileSync(htmlFilePath, parsedRet.htmlContent, { encoding: 'utf-8' });
|
|
995
|
-
}
|
|
1284
|
+
console.log('Final JS files for chunkJsSrcList:', jsFiles);
|
|
996
1285
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
if(userExtractOptions.isDev) {
|
|
1000
|
-
// 开发模式:写入内存文件系统
|
|
1001
|
-
const metaFilename = 'ark-meta.json';
|
|
1002
|
-
verbose('[begin] write memory ark meta ' + metaFilename);
|
|
1286
|
+
// 创建 customAssets 数组用于收集自定义资产(如从 HTML 内联 script 提取的代码)
|
|
1287
|
+
const customAssets = [];
|
|
1003
1288
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1289
|
+
await extractArkMetaJson({
|
|
1290
|
+
...useExtractOptions,
|
|
1291
|
+
chunkJsFiles: jsFiles,
|
|
1292
|
+
isDev: isDev,
|
|
1293
|
+
compilation: compilation,
|
|
1294
|
+
customAssets: customAssets
|
|
1295
|
+
});
|
|
1006
1296
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const { sources } = compilation.compiler.webpack;
|
|
1010
|
-
compilation.emitAsset(
|
|
1011
|
-
metaFilename,
|
|
1012
|
-
new sources.RawSource(metaContent)
|
|
1013
|
-
);
|
|
1014
|
-
} else {
|
|
1015
|
-
// Webpack 4 兼容
|
|
1016
|
-
compilation.assets[metaFilename] = {
|
|
1017
|
-
source: () => metaContent,
|
|
1018
|
-
size: () => metaContent.length
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
verbose('[finish] write memory ark meta ' + metaFilename);
|
|
1022
|
-
}else if (writeMetaJsonToDist) {
|
|
1023
|
-
// 生产模式:写入物理文件
|
|
1024
|
-
const arkMetaJsonFile = `${buildDirFullPath}/ark-meta.json`;
|
|
1025
|
-
verbose('finish write ark meta');
|
|
1026
|
-
fs__default["default"].writeFileSync(arkMetaJsonFile, JSON.stringify(arkMeta, null, 2));
|
|
1297
|
+
return Promise.resolve();
|
|
1298
|
+
});
|
|
1027
1299
|
}
|
|
1028
|
-
|
|
1029
|
-
return arkMeta;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
var presetExternals = {
|
|
1033
|
-
react: {
|
|
1034
|
-
react: 'React',
|
|
1035
|
-
'react-dom': 'ReactDOM',
|
|
1036
|
-
'react-is': 'ReactIs',
|
|
1037
|
-
},
|
|
1038
|
-
/** 历史原因,暂留 vue2 */
|
|
1039
|
-
vue2: {
|
|
1040
|
-
vue: 'Vue',
|
|
1041
|
-
},
|
|
1042
|
-
/** 历史原因,暂留 vue3 */
|
|
1043
|
-
vue3: {
|
|
1044
|
-
vue: 'Vue',
|
|
1045
|
-
},
|
|
1046
|
-
vue: {
|
|
1047
|
-
vue: 'Vue',
|
|
1048
|
-
},
|
|
1049
|
-
};
|
|
1050
|
-
|
|
1051
|
-
/** @typedef {import('../../typings').IInnerSubAppOptions} IInnerSubAppOptions */
|
|
1052
|
-
|
|
1053
|
-
/**
|
|
1054
|
-
*
|
|
1055
|
-
* @param {Record<string, any>} pkg
|
|
1056
|
-
* @param {IInnerSubAppOptions} innerOptions
|
|
1057
|
-
* @param {ICreateSubAppOptions} [userOptions]
|
|
1058
|
-
* @returns
|
|
1059
|
-
*/
|
|
1060
|
-
function createSubApp(pkg, innerOptions, userOptions) {
|
|
1061
|
-
const { frameworkType } = innerOptions;
|
|
1062
|
-
const optionsVar = Object.assign(
|
|
1063
|
-
{
|
|
1064
|
-
platform: cst.DEFAULT_PLAT,
|
|
1065
|
-
npmCdnType: cst.DEFAULT_NPM_CDN_TYPE,
|
|
1066
|
-
handleHomePage: true,
|
|
1067
|
-
semverApi: cst.DEFAULT_SEMVER_API,
|
|
1068
|
-
distDir: cst.ARK_DIST_DIR,
|
|
1069
|
-
},
|
|
1070
|
-
userOptions || {},
|
|
1071
|
-
);
|
|
1072
|
-
const envParams = getHelEnvParams(pkg, optionsVar);
|
|
1073
|
-
const externals = Object.assign({}, optionsVar.externals || {}, presetExternals[frameworkType] || {});
|
|
1074
|
-
const jsonpFnName = getJsonpFnName(envParams.appName || pkg.name);
|
|
1075
|
-
|
|
1076
|
-
return {
|
|
1077
|
-
platform: optionsVar.platform,
|
|
1078
|
-
/**
|
|
1079
|
-
* 资源的网络根目录
|
|
1080
|
-
* 形如:
|
|
1081
|
-
* 1 /web-app/sub-apps/ticket
|
|
1082
|
-
* 2 http://www.cdn.com/xxx/yyy
|
|
1083
|
-
*/
|
|
1084
|
-
homePage: envParams.appHomePage,
|
|
1085
|
-
npmCdnType: optionsVar.npmCdnType,
|
|
1086
|
-
groupName: envParams.appGroupName,
|
|
1087
|
-
/** 构建时可注入到应用的APP_NAME下 */
|
|
1088
|
-
name: envParams.appName,
|
|
1089
|
-
externals,
|
|
1090
|
-
/**
|
|
1091
|
-
* @param {Record<string, any>} userExternals
|
|
1092
|
-
* @returns
|
|
1093
|
-
*/
|
|
1094
|
-
getExternals: (userExternals) => {
|
|
1095
|
-
if (userExternals && !Array.isArray(userExternals)) {
|
|
1096
|
-
return { ...userExternals, externals };
|
|
1097
|
-
}
|
|
1098
|
-
return externals;
|
|
1099
|
-
},
|
|
1100
|
-
jsonpFnName,
|
|
1101
|
-
/**
|
|
1102
|
-
* @param {string} [fallbackPathOrUrl] 兜底用的 publicPathOrUrl
|
|
1103
|
-
* @param {boolean} [needEndSlash]
|
|
1104
|
-
* @returns
|
|
1105
|
-
*/
|
|
1106
|
-
getPublicPathOrUrl: (fallbackPathOrUrl = '/', needEndSlash = true) => {
|
|
1107
|
-
let pathOrUrl = envParams.appHomePage;
|
|
1108
|
-
// 用户传递了非 / 的值时,优先采用用户传递的值
|
|
1109
|
-
if (pathOrUrl === '/' && fallbackPathOrUrl !== '/') {
|
|
1110
|
-
pathOrUrl = fallbackPathOrUrl;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
const finalPathOrUrl = getPublicPathOrUrl(pathOrUrl, needEndSlash);
|
|
1114
|
-
return finalPathOrUrl;
|
|
1115
|
-
},
|
|
1116
|
-
distDir: optionsVar.distDir,
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
*
|
|
1124
|
-
* @param {Record<string, any>} pkg
|
|
1125
|
-
* @param {ICreateSubAppOptions} [options]
|
|
1126
|
-
* @returns
|
|
1127
|
-
*/
|
|
1128
|
-
function createLibSubApp(pkg, options) {
|
|
1129
|
-
return createSubApp(pkg, { frameworkType: 'lib' }, options);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
1133
|
-
|
|
1134
|
-
/**
|
|
1135
|
-
*
|
|
1136
|
-
* @param {Record<string, any>} pkg
|
|
1137
|
-
* @param {ICreateSubAppOptions} [options]
|
|
1138
|
-
* @returns
|
|
1139
|
-
*/
|
|
1140
|
-
function createReactSubApp(pkg, options) {
|
|
1141
|
-
return createSubApp(pkg, { frameworkType: 'react' }, options);
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
/** @typedef {import('../../typings').ICreateSubAppOptions} ICreateSubAppOptions */
|
|
1145
|
-
|
|
1146
|
-
/**
|
|
1147
|
-
* 创建 vue 应用的描述对象
|
|
1148
|
-
* @param {Record<string, any>} pkg
|
|
1149
|
-
* @param {ICreateSubAppOptions} [options]
|
|
1150
|
-
* @returns
|
|
1151
|
-
*/
|
|
1152
|
-
function createVueSubApp(pkg, options) {
|
|
1153
|
-
return createSubApp(pkg, { frameworkType: 'vue' }, options);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
var ExternalsDefault = {
|
|
1157
|
-
|
|
1158
|
-
vue: 'Vue',
|
|
1159
|
-
axios: 'axios',
|
|
1160
|
-
// vuex: 'Vuex',
|
|
1161
|
-
// 'vue-dim'
|
|
1162
|
-
pinia: 'Pinia',
|
|
1163
|
-
'pinia-plugin-persistedstate': 'piniaPluginPersistedstate',
|
|
1164
|
-
'vue-i18n': 'VueI18n',
|
|
1165
|
-
'vue-router': 'VueRouter',
|
|
1166
|
-
'element-plus': 'ElementPlus',
|
|
1167
|
-
'@arkxio/ark-micro-core': 'ArkMicroCore',
|
|
1168
|
-
'@arkxio/ark-lib-proxy': 'ArkLibProxy',
|
|
1169
|
-
'@arkxio/ark-micro': 'ArkMicro',
|
|
1170
|
-
'@arkxio/ark-plugin': 'ArkPlugin'
|
|
1171
|
-
// 'echarts': 'echarts',
|
|
1172
|
-
// 'tinymce': 'tinymce'
|
|
1173
|
-
// "loadjs": { //将vue依赖 "外部化",不打包进组件库
|
|
1174
|
-
// root: 'loadjs',
|
|
1175
|
-
// commonjs: 'loadjs',
|
|
1176
|
-
// commonjs2: 'loadjs',
|
|
1177
|
-
// amd: 'loadjs'
|
|
1178
|
-
// }
|
|
1179
|
-
|
|
1180
|
-
};
|
|
1181
|
-
|
|
1182
|
-
class ArkWebpackPlugin {
|
|
1183
|
-
|
|
1184
|
-
constructor(options) {
|
|
1185
|
-
this.metaOptions = options;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
apply(compiler) {
|
|
1189
|
-
// 获取项目根目录路径
|
|
1190
|
-
compiler.context;
|
|
1191
|
-
const useExtractOptions = this.metaOptions;
|
|
1192
|
-
|
|
1193
|
-
// 在每次编译开始时重置脚本索引计数器,确保文件名一致性
|
|
1194
|
-
compiler.hooks.thisCompilation.tap('ArkResetScriptIdx', () => {
|
|
1195
|
-
resetScriptIdx();
|
|
1196
|
-
});
|
|
1197
|
-
|
|
1198
|
-
compiler.hooks.thisCompilation.tap('ArkDynamicCdnChunkLoaderPlugin', (compilation) => {
|
|
1199
|
-
compilation.hooks.processAssets.tap(
|
|
1200
|
-
{
|
|
1201
|
-
name: 'ArkDynamicCdnChunkLoaderPlugin',
|
|
1202
|
-
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
|
|
1203
|
-
},
|
|
1204
|
-
(assets) => {
|
|
1205
|
-
// 新增分块文件过滤逻辑
|
|
1206
|
-
const chunkFiles = new Set();
|
|
1207
|
-
compilation.chunks.forEach(chunk => {
|
|
1208
|
-
chunk.files.forEach(file => {
|
|
1209
|
-
if (file.endsWith('.js')) chunkFiles.add(file);
|
|
1210
|
-
});
|
|
1211
|
-
});
|
|
1212
|
-
// 调试日志
|
|
1213
|
-
verbose('[分块文件列表]', Array.from(chunkFiles));
|
|
1214
|
-
|
|
1215
|
-
Object.entries(assets).forEach(([filename, source]) => {
|
|
1216
|
-
// 仅处理分块JS文件
|
|
1217
|
-
if (!chunkFiles.has(filename)) return;
|
|
1218
|
-
|
|
1219
|
-
// 检查资源内容有效性
|
|
1220
|
-
if (
|
|
1221
|
-
!source ||
|
|
1222
|
-
!source.source ||
|
|
1223
|
-
typeof source.source() !== 'string' ||
|
|
1224
|
-
source.source().trim() === ''
|
|
1225
|
-
) {
|
|
1226
|
-
console.warn(`资源 ${filename} 内容为空或无效,跳过处理`);
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
let content = source.source().toString();
|
|
1231
|
-
|
|
1232
|
-
// 替换逻辑
|
|
1233
|
-
// /(__webpack_require__\.l)\(url,/g)
|
|
1234
|
-
// const webpackRequireLPattern = /(__webpack_require__\.l)\(url,/g;
|
|
1235
|
-
// if (webpackRequireLPattern.test(content)) {
|
|
1236
|
-
// verbose('检测到 __webpack_require__.l 模式');
|
|
1237
|
-
// content = content.replace(
|
|
1238
|
-
// webpackRequireLPattern,
|
|
1239
|
-
// `(function(url, done, key, chunkId) {
|
|
1240
|
-
// if (typeof window.ArkConfig !== 'undefined' && window.ArkConfig.cdnHost) {
|
|
1241
|
-
// if (!url.startsWith(window.ArkConfig.cdnHost) && url.startsWith('https://unpkg.com')) {
|
|
1242
|
-
// url = window.ArkConfig.cdnHost + url.slice('https://unpkg.com'.length);
|
|
1243
|
-
// }
|
|
1244
|
-
// }
|
|
1245
|
-
// return $1(url, done, key, chunkId);
|
|
1246
|
-
// })(url,`
|
|
1247
|
-
// );
|
|
1248
|
-
const webpackRequireLPattern = /"(https:\/\/unpkg\.com)/g;
|
|
1249
|
-
if (webpackRequireLPattern.test(content)) {
|
|
1250
|
-
verbose('检测到 unpkg.com 模式');
|
|
1251
|
-
content = content.replace(
|
|
1252
|
-
webpackRequireLPattern,
|
|
1253
|
-
`window.ArkConfig.cdnHost + "`
|
|
1254
|
-
);
|
|
1255
|
-
|
|
1256
|
-
// 更新资源
|
|
1257
|
-
compilation.updateAsset(
|
|
1258
|
-
filename,
|
|
1259
|
-
new compiler.webpack.sources.RawSource(content)
|
|
1260
|
-
);
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1265
|
-
);
|
|
1266
|
-
});
|
|
1267
|
-
|
|
1268
|
-
compiler.hooks.emit.tapPromise('ArkMetaJsonGeneratorPlugin', async (compilation) => {
|
|
1269
|
-
// 注入环境判断脚本
|
|
1270
|
-
const isDev = process.env.NODE_ENV === 'development';
|
|
1271
|
-
|
|
1272
|
-
// 收集所有生成的JS文件
|
|
1273
|
-
const outputPath = compilation.options.output.publicPath || '';
|
|
1274
|
-
const jsFiles = [];
|
|
1275
|
-
|
|
1276
|
-
// Webpack 5 正确的API:使用 getAssets()
|
|
1277
|
-
const assets = compilation.getAssets ? compilation.getAssets() : compilation.assets;
|
|
1278
|
-
|
|
1279
|
-
// 遍历所有资产,收集JS文件
|
|
1280
|
-
assets.forEach((asset) => {
|
|
1281
|
-
const assetName = asset.name || asset;
|
|
1282
|
-
if (assetName.endsWith('.js')) {
|
|
1283
|
-
console.log('Found JS file:', assetName);
|
|
1284
|
-
// 排除 ark-meta.json 和 ark_userChunk 文件
|
|
1285
|
-
if (assetName.indexOf('js/app.') !== -1 ||
|
|
1286
|
-
assetName.indexOf('js/runtime.') !== -1 ||
|
|
1287
|
-
assetName.indexOf('js/vendors.') !== -1 ||
|
|
1288
|
-
assetName.indexOf('js/main.') !== -1) {
|
|
1289
|
-
jsFiles.push(outputPath + assetName);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
});
|
|
1293
|
-
|
|
1294
|
-
console.log('Final JS files for chunkJsSrcList:', jsFiles);
|
|
1295
|
-
|
|
1296
|
-
// 创建 customAssets 数组用于收集自定义资产(如从 HTML 内联 script 提取的代码)
|
|
1297
|
-
const customAssets = [];
|
|
1298
|
-
|
|
1299
|
-
await extractArkMetaJson({
|
|
1300
|
-
...useExtractOptions,
|
|
1301
|
-
chunkJsFiles: jsFiles,
|
|
1302
|
-
isDev: isDev,
|
|
1303
|
-
compilation: compilation,
|
|
1304
|
-
customAssets: customAssets
|
|
1305
|
-
});
|
|
1306
|
-
|
|
1307
|
-
return Promise.resolve();
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
1300
|
}
|
|
1311
1301
|
|
|
1312
|
-
var index = {
|
|
1313
|
-
cst,
|
|
1314
|
-
check,
|
|
1315
|
-
baseUtils,
|
|
1316
|
-
createReactSubApp,
|
|
1317
|
-
createVueSubApp,
|
|
1318
|
-
createLibSubApp,
|
|
1319
|
-
extractArkMetaJson,
|
|
1320
|
-
ExternalsDefault,
|
|
1321
|
-
ArkWebpackPlugin
|
|
1302
|
+
var index = {
|
|
1303
|
+
cst,
|
|
1304
|
+
check,
|
|
1305
|
+
baseUtils,
|
|
1306
|
+
createReactSubApp,
|
|
1307
|
+
createVueSubApp,
|
|
1308
|
+
createLibSubApp,
|
|
1309
|
+
extractArkMetaJson,
|
|
1310
|
+
ExternalsDefault,
|
|
1311
|
+
ArkWebpackPlugin
|
|
1322
1312
|
};
|
|
1323
1313
|
|
|
1324
1314
|
exports.ArkWebpackPlugin = ArkWebpackPlugin;
|