Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Nov 24, 2024
1 parent eeee551 commit ba70b1e
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 97 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 2.0.0

- Minimal Node.js requirement changed from 14.17.16 to 18.20.5
- Deprecated option `BackgroundOptions.entry` is removed. Use `pageEntry` and/or `serviceWorkerEntry` instead.
- Deprecated option `BackgroundOptions.manifest` is removed.
- Option `noWarningDynamicEntry` has been renamed to `noDynamicEntryWarning`.

## 1.1.2

- Support main world content script to be bundled. Also added a guide and example for this.
Expand Down
197 changes: 143 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,161 @@
- TODO: let's set output.environment.dynamicImport to `true` by default.
- TODO: let's set hmr file names by default.

# webpack-target-webextension

[![npm-version](https://img.shields.io/npm/v/webpack-target-webextension.svg)](https://www.npmjs.com/package/webpack-target-webextension)

WebExtension Plugin for Webpack 5. Supports code-splitting and Hot Module Reload.
This webpack 5 plugin provides reasonable preset and fixes things don't work for a WebExtension.

Looking for webpack 4 support? See 0.2.1. [Document for 0.2.1](https://github.com/awesome-webextension/webpack-target-webextension/tree/a738d2ce96795cd032eb0ad3d6b6be74376550db).

The list of things we fixed in this plugin:

- Code splitting (chunk loader)
- Hot Module Reload
- Public path

Looking for webpack 4 support? Please install 0.2.1. [Document for 0.2.1](https://github.com/awesome-webextension/webpack-target-webextension/tree/a738d2ce96795cd032eb0ad3d6b6be74376550db).
## A quick guide

## Installation
This guide is a reasonable default for a modern WebExtension project.

Choose the package manager you're using.
A minimal config should be like this:

> webpack.config.js
```js
module.exports = {
context: __dirname,
entry: {
background: join(__dirname, './src/background/index.js'),
content: join(__dirname, './src/content-script/index.js'),
options: join(__dirname, './src/options-page/index.js'),
},
output: {
path: join(__dirname, './dist'),
publicPath: '/dist/',
},
plugins: [
new HtmlRspackPlugin({ filename: 'options.html', chunks: ['options'] }),
new WebExtension({
background: { pageEntry: 'background' },
}),
],
}
```

```bash
yarn add -D webpack-target-webextension
npm install -D webpack-target-webextension
pnpm install -D webpack-target-webextension
> manifest.json
```json
{
"manifest_version": 3,
"name": "Your extension",
"version": "1.0.0",
"background": {
"service_worker": "./dist/background.js"
},
// ⚠ Those files can be accessed by normal web sites too.
"web_accessible_resources": [
{
"resources": ["/dist/*.js"],
"matches": ["<all_urls>"]
},
// only needed for development (hot module reload)
{
"resources": ["/dist/hot/*.js", "/dist/hot/*.json"],
"matches": ["<all_urls>"]
}
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["./dist/content.js"]
}
],
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"],
"options_ui": {
"page": "options.html",
"open_in_tab": true
}
}
```

You can also refer to [./examples/react-hmr](./examples/react-hmr) which is a working project.

## Features & How to configure

### Code splitting

#### Content script

You need to configure at least one of the following
to make code-splitting work for the content script.
To load an async chunk in content scripts, you need to configure the chunk loader.

1. dynamic `import()`
- Requires [Firefox 89](https://bugzilla.mozilla.org/show_bug.cgi?id=1536094) and
Chrome 63(?).
- Set `output.environment.dynamicImport` to `true` in your webpack config.
- You must set `web_accessible_resources` to your JS files in your `manifest.json`.
- ⚠ Normal web sites can access your resources in `web_accessible_resources` too.
- Example: [./examples/code-splitting-way-1](./examples/code-splitting-way-1)
2. via `chrome.tabs.executeScript` (Manifest V2)
- Requires [`options.background`](#options-background) to be configured
and [`options.background.classicLoader`](#options-background) is not **false** (defaults to **true**).
- Example: [./examples/code-splitting-way-2](./examples/code-splitting-way-2)
3. via `chrome.scripting.executeScript` (Manifest V3)
- It will fallback to _method 2_ when there is no `chrome.scripting`.
- Requires `"scripting"` permission in the `manifest.json`.
- Requires [`options.background`](#options-background) to be configured
and [`options.background.classicLoader`](#options-background) is not **false** (defaults to **true**).
- Example: [./examples/code-splitting-way-3](./examples/code-splitting-way-3)
##### (default) dynamic `import()`

##### Main world content script
Compability: at least [Firefox 89](https://bugzilla.mozilla.org/show_bug.cgi?id=1536094) and Chrome 63.

You have to configure the content script in the first way mentioned above (dynamic `import()`).
You can set [`output.environment.dynamicImport`](https://webpack.js.org/configuration/output/#outputenvironment) to `false` to disable this loader.

You also need to set `output.publicPath` manually (like `chrome-extension://jknoiechepeohmcaoeehjaecapdplcia/dist/`, the full URL is necessary).
You MUST add your JS files to `web_accessible_resources` in the `manifest.json`, otherwise the `import()` will fail.

See Example: [./examples/code-splitting-main-world](./examples/code-splitting-main-world).
> [!WARNING]
> Adding files to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources) allows normal websites to access them.
#### Background worker (Manifest V3)
Example: [./examples/code-splitting-way-1](./examples/code-splitting-way-1)

> ⚠ Not working with `"background.type"` set to `"module"` (native ES Module service worker). Tracking issue: [#24](https://github.com/awesome-webextension/webpack-target-webextension/issues/24)
#### [`chrome.tabs.executeScript`](https://developer.chrome.com/docs/extensions/reference/api/tabs#method-executeScript) (Manifest V2 only)

Support code-splitting out of the box,
but it will load **all** chunks (without executing them).
- This method requires [`options.background`](#options-background) to be configured.
- This method requires [`options.background.classicLoader`](#options-background) is not **false** (defaults to **true**).

See <https://bugs.chromium.org/p/chromium/issues/detail?id=1198822> for the reason.
Example: [./examples/code-splitting-way-2](./examples/code-splitting-way-2)

This fix can be turned off by setting
[`options.background.eagerChunkLoading`](#options-background) to **false**.
#### [`chrome.scripting.executeScript`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript) (Manifest V3 only)

- This method will fallback to `chrome.tabs.executeScript` when there is no `chrome.scripting`.
- This mehtod requires `"scripting"` permission in the `manifest.json`.
- This method requires [`options.background`](#options-background) to be configured.
- This method requires [`options.background.classicLoader`](#options-background) is not **false** (defaults to **true**).

Example: [./examples/code-splitting-way-3](./examples/code-splitting-way-3)

##### [Main world](https://developer.chrome.com/docs/extensions/reference/api/scripting#type-ExecutionWorld) content script

You need to configure the content script by dynamic `import()`. You also need to set [`output.publicPath`](https://webpack.js.org/configuration/output/#outputpublicpath) manually (like `chrome-extension://jknoiechepeohmcaoeehjaecapdplcia/dist/`, the full URL is necessary).

Example: [./examples/code-splitting-main-world](./examples/code-splitting-main-world).

#### Background worker (Manifest V3)

> [!WARNING]
> This plugin does not work with [`"background.type"`](https://developer.chrome.com/docs/extensions/reference/manifest/background) in `manifest.json` set to `"module"` (native ES Module service worker).
> Tracking issue: [#24](https://github.com/awesome-webextenson/webpack-target-webextension/issues/24)
Code splitting is supported for background service worker, but it will **load all chunks** initially.
See <https://bugs.chromium.org/p/chromium/issues/detail?id=1198822>.

This fix can be turned off by set [`options.background.eagerChunkLoading`](#options-background) to **false**.
If you turned of this fix, loading an async chunk will be a runtime error.

Example: [./examples/code-splitting-way-3](./examples/code-splitting-way-3)

### Hot Module Reload

> ⚠ It's not possible to support HMR for Manifest V3 background worker before
> this bug is fixed. <https://bugs.chromium.org/p/chromium/issues/detail?id=1198822>
> [!WARNING]
> It's not possible to support HMR for Manifest V3 background worker.
> See <https://bugs.chromium.org/p/chromium/issues/detail?id=1198822>
> [!WARNING]
> The HMR WebSocket server might be blocked by the Content Security Policy and prevent the reset of the code to be executed.
> Please disable hmr if you encountered this problem.
> ⚠ In content script of Firefox, the HMR WebSocket server might be blocked by the Content Security Policy and prevent the reset of the code to be executed. Please disable hmr if you encountered this problem.
This plugin fixes Hot Module Reload and provide reasonable defaults for DevServer.
Please set `devServer.hot` to `false` to disable HMR support.

This plugin works with Hot Module Reload.
Please set `devServer.hot` to `"only"` (or `true`) to enable it.
It will modify your `devServer` configuration to adapt to the Web Extension environment.
To disable this behavior, set [`options.hmrConfig`](#options-hmrConfig) to **false**.
To disable this fix, set [`options.hmrConfig`](#options-hmrConfig) to **false**.

You need to add `*.json` to your `web_accessible_resources` in order to download HMR manifest.
You need to add `*.json` to your `web_accessible_resources` in order to make HMR work.

Example: Manifest V2 [./examples/hmr-mv2](./examples/hmr-mv2)

Expand All @@ -87,17 +165,21 @@ Example: Draw UI in the content scripts with React and get React HRM. [./example

### Source map

To use source map based on `eval`, you must use Manifest V2 and have `script-src 'self' 'unsafe-eval';` in your CSP (content security policy).
> [!WARNING]
> No `eval` based source-map is available in Manifest v3.
> [!WARNING]
> DO NOT add `unsafe-eval` to your CSP in production mode!
> ⚠ DO NOT add `unsafe-eval` to your CSP in production mode!
To use source map based on `eval`, you must use Manifest v2 and have `script-src 'self' 'unsafe-eval';` in your [CSP (content security policy)](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_security_policy).

### Public path

This plugin supports the public path when `output.path` is set.
This plugin fixes public path whether `output.path` is set or not.

## <a id="options"></a>Options
## Options

### <a id="options-background"></a>`options`.`background`
### options.background

Example:

Expand Down Expand Up @@ -158,7 +240,7 @@ export interface BackgroundOptions {
}
```

### <a id="options-hmrConfig"></a>`options`.`hmrConfig`
### options.hmrConfig

Default value: **true**

Expand All @@ -168,6 +250,13 @@ Example:
new WebExtensionPlugin({ hmrConfig: false })
```

### <a id="options-weakRuntimeCheck"></a>`options`.`weakRuntimeCheck`
### options.weakRuntimeCheck

If you encountered compatibility issue with any of the following plugin, you can enable this option:

- [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin)
- [HtmlWebpackPlugin](https://github.com/jantimon/html-webpack-plugin)

## rspack limitation

If you need to use this plugin with `mini-css-extract-plugin` or `HtmlWebpackPlugin`, please enable this option.
rspack is missing
4 changes: 1 addition & 3 deletions examples/code-splitting-way-3/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ const config = {
plugins: [
new WebExtension({
background: {
entry: 'background',
// !! Add this to support manifest v3
manifest: 3,
serviceWorkerEntry: 'background',
},
}),
],
Expand Down
4 changes: 1 addition & 3 deletions examples/hmr-mv3/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ const config = {
plugins: [
new WebExtension({
background: {
entry: 'background',
// !! Add this to support manifest v3
manifest: 3,
serviceWorkerEntry: 'background',
},
}),
],
Expand Down
1 change: 0 additions & 1 deletion examples/react-hmr/rspack.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export default (_, env) => {
}),
new WebExtension({
background: { pageEntry: 'background' },
// Remove this if you're not using mini-css-extract-plugin.
}),
isProduction ? null : new RefreshPlugin(),
].filter(Boolean),
Expand Down
21 changes: 5 additions & 16 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import { Compiler } from 'webpack'

export interface BackgroundOptions {
// TODO: rename to noDynamicEntryWarning
/** Undocumented. */
noWarningDynamicEntry?: boolean
noDynamicEntryWarning?: boolean
/**
* The entry point of the background scripts
* in your webpack config.
* @deprecated
* Use pageEntry and serviceWorkerEntry instead.
* Use pageEntry and/or serviceWorkerEntry instead.
*/
entry?: string
/**
* Using Manifest V2 or V3.
*
* If using Manifest V3,
* the entry you provided will be packed as a Worker.
*
* @defaultValue 2
* @deprecated
*/
manifest?: 2 | 3
entry?: never
/** @deprecated */
manifest?: never
/**
* The entry point of the background page.
*/
Expand Down
4 changes: 2 additions & 2 deletions lib/webpack5/ServiceWorkerPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ module.exports = class ServiceWorkerEntryPlugin {
// @ts-ignore DO NOT add return boolean to this function, this is a BailHook and we don't want to bail.
hook.tap(ServiceWorkerEntryPlugin.name, (context, entries) => {
if (typeof entries === 'function') {
if (this.options.noWarningDynamicEntry) return
if (this.options.noDynamicEntryWarning) return
console.warn(`[webpack-extension-target] Dynamic entry points not supported yet.
You must manually set the chuck loading of entry point ${this.entry} to "import-scripts".
See https://webpack.js.org/configuration/entry-context/#entry-descriptor
Set background.noWarningDynamicEntry to true to disable this warning.
Set background.noDynamicEntryWarning to true to disable this warning.
`)
}
/** @type {import('@rspack/core').EntryDescriptionNormalized} */
Expand Down
24 changes: 8 additions & 16 deletions lib/webpack5/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
// @ts-check

// Make dynamic import & chunk splitting works.
const ChuckLoaderRuntimePlugin = require('./ChunkLoader')
const ChuckLoaderRuntimePlugin = require('./ChunkLoader.js')
// Ban invalid file names in web extension
const NoDangerNamePlugin = require('./NoDangerNamePlugin')
const NoDangerNamePlugin = require('./NoDangerNamePlugin.js')
// Provide support for MV3
const ServiceWorkerEntryPlugin = require('./ServiceWorkerPlugin')
const ServiceWorkerEntryPlugin = require('./ServiceWorkerPlugin.js')
// Automatically tweak HMR server
const HMRDevServerPlugin = require('./HMRDevServer')
const HMRDevServerPlugin = require('./HMRDevServer.js')

module.exports = class WebExtensionPlugin {
/**
* @param {import('../../index.d.ts').WebExtensionPluginOptions} options
*/
constructor(options = {}) {
const { background } = options
if (background && (background.entry || background.manifest)) {
console.warn(`[webpack-extension-target] background.entry and background.manifest has been deprecated.
- background.manifest is no longer needed.
- background.entry should be replaced with background.pageEntry and background.serviceWorkerEntry instead.`)
if (background.pageEntry || background.serviceWorkerEntry) {
throw new Error(
`[webpack-extension-target] Deprecated background.entry and background.manifest cannot be specified with background.pageEntry or background.serviceWorkerEntry.`
)
}
if ('entry' in background || 'manifest' in background) {
throw new Error(
`[webpack-extension-target] background.entry and background.manifest has been removed. Use background.pageEntry and/or background.serviceWorkerEntry instead.`
)
}
this.options = options
}
Expand All @@ -43,9 +38,6 @@ module.exports = class WebExtensionPlugin {
}
new ServiceWorkerEntryPlugin(background, background.serviceWorkerEntry).apply(compiler)
}
if (background.manifest === 3 && background.entry) {
new ServiceWorkerEntryPlugin(background, background.entry).apply(compiler)
}
}
new ChuckLoaderRuntimePlugin(this.options.background, this.options.weakRuntimeCheck).apply(compiler)
new NoDangerNamePlugin().apply(compiler)
Expand Down
Loading

0 comments on commit ba70b1e

Please sign in to comment.