@afterpack/parcel-optimizer 0.2.0-rc.20260930T1421 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/index.js +11 -3
- package/dist/records-Bgj9LonR.js +49 -0
- package/dist/reporter.d.ts +6 -0
- package/dist/reporter.js +74 -0
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -28,6 +28,24 @@ that way.
|
|
|
28
28
|
Keep the `"..."`: it runs Parcel's own minifier first and AfterPack last. If obfuscation fails, the
|
|
29
29
|
build fails.
|
|
30
30
|
|
|
31
|
+
## Check a deploy
|
|
32
|
+
|
|
33
|
+
Add the plugin's reporter to the build command, and each build writes `.afterpack-protection.json`
|
|
34
|
+
into the target's output directory, a receipt with a hash per obfuscated bundle:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
parcel build index.html --reporter @afterpack/parcel-optimizer/reporter
|
|
38
|
+
npx afterpack verify dist # in the deploy step, before the upload
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`afterpack verify` fails if a file changed after it was obfuscated, or if there is no receipt. The
|
|
42
|
+
reporter writes none when a bundle it shipped was not obfuscated in that build: the optimizer is
|
|
43
|
+
missing from `.parcelrc`, `build.autorun` is off, or Parcel reused the bundle from `.parcel-cache`.
|
|
44
|
+
Parcel's naming rule for plugins in `.parcelrc` leaves no place there for a reporter in this
|
|
45
|
+
package, so it goes on the command line. The optimizer leaves the reporter one small record per
|
|
46
|
+
bundle in `.afterpack/parcel/`. The reporter clears them as each build starts; without it they stay
|
|
47
|
+
there, ignored by git.
|
|
48
|
+
|
|
31
49
|
## Configuration
|
|
32
50
|
|
|
33
51
|
`.parcelrc` cannot pass options, so put them in `afterpack.json` at your project root, or in
|
|
@@ -81,8 +99,7 @@ source, so never deploy or commit it.
|
|
|
81
99
|
`"preset": "minify"`. Parcel's default ES module output is not affected.
|
|
82
100
|
- **Cache.** Parcel caches optimizer output. Clear `.parcel-cache` for a fresh seed on an unchanged
|
|
83
101
|
build.
|
|
84
|
-
- **Not available here:** [`build.backup`][build.backup] and [`paths.include`][paths.include]
|
|
85
|
-
`afterpack verify` has nothing to check on a Parcel build.
|
|
102
|
+
- **Not available here:** [`build.backup`][build.backup] and [`paths.include`][paths.include].
|
|
86
103
|
- Parcel may print that ES module dependencies are experimental and that the plugin has
|
|
87
104
|
non-statically analyzable dependencies. Both are harmless.
|
|
88
105
|
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearBundleRecord, escapeParcelMarkdown, writeBundleRecord } from "./records-Bgj9LonR.js";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import { text } from "node:stream/consumers";
|
|
3
4
|
import { processBatch, version } from "@afterpack/core";
|
|
@@ -19,9 +20,6 @@ async function blobToString(blob) {
|
|
|
19
20
|
if (Buffer.isBuffer(blob)) return blob.toString("utf8");
|
|
20
21
|
return await text(blob);
|
|
21
22
|
}
|
|
22
|
-
function escapeParcelMarkdown(message) {
|
|
23
|
-
return message.replace(/[\\*_`~]/g, (c) => `\\${c}`);
|
|
24
|
-
}
|
|
25
23
|
const CONTENT_HASH_REF = /HASH_REF_\w{16}/g;
|
|
26
24
|
function contentHashRefsExcludingSourceMapComment(code) {
|
|
27
25
|
const refs = new Set(code.match(CONTENT_HASH_REF) ?? []);
|
|
@@ -61,6 +59,7 @@ var src_default = new Optimizer({
|
|
|
61
59
|
map
|
|
62
60
|
};
|
|
63
61
|
if (bundle.type !== "js") return unchanged;
|
|
62
|
+
clearBundleRecord(options.projectRoot, bundle.id);
|
|
64
63
|
if (!bundle.env.shouldOptimize) return unchanged;
|
|
65
64
|
const autorun = config.options.build?.autorun ?? true;
|
|
66
65
|
if (!autorun) return unchanged;
|
|
@@ -138,6 +137,15 @@ var src_default = new Optimizer({
|
|
|
138
137
|
origin: PLUGIN_NAME,
|
|
139
138
|
hints: ["Run `parcel build --no-content-hash` (chunk URLs stop being content-addressed).", "Or set `\"preset\": \"minify\"` in afterpack.json to ship minify-only."]
|
|
140
139
|
} });
|
|
140
|
+
const receipt = result.deferredReceipt;
|
|
141
|
+
if (receipt) writeBundleRecord(options.projectRoot, bundle.id, {
|
|
142
|
+
tool: receipt.tool,
|
|
143
|
+
engine: receipt.engine,
|
|
144
|
+
engineVersion: receipt.engineVersion,
|
|
145
|
+
seed: String(receipt.seed),
|
|
146
|
+
seedOrigin: receipt.seedOrigin,
|
|
147
|
+
transformed: receipt.transformed.includes(projectedFilePath)
|
|
148
|
+
});
|
|
141
149
|
let outCode = out.code;
|
|
142
150
|
let originalRelativeSourceMap = null;
|
|
143
151
|
if (result.policy.sourceMap && out.sourceMap != null) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { ensureAfterpackGitignore } from "@afterpack/integration-utils";
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
//#region src/markdown.ts
|
|
6
|
+
function escapeParcelMarkdown(message) {
|
|
7
|
+
return message.replace(/[\\*_`~]/g, (c) => `\\${c}`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/records.ts
|
|
12
|
+
function afterpackDirOf(projectRoot) {
|
|
13
|
+
return join(projectRoot, ".afterpack");
|
|
14
|
+
}
|
|
15
|
+
function recordsDirOf(projectRoot) {
|
|
16
|
+
return join(afterpackDirOf(projectRoot), "parcel");
|
|
17
|
+
}
|
|
18
|
+
function recordPathOf(projectRoot, bundleId) {
|
|
19
|
+
return join(recordsDirOf(projectRoot), `${bundleId}.json`);
|
|
20
|
+
}
|
|
21
|
+
function writeBundleRecord(projectRoot, bundleId, record) {
|
|
22
|
+
const path = recordPathOf(projectRoot, bundleId);
|
|
23
|
+
mkdirSync(recordsDirOf(projectRoot), { recursive: true });
|
|
24
|
+
ensureAfterpackGitignore(afterpackDirOf(projectRoot));
|
|
25
|
+
writeFileSync(path, `${JSON.stringify(record)}\n`);
|
|
26
|
+
}
|
|
27
|
+
function clearBundleRecord(projectRoot, bundleId) {
|
|
28
|
+
rmSync(recordPathOf(projectRoot, bundleId), { force: true });
|
|
29
|
+
}
|
|
30
|
+
function clearBundleRecords(projectRoot) {
|
|
31
|
+
rmSync(recordsDirOf(projectRoot), {
|
|
32
|
+
recursive: true,
|
|
33
|
+
force: true
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function readBundleRecord(projectRoot, bundleId) {
|
|
37
|
+
try {
|
|
38
|
+
const raw = JSON.parse(readFileSync(recordPathOf(projectRoot, bundleId), "utf8"));
|
|
39
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
40
|
+
const record = raw;
|
|
41
|
+
if (typeof record.tool !== "string" || typeof record.transformed !== "boolean") return null;
|
|
42
|
+
return record;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { clearBundleRecord, clearBundleRecords, escapeParcelMarkdown, readBundleRecord, writeBundleRecord };
|
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { clearBundleRecords, escapeParcelMarkdown, readBundleRecord } from "./records-Bgj9LonR.js";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { PROTECTION_RECEIPT_FILE, writeProtectionReceipt } from "@afterpack/integration-utils";
|
|
4
|
+
import { Reporter } from "@parcel/plugin";
|
|
5
|
+
import { rmSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
//#region src/reporter.ts
|
|
8
|
+
const LABEL = "afterpack-parcel";
|
|
9
|
+
function shipsObfuscatedJs(bundle) {
|
|
10
|
+
return bundle.type === "js" && bundle.env.shouldOptimize && bundle.bundleBehavior !== "inline";
|
|
11
|
+
}
|
|
12
|
+
function agreed(values) {
|
|
13
|
+
const distinct = [...new Set(values)];
|
|
14
|
+
return distinct.length === 1 ? distinct[0] : null;
|
|
15
|
+
}
|
|
16
|
+
function joined(values) {
|
|
17
|
+
return [...new Set(values)].join(",");
|
|
18
|
+
}
|
|
19
|
+
function collectDistTrees(bundles, projectRoot) {
|
|
20
|
+
const trees = new Map();
|
|
21
|
+
for (const bundle of bundles) {
|
|
22
|
+
if (!shipsObfuscatedJs(bundle)) continue;
|
|
23
|
+
const dir = bundle.target.distDir;
|
|
24
|
+
let tree = trees.get(dir);
|
|
25
|
+
if (!tree) {
|
|
26
|
+
tree = {
|
|
27
|
+
files: [],
|
|
28
|
+
records: [],
|
|
29
|
+
transformed: [],
|
|
30
|
+
unrecorded: []
|
|
31
|
+
};
|
|
32
|
+
trees.set(dir, tree);
|
|
33
|
+
}
|
|
34
|
+
const record = readBundleRecord(projectRoot, bundle.id);
|
|
35
|
+
if (!record) {
|
|
36
|
+
tree.unrecorded.push(relative(projectRoot, bundle.filePath));
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
tree.files.push(bundle.filePath);
|
|
40
|
+
tree.records.push(record);
|
|
41
|
+
if (record.transformed) tree.transformed.push(bundle.filePath);
|
|
42
|
+
}
|
|
43
|
+
return trees;
|
|
44
|
+
}
|
|
45
|
+
var reporter_default = new Reporter({ report({ event, options, logger }) {
|
|
46
|
+
if (event.type === "buildStart") clearBundleRecords(options.projectRoot);
|
|
47
|
+
if (event.type !== "buildSuccess") return;
|
|
48
|
+
const trees = collectDistTrees(event.bundleGraph.getBundles(), options.projectRoot);
|
|
49
|
+
for (const [dir, tree] of trees) {
|
|
50
|
+
const receiptPath = join(dir, PROTECTION_RECEIPT_FILE);
|
|
51
|
+
if (tree.unrecorded.length > 0) {
|
|
52
|
+
rmSync(receiptPath, { force: true });
|
|
53
|
+
logger.warn({ message: escapeParcelMarkdown(`[${LABEL}] no protection receipt for ${relative(options.projectRoot, dir)}: AfterPack has no record of obfuscating ${tree.unrecorded.join(", ")} in this build. Keep @afterpack/parcel-optimizer in .parcelrc and build.autorun on; a bundle Parcel reused from .parcel-cache has no record either, so clear .parcel-cache and rebuild.`) });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const records = tree.records;
|
|
57
|
+
writeProtectionReceipt({
|
|
58
|
+
dir,
|
|
59
|
+
tool: records[0].tool,
|
|
60
|
+
engine: agreed(records.map((r) => r.engine)),
|
|
61
|
+
engineVersion: agreed(records.map((r) => r.engineVersion)),
|
|
62
|
+
seed: joined(records.map((r) => r.seed)),
|
|
63
|
+
seedOrigin: joined(records.map((r) => r.seedOrigin)),
|
|
64
|
+
bundler: "parcel",
|
|
65
|
+
buildId: null,
|
|
66
|
+
files: tree.files,
|
|
67
|
+
transformed: tree.transformed
|
|
68
|
+
});
|
|
69
|
+
logger.info({ message: escapeParcelMarkdown(`[${LABEL}] wrote protection receipt -> ${receiptPath} (afterpack verify)`) });
|
|
70
|
+
}
|
|
71
|
+
} });
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { reporter_default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afterpack/parcel-optimizer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Parcel 2 optimizer for AfterPack: obfuscate each JavaScript bundle in memory during parcel build",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./reporter": {
|
|
15
|
+
"types": "./dist/reporter.d.ts",
|
|
16
|
+
"import": "./dist/reporter.js",
|
|
17
|
+
"default": "./dist/reporter.js"
|
|
13
18
|
}
|
|
14
19
|
},
|
|
15
20
|
"files": [
|
|
@@ -21,11 +26,11 @@
|
|
|
21
26
|
"@parcel/core": ">=2.9.0"
|
|
22
27
|
},
|
|
23
28
|
"dependencies": {
|
|
24
|
-
"@afterpack/core": "~0.2.
|
|
29
|
+
"@afterpack/core": "~0.2.2",
|
|
25
30
|
"@parcel/diagnostic": "^2.9.0",
|
|
26
31
|
"@parcel/plugin": "^2.9.0",
|
|
27
32
|
"@parcel/source-map": "^2.1.1",
|
|
28
|
-
"@afterpack/integration-utils": "0.2.
|
|
33
|
+
"@afterpack/integration-utils": "0.2.1"
|
|
29
34
|
},
|
|
30
35
|
"devDependencies": {
|
|
31
36
|
"@biomejs/biome": "2.4.4",
|
|
@@ -68,8 +73,8 @@
|
|
|
68
73
|
"url": "https://github.com/afterpack-dev/afterpack/issues"
|
|
69
74
|
},
|
|
70
75
|
"scripts": {
|
|
71
|
-
"build": "tsdown src/index.ts --format esm --dts",
|
|
72
|
-
"dev": "tsdown src/index.ts --format esm --watch",
|
|
76
|
+
"build": "tsdown src/index.ts src/reporter.ts --format esm --dts",
|
|
77
|
+
"dev": "tsdown src/index.ts src/reporter.ts --format esm --watch",
|
|
73
78
|
"lint": "biome check .",
|
|
74
79
|
"lint:fix": "biome check --write .",
|
|
75
80
|
"typecheck": "tsc --noEmit",
|