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

#!/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)`);