Skip to content

✨ MV3 + other misc improvements/fixes #386

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 9 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 36 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,41 @@ A Webpack plugin to automatically reload browser extensions during development.
<br>
<br>
</div>

[![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/)
[![NPM Downloads](https://img.shields.io/npm/dt/webpack-ext-reloader.svg)](https://www.npmjs.com/package/webpack-ext-reloader)

## Installing

npm
For npm:

```bash
npm install webpack-ext-reloader --save-dev
```

yarn
For yarn:

```bash
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');
Expand All @@ -58,7 +57,7 @@ plugins: [
]
```

You can point to your `manifest.json file`...
You can point to your `manifest.json` file...

```js
plugins: [
Expand All @@ -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
Expand All @@ -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).
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion src/messages/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
);
11 changes: 7 additions & 4 deletions src/middleware/wer-middleware.raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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 */
Expand Down
13 changes: 7 additions & 6 deletions src/utils/manifest.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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) =>
Expand All @@ -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),
),
)
Expand Down
22 changes: 4 additions & 18 deletions typings/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ declare interface IMiddlewareTemplateParams {
reloadPage: boolean;
}

declare type InjectMiddleware = (
assets: Record<string, any>,
chunks: Set<any>,
) => Record<string, any>;
declare type InjectMiddleware = (assets: Record<string, any>, chunks: Set<any>) => Record<string, any>;

declare type MiddlewareInjector = (
{ background, contentScript, extensionPage }: IEntriesOption,
Expand All @@ -22,10 +19,7 @@ declare type MiddlewareInjector = (

declare type Triggerer = (onlyPageChanged: boolean) => Promise<any>;

declare type TriggererFactory = (
port: number,
reloadPage: boolean,
) => Triggerer;
declare type TriggererFactory = (port: number, reloadPage: boolean) => Triggerer;

declare type VersionPair = [number | undefined, number | undefined];

Expand All @@ -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[];
Expand All @@ -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[];
Expand Down
10 changes: 8 additions & 2 deletions typings/webpack-ext-reloader.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}