@afterpack/electron एक Electron app के ship किए जाने वाले तीनों programs (main, preload, renderer) को एक ही call से obfuscate करता है, और उन सबमें एक build seed साझा करता है। हाथ से wire करने पर हर leg अपनी अलग bundler build होता है और अपना random seed लेता है, इसलिए एक ही application के हिस्से संरचना में एक-दूसरे से असंबंधित निकलते हैं।
electron-builder कोई bundler नहीं है। यह पहले से built app को installer में package करता है और आपके JavaScript को कभी नहीं छूता, इसलिए AfterPack हो या न हो, इसके ऊपर एक bundler चाहिए।
Install
$ npm install -D @afterpack/electron// electron.vite.config.ts
import { withAfterpack } from "@afterpack/electron";
import { defineConfig, externalizeDepsPlugin } from "electron-vite";
export default withAfterpack(
defineConfig({
main: { plugins: [externalizeDepsPlugin()] },
preload: { plugins: [externalizeDepsPlugin()] },
renderer: {},
}),
);withAfterpack पूरे electron-vite config को wrap करता है (object, function या promise, जिस भी रूप में defineConfig लौटाए) और config में घोषित हर leg में @afterpack/vite wire करता है, किसी अघोषित leg में कभी नहीं। Peer dependency: vite ^5 \| ^6 \| ^7 \| ^8।
Build
Pass को कैसे wire करें, यह इस पर निर्भर है कि आपका toolchain तीनों legs को एक call में configure करता है, या हर leg के लिए एक config।
एक समय में एक leg
जो toolchains हर leg को अलग से configure करते हैं (@electron-forge/plugin-vite के साथ Electron Forge), वे इसके बजाय हर config में एक बार afterpackElectron call करते हैं:
// vite.main.config.ts (and vite.preload.config.ts / vite.renderer.config.ts)
import { afterpackElectron } from "@afterpack/electron";
export default { plugins: [afterpackElectron({ leg: "main" })] };leg ज़रूरी है।
एक seed, तीन तरीके
- electron-vite (
withAfterpack) और Forge +plugin-vite: तीनों configs एक ही Node process में build होते हैं, इसलिए legs अपने आप एक seed साझा करते हैं। vite-plugin-electron: यह भी एक process है।afterpackElectron({ leg: "renderer" })कोpluginsमें सबसे आख़िर में,vite-plugin-electronके बाद रखें, ताकि AfterPack काgenerateBundleतैयार bundle को seal करे।- हाथ से लिखी scripts जो हर leg को अलग command के रूप में चलाती हैं: कोई साझा process नहीं है, इसलिए उनमें seed को एक environment variable से pin करें:
// package.json
"build": "AFTERPACK_seed=git vite build -c vite.main.ts && vite build -c vite.renderer.ts"AFTERPACK_seed वही values लेता है जो seed: एक integer, "git" (HEAD से निकाला गया), या कोई भी दूसरी string, जिसे engine hash करता है। स्पष्ट seed option फिर भी इससे ऊपर रहता है। Seed strategy बताती है कि seed कब pin करें और कब नहीं।
Verify
Protection Map हर leg के लिए अलग, .afterpack/<leg>/protectionMap.html में लिखा जाता है, जब कोई source map मिल सके और build CI (CI=true या CI=1) में न चल रही हो। Production mode इसे बंद नहीं करता, लेकिन electron-vite तब तक कोई source maps emit नहीं करता जब तक आप उन्हें न माँगें, इसलिए map डिफ़ॉल्ट रूप से बंद रहता है। main, preload और renderer को अलग-अलग जाँचें। हर रिपोर्ट में आपका पूरा source होता है। इसे कभी commit या serve न करें।
app.asar में मौजूद एक .map आपके मूल source तक पहुँचता है। जो कुछ भी आप package करते हैं, उसके लिए source-map emission बंद रखें (production में डिफ़ॉल्ट यही है)।
Options
हर configuration key यहाँ भी काम करती है, withAfterpack(config, options) से हर leg पर लागू, या afterpackElectron(options) से हर leg के लिए आगे भेजी गई। ये options केवल इसी package में हैं:
| Option | Type | Default |
|---|---|---|
leg | "main" | "preload" | "renderer" | afterpackElectron पर ज़रूरी |
projectRoot | string | process.cwd()। electron-vite renderer build का root src/renderer/ रखता है। अगर आपकी build कहीं और से चलती है तो इसे override करें |
seed डिफ़ॉल्ट रूप से एक नया value होता है जो एक build session के हर leg में साझा होता है। protectionMap.enabled सामान्य source-map नियम का पालन करता है, और हर leg अपनी रिपोर्ट .afterpack/<leg>/protectionMap.html में लिखता है।
// Heavier protection, reproducible per commit, applied to every leg:
withAfterpack(config, { preset: "hard", seed: "git" });Directives
हर leg के नीचे @afterpack/vite wire होता है, इसलिए directive capture वहाँ भी उसी तरह काम करता है।
Edge cases
Support matrix
| Toolchain | Wiring | अपने आप एक seed? |
|---|---|---|
| electron-vite | withAfterpack(defineConfig({ … })), एक call | हाँ |
Forge + plugin-vite | तीनों configs में से हर एक में afterpackElectron({ leg }) | हाँ |
Forge + plugin-webpack | हर config में @afterpack/webpack | हाँ |
vite-plugin-electron | afterpackElectron({ leg: "renderer" }), सबसे आख़िर में रखा गया | हाँ, जब उस plugin के बाद रखा जाए |
| electron-builder | यह package करता है, bundle नहीं; इसके ऊपर वाले bundler को wire करें | लागू नहीं |
| हाथ से लिखी scripts (अलग processes) | हर leg के लिए एक plugin, या npx afterpack@latest <dir> | नहीं; AFTERPACK_seed pin करें |
यह क्या अस्वीकार करता है
इस package से wire किए गए किसी भी leg पर दो fail-closed checks लागू होते हैं।
bytecodePlugin। electron-vite का अपना bytecodePlugin emit किए गए हर chunk को 3 लाइन के loader stub से बदल देता है और असली application को V8 bytecode (.jsc) के रूप में ship करता है। दोनों एक साथ नहीं चल सकते, क्योंकि bytecode उस chunk को बदल देता है जिसे Rollup ने generate किया था। Build उस leg पर fail होती है जहाँ bytecodePlugin चालू है, या जहाँ भी बाद में output में कोई .jsc/.cjsc फ़ाइल दिखे। दोनों में से एक को हटाएँ।
build.backup: true। Backups सीधे अस्वीकार किए जाते हैं: call आगे बढ़ने के बजाय throw करता है, क्योंकि .backup.<hash>.js आपका पूरा मूल source है, जो output के बगल में उसी tree में लिखा जाता है जिसे आपका packager app.asar में copy करता है। App install करने वाले हर user को यह मिल जाता।
यही जोखिम हर उस artifact पर लागू होता है जिसे बाहर रखना आपका packager नहीं जानता। electron-builder का डिफ़ॉल्ट files glob dot: true के साथ match करता है और डिफ़ॉल्ट रूप से .afterpack/ को बाहर नहीं रखता। इन्हें package की जाने वाली चीज़ों से बाहर रखें:
// electron-builder
"files": ["**/*", "!.afterpack/**", "!**/*.backup.*", "!**/*.map"]
// electron-forge
packagerConfig: { ignore: [/^\/\.afterpack/, /\.backup\./, /\.map$/] }Source maps पर अस्वीकार के बजाय एक warning मिलती है: app.asar के अंदर sourcesContent वाला .map पूरा deobfuscation है। sourceMap: { enabled: true, emitUrl: true } केवल local debug builds तक सीमित रखें और package की जाने वाली चीज़ों से *.map बाहर रखें।
IPC channels
Channel names दोनों bundles में byte-for-byte मेल खाने चाहिए। ipcMain.on("save-file", …) और ipcRenderer.send("save-file", …) दोनों तरफ़ एक ही string हैं। वे सामान्य emit किए गए JavaScript में सामान्य string literals हैं, इसलिए दोनों legs को एक ही seed और एक ही posture (preset / complexity) पर रखना ही दोनों हिस्सों को consistent रखता है।
अगर कोई channel name literal के रूप में लिखे जाने के बजाय runtime पर बनाया जाता है, तो उस construction को ऐसे module में रखें जिसे आप paths.exclude से बाहर रखते हैं, या bundler-plugin की एक regions entry (Pro) से उस region का posture कम करें।
Plugin के बिना: CLI
Dependency नहीं जोड़ना चाहते, या किसी leg को Vite के अलावा किसी और चीज़ से build कर रहे हैं? तो build के बाद हर output directory पर एक बार npx afterpack@latest चलाएँ:
npx afterpack@latest out/main --seed=git
npx afterpack@latest out/renderer --seed=gitPath argument न दें, तो CLI out/ पहचान लेता है, वह directory जिसमें electron-vite लिखता है; आपकी directory अलग हो तो उसका नाम दें। हर run directory में जाता है, हर .js/.mjs/.cjs इकट्ठा करता है, और उन्हें उसी जगह obfuscate करता है; argument के रूप में एक अकेली फ़ाइल भी काम करती है। हर run में एक ही --seed pin करें: मौजूदा commit से निकाला गया git इसकी गारंटी का सबसे आसान तरीका है। CLI run पर ऊपर के कोई भी अस्वीकार लागू नहीं होते; उसे जो भी JavaScript मिलता है, उसे obfuscate कर देता है। सभी runs में एक ही --seed और --preset रखें।
आगे
- Frameworks: पूरी matrix।
- Vite: options का वह अर्थ जिसे यह package आगे भेजता है।
- CLI reference: fallback तरीके की flags सूची।
- Builds & CI: seeds, dev-बनाम-prod, वही bytes promote करना।
- Electron app के license logic को protect करें: इसे local license या trial check पर लागू करना।
- Best practices → Seed strategy: कब pin करें, कब नहीं।
- Protection Map: रिपोर्ट पढ़ना।
- Configuration: हर option, और कौन-सी surface उसे सेट कर सकती है।