1
wwl
2025-07-31 c3c6cf8cb3f73755ac9fad29f2b2c0cf64f27979
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const fs = require('fs');
const path = require('path');
const obfuscator = require('javascript-obfuscator');
 
const distDir = path.resolve(__dirname, 'unpackage/dist/build/web');
const jsDir = path.join(distDir, 'static/js');
const htmlPath = path.join(distDir, 'index.html');
 
if (!fs.existsSync(jsDir)) {
  console.error('❌ 找不到 static/js 目录,打包还没生成?');
  process.exit(1);
}
 
// 读取所有 js 文件
const files = fs.readdirSync(jsDir).filter(f => f.endsWith('.js'));
 
// 辅助:提取 webpackChunkName
function extractChunkName(content) {
  // webpackChunkName 通常写成 /* webpackChunkName: "xxx" */
  const match = content.match(/webpackChunkName:\s*["']([^"']+)["']/);
  if (match) return match[1];
  // 如果没有注释,尝试提取其他线索,或者返回空
  return null;
}
 
// 生成映射:逻辑 chunk 名 → 纯 hash 文件名
const map = {};
 
files.forEach(file => {
  const filePath = path.join(jsDir, file);
  const content = fs.readFileSync(filePath, 'utf-8');
  const chunkName = extractChunkName(content);
  if (chunkName) {
    map[`${chunkName}.js`] = file;
  } else {
    // 入口文件(比如index.js)可能没 webpackChunkName 注释,尝试用文件名(hash.js)临时映射
    // 或你可以手动补充
  }
});
 
// 入口文件手动添加,比如 index.js 映射到某个文件
// 你可以根据实际情况手动确认替换
// 例如找 index 文件内容包含特定关键词,或者人工指定:
if (!map['index.js']) {
  // 尝试找内容包含 "new Vue" 的文件作为入口
  for (const file of files) {
    const content = fs.readFileSync(path.join(jsDir, file), 'utf-8');
    if (content.includes('new Vue') || content.includes('createApp')) {
      map['index.js'] = file;
      break;
    }
  }
}
 
if (!map['index.js']) {
  console.warn('⚠️ 找不到入口 index.js 对应的纯 hash 文件,请确认入口文件');
}
 
// 写入映射文件
const mapPath = path.join(distDir, 'pure-chunk-map.json');
fs.writeFileSync(mapPath, JSON.stringify(map, null, 2), 'utf-8');
console.log('✅ 生成纯 hash 映射:pure-chunk-map.json');
 
// 生成映射加载脚本,注入页面使用
const mapScript = `
window.__PURE_CHUNK_MAP__ = ${JSON.stringify(map, null, 2)};
(function() {
  const originalChunkFilename = __webpack_chunk_load__;
  // 覆写 webpack chunk 加载函数
  __webpack_chunk_load__ = function(chunkId) {
    const pureName = window.__PURE_CHUNK_MAP__[chunkId + '.js'];
    if (!pureName) {
      console.warn('[pure-chunk-map] 未找到映射', chunkId);
      return originalChunkFilename(chunkId);
    }
    const url = 'static/js/' + pureName;
    return originalChunkFilename(url);
  };
})();
`;
 
// 写入映射脚本文件
const mapScriptPath = path.join(distDir, 'pure-chunk-map.js');
fs.writeFileSync(mapScriptPath, mapScript, 'utf-8');
console.log('✅ 生成映射加载脚本:pure-chunk-map.js');
 
// 替换 index.html 中入口 chunk,注入映射脚本
if (!fs.existsSync(htmlPath)) {
  console.warn('⚠️ 找不到 index.html,可能打包失败或路径不对');
  process.exit(1);
}
 
let html = fs.readFileSync(htmlPath, 'utf-8');
 
// 替换入口 chunk 文件名,比如 index.js 替换为纯 hash 文件名
Object.keys(map).forEach(logicName => {
  const pureName = map[logicName];
  const regex = new RegExp(logicName.replace('.', '\\.'), 'g');
  html = html.replace(regex, pureName);
});
 
// 注入映射加载脚本到 html 底部(紧靠 </body> 前)
html = html.replace('</body>', `<script src="./pure-chunk-map.js"></script></body>`);
 
fs.writeFileSync(htmlPath, html, 'utf-8');
console.log('🎉 替换 index.html 入口 chunk 并注入映射加载脚本');
 
// 混淆所有 .js
files.forEach(file => {
  const filePath = path.join(jsDir, file);
  const code = fs.readFileSync(filePath, 'utf-8');
  const obfuscated = obfuscator.obfuscate(code, {
    compact: true,
    controlFlowFlattening: true,
    deadCodeInjection: true,
    selfDefending: true,
  }).getObfuscatedCode();
  fs.writeFileSync(filePath, obfuscated, 'utf-8');
  console.log(`🔒 已混淆 ${file}`);
});
 
console.log('🚀 全部完成!');