diff --git a/README.md b/README.md index 57d3f3c2..7d9606d8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A Webpack plugin to automatically reload browser extensions during development.

- + [![npm version](https://img.shields.io/npm/v/webpack-ext-reloader)](https://www.npmjs.com/package/webpack-ext-reloader) [![Test Status](https://github.com/SimplifyJobs/webpack-ext-reloader/workflows/tests/badge.svg)](https://github.com/SimplifyJobs/webpack-ext-reloader/actions?query=branch%3Amaster) [![Known Vulnerabilities](https://snyk.io/test/github/SimplifyJobs/webpack-ext-reloader/badge.svg)](https://snyk.io/test/github/SimplifyJobs/webpack-ext-reloader/) @@ -17,13 +17,13 @@ A Webpack plugin to automatically reload browser extensions during development. ## Installing -npm +For npm: ```bash npm install webpack-ext-reloader --save-dev ``` -yarn +For yarn: ```bash yarn add webpack-ext-reloader --dev @@ -31,20 +31,19 @@ yarn add webpack-ext-reloader --dev ## What is this? -This is a webpack plugin that allows you to bring hot reloading functionality to WebExtensions, essentially `webpack-dev-server`, but for (WebExtensions)[https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions]. +This is a webpack plugin that brings hot reloading functionality to WebExtensions, essentially resembling `webpack-dev-server` but for [WebExtensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions). -This is a fork from [`webpack-extension-reloader`](https://github.com/rubenspgcavalcante/webpack-extension-reloader), maintained and updated by the team here at Simplify. The goal here is to continue to support the latest version of webpack (`webpack-extension-reloader` only supports webpack v4) while adding new improvements (i.e. HMR). +This project is a fork of [`webpack-extension-reloader`](https://github.com/rubenspgcavalcante/webpack-extension-reloader), maintained and updated by the team at Simplify. The goal is to continue supporting the latest version of webpack (`webpack-extension-reloader` only supports webpack v4) while introducing new improvements, such as HMR. ![](.github/sample-gif.gif) -**Note**: This plugin doesn't support [**Hot Module Replacement (HMR)**](https://webpack.js.org/concepts/hot-module-replacement/) yet. +**Note**: This plugin does not support [**Hot Module Replacement (HMR)**](https://webpack.js.org/concepts/hot-module-replacement/) yet. ## How to use ### Using as a plugin -Add `webpack-ext-reloader` to the plugins section of your webpack configuration file. Note that this plugin don't outputs the manifest (at most read it to gather information). -For outputing not only the `manifest.json` but other static files too, use `CopyWebpackPlugin`. +Add `webpack-ext-reloader` to the plugins section of your webpack configuration file. This plugin does not output the manifest; it might read it for information at most. For outputting not only the `manifest.json` but other static files as well, use `CopyWebpackPlugin`. ```js const ExtReloader = require('webpack-ext-reloader'); @@ -58,7 +57,7 @@ plugins: [ ] ``` -You can point to your `manifest.json file`... +You can point to your `manifest.json` file... ```js plugins: [ @@ -81,7 +80,7 @@ module.exports = { background: './my-background-script.js', popup: 'popup', }, - //... + // ... plugins: [ new ExtReloader({ port: 9090, // Which port use to create the server @@ -97,89 +96,86 @@ module.exports = { } ``` -**Note I**: `entry` or `manifest` are needed. If both are given, entry will override the information comming from `manifest.json`. If none are given the default `entry` values (see above) are used. +**Note I**: Either `entry` or `manifest` is needed. If both are provided, the `entry` will override the information from `manifest.json`. If neither is provided, the default `entry` values (as shown above) are used. -And then just run your application with Webpack in watch mode: +Run your application with Webpack in watch mode: ```bash NODE_ENV=development webpack --config myconfig.js --mode=development --watch ``` -**Note II**: You need to set `--mode=development` to activate the plugin (only if you didn't set on the webpack.config.js already) then you need to run with `--watch`, as the plugin will be able to sign the extension only if webpack triggers the rebuild (again, only if you didn't set on webpack.config). +**Note II**: You need to set `--mode=development` to activate the plugin. If you didn't set it in the webpack.config.js already, you need to run with `--watch` since the plugin will be able to sign the extension only if webpack triggers the rebuild. ### Multiple Content Script and Extension Page support -If you use more than one content script or extension page in your extension, like: +If your extension uses more than one content script or extension page, like: ```js entry: { 'my-first-content-script': './my-first-content-script.js', 'my-second-content-script': './my-second-content-script.js', - // and so on ... background: './my-background-script.js', 'popup': './popup.js', 'options': './options.js', - // and so on ... } ``` -You can use the `entries.contentScript` or `entries.extensionPage` options as an array: +Use the `entries.contentScript` or `entries.extensionPage` options as an array: ```js plugins: [ new ExtReloader({ entries: { - contentScript: ['my-first-content-script', 'my-second-content-script', /* and so on ... */], + contentScript: ['my-first-content-script', 'my-second-content-script'], background: 'background', - extensionPage: ['popup', 'options', /* and so on ... */], + extensionPage: ['popup', 'options'], } }), - // ... ] ``` ### CLI -If you don't want all the plugin setup, you can just use the client that comes with the package. -You can use by installing the package globally, or directly using `npx`: +If you'd rather skip the plugin setup, you can use the client included with the package. Install the package globally or use `npx`: ```bash npx webpack-ext-reloader ``` -If you run directly, it will use the default configurations, but if you want to customize -you can call it with the following options: +If run directly, it will use the default configurations. But if you'd like customization: ```bash npx webpack-ext-reloader --config wb.config.js --port 9080 --no-page-reload --content-script my-content.js --background bg.js --extension-page popup.js ``` -If you have **multiple** content scripts or extension pages, just use comma (with no spaces) while passing the option +For **multiple** content scripts or extension pages, separate the options with commas (without spaces): ```bash -npx webpack-ext-reloader --content-script my-first-content.js,my-second-content.js,my-third-content.js --extension-page popup.js,options.js +npx webpack-ext-reloader --content-script my-first-content.js,my-second-content.js --extension-page popup.js,options.js ``` ### Client options -| name | default | description | -| ---------------- | ----------------- | ----------------------------------------------------------------- | -| --help | | Shows this help | -| --config | webpack.config.js | The webpack configuration file path | -| --port | 9090 | The port to run the server | -| --manifest | | The path to the extension **manifest.json** file | -| --content-script | content-script | The **entry/entries** name(s) for the content script(s) | -| --background | background | The **entry** name for the background script | -| --extension-page | popup | The **entry/entries** name(s) for the extension pages(s) | -| --no-page-reload | | Disable the auto reloading of all **pages** which runs the plugin | +| Name | Default | Description | +| ----------------- | ----------------- | ----------------------------------------------------------------- | +| --help | | Shows help | +| --config | webpack.config.js | Path to the webpack configuration file | +| --port | 9090 | Port to run the server on | +| --manifest | | Path to the extension's **manifest.json** file | +| --content-script | content-script | **Entry/entries** name(s) for the content script(s) | +| --background | background | **Entry** name for the background script | +| --extension-page | popup | **Entry/entries** name(s) for the extension page(s) | +| --no-page-reload | | Disable auto-reloading of all **pages** running the plugin | -Every time content or background scripts are modified, the extension is reloaded :) -**Note:** the plugin only works on **development** mode, so don't forget to set the NODE_ENV before run the command above +Whenever content or background scripts are modified, the extension will reload. +**Note**: This plugin only works in **development** mode. Remember to set the NODE_ENV before running the commands above. ### Contributing -Please before opening any **issue** or **pull request** check the [contribution guide](/.github/CONTRIBUTING.MD). +Before opening any **issue** or **pull request**, please refer to the [contribution guide](/.github/CONTRIBUTING.MD). ### License -This project has been forked from [rubenspgcavalcante/webpack-extension-reloader](https://github.com/rubenspgcavalcante/webpack-extension-reloader), which is licensed under the [MIT license](https://github.com/rubenspgcavalcante/webpack-extension-reloader/blob/master/LICENSE). All changes made in this fork have been licensed via the [MIT license](https://github.com/SimplifyJobs/webpack-ext-reloader/blob/master/LICENSE). +This project is a fork from [rubenspgcavalcante/webpack-extension-reloader](https://github.com/rubenspgcavalcante/webpack-extension-reloader), which is licensed under the [MIT license](https://github.com/rubenspgcavalcante/webpack-extension-reloader/blob/master/LICENSE + +). All modifications made in this fork are also licensed under the [MIT license](https://github.com/SimplifyJobs/webpack-ext-reloader/blob/master/LICENSE). diff --git a/package-lock.json b/package-lock.json index ddb209ba..fb93772d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "clean-webpack-plugin": "^4.0.0", "colors": "^1.4.0", "cross-env": "^7.0.3", + "json5": "^2.2.3", "lodash": "^4.17.21", "minimist": "^1.2.6", "useragent": "^2.3.0", @@ -6317,7 +6318,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, diff --git a/package.json b/package.json index fe0cb1a5..7ec26081 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,10 @@ ] }, "scripts": { - "build": "NODE_ENV=production webpack", - "test": "NODE_ENV=test webpack && mocha dist/tests.js", - "analyze": "NODE_ENV=production webpack --env.analyze", - "start:dev": "NODE_ENV=development webpack --watch", + "build": "cross-env NODE_ENV=production webpack", + "test": "cross-env NODE_ENV=test webpack && mocha dist/tests.js", + "analyze": "cross-env NODE_ENV=production webpack --env.analyze", + "start:dev": "cross-env NODE_ENV=development webpack --watch", "start:sample:chrome": "cross-env NODE_ENV=development cross-env TARGET_BROWSER=chrome webpack --config sample/webpack.plugin.js --watch", "start:sample:firefox": "cross-env NODE_ENV=development cross-env TARGET_BROWSER=firefox webpack --config sample/webpack.plugin.js --watch", "prepublishOnly": "npm run build", @@ -56,6 +56,7 @@ "clean-webpack-plugin": "^4.0.0", "colors": "^1.4.0", "cross-env": "^7.0.3", + "json5": "^2.2.3", "lodash": "^4.17.21", "minimist": "^1.2.6", "useragent": "^2.3.0", diff --git a/src/messages/errors.ts b/src/messages/errors.ts index ded58890..5c48dfc8 100644 --- a/src/messages/errors.ts +++ b/src/messages/errors.ts @@ -10,4 +10,8 @@ the provided on 'manifest.json' or 'entry.background' \ option of the plugin", ); -export const bgScriptManifestRequiredMsg = new Message(ERROR, 2, "Background script on manifest is required"); +export const bgScriptManifestRequiredMsg = new Message( + ERROR, + 2, + "Background script or service worker on manifest is required", +); diff --git a/src/middleware/wer-middleware.raw.ts b/src/middleware/wer-middleware.raw.ts index 2f6089ab..9f16dd8a 100644 --- a/src/middleware/wer-middleware.raw.ts +++ b/src/middleware/wer-middleware.raw.ts @@ -5,7 +5,7 @@ /* This will be converted into a lodash templ., any */ /* external argument must be provided using it */ /* -------------------------------------------------- */ -(function(window) { +(function() { const injectionContext = this || window || {browser: null}; @@ -71,7 +71,9 @@ if (type === SIGN_CHANGE && (!payload || !payload.onlyPageChanged)) { tabs.query({ status: "complete" }).then(loadedTabs => { loadedTabs.forEach( - tab => tab.id && tabs.sendMessage(tab.id, { type: SIGN_RELOAD }), + // in MV3 tabs.sendMessage returns a Promise and we need to catch the errors + // https://groups.google.com/a/chromium.org/g/chromium-extensions/c/st_Nh7j3908/m/1muOgSX5AwAJ + tab => tab.id && tabs.sendMessage(tab.id, { type: SIGN_RELOAD })?.catch(() => null), ); socket.send( JSON.stringify({ @@ -137,9 +139,10 @@ // ======================= Bootstraps the middleware =========================== // runtime.reload - ? extension.getBackgroundPage() === window ? backgroundWorker(new WebSocket(wsHost)) : extensionPageWorker() + // in MV3 background service workers don't have access to the DOM + ? (typeof window === 'undefined' || extension.getBackgroundPage() === window) ? backgroundWorker(new WebSocket(wsHost)) : extensionPageWorker() : contentScriptWorker(); -})(window); +})(); /* ----------------------------------------------- */ /* End of Webpack Hot Extension Middleware */ diff --git a/src/utils/manifest.ts b/src/utils/manifest.ts index b3ed89f2..81e5ced8 100644 --- a/src/utils/manifest.ts +++ b/src/utils/manifest.ts @@ -1,5 +1,6 @@ import { readFileSync } from "fs"; import { flatMapDeep } from "lodash"; +import JSON5 from "json5"; import { Compiler, Entry } from "webpack"; import { bgScriptEntryErrorMsg, bgScriptManifestRequiredMsg } from "../messages/errors"; @@ -8,19 +9,19 @@ export function extractEntries( manifestPath: string, webpackOutput: Compiler["options"]["output"] = {}, ): IEntriesOption { - const manifestJson = JSON.parse(readFileSync(manifestPath).toString()) as IExtensionManifest; - const { background, content_scripts } = manifestJson; + const manifestJson = JSON5.parse(readFileSync(manifestPath).toString()) as IExtensionManifest; + const { background, content_scripts: contentScripts } = manifestJson; const { filename } = webpackOutput; if (!filename) { throw new Error("Please specify the `output.filename` in your webpack config."); } - if (!background?.scripts) { + if (!(background?.scripts || background?.service_worker)) { throw new TypeError(bgScriptManifestRequiredMsg.get()); } - const bgScriptFileNames = background.scripts; + const bgScriptFileNames = background.service_worker ? [background.service_worker] : background.scripts ?? []; const toRemove = (filename as string).replace("[name]", ""); const bgWebpackEntry = Object.keys(webpackEntry).find((entryName) => @@ -31,9 +32,9 @@ export function extractEntries( throw new TypeError(bgScriptEntryErrorMsg.get()); } - const contentEntries: unknown = content_scripts + const contentEntries: unknown = contentScripts ? flatMapDeep(Object.keys(webpackEntry), (entryName) => - content_scripts.map(({ js }) => + contentScripts.map(({ js }) => js.map((contentItem) => contentItem.replace(toRemove, "")).filter((contentItem) => contentItem === entryName), ), ) diff --git a/typings/declarations.d.ts b/typings/declarations.d.ts index eb3bc18c..e088f087 100644 --- a/typings/declarations.d.ts +++ b/typings/declarations.d.ts @@ -10,10 +10,7 @@ declare interface IMiddlewareTemplateParams { reloadPage: boolean; } -declare type InjectMiddleware = ( - assets: Record, - chunks: Set, -) => Record; +declare type InjectMiddleware = (assets: Record, chunks: Set) => Record; declare type MiddlewareInjector = ( { background, contentScript, extensionPage }: IEntriesOption, @@ -22,10 +19,7 @@ declare type MiddlewareInjector = ( declare type Triggerer = (onlyPageChanged: boolean) => Promise; -declare type TriggererFactory = ( - port: number, - reloadPage: boolean, -) => Triggerer; +declare type TriggererFactory = (port: number, reloadPage: boolean) => Triggerer; declare type VersionPair = [number | undefined, number | undefined]; @@ -45,13 +39,7 @@ declare type LOG_WARN = 3; declare type LOG_ERROR = 4; declare type LOG_DEBUG = 5; -declare type LOG_LEVEL = - | LOG_NONE - | LOG_LOG - | LOG_INFO - | LOG_WARN - | LOG_ERROR - | LOG_DEBUG; +declare type LOG_LEVEL = LOG_NONE | LOG_LOG | LOG_INFO | LOG_WARN | LOG_ERROR | LOG_DEBUG; declare interface IWebpackChunk { files: string[]; @@ -73,13 +61,11 @@ declare interface IExtensionManifest { background?: { page?: string; scripts?: string[]; + service_worker?: string; }; icons?: { [key: string]: string; }; - browser_action?: { - default_popup: string; - }; content_scripts?: [ { matches: string[]; diff --git a/typings/webpack-ext-reloader.d.ts b/typings/webpack-ext-reloader.d.ts index baf886f9..3fd6b023 100644 --- a/typings/webpack-ext-reloader.d.ts +++ b/typings/webpack-ext-reloader.d.ts @@ -11,9 +11,15 @@ export interface IExtensionReloaderInstance { apply(compiler: Compiler): void; } -export type ExtensionReloader = new (options?: IPluginOptions) => IExtensionReloaderInstance; +export declare class ExtensionReloader implements IExtensionReloaderInstance { + constructor(options?: IPluginOptions); -declare module "webpack-extension-reloader" { + apply(compiler: Compiler): void; +} + +export default ExtensionReloader; + +declare module "webpack-ext-reloader" { export default ExtensionReloader; export = IExtensionReloaderInstance; }