@wiajs/core 1.1.28 → 1.1.30
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/dist/core.cjs +7 -8
- package/dist/core.js +3 -7
- package/dist/core.min.js +5 -5
- package/dist/core.mjs +7 -8
- package/dist/jsx-runtime.js +18 -18
- package/package.json +1 -1
- package/util/wiafile.js +599 -350
- package/util/wiapack.js +978 -0
- package/util/wiapage.js +143 -133
package/util/wiapack.js
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将rspack、swc等编译生成的less、html、js文件打包到一个js文件并zip压缩
|
|
3
|
+
* 开发模式与生产模式输出的代码差异很大:
|
|
4
|
+
* - 入口文件直接放最后调用,没有封装到代码数组中
|
|
5
|
+
* - __webpack_require__ 被压缩成不同的字母,无法跨文件调用
|
|
6
|
+
* - 奇怪的是,local模式生产打包,保留了 __webpack_require__,未保留入口文件
|
|
7
|
+
* - local模式时,router 使用了 __webpack_require__,如果被压缩改名,将无法运行
|
|
8
|
+
*/
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import util from 'node:util'
|
|
11
|
+
import crypto from 'node:crypto'
|
|
12
|
+
import zlib from 'node:zlib'
|
|
13
|
+
import fs from 'fs-extra'
|
|
14
|
+
import ld from 'lodash'
|
|
15
|
+
// const {minify, minify_sync} = require('terser')
|
|
16
|
+
import {parseSync, Compiler, minify} from '@swc/core' // acorn 替换为 swc
|
|
17
|
+
|
|
18
|
+
import cfg from './config.js'
|
|
19
|
+
|
|
20
|
+
const gzip = util.promisify(zlib.gzip)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} Opts
|
|
24
|
+
* @prop {string} owner - 应用所有者
|
|
25
|
+
* @prop {string} name - 应用名称
|
|
26
|
+
* @prop {string} ver - 版本号
|
|
27
|
+
* @prop {string} [dir] - 代码根路径
|
|
28
|
+
* @prop {string} [out] - 发布打包输出路径
|
|
29
|
+
* @prop {string} [cos] - 资源托管网址
|
|
30
|
+
* @prop {string[]} [load] - 启动时需加载的模块,启动器需要,一般加载wia公用模块
|
|
31
|
+
* @prop {boolean} [press] - 压缩代码
|
|
32
|
+
* @prop {boolean} [gzip] - gzip打包
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} Opt
|
|
37
|
+
* @prop {string} owner
|
|
38
|
+
* @prop {string} name
|
|
39
|
+
* @prop {string} ver
|
|
40
|
+
* @prop {string} cos
|
|
41
|
+
* @prop {string[]} [load] - 启动器需要,一般加载wia公用模块
|
|
42
|
+
* @prop {string} dir
|
|
43
|
+
* @prop {string} out
|
|
44
|
+
* @prop {boolean} press - 压缩代码
|
|
45
|
+
* @prop {boolean} gzip - gzip打包
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const def = {
|
|
49
|
+
owner: '',
|
|
50
|
+
name: '',
|
|
51
|
+
ver: '1.0.0',
|
|
52
|
+
cos: 'https://cos.wia.pub', // 资源主机
|
|
53
|
+
dir: cfg.code.dir,
|
|
54
|
+
out: 'dist', // 输出路径
|
|
55
|
+
press: true,
|
|
56
|
+
gzip: true,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default class WiaPack {
|
|
60
|
+
/** @type {Opt} */
|
|
61
|
+
opt
|
|
62
|
+
/** @type {Object.<string, string>} */
|
|
63
|
+
files
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
* @param {Opts} opts
|
|
68
|
+
*/
|
|
69
|
+
constructor(opts) {
|
|
70
|
+
/** @type {Opt} */
|
|
71
|
+
const opt = {...def, ...opts}
|
|
72
|
+
this.opt = opt
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 调用rspack/webpack进行编译打包
|
|
77
|
+
* @param {{js: Object.<*, string>}} uf - 更新文件对象
|
|
78
|
+
* {js: {'page/login': './dist/page/login.js'}}
|
|
79
|
+
* @param {*} pf - page目录下,所有改变、未改变js文件,pf为空,表示不生成page.js
|
|
80
|
+
*/
|
|
81
|
+
async work(uf, pf) {
|
|
82
|
+
let R = {}
|
|
83
|
+
const _ = this
|
|
84
|
+
const {opt} = _
|
|
85
|
+
const {owner, name, ver, dir} = opt
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
_.files = uf
|
|
89
|
+
|
|
90
|
+
console.log('work...', {uf, pf})
|
|
91
|
+
|
|
92
|
+
// 处理编译后的代码
|
|
93
|
+
R = await _.process(uf.js, pf)
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.log(`work exp:${e.message}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return R
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 处理webpack生成的文件,分离wia、index、page,并将页面的js、css、html 合并成一个js文件
|
|
103
|
+
* @param {*} uf - 更新文件对象:{'page/login': './dist/page/login.js'}
|
|
104
|
+
* @param {*} pf - 为页面对象,包含所有改变、未改变js文件,pf为空,表示不生成page.js
|
|
105
|
+
* @returns {Promise<*[]>} - 对象数组[{'mall/page/login':'./dist/mall/page/login.js'}]
|
|
106
|
+
*/
|
|
107
|
+
async process(uf, pf) {
|
|
108
|
+
const R = []
|
|
109
|
+
const _ = this
|
|
110
|
+
const {opt} = _
|
|
111
|
+
const {owner, name, ver, dir, out} = opt
|
|
112
|
+
|
|
113
|
+
console.log(`${owner}/${name} process ...`)
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
let index // index中包含的模块
|
|
117
|
+
|
|
118
|
+
// 模块引用次数,查找仅引用一次的模块,包含到页面,不作为共用
|
|
119
|
+
/** @type {Object.<*,string>} */
|
|
120
|
+
let rf = {}
|
|
121
|
+
|
|
122
|
+
// 针对每个变化源文件生成的打包文件进行处理,提取所有页面公共部分到 page.js 文件
|
|
123
|
+
const ufk = Object.keys(uf)
|
|
124
|
+
for (const k of ufk) {
|
|
125
|
+
let f = path.resolve(dir, `dist/${k}.js`)
|
|
126
|
+
if (path.sep !== '/') f = f.replace(/\\/gim, '/') // 统一路径为/,方便处理
|
|
127
|
+
|
|
128
|
+
if (fs.existsSync(f)) {
|
|
129
|
+
const ms = await _.getModule(f)
|
|
130
|
+
if (ms) {
|
|
131
|
+
for (const m of ms) {
|
|
132
|
+
if (m.org) for (const n of ms) n.code = n.code.replaceAll(m.org, m.name)
|
|
133
|
+
|
|
134
|
+
if (!cfg.module?.includes(m.name)) rf[m.name] = (rf[m.name] ?? 0) + 1
|
|
135
|
+
}
|
|
136
|
+
console.log('%s modules:%d file:%s', k, ms.length, uf[k])
|
|
137
|
+
} else console.error('%s modules:%d file:%s', k, 0, uf[k])
|
|
138
|
+
|
|
139
|
+
// 保存模块,稍后处理
|
|
140
|
+
uf[k] = {name: uf[k], ms, f}
|
|
141
|
+
// 首页
|
|
142
|
+
if (/\/dist\/index.js$/.test(f)) index = ms
|
|
143
|
+
|
|
144
|
+
// index.js与页面js各自打包,页面公共部分,不在index中的,打入page.js,wia引用部分打入wia.js
|
|
145
|
+
// const wf = this.procFile(f, uf[k], ms, index, page, appcfg.load);
|
|
146
|
+
// if (wf) R.push({[k]: wf});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 提取未修改page文件的引用模块,重新生成page.js
|
|
151
|
+
const fk = ld.difference(Object.keys(pf), Object.keys(uf))
|
|
152
|
+
|
|
153
|
+
const pfile = path.resolve(`${dir}/${out}`, './page.js')
|
|
154
|
+
let lp // lastPage
|
|
155
|
+
const lpf = {} // lastPageFiles 未更改页面文件引用模块
|
|
156
|
+
// 获取当前页面共用模块
|
|
157
|
+
if (fs.existsSync(pfile)) {
|
|
158
|
+
const tx = await fs.readFile(pfile, 'utf8')
|
|
159
|
+
try {
|
|
160
|
+
lp = JSON.parse(tx)
|
|
161
|
+
} catch (e) {}
|
|
162
|
+
} else console.warn('process %s.%s page.js not found!', owner, name)
|
|
163
|
+
|
|
164
|
+
// 未更新的页面,提取引用部分模块,排除index,保存到page中,代码从page.js获取
|
|
165
|
+
// 如page.js 不存在,则需清除wiafile.yml 文件中的页面js,重新编译生成page.js!!!
|
|
166
|
+
if (lp && fk && fk.length > 0) {
|
|
167
|
+
// 提取未修改page文件的引用模块,使用现有page.js中的代码
|
|
168
|
+
for (const k of fk) {
|
|
169
|
+
const f = path.resolve(`${dir}/dist`, `${k}.js`)
|
|
170
|
+
|
|
171
|
+
if (fs.existsSync(f)) {
|
|
172
|
+
try {
|
|
173
|
+
const tx = await fs.readFile(f, 'utf8')
|
|
174
|
+
const p = JSON.parse(tx)
|
|
175
|
+
lpf[k] = {f, name: pf[k], pf: p, ms: []}
|
|
176
|
+
const {ms} = lpf[k]
|
|
177
|
+
// 还原页面引用模块,排除wia模块
|
|
178
|
+
if (p && p.M) {
|
|
179
|
+
p.M.forEach(m => {
|
|
180
|
+
if (!cfg.module?.includes(m)) ms.push({name: m, code: lp[m]})
|
|
181
|
+
})
|
|
182
|
+
// 增加模块引用计数
|
|
183
|
+
if (ms.length) {
|
|
184
|
+
ms.forEach(m => {
|
|
185
|
+
rf[m.name] = (rf[m.name] ?? 0) + 1
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
console.log('%s modules:%d', k, ms.length)
|
|
189
|
+
}
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.error(`process last page:${k} exp:${e.message}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 所有page共用的js
|
|
198
|
+
const page = {
|
|
199
|
+
R: {
|
|
200
|
+
name: `${owner}/${name}/page`,
|
|
201
|
+
ver,
|
|
202
|
+
time: +new Date(),
|
|
203
|
+
},
|
|
204
|
+
M: [],
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 共用的wia模块
|
|
208
|
+
const wia = {
|
|
209
|
+
R: {
|
|
210
|
+
name: `${owner}/${name}/wia`,
|
|
211
|
+
ver,
|
|
212
|
+
time: +new Date(),
|
|
213
|
+
},
|
|
214
|
+
M: [],
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 首页模块
|
|
218
|
+
index = {js: {}}
|
|
219
|
+
|
|
220
|
+
// 唯一引用,页面唯一引用包含到页面
|
|
221
|
+
const rfs = []
|
|
222
|
+
Object.keys(rf).forEach(k => {
|
|
223
|
+
if (rf[k] === 1) rfs.push(k)
|
|
224
|
+
})
|
|
225
|
+
rf = rfs
|
|
226
|
+
|
|
227
|
+
// 处理更新文件
|
|
228
|
+
for (const k of Object.keys(uf)) {
|
|
229
|
+
// index.js与页面js各自打包,页面公共部分,不在index中的,打入page.js,wia引用部分打入wia.js
|
|
230
|
+
const wf = await _.procFile(uf[k], rf, index, wia, page)
|
|
231
|
+
if (wf) R.push({[k]: wf})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 处理未更新页面文件
|
|
235
|
+
for (const k of Object.keys(lpf)) {
|
|
236
|
+
// index.js与页面js各自打包,页面公共部分,不在index中的,打入page.js,wia引用部分打入wia.js
|
|
237
|
+
const wf = await _.procPgfile(lpf[k], rf, index, wia, page)
|
|
238
|
+
if (wf) R.push({[k]: wf})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 最后处理 index,页面共享代码,并入 index
|
|
242
|
+
if (index.name && !ld.isEmpty(index.js)) {
|
|
243
|
+
const wf = await _.pack(index.name, index.f, index.js, index.pms)
|
|
244
|
+
if (wf) R.push({[index.name]: wf})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 非页面自身的引用文件,写入 page
|
|
248
|
+
if (page?.M.length) {
|
|
249
|
+
const wf = await _.procPage(page)
|
|
250
|
+
if (wf) R.push({page: wf})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 非共用wia,写入 wia
|
|
254
|
+
if (wia?.M.length) {
|
|
255
|
+
const wf = await _.procWia(wia)
|
|
256
|
+
if (wf) R.push({wia: wf})
|
|
257
|
+
}
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.log(`process exp:${e.message}`)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return R
|
|
263
|
+
} // process
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 处理更新的js文件,当前页面的js、html、css 打包压缩成一个文件
|
|
267
|
+
* @param {*} u 更新文件对象,{f, ms, name}
|
|
268
|
+
* f: 文件在硬盘中的绝对文件名
|
|
269
|
+
* rf: 相对文件名称 mall/page/shopList.js
|
|
270
|
+
* ms: 该文件包含的模块数组
|
|
271
|
+
* 模块名称:“./wia/store/src/app.js” 或 “./code/wia/store/src/app.js” 取决于运行路径
|
|
272
|
+
* @param {*} rf 当前页面唯一引用模块,需打包到当前页面文件
|
|
273
|
+
* @param {*} index 首页模块,页面共用模块放入index中加载,所有页面加载之前,需加载index
|
|
274
|
+
* @param {*} wia wia 共用wia中未包含,放入当前应用wia,便于生成共用wia
|
|
275
|
+
* @param {*} page 非页面模块(包括共用和非共用模块),全部放入 page 保存,加快未变更页面模块分析速度
|
|
276
|
+
*/
|
|
277
|
+
async procFile(u, rf, index, wia, page) {
|
|
278
|
+
let R = ''
|
|
279
|
+
const _ = this
|
|
280
|
+
const {opt} = _
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const js = {}
|
|
284
|
+
let ps
|
|
285
|
+
|
|
286
|
+
// 页面引用模块,排除自己
|
|
287
|
+
const pms = u.ms.filter(m => m.name !== u.name).map(m => m.name)
|
|
288
|
+
const rgwia = new RegExp(cfg.code.wia.join('|'))
|
|
289
|
+
|
|
290
|
+
// index.js 应用入口代码,wia.js 除外的模块,全部保留在index.js中
|
|
291
|
+
if (/\/dist\/index.js$/.test(u.f)) {
|
|
292
|
+
// 符合wia规则,未包含在共用wia中的,存入应用wia,便于更新wia包
|
|
293
|
+
ps = u.ms.filter(m => !cfg.module?.includes(m.name) && rgwia.test(m.name))
|
|
294
|
+
for (const m of ps) {
|
|
295
|
+
if (!wia[m.name]) {
|
|
296
|
+
wia[m.name] = m.code
|
|
297
|
+
wia.M.push(m.name)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
index.pms = pms // 引用模块
|
|
302
|
+
index.f = u.f
|
|
303
|
+
index.name = u.name
|
|
304
|
+
index.js = {}
|
|
305
|
+
|
|
306
|
+
// ps = u.ms.filter(m => !rg.test(m.name));
|
|
307
|
+
// 未包含在共用wia中,不符合wia规则,放入 index,跟page共用模块合并后生成index.js
|
|
308
|
+
ps = u.ms.filter(m => !cfg.module?.includes(m.name) && !rgwia.test(m.name))
|
|
309
|
+
for (const m of ps) {
|
|
310
|
+
// index.push(m.name);
|
|
311
|
+
// js[m.name] = m.code;
|
|
312
|
+
if (m && m.code && m.name) {
|
|
313
|
+
// 压缩代码, 生产模式ast效率低,使用eval开发模式编译,代码为字符串,效率高
|
|
314
|
+
index.js[m.name] = opt.press ? await pressCode(m.code) : m.code
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
// 页面共用模块存入 page
|
|
319
|
+
// 非页面自身,非共用wia
|
|
320
|
+
ps = u.ms.filter(m => m.name !== u.name && !cfg.module?.includes(m.name))
|
|
321
|
+
// 页面引用模块写入统一的page对象
|
|
322
|
+
for (const m of ps) {
|
|
323
|
+
// 非唯一引用的共用模块,放入 index
|
|
324
|
+
if (!rf.includes(m.name)) {
|
|
325
|
+
// 符合wia规则,非共用wia,存入应用wia,便于更新wia包
|
|
326
|
+
if (rgwia.test(m.name)) {
|
|
327
|
+
if (!wia[m.name]) {
|
|
328
|
+
wia[m.name] = m.code
|
|
329
|
+
wia.M.push(m.name)
|
|
330
|
+
}
|
|
331
|
+
} else if (!index.js?.[m.name]) index.js[m.name] = m.code
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 所有引用模块(非页面自身),全部放入 page 保存,加快未变更页面的模块分析速度
|
|
335
|
+
if (!page[m.name]) {
|
|
336
|
+
page[m.name] = m.code
|
|
337
|
+
page.M.push(m.name)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 页面自身 和 唯一引用,存于当前页面
|
|
342
|
+
ps = u.ms.filter(m => m.name === u.name || rf.includes(m.name))
|
|
343
|
+
for (const m of ps) {
|
|
344
|
+
if (rgwia.test(m.name)) {
|
|
345
|
+
if (!wia[m.name]) {
|
|
346
|
+
wia[m.name] = m.code
|
|
347
|
+
wia.M.push(m.name)
|
|
348
|
+
}
|
|
349
|
+
} else if (m && m.code && m.name) {
|
|
350
|
+
// 压缩代码, 生产模式ast效率低,使用eval开发模式编译,代码为字符串,效率高
|
|
351
|
+
js[m.name] = opt.press ? await pressCode(m.code) : m.code
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 当前页面的js、html、css 打包压缩成一个文件
|
|
357
|
+
if (!ld.isEmpty(js)) await _.pack(u.name, u.f, js, pms)
|
|
358
|
+
|
|
359
|
+
R = u.f
|
|
360
|
+
} catch (e) {
|
|
361
|
+
console.error(`procfile exp:${e.message}`)
|
|
362
|
+
}
|
|
363
|
+
return R
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 处理未变更页面代码
|
|
368
|
+
* 1、共享代码打包到index
|
|
369
|
+
* 2、wia 代码,非缓存,打包到 wia.js,方便监测、收集独立的共享wia包
|
|
370
|
+
* 3、原来共享的,可能变为独享,需加入到页面js
|
|
371
|
+
* @param {*} u 更新文件对象,{f, ms, name, pf}
|
|
372
|
+
* f: 文件在硬盘中的绝对文件名
|
|
373
|
+
* name: 相对文件名称 mall/page/shopList.js
|
|
374
|
+
* ms: 该文件包含的模块数组
|
|
375
|
+
* pf:页面文件 对象
|
|
376
|
+
* 模块名称:“./wia/store/src/app.js” 或 “./code/wia/store/src/app.js” 取决于运行路径
|
|
377
|
+
* @param {*} rf 引用模块次数
|
|
378
|
+
* @param {*} index 首页模块
|
|
379
|
+
* @param {*} wia wia模块
|
|
380
|
+
* @param {*} load 运行依赖,需提前加载的模块,需修改加载版本号
|
|
381
|
+
* @returns {Promise<*>}
|
|
382
|
+
*/
|
|
383
|
+
async procPgfile(u, rf, index, wia, page, load) {
|
|
384
|
+
let R = ''
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const {owner, name: app} = this
|
|
388
|
+
// 页面引用模块,排除自己
|
|
389
|
+
const rg = new RegExp(cfg.code.wia.join('|'))
|
|
390
|
+
const ljs = u.pf.js
|
|
391
|
+
const js = {}
|
|
392
|
+
js[u.name] = ljs[u.name]
|
|
393
|
+
|
|
394
|
+
// index.js 应用入口代码,wia.js 除外的模块,全部保留在index.js中
|
|
395
|
+
// 页面共用模块存入 page
|
|
396
|
+
// 排除自己,排除 wia
|
|
397
|
+
const ps = u.ms.filter(m => m.name !== u.name && !cfg.module?.includes(m.name))
|
|
398
|
+
// 页面引用模块写入统一的page对象
|
|
399
|
+
ps.forEach(m => {
|
|
400
|
+
// 共用模块,放入 index
|
|
401
|
+
if (!rf.includes(m.name) && !index[m.name]) index[m.name] = m.code
|
|
402
|
+
|
|
403
|
+
// 非页面模块,全部放入 page 保存,加快未变更页面的模块分析速度
|
|
404
|
+
if (!page[m.name]) {
|
|
405
|
+
page[m.name] = m.code
|
|
406
|
+
page.M.push(m.name)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// 符合wia规则,未包含在wia中的,存入wia,便于更新wia包
|
|
410
|
+
if (rg.test(m.name) && !wia[m.name]) {
|
|
411
|
+
wia[m.name] = m.code
|
|
412
|
+
wia.M.push(m.name)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// 唯一引用,包含到页面代码
|
|
416
|
+
if (rf.includes(m.name)) {
|
|
417
|
+
// 非唯一依赖由于其他页面变化,可能会变成唯一依赖
|
|
418
|
+
if (!js[m.name]) {
|
|
419
|
+
js[m.name] = m.code
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
// 有变化,则重新保存
|
|
425
|
+
if (!ld.isEmpty(ld.difference(Object.keys(js), Object.keys(ljs)))) {
|
|
426
|
+
u.pf.R = {
|
|
427
|
+
name: u.name.replace(/\.js$/i, ''),
|
|
428
|
+
ver: this.ver,
|
|
429
|
+
time: +new Date(),
|
|
430
|
+
}
|
|
431
|
+
u.pf.js = js
|
|
432
|
+
const wf = JSON.stringify(u.pf)
|
|
433
|
+
await fs.writeFile(u.f, wf, e => {
|
|
434
|
+
if (e) console.log(`save ${u.f} exp:${e.message}`)
|
|
435
|
+
R = u.f
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
} catch (e) {
|
|
439
|
+
console.error(`procPgFile exp:${e.message}`)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return R
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* js、html、css 打包保存
|
|
447
|
+
* @param {*} name - 相对路径文件js文件
|
|
448
|
+
* @param {*} f - 绝对路径js文件
|
|
449
|
+
* @param {*} js - 引用模块
|
|
450
|
+
* @param {*} pms
|
|
451
|
+
*/
|
|
452
|
+
async pack(name, f, js, pms) {
|
|
453
|
+
const _ = this
|
|
454
|
+
const {opt} = _
|
|
455
|
+
const {owner, name: appName, ver, dir, out} = opt
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
// 将页面文件打包到一个文件中
|
|
459
|
+
const w = {
|
|
460
|
+
R: {
|
|
461
|
+
name: name.replace(/\.js$/i, ''),
|
|
462
|
+
ver: ver,
|
|
463
|
+
time: +new Date(),
|
|
464
|
+
},
|
|
465
|
+
html: '',
|
|
466
|
+
css: '',
|
|
467
|
+
js: '',
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// 加入 html,index.html 不打入包中!!!
|
|
471
|
+
if (!/\/dist\/index\.js$/.test(f)) {
|
|
472
|
+
const htmlf = f.replace(/\.js$/i, '.html')
|
|
473
|
+
if (fs.existsSync(htmlf)) {
|
|
474
|
+
const html = await fs.readFile(htmlf, 'utf8')
|
|
475
|
+
if (html) w.html = html
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 加入 css
|
|
480
|
+
const cssf = f.replace(/\.js$/i, '.css')
|
|
481
|
+
if (fs.existsSync(cssf)) {
|
|
482
|
+
const css = await fs.readFile(cssf, 'utf8')
|
|
483
|
+
if (css) w.css = css
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
w.js = js
|
|
487
|
+
w.M = pms
|
|
488
|
+
|
|
489
|
+
let wf = '' // 写入文件内容
|
|
490
|
+
// 配置了load参数(如 wiastore),index 作为wia应用启动器,需使用$.M 加载 index 及 wia 模块
|
|
491
|
+
// 未配置load参数,只有模块,由其他启动器动态加载运行,wia应用由wiastore 加载运行
|
|
492
|
+
if (opt.load && /\/dist\/index\.js$/.test(f)) {
|
|
493
|
+
// 更改 load中的版本号,确保客户端加载新发布的版本
|
|
494
|
+
let load = opt.load.map(v => `"${v}"`).join(',')
|
|
495
|
+
// 替换 ?v=${ver} 为当前版本
|
|
496
|
+
load = load.replace(/\?v=\$\{ver\}/gim, `?v=${ver}`)
|
|
497
|
+
|
|
498
|
+
// 获取并加载 wia.js,加载index.js中的所有模块,调用index模块
|
|
499
|
+
wf =
|
|
500
|
+
`(function(){var app=${JSON.stringify(w)};\r\n` +
|
|
501
|
+
`$.M.get("${opt.cos}", [${load}])\r\n` +
|
|
502
|
+
`.then(function(){$.M.add(app.js);$.M("${owner}/${appName}/index.js");})})();\r\n`
|
|
503
|
+
|
|
504
|
+
// 未压缩
|
|
505
|
+
f = f.replace(/([\\\/])dist[\\\/]/, `$1${out}$1`)
|
|
506
|
+
// await fs.ensureDir(path.dirname(f))
|
|
507
|
+
// fs.writeFile(f, wf)
|
|
508
|
+
} else wf = JSON.stringify(w)
|
|
509
|
+
|
|
510
|
+
// 未压缩,生产需屏蔽
|
|
511
|
+
// f = f.replace(/([\\\/])dist[\\\/]/, `$1${out}$1`)
|
|
512
|
+
// await fs.ensureDir(path.dirname(f))
|
|
513
|
+
// fs.writeFile(f, wf)
|
|
514
|
+
|
|
515
|
+
// 压缩
|
|
516
|
+
f = f.replace(/([\\\/])dist[\\\/]/, `$1${out}$1`).replace(/\.js$/, '.zip') // write file
|
|
517
|
+
// @ts-ignore
|
|
518
|
+
const buf = await gzip(wf)
|
|
519
|
+
await fs.ensureDir(path.dirname(f))
|
|
520
|
+
fs.writeFile(f, buf)
|
|
521
|
+
console.log(
|
|
522
|
+
`Build ${name} gzipped(${(wf.length / 1024).toFixed(2)}KB -> ${(buf.length / 1024).toFixed(2)}KB)`
|
|
523
|
+
)
|
|
524
|
+
} catch (e) {
|
|
525
|
+
console.error(`pack exp:${e.message}`)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* 页面模块按名称排序
|
|
531
|
+
*/
|
|
532
|
+
sortPage(ms) {
|
|
533
|
+
const R = {}
|
|
534
|
+
|
|
535
|
+
if (ld.isEmpty(ms)) return {}
|
|
536
|
+
R.R = ms.R
|
|
537
|
+
const ks = ld.sortBy(ld.keys(ms))
|
|
538
|
+
ld.forIn(ks, k => {
|
|
539
|
+
if (k !== 'R') R[k] = ms[k]
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
return R
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* 生成指定文件的 MD5值,用于判断文件内容是否变化
|
|
547
|
+
* @param {string} tx
|
|
548
|
+
* @returns {string}
|
|
549
|
+
*/
|
|
550
|
+
hash(tx) {
|
|
551
|
+
let R = ''
|
|
552
|
+
const hash = crypto.createHash('md5')
|
|
553
|
+
hash.update(tx)
|
|
554
|
+
R = hash.digest('hex')
|
|
555
|
+
return R
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* 将首页之外的页面共用模块,保存到page.js,便于分析
|
|
560
|
+
* 该部分模块已包含在index.js,
|
|
561
|
+
* 由于index加载前,需要加载page,因此合并page到index,不单独加载page
|
|
562
|
+
* @param {*} page 首页之外的模块
|
|
563
|
+
*/
|
|
564
|
+
async procPage(page) {
|
|
565
|
+
let R
|
|
566
|
+
const _ = this
|
|
567
|
+
const {opt} = _
|
|
568
|
+
const {owner, name, dir, out} = opt
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
if (!page?.M.length) return
|
|
572
|
+
|
|
573
|
+
console.log(`${owner}/${name} procPage(M:${page.M.length}) ...`)
|
|
574
|
+
|
|
575
|
+
// 压缩代码
|
|
576
|
+
if (opt.press) {
|
|
577
|
+
for (const k of Object.keys(page)) {
|
|
578
|
+
if (k !== 'R' && k !== 'M') page[k] = await pressCode(page[k])
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const f = path.resolve(`${dir}/${out}`, './page.js')
|
|
583
|
+
|
|
584
|
+
// 添加自动合并模块 代码
|
|
585
|
+
// const wf = `(function(){var js=${JSON.stringify(wia)};\r\n`
|
|
586
|
+
// + '$._m.add(js);})();\r\n';
|
|
587
|
+
const wf = JSON.stringify(page)
|
|
588
|
+
|
|
589
|
+
// console.log('Module:%s%s', JSON.stringify(p), '\r\n');
|
|
590
|
+
await fs.ensureDir(path.dirname(f))
|
|
591
|
+
await fs.writeFile(f, wf, e => {
|
|
592
|
+
if (e) console.error(`save ${f} error:${e.message}`)
|
|
593
|
+
})
|
|
594
|
+
R = f
|
|
595
|
+
} catch (e) {
|
|
596
|
+
console.error(`procPage exp:${e.message}`)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return R
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* 处理分离的 wia 包,共享缓存的wia已被排除
|
|
604
|
+
* @param {*} wia wia模块
|
|
605
|
+
* @returns {Promise<string>}
|
|
606
|
+
*/
|
|
607
|
+
async procWia(wia) {
|
|
608
|
+
let R = ''
|
|
609
|
+
const _ = this
|
|
610
|
+
const {opt} = _
|
|
611
|
+
const {owner, name, dir, out} = opt
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
if (!wia?.M.length) return
|
|
615
|
+
|
|
616
|
+
console.log(`${owner}/${name} procWia(M:${wia.M.length}) ...`)
|
|
617
|
+
|
|
618
|
+
// 压缩代码
|
|
619
|
+
if (opt.press) {
|
|
620
|
+
for (const k of Object.keys(wia)) {
|
|
621
|
+
if (k !== 'R' && k !== 'M') wia[k] = await pressCode(wia[k])
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const f = path.resolve(`${dir}/${out}`, './wia.js')
|
|
626
|
+
|
|
627
|
+
// 添加自动合并模块 代码
|
|
628
|
+
// const wf = `(function(){var js=${JSON.stringify(wia)};\r\n`
|
|
629
|
+
// + '$._m.add(js);})();\r\n';
|
|
630
|
+
const wf = JSON.stringify(wia)
|
|
631
|
+
|
|
632
|
+
// console.log('Module:%s%s', JSON.stringify(p), '\r\n');
|
|
633
|
+
await fs.ensureDir(path.dirname(f))
|
|
634
|
+
fs.writeFile(f, wf)
|
|
635
|
+
R = f
|
|
636
|
+
} catch (e) {
|
|
637
|
+
console.log(`procWia exp:${e.message}`)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return R
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* page.js 未修改,则恢复原来的版本,无需更新!
|
|
645
|
+
* @param {*} pfile
|
|
646
|
+
* @param {*} lastP
|
|
647
|
+
*/
|
|
648
|
+
async loadPageVer(pfile, lastP) {
|
|
649
|
+
if (ld.isEmpty(lastP)) {
|
|
650
|
+
if (fs.existsSync(pfile)) {
|
|
651
|
+
const tx = await fs.readFile(pfile, 'utf8')
|
|
652
|
+
lastP = JSON.parse(tx)
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const appf = pfile.replace(/page\.js$/i, 'app.js')
|
|
657
|
+
let tx = await fs.readFile(appf, 'utf8')
|
|
658
|
+
tx = tx.replace(`/mall/page.js?v=${this.ver}`, `/mall/page.js?v=${lastP.R.ver}`)
|
|
659
|
+
await fs.writeFile(appf, tx, e => e && console.log(`save app.js ${pfile} exp:${e.message}`))
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* 通过AST解析webpack开发模式打包文件中的__webpack_modules__,获取模块,返回数组
|
|
664
|
+
* webpack/rspack 打包输出参数:mode: 'development', devtool: 'eval', 代码用字符串封装,ast 解析快,并且 入口文件也封装了
|
|
665
|
+
* 生产模式,不同文件压缩后函数名称不统一
|
|
666
|
+
* swc ast 让人迷惑的事情:
|
|
667
|
+
* 1. 代码中有中文注释,按swc ast 解析位置获取代码,多获取中文字数的双倍
|
|
668
|
+
* 2. 回车换行符,是一个字符,在文本编辑器中是两个字符,swc ast,好像是按两个字符计算,
|
|
669
|
+
* 但是 使用 slice 获取的 字符串又是对的
|
|
670
|
+
* 3. 注意,swc 是从1 开始计数,不是0!
|
|
671
|
+
* 原因:
|
|
672
|
+
* SWC 的 span 是基于 UTF-8 字节偏移量
|
|
673
|
+
* 中文字符在 UTF-8 编码中占用 2~3 个字节。
|
|
674
|
+
* 例如,"中文" 在字符串中占用 2 个字符,但在 UTF-8 中占用 6 个字节。
|
|
675
|
+
* 因此,使用buffer字节数组获取代码片段,转换为字符串,buffer 比字符串快,swc内部使用的buffer
|
|
676
|
+
* @param {string} f - 注意是utf8字节数组,不是字符串
|
|
677
|
+
* @returns {Promise<{name:string, org: string, code:string}[]>}
|
|
678
|
+
*/
|
|
679
|
+
async getModule(f) {
|
|
680
|
+
/** @type {{name:string, org: string, code:string}[]} */
|
|
681
|
+
let R // 模块列表
|
|
682
|
+
const _ = this
|
|
683
|
+
const {opt} = _
|
|
684
|
+
|
|
685
|
+
try {
|
|
686
|
+
const buf = await fs.readFile(f)
|
|
687
|
+
|
|
688
|
+
console.log('getModule read file:%s len:%d', f, buf.length)
|
|
689
|
+
const ast = await parseSync(buf.toString(), {
|
|
690
|
+
syntax: 'ecmascript',
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
// const compile = new Compiler()
|
|
694
|
+
// const ast = compile.parseSync(
|
|
695
|
+
// buf.toString(),
|
|
696
|
+
// {
|
|
697
|
+
// syntax: 'ecmascript', // "ecmascript" | "typescript"
|
|
698
|
+
// // comments: false,
|
|
699
|
+
// // script: true, // parsed as a script instead of module.
|
|
700
|
+
// // isModule: true, // 强制重新初始化
|
|
701
|
+
// // sourceFileName: f, // 不同的源文件名,Span 独立
|
|
702
|
+
// // sourceFileName: undefined, // 让 span 从 0 开始
|
|
703
|
+
// },
|
|
704
|
+
// f
|
|
705
|
+
// )
|
|
706
|
+
let offset = ast.span.start
|
|
707
|
+
|
|
708
|
+
// console.log(util.inspect(ast, true, 5, true), '【ast】')
|
|
709
|
+
|
|
710
|
+
// getEntry(fn, ast) // devtool: 'eval' 入口函数已打包,直接获取
|
|
711
|
+
|
|
712
|
+
let ms // = await ps
|
|
713
|
+
// 从根节点 查找 __webpack_modules__
|
|
714
|
+
// let ps = new Promise((res, rej) => {
|
|
715
|
+
walkNode(ast, (n, parent) => {
|
|
716
|
+
if (n.type === 'VariableDeclarator' && n.id?.value === '__webpack_modules__') {
|
|
717
|
+
console.log('find __webpack_modules__')
|
|
718
|
+
ms = n
|
|
719
|
+
return true // 找到终止遍历
|
|
720
|
+
}
|
|
721
|
+
})
|
|
722
|
+
// })
|
|
723
|
+
|
|
724
|
+
// console.log(util.inspect(ms, true, 5, true), '【ms】')
|
|
725
|
+
|
|
726
|
+
// 从根节点开始检查每个代码节点
|
|
727
|
+
// ps = new Promise((res, rej) => {
|
|
728
|
+
// let rs
|
|
729
|
+
walkNode(ms, (n, parent) => {
|
|
730
|
+
// require 我们认为是一个函数调用,并且函数名为 require,参数只有一个,且必须是字面量
|
|
731
|
+
/*
|
|
732
|
+
{"./src/index.js":(function (module, __webpack_exports__, __webpack_require__) {
|
|
733
|
+
eval("");
|
|
734
|
+
})
|
|
735
|
+
// no static exports found
|
|
736
|
+
(function(module, exports) {
|
|
737
|
+
eval()
|
|
738
|
+
})
|
|
739
|
+
*/
|
|
740
|
+
// console.log({type: n.type})
|
|
741
|
+
// 根据模块特征,筛选模块节点
|
|
742
|
+
if (n.type === 'ObjectExpression' && Array.isArray(n.properties)) {
|
|
743
|
+
const {properties: props} = n
|
|
744
|
+
const [prop] = props
|
|
745
|
+
|
|
746
|
+
console.log({
|
|
747
|
+
len: n.properties?.length,
|
|
748
|
+
ktype: prop.key?.type,
|
|
749
|
+
kvalue: prop.key.value,
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
if (
|
|
753
|
+
prop?.type === 'KeyValueProperty' &&
|
|
754
|
+
prop.key?.type === 'StringLiteral' && // 属性名称为字面量
|
|
755
|
+
/\.js$|.mjs$|.cjs$|.ts$|\.less$|\.css$|\.html$|\.json$|\.css\$$/.test(prop.key.value) // 属性名称包含 .js .less
|
|
756
|
+
) {
|
|
757
|
+
for (const prop of props) {
|
|
758
|
+
// console.log(util.inspect(prop, true, 20, true))
|
|
759
|
+
let name = prop.key.value
|
|
760
|
+
const {value: v} = prop
|
|
761
|
+
const start = v.span.start - offset // - _astSpan - 1
|
|
762
|
+
const end = v.span.end - offset // _astSpan - 1
|
|
763
|
+
// 获取依赖的相关信息
|
|
764
|
+
// console.log({name, span: v.span}, 'getModule-0')
|
|
765
|
+
let code = buf.subarray(start, end).toString()
|
|
766
|
+
// code = code.replaceAll('__webpack_require__', '__m__') // 全局替换
|
|
767
|
+
|
|
768
|
+
let pos = code.indexOf('eval("')
|
|
769
|
+
if (pos !== -1) {
|
|
770
|
+
let head = code.slice(0, pos)
|
|
771
|
+
let body = code.slice(
|
|
772
|
+
code.indexOf('eval("') + 6,
|
|
773
|
+
code.lastIndexOf('//# sourceURL=webpack:') // devtool: eval 才有
|
|
774
|
+
// code.lastIndexOf('")' + 1)) // eval的结尾
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
// 将eval中的代码字符串做了转义,需将转义字符还原成真实字符,否则代码压缩报错
|
|
778
|
+
body = JSON.parse(`{"m":"${body}"}`).m
|
|
779
|
+
if (head.startsWith('(')) head = head.slice(1) // 去掉(),带()压缩无效
|
|
780
|
+
code = `${head}${body}}`
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
console.log(
|
|
784
|
+
{name, offset, span: v.span, start: code.slice(0, 50), tail: code.slice(-50)},
|
|
785
|
+
'getModule'
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
// 路径替换,以最后一个 node_modules 路径为准
|
|
789
|
+
pos = name.lastIndexOf('/node_modules/')
|
|
790
|
+
const org = name
|
|
791
|
+
if (pos !== -1) {
|
|
792
|
+
// ../../node_modules/.pnpm/@wiajs+core@1.1.28/node_modules/@wiajs/core/dist/core.mjs
|
|
793
|
+
/** @type {string} */
|
|
794
|
+
const pre = name.slice(0, pos)
|
|
795
|
+
if (name.endsWith('.less') && pre.includes('/node_modules/less-loader/'))
|
|
796
|
+
name = `~~/${name.slice(pos + 14)}`
|
|
797
|
+
else name = `~/${name.slice(pos + 14)}`
|
|
798
|
+
} else if (/^\.\/src\//.test(name)) {
|
|
799
|
+
name = name.replace(/^\.\/src\//, `${opt.owner}/${opt.name}/`)
|
|
800
|
+
} else if (/^\.\/wia\.config\.js/.test(name)) {
|
|
801
|
+
name = name.replace(
|
|
802
|
+
/^\.\/wia\.config\.js/,
|
|
803
|
+
`${opt.owner}/${opt.name}/wia.config.js`
|
|
804
|
+
)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// name = name.replace(/[.]{1,2}\/[./]*\/node_modules\//gi, '~/')
|
|
808
|
+
// // "~/.pnpm/@wiajs+core@1.1.28/node_modules/@wiajs/core/dist/core.mjs"
|
|
809
|
+
// name = name.replace(/~\/\S+\/node_modules\//gi, '~/')
|
|
810
|
+
|
|
811
|
+
// code = code.replace(/[.]{1,2}\/[./]*\/node_modules\//gi, '~/')
|
|
812
|
+
// code = code.replace(/~\/\S+\/node_modules\//gi, '~/')
|
|
813
|
+
if (!R) R = []
|
|
814
|
+
R.push({
|
|
815
|
+
name,
|
|
816
|
+
org, // 原始名称,需全代码替换为name,不在这里替换
|
|
817
|
+
code, // 由于存在很多重复模块,在生成唯一模块时做压缩,不在这里做压缩,
|
|
818
|
+
})
|
|
819
|
+
}
|
|
820
|
+
return true // 终止遍历
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
// console.log({R}, 'getModule')
|
|
826
|
+
} catch (e) {
|
|
827
|
+
console.error(`getModule exp:${e.message}`)
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return R
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* 压缩代码
|
|
836
|
+
* @param {*} src 代码
|
|
837
|
+
* @returns {Promise<string>}
|
|
838
|
+
*/
|
|
839
|
+
async function pressCode(src) {
|
|
840
|
+
let R = src
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
// console.log({src}, '【src】')
|
|
844
|
+
|
|
845
|
+
// swc
|
|
846
|
+
const opts = {
|
|
847
|
+
compress: {
|
|
848
|
+
// booleans: true, // 优化布尔表达式
|
|
849
|
+
// conditionals: true, // 优化条件表达式
|
|
850
|
+
drop_console: true, // 删除 `console.log` 等调试信息
|
|
851
|
+
drop_debugger: true, // 删除 debugger
|
|
852
|
+
dead_code: true,
|
|
853
|
+
unused: true, // 删除未使用的变量和函数
|
|
854
|
+
},
|
|
855
|
+
mangle: {
|
|
856
|
+
// reserved: ['__unused_webpack_module', '__webpack_exports__', '__webpack_require__'],
|
|
857
|
+
// reserved: ['__m__'],
|
|
858
|
+
},
|
|
859
|
+
format: {
|
|
860
|
+
comments: false, // 仅保留特定注释,例如 /*! */ false
|
|
861
|
+
},
|
|
862
|
+
ecma: 6, // 5: ES5 6或2015: ES6 specify one of: 5, 2015, 2016, etc.
|
|
863
|
+
module: false, // 指定是否将代码视为模块
|
|
864
|
+
safari10: true, // Safari 10 的特定问题,如 for-of 迭代器兼容性
|
|
865
|
+
// toplevel: true, // 缺省 false,是否优化顶层作用域的变量和函数
|
|
866
|
+
sourceMap: false, // 为压缩后的代码生成 source map 文件,便于调试
|
|
867
|
+
// outputPath: undefined, // 指定压缩后文件的输出路径
|
|
868
|
+
// inlineSourcesContent: false, // 将源代码内容内联到生成的 source map 中
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// terser
|
|
872
|
+
const opts2 = {
|
|
873
|
+
ecma: 6, // specify one of: 5, 2015, 2016, etc.
|
|
874
|
+
enclose: false, // or specify true, or "args:values"
|
|
875
|
+
keep_classnames: false,
|
|
876
|
+
keep_fnames: false,
|
|
877
|
+
ie8: false,
|
|
878
|
+
module: false,
|
|
879
|
+
nameCache: null, // or specify a name cache object
|
|
880
|
+
safari10: false,
|
|
881
|
+
toplevel: false,
|
|
882
|
+
mangle: {
|
|
883
|
+
toplevel: false, // 防止顶层作用域的变量和参数被混淆或重命名。
|
|
884
|
+
},
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// const rs = await mini(src.replace('function (', 'function __xxx__('), opts2)
|
|
888
|
+
const rs = await minify(src.replace('function (', 'function __xxx__('), opts) // 没有函数名称无法压缩
|
|
889
|
+
// console.log({src, rs})
|
|
890
|
+
if (rs?.code) R = `${rs.code.replace('function __xxx__(', 'function (')}`
|
|
891
|
+
} catch (e) {
|
|
892
|
+
console.log(`pressCode exp:${e.message}`)
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return R
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* 返回入口函数,devtool: false 时,入口函数未封装,ast 会很大,不用
|
|
900
|
+
* @param {string} fn
|
|
901
|
+
* @param {*} ast
|
|
902
|
+
* @returns {string}
|
|
903
|
+
*/
|
|
904
|
+
function getEntry(fn, ast) {
|
|
905
|
+
let R
|
|
906
|
+
|
|
907
|
+
try {
|
|
908
|
+
let entry
|
|
909
|
+
// 从根节点 查找 __webpack_modules__
|
|
910
|
+
walkNode(ast, (n, parent) => {
|
|
911
|
+
if (n.type === 'VariableDeclarator' && n.id?.value === '__webpack_exports__') {
|
|
912
|
+
entry = n
|
|
913
|
+
return true // 找到终止遍历
|
|
914
|
+
}
|
|
915
|
+
})
|
|
916
|
+
|
|
917
|
+
entry = buf.subarray(entry.span.end).toString()
|
|
918
|
+
entry = entry.slice(0, entry.lastIndexOf('})()')).replace(/..\/[./]*\/node_modules\//gi, '~/')
|
|
919
|
+
// .replace(/__webpack_require__/gi, '__m__') // 全局替换
|
|
920
|
+
|
|
921
|
+
// This entry need to be wrapped in an IIFE because it need to be in strict mode.
|
|
922
|
+
const IIFE = entry.includes('wrapped in an IIFE')
|
|
923
|
+
if (IIFE) entry = entry.slice(entry.indexOf('(() => {') + 8, entry.lastIndexOf('})()'))
|
|
924
|
+
|
|
925
|
+
entry = `function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {${entry}}`
|
|
926
|
+
|
|
927
|
+
console.log({fn, start: entry.slice(0, 80), tail: entry.slice(-80)}, 'getEntry')
|
|
928
|
+
R = entry
|
|
929
|
+
} catch (e) {
|
|
930
|
+
console.error(`getEntry exp:${e.message}`)
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return R
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* 迭代遍历所有节点(包括子节点),回调函数返回 true,则终止剩余节点遍历
|
|
938
|
+
* @param {*} node
|
|
939
|
+
* @param {(node: *, parent: *) => boolean} cb
|
|
940
|
+
* @param {*} parent
|
|
941
|
+
* @returns {boolean}
|
|
942
|
+
*/
|
|
943
|
+
function walkNode(node, cb, parent) {
|
|
944
|
+
if (!node || typeof node !== 'object') return
|
|
945
|
+
|
|
946
|
+
// 调用回调函数处理当前节点和父节点
|
|
947
|
+
if (cb(node, parent) === true) return true
|
|
948
|
+
|
|
949
|
+
// 有 type 字段的我们认为是一个节点
|
|
950
|
+
for (const k of Object.keys(node)) {
|
|
951
|
+
const item = node[k]
|
|
952
|
+
if (Array.isArray(item)) {
|
|
953
|
+
// 遍历数组中的每个子节点,返回成功则停止遍历剩余节点
|
|
954
|
+
for (const v of item) {
|
|
955
|
+
if (walkNode(v, cb, node)) return true
|
|
956
|
+
}
|
|
957
|
+
// } else if (item?.type) walkNode(item, cb, node) // 遍历单个子节点
|
|
958
|
+
} else if (item && typeof item === 'object') {
|
|
959
|
+
// 遍历单个子节点,返回成功则停止遍历剩余节点
|
|
960
|
+
if (walkNode(item, cb, node)) return true
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function sortObj(obj) {
|
|
966
|
+
const R = {}
|
|
967
|
+
const ks = ld.sortBy(ld.keys(obj))
|
|
968
|
+
|
|
969
|
+
ld.forIn(ks, k => {
|
|
970
|
+
R[k] = obj[k]
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
return R
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// test
|
|
977
|
+
// const pack = new Pack();
|
|
978
|
+
// pack.work({login: './src/mall/page/login.js'});
|