diff --git a/.changeset/healthy-mangos-wait.md b/.changeset/healthy-mangos-wait.md
new file mode 100644
index 000000000..5317c2847
--- /dev/null
+++ b/.changeset/healthy-mangos-wait.md
@@ -0,0 +1,5 @@
+---
+'wmr': minor
+---
+
+Add built-in support for [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) based on browser APIs. Example on how to use Web Workers with WMR: https://wmr.dev/docs/web-workers
diff --git a/docs/public/content/_config.yml b/docs/public/content/_config.yml
index f3de9480a..177fb1dac 100644
--- a/docs/public/content/_config.yml
+++ b/docs/public/content/_config.yml
@@ -8,5 +8,6 @@ collections:
- configuration
- plugins
- prerendering
+ - web-workers
- { heading: 'API' }
- plugin-api
diff --git a/docs/public/content/docs/web-workers.md b/docs/public/content/docs/web-workers.md
new file mode 100644
index 000000000..dc4aa592c
--- /dev/null
+++ b/docs/public/content/docs/web-workers.md
@@ -0,0 +1,42 @@
+---
+nav: Web Workers
+title: 'Web Workers'
+---
+
+[Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) are a way to do threading in JavaScript. It is a simple mean to run work in the background to keep the main thread responsive for UI work.
+
+To use web workers with WMR you can use the [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#web_workers_api) directly.
+
+```js
+// index.js
+const worker = new Worker(new URL('./my.worker.js', import.meta.url));
+
+// Subscribe to messages coming from the worker
+worker.addEventListener('message', e => console.log(e.data));
+
+// Ping worker
+worker.postMessage("Let's go");
+```
+
+WMR relies on the filename to detect workers. That's why it must have the `.worker` suffix in the filename.
+
+```js
+// my.worker.js
+addEventListener('message', () => {
+ // Always answer with "hello"
+ postMessage('hello');
+});
+```
+
+> We highly recommend using [comlink](https://github.com/GoogleChromeLabs/comlink) for working with web workers. It abstracts away the manual message passing that's required to communicate workers.
+
+## ESM Support in Web Workers
+
+Support for module mode so that you can use `import` and `export` statements can be turned on by passing `{ format: 'module' }` to the `Worker` constructor.
+
+```js
+const workerUrl = new URL('./my.worker.js', import.meta.url);
+const worker = new Worker(workerUrl, { type: 'module' });
+```
+
+> Be cautious: ESM is not yet supported in every mainstream browser.
diff --git a/docs/public/content/index.md b/docs/public/content/index.md
index 886c45f31..950affcf4 100644
--- a/docs/public/content/index.md
+++ b/docs/public/content/index.md
@@ -18,6 +18,7 @@ All the features you'd expect and more, from development to production:
🗜 Highly optimized Rollup-based production output (`wmr build`)
📑 Crawls and pre-renders your app's pages to static HTML at build time
🏎 Built-in HTTP2 in dev and prod (`wmr serve --http2`)
+👷 Built-in support for web workers
🔧 Supports [Rollup plugins](/docs/plugins), even in development where Rollup isn't used
diff --git a/packages/wmr/src/lib/plugins.js b/packages/wmr/src/lib/plugins.js
index acf6eea79..474749576 100644
--- a/packages/wmr/src/lib/plugins.js
+++ b/packages/wmr/src/lib/plugins.js
@@ -26,13 +26,26 @@ import { acornDefaultPlugins } from './acorn-default-plugins.js';
import { prefreshPlugin } from '../plugins/preact/prefresh.js';
import { absolutePathPlugin } from '../plugins/absolute-path-plugin.js';
import { lessPlugin } from '../plugins/less-plugin.js';
+import { workerPlugin } from '../plugins/worker-plugin.js';
/**
- * @param {import("wmr").Options} options
+ * @param {import("wmr").Options & { runtimeEnv: "default" | "worker"}} options
* @returns {import("wmr").Plugin[]}
*/
export function getPlugins(options) {
- const { plugins, publicPath, alias, root, env, minify, mode, sourcemap, features, visualize } = options;
+ const {
+ plugins,
+ publicPath,
+ alias,
+ root,
+ env,
+ minify,
+ mode,
+ runtimeEnv = 'default',
+ sourcemap,
+ features,
+ visualize
+ } = options;
// Plugins are pre-sorted
let split = plugins.findIndex(p => p.enforce === 'post');
@@ -41,6 +54,8 @@ export function getPlugins(options) {
const production = mode === 'build';
const mergedAssets = new Set();
+ const isWorker = runtimeEnv === 'worker';
+
return [
acornDefaultPlugins(),
...plugins.slice(0, split),
@@ -74,13 +89,15 @@ export function getPlugins(options) {
env,
NODE_ENV: production ? 'production' : 'development'
}),
+ // Nested workers are not supported at the moment
+ !isWorker && workerPlugin(options),
htmPlugin({ production, sourcemap: options.sourcemap }),
wmrPlugin({ hot: !production, sourcemap: options.sourcemap }),
fastCjsPlugin({
// Only transpile CommonJS in node_modules and explicit .cjs files:
include: /(^npm\/|[/\\]node_modules[/\\]|\.cjs$)/
}),
- production && npmPlugin({ external: false }),
+ (production || isWorker) && npmPlugin({ external: false }),
resolveExtensionsPlugin({
extensions: ['.ts', '.tsx', '.js', '.cjs'],
index: true
diff --git a/packages/wmr/src/plugins/wmr/client.js b/packages/wmr/src/plugins/wmr/client.js
index 6483801b7..58d6a89ac 100644
--- a/packages/wmr/src/plugins/wmr/client.js
+++ b/packages/wmr/src/plugins/wmr/client.js
@@ -46,7 +46,9 @@ function handleMessage(e) {
const data = JSON.parse(e.data);
switch (data.type) {
case 'reload':
- location.reload();
+ if (HAS_DOM) {
+ location.reload();
+ }
break;
case 'update':
if (errorOverlay) {
@@ -64,7 +66,10 @@ function handleMessage(e) {
return;
}
} else if (url.replace(URL_SUFFIX, '') === resolve(location.pathname).replace(URL_SUFFIX, '')) {
- return location.reload();
+ if (HAS_DOM) {
+ location.reload();
+ }
+ return;
} else {
if (!HAS_DOM) return;
for (const el of document.querySelectorAll('[src],[href]')) {
diff --git a/packages/wmr/src/plugins/wmr/plugin.js b/packages/wmr/src/plugins/wmr/plugin.js
index d57cfbb65..2c2238709 100644
--- a/packages/wmr/src/plugins/wmr/plugin.js
+++ b/packages/wmr/src/plugins/wmr/plugin.js
@@ -43,7 +43,7 @@ export default function wmrPlugin({ hot = true, sourcemap } = {}) {
},
transform(code, id) {
const ch = id[0];
- if (ch === '\0' || !/\.[tj]sx?$/.test(id)) return;
+ if (ch === '\0' || !/\.[tj]sx?$/.test(id) || /\.worker\.[tj]sx?$/.test(id)) return;
let hasHot = /(import\.meta\.hot|\$IMPORT_META_HOT\$)/.test(code);
let before = '';
let after = '';
diff --git a/packages/wmr/src/plugins/worker-plugin.js b/packages/wmr/src/plugins/worker-plugin.js
new file mode 100644
index 000000000..bdd2d46fa
--- /dev/null
+++ b/packages/wmr/src/plugins/worker-plugin.js
@@ -0,0 +1,136 @@
+import MagicString from 'magic-string';
+import * as rollup from 'rollup';
+import path from 'path';
+import { getPlugins } from '../lib/plugins.js';
+import * as kl from 'kolorist';
+
+/**
+ * @param {import("wmr").Options} options
+ * @returns {import('rollup').Plugin}
+ */
+export function workerPlugin(options) {
+ const plugins = getPlugins({ ...options, runtimeEnv: 'worker' });
+
+ /** @type {Map} */
+ const moduleWorkers = new Map();
+ let didWarnESM = false;
+
+ return {
+ name: 'worker',
+ async transform(code, id) {
+ // Transpile worker file if we're dealing with a worker
+ if (/\.worker\.(?:[tj]sx?|mjs)$/.test(id)) {
+ const resolved = await this.resolve(id);
+ const resolvedId = resolved ? resolved.id : id;
+
+ if (moduleWorkers.has(resolvedId)) {
+ if (!didWarnESM) {
+ const relativeId = path.relative(options.root, resolvedId);
+ this.warn(
+ kl.yellow(
+ `Warning: Module workers are not widely supported yet. Use at your own risk. This warning occurs, because file `
+ ) +
+ kl.cyan(relativeId) +
+ kl.yellow(` was loaded as a Web Worker with type "module"`)
+ );
+ didWarnESM = true;
+ }
+
+ // ..but not in module mode
+ return;
+ }
+
+ // TODO: Add support for HMR inside a worker.
+
+ // Firefox doesn't support modules inside web workers. They're
+ // the only main browser left to implement that feature. Until
+ // that's resolved we need to pre-bundle the worker code as a
+ // single script with no dependencies. Once they support that
+ // we can drop the bundling part and have nested workers work
+ // out of the box.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1247687
+ const bundle = await rollup.rollup({
+ input: id,
+ plugins: [
+ {
+ name: 'worker-meta',
+ resolveImportMeta(property) {
+ // `import.meta.url` is only available in ESM environments
+ if (property === 'url') {
+ return 'location.href';
+ }
+ }
+ },
+ ...plugins
+ ],
+ // Inline all dependencies
+ external: () => false
+ });
+
+ const res = await bundle.generate({
+ format: 'iife',
+
+ sourcemap: options.sourcemap,
+ inlineDynamicImports: true
+ });
+
+ await bundle.close();
+
+ return {
+ code: res.output[0].code,
+ map: res.output[0].map || null
+ };
+ }
+ // Check if a worker is referenced anywhere in the file
+ else if (/\.(?:[tj]sx?|mjs|cjs)$/.test(id)) {
+ const WORKER_REG = /new URL\(\s*['"]([\w.-/:~]+)['"],\s*import\.meta\.url\s*\)(,\s*{.*?["']module["'].*?})?/gm;
+
+ if (WORKER_REG.test(code)) {
+ const s = new MagicString(code, {
+ filename: id,
+ // @ts-ignore
+ indentExclusionRanges: undefined
+ });
+
+ let match;
+ WORKER_REG.lastIndex = 0;
+ while ((match = WORKER_REG.exec(code))) {
+ const spec = match[1];
+
+ // Worker URLs must be relative to properly work with chunks
+ if (/^https?:/.test(spec) || !/^\.\.?\//.test(spec)) {
+ throw new Error(`Worker import specifier must be relative. Got "${spec}" instead.`);
+ }
+
+ const ref = this.emitFile({
+ type: 'chunk',
+ id: spec
+ });
+
+ const resolved = await this.resolve(spec, id);
+ const resolvedId = resolved ? resolved.id : spec;
+
+ let usageCount = moduleWorkers.get(resolvedId) || 0;
+ if (match[2]) {
+ moduleWorkers.set(resolvedId, usageCount + 1);
+ } else if (usageCount === 0) {
+ moduleWorkers.delete(resolvedId);
+ }
+
+ const start = match.index + match[0].indexOf(spec);
+ // Account for quoting characters and force URL to be
+ // relative.
+ s.overwrite(start - 1, start + spec.length + 1, `'.' + import.meta.ROLLUP_FILE_URL_${ref}`);
+ }
+
+ return {
+ code: s.toString(),
+ map: options.sourcemap
+ ? s.generateMap({ source: id, file: path.posix.basename(id), includeContent: true })
+ : null
+ };
+ }
+ }
+ }
+ };
+}
diff --git a/packages/wmr/test/fixtures/worker-esm/dep-a.js b/packages/wmr/test/fixtures/worker-esm/dep-a.js
new file mode 100644
index 000000000..8fd3dc4bd
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-esm/dep-a.js
@@ -0,0 +1 @@
+export { value } from './dep-b';
diff --git a/packages/wmr/test/fixtures/worker-esm/dep-b.js b/packages/wmr/test/fixtures/worker-esm/dep-b.js
new file mode 100644
index 000000000..2c47d2b98
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-esm/dep-b.js
@@ -0,0 +1 @@
+export const value = 'it works';
diff --git a/packages/wmr/test/fixtures/worker-esm/entry-1.js b/packages/wmr/test/fixtures/worker-esm/entry-1.js
new file mode 100644
index 000000000..623084c3e
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-esm/entry-1.js
@@ -0,0 +1 @@
+export { value } from './dep-a';
diff --git a/packages/wmr/test/fixtures/worker-esm/foo.worker.js b/packages/wmr/test/fixtures/worker-esm/foo.worker.js
new file mode 100644
index 000000000..adbad6b49
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-esm/foo.worker.js
@@ -0,0 +1,5 @@
+import { value } from './dep-b';
+
+addEventListener('message', () => {
+ postMessage(value);
+});
diff --git a/packages/wmr/test/fixtures/worker-esm/index.html b/packages/wmr/test/fixtures/worker-esm/index.html
new file mode 100644
index 000000000..e3f64f80c
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-esm/index.html
@@ -0,0 +1,3 @@
+it doesn't work
+it doesn't work
+
diff --git a/packages/wmr/test/fixtures/worker-esm/index.js b/packages/wmr/test/fixtures/worker-esm/index.js
new file mode 100644
index 000000000..a76961b3f
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-esm/index.js
@@ -0,0 +1,11 @@
+import { value as value2 } from './entry-1';
+
+document.querySelector('h2').textContent = value2;
+
+const worker = new Worker(new URL('./foo.worker.js', import.meta.url), { type: 'module' });
+
+worker.addEventListener('message', e => {
+ document.querySelector('h1').textContent = e.data;
+});
+
+worker.postMessage('hello');
diff --git a/packages/wmr/test/fixtures/worker-multi/bar.worker.js b/packages/wmr/test/fixtures/worker-multi/bar.worker.js
new file mode 100644
index 000000000..bd05ed848
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-multi/bar.worker.js
@@ -0,0 +1,3 @@
+addEventListener('message', () => {
+ postMessage('it works');
+});
diff --git a/packages/wmr/test/fixtures/worker-multi/foo.worker.js b/packages/wmr/test/fixtures/worker-multi/foo.worker.js
new file mode 100644
index 000000000..bd05ed848
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-multi/foo.worker.js
@@ -0,0 +1,3 @@
+addEventListener('message', () => {
+ postMessage('it works');
+});
diff --git a/packages/wmr/test/fixtures/worker-multi/index.html b/packages/wmr/test/fixtures/worker-multi/index.html
new file mode 100644
index 000000000..e3f64f80c
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-multi/index.html
@@ -0,0 +1,3 @@
+it doesn't work
+it doesn't work
+
diff --git a/packages/wmr/test/fixtures/worker-multi/index.js b/packages/wmr/test/fixtures/worker-multi/index.js
new file mode 100644
index 000000000..b1f2d95fc
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-multi/index.js
@@ -0,0 +1,12 @@
+const foo = new Worker(new URL('./foo.worker.js', import.meta.url));
+const bar = new Worker(new URL('./foo.worker.js', import.meta.url));
+
+foo.addEventListener('message', e => {
+ document.querySelector('h1').textContent = e.data;
+});
+bar.addEventListener('message', e => {
+ document.querySelector('h2').textContent = e.data;
+});
+
+foo.postMessage('hello');
+bar.postMessage('hello');
diff --git a/packages/wmr/test/fixtures/worker-relative/foo/foo.worker.js b/packages/wmr/test/fixtures/worker-relative/foo/foo.worker.js
new file mode 100644
index 000000000..bd05ed848
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-relative/foo/foo.worker.js
@@ -0,0 +1,3 @@
+addEventListener('message', () => {
+ postMessage('it works');
+});
diff --git a/packages/wmr/test/fixtures/worker-relative/foo/index.js b/packages/wmr/test/fixtures/worker-relative/foo/index.js
new file mode 100644
index 000000000..5bb0e2fdd
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-relative/foo/index.js
@@ -0,0 +1,7 @@
+const worker = new Worker(new URL('./foo.worker.js', import.meta.url));
+
+worker.addEventListener('message', e => {
+ document.querySelector('h1').textContent = e.data;
+});
+
+worker.postMessage('hello');
diff --git a/packages/wmr/test/fixtures/worker-relative/index.html b/packages/wmr/test/fixtures/worker-relative/index.html
new file mode 100644
index 000000000..9cfb45a0e
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-relative/index.html
@@ -0,0 +1,2 @@
+it doesn't work
+
diff --git a/packages/wmr/test/fixtures/worker-relative/index.js b/packages/wmr/test/fixtures/worker-relative/index.js
new file mode 100644
index 000000000..76a6714be
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker-relative/index.js
@@ -0,0 +1 @@
+import './foo/index.js';
diff --git a/packages/wmr/test/fixtures/worker/dep-a.js b/packages/wmr/test/fixtures/worker/dep-a.js
new file mode 100644
index 000000000..8fd3dc4bd
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker/dep-a.js
@@ -0,0 +1 @@
+export { value } from './dep-b';
diff --git a/packages/wmr/test/fixtures/worker/dep-b.js b/packages/wmr/test/fixtures/worker/dep-b.js
new file mode 100644
index 000000000..2c47d2b98
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker/dep-b.js
@@ -0,0 +1 @@
+export const value = 'it works';
diff --git a/packages/wmr/test/fixtures/worker/entry-1.js b/packages/wmr/test/fixtures/worker/entry-1.js
new file mode 100644
index 000000000..623084c3e
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker/entry-1.js
@@ -0,0 +1 @@
+export { value } from './dep-a';
diff --git a/packages/wmr/test/fixtures/worker/foo.worker.js b/packages/wmr/test/fixtures/worker/foo.worker.js
new file mode 100644
index 000000000..adbad6b49
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker/foo.worker.js
@@ -0,0 +1,5 @@
+import { value } from './dep-b';
+
+addEventListener('message', () => {
+ postMessage(value);
+});
diff --git a/packages/wmr/test/fixtures/worker/index.html b/packages/wmr/test/fixtures/worker/index.html
new file mode 100644
index 000000000..e3f64f80c
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker/index.html
@@ -0,0 +1,3 @@
+it doesn't work
+it doesn't work
+
diff --git a/packages/wmr/test/fixtures/worker/index.js b/packages/wmr/test/fixtures/worker/index.js
new file mode 100644
index 000000000..db614a958
--- /dev/null
+++ b/packages/wmr/test/fixtures/worker/index.js
@@ -0,0 +1,11 @@
+import { value as value2 } from './entry-1';
+
+document.querySelector('h2').textContent = value2;
+
+const worker = new Worker(new URL('./foo.worker.js', import.meta.url));
+
+worker.addEventListener('message', e => {
+ document.querySelector('h1').textContent = e.data;
+});
+
+worker.postMessage('hello');
diff --git a/packages/wmr/test/worker.test.js b/packages/wmr/test/worker.test.js
new file mode 100644
index 000000000..c13fdd2c9
--- /dev/null
+++ b/packages/wmr/test/worker.test.js
@@ -0,0 +1,171 @@
+import path from 'path';
+import {
+ getOutput,
+ loadFixture,
+ runWmr,
+ runWmrFast,
+ serveStatic,
+ setupTest,
+ teardown,
+ waitForMessage,
+ waitForPass,
+ withLog
+} from './test-helpers.js';
+
+jest.setTimeout(30000);
+
+describe('Workers', () => {
+ describe('development', () => {
+ /** @type {TestEnv} */
+ let env;
+ /** @type {WmrInstance} */
+ let instance;
+
+ beforeEach(async () => {
+ env = await setupTest();
+ });
+
+ afterEach(async () => {
+ await teardown(env);
+ instance.close();
+ });
+
+ it('should load worker', async () => {
+ await loadFixture('worker', env);
+ instance = await runWmrFast(env.tmp.path);
+ await getOutput(env, instance);
+
+ await withLog(instance.output, async () => {
+ await waitForPass(async () => {
+ const h1 = await env.page.evaluate('document.querySelector("h1").textContent');
+ const h2 = await env.page.evaluate('document.querySelector("h2").textContent');
+
+ expect(h1).toMatch('it works');
+ expect(h2).toMatch('it works');
+ });
+ });
+ });
+
+ it('should load multiple workers', async () => {
+ await loadFixture('worker-multi', env);
+ instance = await runWmrFast(env.tmp.path);
+ await getOutput(env, instance);
+
+ await withLog(instance.output, async () => {
+ await waitForPass(async () => {
+ const h1 = await env.page.evaluate('document.querySelector("h1").textContent');
+ const h2 = await env.page.evaluate('document.querySelector("h2").textContent');
+
+ expect(h1).toMatch('it works');
+ expect(h2).toMatch('it works');
+ });
+ });
+ });
+
+ it('should support ESM workers', async () => {
+ await loadFixture('worker-esm', env);
+ instance = await runWmrFast(env.tmp.path);
+ await getOutput(env, instance);
+
+ await withLog(instance.output, async () => {
+ await waitForPass(async () => {
+ const h1 = await env.page.evaluate('document.querySelector("h1").textContent');
+ const h2 = await env.page.evaluate('document.querySelector("h2").textContent');
+
+ expect(h1).toMatch('it works');
+ expect(h2).toMatch('it works');
+ });
+
+ await waitForMessage(instance.output, /Module workers are not widely supported/);
+ });
+ });
+ });
+
+ describe('production', () => {
+ /** @type {TestEnv} */
+ let env;
+ /** @type {WmrInstance} */
+ let instance;
+ /** @type {(()=>void)[]} */
+ let cleanup = [];
+
+ beforeEach(async () => {
+ env = await setupTest();
+ });
+
+ afterEach(async () => {
+ await teardown(env);
+ instance.close();
+ await Promise.all(cleanup.map(c => Promise.resolve().then(c)));
+ cleanup.length = 0;
+ });
+
+ it('should load worker', async () => {
+ await loadFixture('worker', env);
+ instance = await runWmr(env.tmp.path, 'build');
+
+ expect(await instance.done).toEqual(0);
+ const { address, stop } = serveStatic(path.join(env.tmp.path, 'dist'));
+ cleanup.push(stop);
+ await env.page.goto(address, {
+ waitUntil: ['networkidle0', 'load']
+ });
+
+ await withLog(instance.output, async () => {
+ await waitForPass(async () => {
+ const h1 = await env.page.evaluate('document.querySelector("h1").textContent');
+ const h2 = await env.page.evaluate('document.querySelector("h2").textContent');
+
+ expect(h1).toMatch('it works');
+ expect(h2).toMatch('it works');
+ });
+ });
+ });
+
+ it('should load multiple workers', async () => {
+ await loadFixture('worker-multi', env);
+ instance = await runWmr(env.tmp.path, 'build');
+
+ expect(await instance.done).toEqual(0);
+ const { address, stop } = serveStatic(path.join(env.tmp.path, 'dist'));
+ cleanup.push(stop);
+ await env.page.goto(address, {
+ waitUntil: ['networkidle0', 'load']
+ });
+
+ await withLog(instance.output, async () => {
+ await waitForPass(async () => {
+ const h1 = await env.page.evaluate('document.querySelector("h1").textContent');
+ const h2 = await env.page.evaluate('document.querySelector("h2").textContent');
+
+ expect(h1).toMatch('it works');
+ expect(h2).toMatch('it works');
+ });
+ });
+ });
+
+ it('should load ESM workers', async () => {
+ await loadFixture('worker-esm', env);
+ instance = await runWmr(env.tmp.path, 'build');
+
+ expect(await instance.done).toEqual(0);
+ const { address, stop } = serveStatic(path.join(env.tmp.path, 'dist'));
+ cleanup.push(stop);
+ await env.page.goto(address, {
+ waitUntil: ['networkidle0', 'load']
+ });
+
+ await withLog(instance.output, async () => {
+ await waitForPass(async () => {
+ const h1 = await env.page.evaluate('document.querySelector("h1").textContent');
+ const h2 = await env.page.evaluate('document.querySelector("h2").textContent');
+
+ expect(h1).toMatch('it works');
+ expect(h2).toMatch('it works');
+ });
+
+ await waitForMessage(instance.output, /Module workers are not widely supported/);
+ });
+ });
+ });
+});