diff options
Diffstat (limited to 'config/webpack')
5 files changed, 140 insertions, 42 deletions
diff --git a/config/webpack/loaders/scoped-loader.js b/config/webpack/loaders/scoped-loader.js new file mode 100644 index 0000000..701afc2 --- /dev/null +++ b/config/webpack/loaders/scoped-loader.js @@ -0,0 +1,3 @@ +module.exports = source => `(() => { +${source} +})();`; diff --git a/config/webpack/plugins/create-manifest-webpack-plugin/index.js b/config/webpack/plugins/create-manifest-webpack-plugin/index.js index 5dac1b2..4a07380 100644 --- a/config/webpack/plugins/create-manifest-webpack-plugin/index.js +++ b/config/webpack/plugins/create-manifest-webpack-plugin/index.js @@ -2,17 +2,31 @@ const path = require("path"); const { version } = require(path.resolve(process.env.ROOT_DIR, "package.json")); +const getRunAt = scriptType => { + switch (scriptType) { + case "before": + case "injected": + case "injected_and_loaded": + return "document_start"; + + case "rendered": + return "document_end"; + + case "loaded": + default: + return "document_idle"; + } +}; + class CreateManifestPlugin { - constructor(scripts) { + constructor({ scripts }) { this.scripts = {}; this.manifest = require("./manifest-template.json"); - this.scripts.document_start = { - ...(scripts.before || {}), - ...(scripts.injected || {}), - ...(scripts.injected_and_loaded || {}) - }; - this.scripts.document_end = scripts.rendered || {}; - this.scripts.document_idle = scripts.loaded || {}; + this.scripts.before = scripts.before || {}; + this.scripts.injected = scripts.injected || {}; + this.scripts.injected_and_loaded = scripts.injected_and_loaded || {}; + this.scripts.rendered = scripts.rendered || {}; + this.scripts.loaded = scripts.loaded || {}; } apply(compiler) { @@ -43,8 +57,8 @@ class CreateManifestPlugin { for (const scriptPath in paths) { this.manifest.content_scripts.push({ matches: [`*://banglarbhumi.gov.in/${scriptPath}`], - js: paths[scriptPath].map(scriptName => `${scriptName}.js`), - run_at: scriptType + js: [`${scriptType}.js`], + run_at: getRunAt(scriptType) }); } } diff --git a/config/webpack/plugins/inject-script-webpack-plugin.js b/config/webpack/plugins/inject-script-webpack-plugin.js index ca75213..6568514 100644 --- a/config/webpack/plugins/inject-script-webpack-plugin.js +++ b/config/webpack/plugins/inject-script-webpack-plugin.js @@ -1,5 +1,23 @@ +const { sources } = require("webpack"); const terser = require("terser"); +const propectualIndexOf = (string, characterList, start) => { + if (start > 0) { + let i = 0; + let x = start; + while (i < characterList.length) { + x = string.indexOf(characterList[i], x + 1); + if (x < 0) { + return -1; + } + i++; + } + return x; + } else { + return -1; + } +}; + class InjectScriptPlugin { constructor({ onload, isFirst }) { this.onload = !!onload; @@ -11,45 +29,39 @@ class InjectScriptPlugin { compilation.hooks.processAssets.tapPromise( { name: "InjectScriptPlugin", - stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, additionalAssets: true }, async assets => { for (const assetName in assets) { if (assetName.endsWith(".js")) { - const asset = assets[assetName]; - let content = asset._source._children; - let x = -1; - for (let i = 0; i < content.length; i++) { - if (typeof content[i] === "string" && content[i].indexOf("__webpack_exports__") >= 0) { - x = i; - break; - } - } - if (x == -1) { - throw Error("Failed to inject script."); - } - content[x] += ` + let currentAssetContent = compilation.getAsset(assetName).source.source(); + let start = ` const observer = new MutationObserver(mutations => { const script = document.querySelector("head > script:nth-child(54)"); if (script) { script.innerHTML `; + let end = ""; if (!this.isFirst) { - content[x] += "+"; + start += "+"; } - content[x] += "= `"; + start += "= `"; if (this.onload) { - content[x] += "document.addEventListener(\"DOMContentLoaded\", () => {"; - content[content.length - 3] += "});"; + start += "document.addEventListener(\"DOMContentLoaded\", () => {"; + end += "});"; } - content[content.length - 3] += `\`; + end += `\`; observer.disconnect(); } }); observer.observe(document, {childList: true, subtree: true}); `; - content[x + 1] = (await terser.minify(content[x + 1]._source._source._valueAsString.replaceAll("`", "\\`"))).code; + const content = (await terser.minify(currentAssetContent.replaceAll("`", "\\`"))).code; + compilation.updateAsset( + assetName, + new sources.RawSource(start + content + end) + ); } } } diff --git a/config/webpack/plugins/sanitize-scripts-import-webpack-plugin.js b/config/webpack/plugins/sanitize-scripts-import-webpack-plugin.js new file mode 100644 index 0000000..61ce804 --- /dev/null +++ b/config/webpack/plugins/sanitize-scripts-import-webpack-plugin.js @@ -0,0 +1,56 @@ +const { sources } = require("webpack"); + +const propectualIndexOf = (string, characterList, start) => { + if (start > 0) { + let i = 0; + let x = start; + while (i < characterList.length) { + x = string.indexOf(characterList[i], x + 1); + if (x < 0) { + return -1; + } + i++; + } + return x; + } else { + return -1; + } +}; + +class SanitizeScriptsImportPlugin { + apply(compiler) { + compiler.hooks.compilation.tap("SanitizeScriptsImportPlugin", compilation => { + compilation.hooks.processAssets.tap( + { + name: "SanitizeScriptsImportPlugin", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, + additionalAssets: true + }, + assets => { + for (const assetName in assets) { + if (assetName.endsWith(".js")) { + const currentAssetContent = compilation.getAsset(assetName).source.source(); + let previouslyEnded = 0; + let code = ""; + while (true) { + const start = propectualIndexOf(currentAssetContent, ["\n/***/", "\n/***/", "\n"], previouslyEnded + 1); + previouslyEnded = propectualIndexOf(currentAssetContent, ["\n/***/"], start + 1); + if (previouslyEnded < 0) { + break; + } else { + code += currentAssetContent.substring(start, previouslyEnded); + } + } + compilation.updateAsset( + assetName, + new sources.RawSource(code) + ); + } + } + } + ); + }); + } +} + +module.exports = SanitizeScriptsImportPlugin; diff --git a/config/webpack/utils/generate-script-build-config.js b/config/webpack/utils/generate-script-build-config.js index 2485e85..f8e1697 100644 --- a/config/webpack/utils/generate-script-build-config.js +++ b/config/webpack/utils/generate-script-build-config.js @@ -1,26 +1,31 @@ const fs = require("fs"); const path = require("path"); +const SanitizeScriptsImportPlugin = require("../plugins/sanitize-scripts-import-webpack-plugin"); const InjectScriptPlugin = require("../plugins/inject-script-webpack-plugin"); const manifest = require(path.resolve(process.env.SOURCE_DIR, "manifest.json")); let count = 0; -const generateScriptBuildConfig = (scriptName, scriptType) => { - let entry = path.resolve(process.env.SOURCE_DIR, `scripts/${scriptName}.ts`); - if (!fs.existsSync(entry)) { - entry = path.resolve(process.env.SOURCE_DIR, `scripts/${scriptName}/index.ts`); - if (!fs.existsSync(entry)) { - entry = path.resolve(process.env.SOURCE_DIR, `scripts/${scriptName}.js`); - } - } +const getImportStatementForScript = script => { + const importPath = path.resolve(process.env.SOURCE_DIR, "scripts", script); + return `import "${importPath}";`; +}; + +const getVirtualEntry = scriptType => { + const string = Object.keys(manifest[scriptType]).map(getImportStatementForScript).join("\n"); + const base64 = Buffer.from(string).toString("base64"); + return `data:text/javascript;base64,${base64}`; +} + +const generateScriptBuildConfig = scriptType => { return { - entry, + entry: getVirtualEntry(scriptType), mode: "production", output: { - filename: `${scriptName}.js` + filename: `${scriptType}.js` }, resolve: { extensions: ["", ".ts", ".js"], @@ -36,15 +41,23 @@ const generateScriptBuildConfig = (scriptName, scriptType) => { options: { configFile: "config/tsconfig.json" } + }, + { + test: new RegExp(`^${path.resolve(process.env.SOURCE_DIR, "scripts")}`), + loader: path.resolve(process.env.CONFIG_DIR, "webpack/loaders/scoped-loader") } ] }, - plugins: scriptType.startsWith("injected") + plugins: [ + new SanitizeScriptsImportPlugin() + ].concat( + scriptType.startsWith("injected") ? [new InjectScriptPlugin({ onload: scriptType === "injected_and_loaded", isFirst: count++ <= 0 })] - : undefined + : [] + ) }; }; |
