Files
jrshikoku/scripts/compile-web-script.ts
harukin-expo-dev-env 5e6ff42fe6 Add script to compile web inject JavaScript with minification and obfuscation options
- Introduced `compile-web-script.ts` for generating JavaScript files for userscripts.
- Supports options for minification and obfuscation.
- Generates base JS, minified JS, and obfuscated userscript.
- Configurable via command line arguments and environment variables.
2026-05-05 08:04:47 +00:00

145 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env tsx
/**
* compile-web-script.ts
*
* Usage:
* yarn compile-web-script
* yarn compile-web-script --no-obf # minifyのみ難読化なし
* yarn compile-web-script --no-userscript # .js のみ生成userscript不要
*
* Options (環境変数でも上書き可):
* UI=tokyo|default (default: tokyo)
* DARK=true|false (default: false)
*/
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { injectJavascriptData } from '../lib/webViewInjectjavascript';
// ---- オプション解析 ----
const args = process.argv.slice(2);
const noObf = args.includes('--no-obf');
const noUserscript = args.includes('--no-userscript');
const options = {
mapSwitch: 'false',
iconSetting: 'true',
stationMenu: 'false',
trainMenu: 'false',
uiSetting: (process.env.UI ?? 'tokyo') as string,
useUnyohub: 'false',
useElesite: 'false',
isDark: (process.env.DARK ?? 'false') === 'true',
backendApiBaseUrl:'https://jr-shikoku-backend-api-v1.haruk.in',
mockApiConfig: null,
} as const;
// ---- 出力ディレクトリ ----
const outDir = path.resolve(__dirname, '../docs/generated');
fs.mkdirSync(outDir, { recursive: true });
// const baseJs = path.join(outDir, 'webInjectJavascript.parsed.current.js'); // 中間: ベタJS
// const minJs = path.join(outDir, 'webInjectJavascript.parsed.current.min.js'); // 中間: minify済み
// const obfJs = path.join(outDir, 'webInjectJavascript.parsed.current.obf.js'); // 中間: 難読化済みJS
// const baseUser = path.join(outDir, 'webInjectJavascript.parsed.current.user.js'); // 中間: ベタuserscript
const obfUser = path.join(outDir, 'webInjectJavascript.parsed.current.obf.user.js');
// 中間ファイルを出力したい場合は上の const を有効化して各ステップの writeFileSync コメントを外す
const baseJs = '/tmp/webInjectJavascript.base.js';
const minJs = '/tmp/webInjectJavascript.min.js';
const obfJs = '/tmp/webInjectJavascript.obf.js';
// ---- userscript ヘッダ ----
const userscriptHeader = `\
// ==UserScript==
// @name JR Shikoku WebInject
// @namespace jrshikoku
// @version 1.0.0
// @description Generated inject script for train.jr-shikoku.co.jp
// @match https://train.jr-shikoku.co.jp/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function(){
`;
const userscriptFooter = `\n})();\n`;
function wrapUserscript(src: string): string {
return userscriptHeader + src + userscriptFooter;
}
// ---- Step 1: ベタJS生成 ----
console.log('[1/3] Generating base JS...');
const shim = `\
/*
Generated from lib/webViewInjectjavascript.ts
Options: ${JSON.stringify(options)}
Date: ${new Date().toISOString()}
*/
if (!window.ReactNativeWebView) {
window.ReactNativeWebView = {
postMessage: (msg) => console.log('[ReactNativeWebView.postMessage]', msg),
};
}
`;
const raw = injectJavascriptData(options);
const baseContent = `${shim}\n${raw}`;
fs.writeFileSync(baseJs, baseContent, 'utf8');
// fs.writeFileSync(baseJs, baseContent, 'utf8'); // 中間ファイルとして保存する場合
// if (!noUserscript) {
// fs.writeFileSync(baseUser, wrapUserscript(baseContent), 'utf8'); // ベタuserscriptを保存する場合
// }
console.log(` -> base JS generated (${Math.round(Buffer.byteLength(baseContent) / 1024)}KB)`);
// ---- Step 2: minify (terser) ----
console.log('[2/3] Minifying with terser...');
const terserCmd = [
'npx', 'terser', JSON.stringify(baseJs),
'--compress', 'passes=3,drop_console=false,pure_getters=true',
'--mangle', 'toplevel=true',
'--output', JSON.stringify(minJs),
].join(' ');
execSync(terserCmd, { stdio: 'inherit' });
// fs.copyFileSync(minJs, path.join(outDir, 'webInjectJavascript.parsed.current.min.js')); // 中間ファイルとして保存する場合
console.log(` -> minified (${Math.round(fs.statSync(minJs).size / 1024)}KB)`);
if (noObf) {
console.log('[3/3] Skipped obfuscation (--no-obf)');
console.log('Done.');
process.exit(0);
}
// ---- Step 3: 難読化 (javascript-obfuscator) ----
console.log('[3/3] Obfuscating with javascript-obfuscator...');
const obfCmd = [
'npx', 'javascript-obfuscator', JSON.stringify(minJs),
'--output', JSON.stringify(obfJs),
'--compact', 'true',
'--control-flow-flattening', 'false',
'--dead-code-injection', 'false',
'--string-array', 'true',
'--string-array-encoding', 'base64',
'--string-array-threshold', '0.75',
'--string-array-rotate', 'true',
'--string-array-shuffle', 'true',
'--string-array-calls-transform', 'true',
'--string-array-index-shift', 'true',
'--string-array-wrappers-count', '2',
'--string-array-wrappers-type', 'function',
'--string-array-wrappers-chained-calls', 'true',
'--split-strings', 'true',
'--split-strings-chunk-length', '8',
'--identifier-names-generator', 'hexadecimal',
'--rename-globals', 'false',
'--self-defending', 'false',
].join(' ');
execSync(obfCmd, { stdio: 'inherit' });
// fs.copyFileSync(obfJs, path.join(outDir, 'webInjectJavascript.parsed.current.obf.js')); // 難読化JSを保存する場合
const obfContent = fs.readFileSync(obfJs, 'utf8');
fs.writeFileSync(obfUser, wrapUserscript(obfContent), 'utf8');
console.log('\nDone. Generated:');
console.log(` ${path.relative(process.cwd(), obfUser)} (${Math.round(fs.statSync(obfUser).size / 1024)}KB)`);