vite.config.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. const fs = require("fs");
  2. const path = require("path");
  3. const { defineConfig } = require("vite");
  4. const { viteSingleFile } = require("vite-plugin-singlefile");
  5. const JavaScriptObfuscator = require("javascript-obfuscator");
  6. const platformBuilds = {
  7. applovin: { adapter: "applovin", output: "applovin" },
  8. unity: { adapter: "unity", output: "unity" },
  9. playturbo: { adapter: "playturbo", output: "playturbo" },
  10. mintegral: { adapter: "playturbo", output: "mintegral" },
  11. google: { adapter: "google", output: "google" },
  12. };
  13. // ── JS 混淆:base64 载荷保护 + 代码混淆 ─────────────────────────
  14. function obfuscateJsInHtml(html) {
  15. const scriptRegex = /(<script>)([\s\S]*?)(<\/script>)/;
  16. const match = html.match(scriptRegex);
  17. if (!match) return html;
  18. let jsCode = match[2];
  19. // 1. 找出所有 base64 数据载荷,替换为短占位符,确保 JS 语法完整
  20. const b64Payloads = [];
  21. const b64Pattern = /(data:(?:image|audio)\/[^;]*?;base64,)([A-Za-z0-9+/=]+)/g;
  22. let b64Match;
  23. while ((b64Match = b64Pattern.exec(jsCode)) !== null) {
  24. const idx = b64Payloads.length;
  25. b64Payloads.push(b64Match[2]);
  26. jsCode =
  27. jsCode.slice(0, b64Match.index + b64Match[1].length) +
  28. `__B64_${idx}__` +
  29. jsCode.slice(b64Match.index + b64Match[1].length + b64Match[2].length);
  30. // 重置 regex lastIndex(因为替换改变了字符串长度)
  31. b64Pattern.lastIndex = b64Match.index + b64Match[1].length + `__B64_${idx}__`.length;
  32. }
  33. console.log(`[obfuscate] Found ${b64Payloads.length} base64 payloads, total ${b64Payloads.reduce((s, p) => s + p.length, 0).toLocaleString()} bytes`);
  34. // 2. 混淆 JS(stringArrayThreshold: 0 确保占位符不被编码到 stringArray)
  35. const result = JavaScriptObfuscator.obfuscate(jsCode, {
  36. compact: true,
  37. controlFlowFlattening: false, // 不影响 WebGL 帧率
  38. deadCodeInjection: false, // 不增加体积
  39. debugProtection: false, // 不用 setInterval 浪费 CPU
  40. disableConsoleOutput: true,
  41. identifierNamesGenerator: "hexadecimal",
  42. log: false,
  43. numbersToExpressions: false,
  44. renameGlobals: false,
  45. selfDefending: false, // 暂时关闭排查语法错误
  46. simplify: true,
  47. splitStrings: false,
  48. stringArray: false, // 关闭 stringArray,保护占位符
  49. transformObjectKeys: false,
  50. unicodeEscapeSequence: false,
  51. });
  52. let obfuscatedJs = result.getObfuscatedCode();
  53. // 3. 恢复 base64 载荷
  54. for (let i = 0; i < b64Payloads.length; i++) {
  55. obfuscatedJs = obfuscatedJs.replace(`__B64_${i}__`, b64Payloads[i]);
  56. }
  57. return html.replace(scriptRegex, match[1] + obfuscatedJs + match[3]);
  58. }
  59. // ── 构建后处理 ───────────────────────────────────────────────────
  60. function patchSingleFileHtml(htmlPath) {
  61. if (!fs.existsSync(htmlPath)) return;
  62. let html = fs.readFileSync(htmlPath, "utf8");
  63. // 1. 移除 HTML 注释
  64. html = html.replace(/<!--[\s\S]*?-->/g, "");
  65. // 2. 清理标签属性(移除 type="module" / crossorigin)
  66. html = html
  67. .replace(/<script\s+type="module"\s+crossorigin>/g, "<script>")
  68. .replace(/<script\s+crossorigin\s+type="module">/g, "<script>")
  69. .replace(/<script\s+type="module">/g, "<script>")
  70. .replace(/<script\s+crossorigin>/g, "<script>")
  71. .replace(/<style\s+rel="stylesheet"\s+crossorigin>/g, "<style>")
  72. .replace(/<style\s+crossorigin\s+rel="stylesheet">/g, "<style>")
  73. .replace(/<style\s+crossorigin>/g, "<style>");
  74. // 3. JS 混淆
  75. html = obfuscateJsInHtml(html);
  76. fs.writeFileSync(htmlPath, html);
  77. }
  78. function finalizeHtmlPlugin(outDir) {
  79. return {
  80. name: "finalize-html-output",
  81. closeBundle() {
  82. const htmlPath = path.resolve(__dirname, outDir, "index.html");
  83. patchSingleFileHtml(htmlPath);
  84. },
  85. };
  86. }
  87. module.exports = defineConfig(({ mode }) => {
  88. const platformBuild = platformBuilds[mode];
  89. const adapter = platformBuild?.adapter || "google";
  90. const output = platformBuild?.output;
  91. const outDir = output ? `dist/${output}` : "dist";
  92. return {
  93. plugins: [viteSingleFile(), finalizeHtmlPlugin(outDir)],
  94. server: {
  95. allowedHosts: ["color2.jccytech.cn", "localhost", ".jccytech.cn"],
  96. },
  97. resolve: {
  98. alias: {
  99. "#ad-config": path.resolve(
  100. __dirname,
  101. process.env.AD_CONFIG_PATH || "src/filler/ad-config.ts",
  102. ),
  103. "./ad-platform/current": path.resolve(
  104. __dirname,
  105. `src/filler/ad-platform/adapters/${adapter}.ts`,
  106. ),
  107. },
  108. },
  109. esbuild: {
  110. drop: ["console", "debugger"],
  111. legalComments: "none",
  112. },
  113. build: {
  114. sourcemap: false,
  115. assetsInlineLimit: 100 * 1024 * 1024,
  116. target: "es2017",
  117. outDir,
  118. emptyOutDir: true,
  119. rollupOptions: {
  120. input: {
  121. main: "./index.html",
  122. },
  123. output: {
  124. entryFileNames: "assets/[name].js",
  125. chunkFileNames: "assets/[name].js",
  126. assetFileNames: "assets/[name][extname]",
  127. },
  128. },
  129. },
  130. };
  131. });