1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
import fs from "fs";
import path from "path";
import webpack from "webpack";
import { getFileName } from "../../utils/build-file.js";
import { getScriptRuntimeFromType } from "../../utils/script-runtime.js";
import scripts from "../../utils/scripts.js";
/** The only host this extension ever touches, used for every match pattern. */
const SITE_HOST = "banglarbhumi.gov.in";
/** Site-wide access: covers `web_accessible_resources` and `host_permissions`. */
const SITE_MATCH = `*://${SITE_HOST}/*`;
/** The application section of the site, where every content script runs. */
const APP_MATCH = `*://${SITE_HOST}/BanglarBhumi/*`;
const manifest = JSON.parse(
fs.readFileSync(
path.resolve("config/webpack/plugins/create-manifest-webpack-plugin/manifest-template.json")
)
);
/**
* Webpack plugin that dynamically generates the extension's
* `manifest.json` asset. It reads the template, resolves the
* current version from `package.json`, and builds `content_scripts`
* entries by matching script definitions from `scripts.json` against
* page URL patterns. Shared modules, injected scripts, and
* web-accessible resources are added automatically based on import
* counts collected during the build.
*
* Every generated pattern is scoped to {@link SITE_HOST}: match patterns
* allow a single trailing `*` in the path, so the `"*"` script path maps to
* {@link APP_MATCH} rather than the invalid `BanglarBhumi/**`.
*/
class CreateManifestPlugin {
/**
* @param {{
* sharedModulesImportedCount: Record<string, number>,
* injectedSharedModulesImportedCount: Record<string, number>,
* sortedSharedModules: string[]
* }} options
*/
constructor({ sharedModulesImportedCount, injectedSharedModulesImportedCount, sortedSharedModules }) {
this.sharedModulesImportedCount = sharedModulesImportedCount;
this.injectedSharedModulesImportedCount = injectedSharedModulesImportedCount;
this.sortedSharedModules = sortedSharedModules;
}
/**
* @param {import("webpack").Compiler} compiler
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap("CreateManifestPlugin", (compilation) => {
compilation.hooks.processAssets.tap(
{
name: "CreateManifestPlugin",
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
(assets) => {
manifest.version = JSON.parse(fs.readFileSync((path.resolve(ROOT_DIR, "package.json")))).version;
manifest.content_scripts = [];
let resources = [];
const arrangedScripts = {};
for (const scriptPath in scripts) {
arrangedScripts[scriptPath] = {};
for (const scriptType in scripts[scriptPath]) {
const scriptRuntime = getScriptRuntimeFromType(scriptType);
if (typeof arrangedScripts[scriptPath][scriptRuntime] === "undefined") {
arrangedScripts[scriptPath][scriptRuntime] = [];
}
arrangedScripts[scriptPath][scriptRuntime].push(`scripts/${getFileName(scriptType, scriptPath)}.js`);
if (scriptType.startsWith("injected")) {
resources.push(`scripts/injected/${getFileName(scriptType, scriptPath)}.js`);
}
}
}
manifest.content_scripts.push({
matches: [APP_MATCH],
js: this.sortedSharedModules
.filter((sharedModule) => this.sharedModulesImportedCount[sharedModule] > 0)
.map((sharedModule) => `shared/${getFileName(sharedModule, "shared")}.js`)
.concat(arrangedScripts["*"]["document_start"])
.filter((x) => x != null),
run_at: "document_start"
});
delete arrangedScripts["*"]["document_start"];
for (const scriptPath in arrangedScripts) {
// Match patterns allow at most one trailing `*`, so `"*"` cannot
// be interpolated into the path the way a literal segment can.
const matches = [scriptPath === "*"
? APP_MATCH
: `*://${SITE_HOST}/BanglarBhumi/${scriptPath}*`];
if (scriptPath === "Home")
matches.push(`*://${SITE_HOST}/BanglarBhumi/${scriptPath}.action*`);
for (const scriptRuntime in arrangedScripts[scriptPath]) {
manifest.content_scripts.push({
matches,
js: arrangedScripts[scriptPath][scriptRuntime],
run_at: scriptRuntime
});
}
}
for (const assetName in assets) {
if (/^assets\/.*\.(jpg|png|gif)$/.test(assetName)) {
resources.push(assetName);
}
}
// `CreateRulesPlugin` skips emitting `rules.json` when `src/rules/`
// holds no rule files, so drop the ruleset reference with it.
if (!("rules.json" in assets)) {
delete manifest.declarative_net_request;
}
const injectedSharedModules = this.sortedSharedModules
.filter((sharedModule) => this.injectedSharedModulesImportedCount[sharedModule] > 0)
.map((sharedModule) => `shared/${getFileName(sharedModule, "shared")}.js`);
if (injectedSharedModules.length > 0) {
resources = resources.concat(injectedSharedModules);
manifest.content_scripts[0].js.unshift(`shared/${getFileName("injected-shared-modules", "shared")}.js`);
}
manifest.content_scripts[0].js.unshift(`shared/${getFileName("script-injector", "shared")}.js`);
if (resources.length > 0) {
manifest.web_accessible_resources = [
{
resources,
matches: [SITE_MATCH]
}
];
}
compilation.emitAsset(
"manifest.json",
new webpack.sources.RawSource(
JSON.stringify(
manifest,
undefined,
process.env.NODE_ENV === "production"
? undefined
: "\t"
)
)
);
}
);
});
}
}
export default CreateManifestPlugin;
|