const path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const { AngularWebpackPlugin } = require("@ngtools/webpack"); const TerserPlugin = require("terser-webpack-plugin"); const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin"); const configurator = require("./config/config"); if (process.env.NODE_ENV == null) { process.env.NODE_ENV = "development"; } const ENV = (process.env.ENV = process.env.NODE_ENV); const manifestVersion = process.env.MANIFEST_VERSION == 3 ? 3 : 2; const browser = process.env.BROWSER; function modifyManifestV3(buffer) { if (manifestVersion === 2 || !browser) { return buffer; } const manifest = JSON.parse(buffer.toString()); if (browser === "chrome") { // Remove unsupported properties delete manifest.applications; delete manifest.sidebar_action; delete manifest.commands._execute_sidebar_action; return JSON.stringify(manifest, null, 2); } // Update the background script reference to be an event page const backgroundScript = manifest.background.service_worker; delete manifest.background.service_worker; manifest.background.scripts = [backgroundScript]; // Remove unsupported properties delete manifest.content_security_policy.sandbox; delete manifest.sandbox; delete manifest.applications; manifest.permissions = manifest.permissions.filter((permission) => permission !== "offscreen"); if (browser === "safari") { delete manifest.sidebar_action; delete manifest.commands._execute_sidebar_action; delete manifest.optional_permissions; manifest.permissions.push("nativeMessaging"); } if (browser === "firefox") { delete manifest.storage; manifest.optional_permissions = manifest.optional_permissions.filter( (permission) => permission !== "privacy", ); } return JSON.stringify(manifest, null, 2); } console.log(`Building Manifest Version ${manifestVersion} app`); const envConfig = configurator.load(ENV); configurator.log(envConfig); const moduleRules = [ { test: /\.(html)$/, loader: "html-loader", }, { test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, exclude: /loading.svg/, generator: { filename: "popup/fonts/[name][ext]", }, type: "asset/resource", }, { test: /\.(jpe?g|png|gif|svg)$/i, exclude: /.*(bwi-font|glyphicons-halflings-regular)\.svg/, generator: { filename: "popup/images/[name][ext]", }, type: "asset/resource", }, { test: /\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, }, "css-loader", "postcss-loader", ], }, { test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader, }, "css-loader", "sass-loader", ], }, { test: /\.[cm]?js$/, use: [ { loader: "babel-loader", options: { configFile: "../../babel.config.json", }, }, ], }, { test: /\.[jt]sx?$/, loader: "@ngtools/webpack", }, { test: /argon2(-simd)?\.wasm$/, loader: "base64-loader", type: "javascript/auto", }, ]; const requiredPlugins = [ new webpack.DefinePlugin({ "process.env": { ENV: JSON.stringify(ENV), }, }), new webpack.EnvironmentPlugin({ FLAGS: envConfig.flags, DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {}, }), ]; const plugins = [ new HtmlWebpackPlugin({ template: "./src/popup/index.html", filename: "popup/index.html", chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"], }), new HtmlWebpackPlugin({ template: "./src/autofill/notification/bar.html", filename: "notification/bar.html", chunks: ["notification/bar"], }), new HtmlWebpackPlugin({ template: "./src/autofill/overlay/inline-menu/pages/button/button.html", filename: "overlay/menu-button.html", chunks: ["overlay/menu-button"], }), new HtmlWebpackPlugin({ template: "./src/autofill/overlay/inline-menu/pages/list/list.html", filename: "overlay/menu-list.html", chunks: ["overlay/menu-list"], }), new HtmlWebpackPlugin({ template: "./src/autofill/overlay/inline-menu/pages/menu-container/menu-container.html", filename: "overlay/menu.html", chunks: ["overlay/menu"], }), new HtmlWebpackPlugin({ template: "./src/autofill/deprecated/overlay/pages/button/legacy-button.html", filename: "overlay/button.html", chunks: ["overlay/button"], }), new HtmlWebpackPlugin({ template: "./src/autofill/deprecated/overlay/pages/list/legacy-list.html", filename: "overlay/list.html", chunks: ["overlay/list"], }), new CopyWebpackPlugin({ patterns: [ manifestVersion == 3 ? { from: "./src/manifest.v3.json", to: "manifest.json", transform: (content) => modifyManifestV3(content), } : "./src/manifest.json", { from: "./src/managed_schema.json", to: "managed_schema.json" }, { from: "./src/_locales", to: "_locales" }, { from: "./src/images", to: "images" }, { from: "./src/popup/images", to: "popup/images" }, { from: "./src/autofill/content/autofill.css", to: "content" }, ], }), new MiniCssExtractPlugin({ filename: "[name].css", chunkFilename: "chunk-[id].css", }), new AngularWebpackPlugin({ tsConfigPath: "tsconfig.json", entryModule: "src/popup/app.module#AppModule", sourceMap: true, }), new webpack.ProvidePlugin({ process: "process/browser.js", }), new webpack.SourceMapDevToolPlugin({ exclude: [/content\/.*/, /notification\/.*/, /overlay\/.*/], filename: "[file].map", }), ...requiredPlugins, ]; /** * @type {import("webpack").Configuration} * This config compiles everything but the background */ const mainConfig = { name: "main", mode: ENV, devtool: false, entry: { "popup/polyfills": "./src/popup/polyfills.ts", "popup/main": "./src/popup/main.ts", "content/trigger-autofill-script-injection": "./src/autofill/content/trigger-autofill-script-injection.ts", "content/bootstrap-autofill": "./src/autofill/content/bootstrap-autofill.ts", "content/bootstrap-autofill-overlay": "./src/autofill/content/bootstrap-autofill-overlay.ts", "content/bootstrap-autofill-overlay-menu": "./src/autofill/content/bootstrap-autofill-overlay-menu.ts", "content/bootstrap-autofill-overlay-notifications": "./src/autofill/content/bootstrap-autofill-overlay-notifications.ts", "content/bootstrap-legacy-autofill-overlay": "./src/autofill/deprecated/content/bootstrap-legacy-autofill-overlay.ts", "content/autofiller": "./src/autofill/content/autofiller.ts", "content/auto-submit-login": "./src/autofill/content/auto-submit-login.ts", "content/notificationBar": "./src/autofill/content/notification-bar.ts", "content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts", "content/content-message-handler": "./src/autofill/content/content-message-handler.ts", "content/fido2-content-script": "./src/autofill/fido2/content/fido2-content-script.ts", "content/fido2-page-script": "./src/autofill/fido2/content/fido2-page-script.ts", "notification/bar": "./src/autofill/notification/bar.ts", "overlay/menu-button": "./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts", "overlay/menu-list": "./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts", "overlay/menu": "./src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts", "overlay/button": "./src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts", "overlay/list": "./src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts", "encrypt-worker": "../../libs/common/src/platform/services/cryptography/encrypt.worker.ts", "content/lp-fileless-importer": "./src/tools/content/lp-fileless-importer.ts", "content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts", "content/lp-suppress-import-download": "./src/tools/content/lp-suppress-import-download.ts", }, optimization: { minimize: ENV !== "development", minimizer: [ new TerserPlugin({ exclude: [/content\/.*/, /notification\/.*/, /overlay\/.*/], terserOptions: { // Replicate Angular CLI behaviour compress: { global_defs: { ngDevMode: false, ngI18nClosureMode: false, }, }, }, }), ], splitChunks: { cacheGroups: { commons: { test(module, chunks) { return ( module.resource != null && module.resource.includes(`${path.sep}node_modules${path.sep}`) && !module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`) ); }, name: "popup/vendor", chunks: (chunk) => { return chunk.name === "popup/main"; }, }, angular: { test(module, chunks) { return ( module.resource != null && module.resource.includes(`${path.sep}node_modules${path.sep}@angular${path.sep}`) ); }, name: "popup/vendor-angular", chunks: (chunk) => { return chunk.name === "popup/main"; }, }, }, }, }, resolve: { extensions: [".ts", ".js"], symlinks: false, modules: [path.resolve("../../node_modules")], fallback: { assert: false, buffer: require.resolve("buffer/"), util: require.resolve("util/"), url: require.resolve("url/"), fs: false, path: require.resolve("path-browserify"), }, }, output: { filename: "[name].js", chunkFilename: "assets/[name].js", webassemblyModuleFilename: "assets/[modulehash].wasm", path: path.resolve(__dirname, "build"), clean: true, }, module: { noParse: /argon2(-simd)?\.wasm$/, rules: moduleRules, }, experiments: { asyncWebAssembly: true, }, plugins: plugins, }; /** * @type {import("webpack").Configuration[]} */ const configs = []; if (manifestVersion == 2) { mainConfig.optimization.splitChunks.cacheGroups.commons2 = { test: /[\\/]node_modules[\\/]/, name: "vendor", chunks: (chunk) => { return chunk.name === "background"; }, }; // Manifest V2 uses Background Pages which requires a html page. mainConfig.plugins.push( new HtmlWebpackPlugin({ template: "./src/platform/background.html", filename: "background.html", chunks: ["vendor", "background"], }), ); // Manifest V2 background pages can be run through the regular build pipeline. // Since it's a standard webpage. mainConfig.entry.background = "./src/platform/background.ts"; mainConfig.entry["content/lp-suppress-import-download-script-append-mv2"] = "./src/tools/content/lp-suppress-import-download-script-append.mv2.ts"; mainConfig.entry["content/fido2-page-script-append-mv2"] = "./src/autofill/fido2/content/fido2-page-script-append.mv2.ts"; mainConfig.entry["content/fido2-page-script-delay-append-mv2"] = "./src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts"; configs.push(mainConfig); } else { // Manifest v3 needs an extra helper for utilities in the content script. // The javascript output of this should be added to manifest.v3.json mainConfig.entry["content/misc-utils"] = "./src/autofill/content/misc-utils.ts"; mainConfig.entry["offscreen-document/offscreen-document"] = "./src/platform/offscreen-document/offscreen-document.ts"; mainConfig.plugins.push( new HtmlWebpackPlugin({ template: "./src/platform/offscreen-document/index.html", filename: "offscreen-document/index.html", chunks: ["offscreen-document/offscreen-document"], }), ); /** * @type {import("webpack").Configuration} */ const backgroundConfig = { name: "background", mode: ENV, devtool: false, entry: "./src/platform/background.ts", target: "webworker", output: { filename: "background.js", path: path.resolve(__dirname, "build"), }, module: { rules: [ { test: /\.tsx?$/, loader: "ts-loader", }, { test: /argon2(-simd)?\.wasm$/, loader: "base64-loader", type: "javascript/auto", }, ], noParse: /argon2(-simd)?\.wasm$/, }, experiments: { asyncWebAssembly: true, }, resolve: { extensions: [".ts", ".js"], symlinks: false, modules: [path.resolve("../../node_modules")], plugins: [new TsconfigPathsPlugin()], fallback: { fs: false, path: require.resolve("path-browserify"), }, }, dependencies: ["main"], plugins: [...requiredPlugins], }; // Safari's desktop build process requires a background.html and vendor.js file to exist // within the root of the extension. This is a workaround to allow us to build Safari // for manifest v2 and v3 without modifying the desktop project structure. if (browser === "safari") { backgroundConfig.plugins.push( new CopyWebpackPlugin({ patterns: [ { from: "./src/safari/mv3/fake-background.html", to: "background.html" }, { from: "./src/safari/mv3/fake-vendor.js", to: "vendor.js" }, ], }), ); } configs.push(mainConfig); configs.push(backgroundConfig); } module.exports = configs;