Skip to content

Commit 46852bd

Browse files
authored
Support ESM templates (#185)
1 parent 5cf20cd commit 46852bd

File tree

7 files changed

+159
-4
lines changed

7 files changed

+159
-4
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import path from 'path'
2+
import {fileURLToPath} from 'url'
3+
import {execSync} from 'child_process'
4+
import {extensionFixtures} from '../extension-fixtures.mjs'
5+
6+
const __filename = fileURLToPath(import.meta.url)
7+
const __dirname = path.dirname(__filename)
8+
9+
const exampleDir = 'examples/content-esm'
10+
const pathToExtension = path.join(__dirname, `dist/chrome`)
11+
12+
const test = extensionFixtures(pathToExtension, true)
13+
14+
test.beforeAll(async () => {
15+
execSync(`pnpm extension build ${exampleDir}`, {
16+
cwd: path.join(__dirname, '..')
17+
})
18+
})
19+
20+
test('should exist an element with the class name content_script-box', async ({
21+
page
22+
}) => {
23+
await page.goto('https://extension.js.org/')
24+
const div = page.locator('body > div.content_script-box')
25+
await test.expect(div).toBeVisible()
26+
})
27+
28+
test('should exist an h1 element with specified content', async ({page}) => {
29+
await page.goto('https://extension.js.org/')
30+
const h1 = page.locator('body > div.content_script-box > h1')
31+
await test.expect(h1).toHaveText('Change the background-color ⬇')
32+
})
33+
34+
test('should exist a default color value', async ({page}) => {
35+
await page.goto('https://extension.js.org/')
36+
const h1 = page.locator('body > div.content_script-box > h1')
37+
const color = await page.evaluate(
38+
(locator) => {
39+
return window.getComputedStyle(locator).getPropertyValue('color')
40+
},
41+
await h1.elementHandle()
42+
)
43+
await test.expect(color).toEqual('rgb(51, 51, 51)')
44+
})

examples/extension-fixtures.mjs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {test as base, chromium} from '@playwright/test'
2+
3+
/**
4+
* @typedef {import('@playwright/test').BrowserContext} BrowserContext
5+
*/
6+
const extensionFixtures = (
7+
/** @type {string} */ pathToExtension,
8+
/** @type {boolean} */ headless
9+
/** @returns {import('@playwright/test').TestModifier<{}, {context: BrowserContext, extensionId: string}>} */
10+
) => {
11+
return base.extend({
12+
/** @type {() => Promise<BrowserContext>} */
13+
// eslint-disable-next-line no-empty-pattern
14+
context: async ({}, use) => {
15+
const context = await chromium.launchPersistentContext('', {
16+
headless: false,
17+
args: [
18+
headless ? `--headless=new` : '',
19+
`--disable-extensions-except=${pathToExtension}`,
20+
`--load-extension=${pathToExtension}`,
21+
'--no-first-run', // Disable Chrome's native first run experience.
22+
'--disable-client-side-phishing-detection', // Disables client-side phishing detection
23+
'--disable-component-extensions-with-background-pages', // Disable some built-in extensions that aren't affected by '--disable-extensions'
24+
'--disable-default-apps', // Disable installation of default apps
25+
'--disable-features=InterestFeedContentSuggestions', // Disables the Discover feed on NTP
26+
'--disable-features=Translate', // Disables Chrome translation, both the manual option and the popup prompt when a page with differing language is detected.
27+
'--hide-scrollbars', // Hide scrollbars from screenshots.
28+
'--mute-audio', // Mute any audio
29+
'--no-default-browser-check', // Disable the default browser check, do not prompt to set it as such
30+
'--no-first-run', // Skip first run wizards
31+
'--ash-no-nudges', // Avoids blue bubble "user education" nudges (eg., "… give your browser a new look", Memory Saver)
32+
'--disable-search-engine-choice-screen', // Disable the 2023+ search engine choice screen
33+
'--disable-features=MediaRoute', // Avoid the startup dialog for `Do you want the application “Chromium.app” to accept incoming network connections?`. Also disables the Chrome Media Router which creates background networking activity to discover cast targets. A superset of disabling DialMediaRouteProvider.
34+
'--use-mock-keychain', // Use mock keychain on Mac to prevent the blocking permissions dialog about "Chrome wants to use your confidential information stored in your keychain"
35+
'--disable-background-networking', // Disable various background network services, including extension updating, safe browsing service, upgrade detector, translate, UMA
36+
'--disable-breakpad', // Disable crashdump collection (reporting is already disabled in Chromium)
37+
'--disable-component-update', // Don't update the browser 'components' listed at chrome://components/
38+
'--disable-domain-reliability', // Disables Domain Reliability Monitoring, which tracks whether the browser has difficulty contacting Google-owned sites and uploads reports to Google.
39+
'--disable-features=AutofillServerCommunicatio', // Disables autofill server communication. This feature isn't disabled via other 'parent' flags.
40+
'--disable-features=CertificateTransparencyComponentUpdate',
41+
'--disable-sync', // Disable syncing to a Google account
42+
'--disable-features=OptimizationHints', // Used for turning on Breakpad crash reporting in a debug environment where crash reporting is typically compiled but disabled. Disable the Chrome Optimization Guide and networking with its service API
43+
'--disable-features=DialMediaRouteProvider', // A weaker form of disabling the MediaRouter feature. See that flag's details.
44+
'--no-pings', // Don't send hyperlink auditing pings
45+
'--enable-features=SidePanelUpdates' // Ensure the side panel is visible. This is used for testing the side panel feature.
46+
].filter((arg) => !!arg)
47+
})
48+
await use(context)
49+
await context.close()
50+
},
51+
/** @type {() => Promise<string>} */
52+
extensionId: async ({context}, use) => {
53+
/*
54+
// for manifest v2:
55+
let [background] = context.backgroundPages()
56+
if (!background)
57+
background = await context.waitForEvent('backgroundpage')
58+
*/
59+
60+
// for manifest v3:
61+
let [background] = context.serviceWorkers()
62+
if (!background) background = await context.waitForEvent('serviceworker')
63+
64+
const extensionId = background.url().split('/')[2]
65+
await use(extensionId)
66+
}
67+
})
68+
}
69+
70+
export {extensionFixtures}

examples/new-esm/template.spec.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import path from 'path'
2+
import {fileURLToPath} from 'url'
3+
import {execSync} from 'child_process'
4+
import {extensionFixtures} from '../extension-fixtures.mjs'
5+
6+
// Recreate __dirname for ES Modules
7+
const __filename = fileURLToPath(import.meta.url)
8+
const __dirname = path.dirname(__filename)
9+
10+
const exampleDir = 'examples/new-esm'
11+
const pathToExtension = path.join(__dirname, `dist/chrome`)
12+
13+
const test = extensionFixtures(pathToExtension, true)
14+
15+
test.beforeAll(async () => {
16+
execSync(`pnpm extension build ${exampleDir}`, {
17+
cwd: path.join(__dirname, '..')
18+
})
19+
})
20+
21+
test('should exist an element with the welcome message text', async ({
22+
page
23+
}) => {
24+
await page.goto('chrome://newtab/')
25+
const h1 = page.locator('h1')
26+
await test.expect(h1).toHaveText('Welcome to your New ESModule Extension')
27+
})
28+
29+
test('should exist a default color value', async ({page}) => {
30+
await page.goto('chrome://newtab/')
31+
const h1 = page.locator('h1')
32+
const color = await page.evaluate(
33+
(locator) => {
34+
return window.getComputedStyle(locator).getPropertyValue('color')
35+
},
36+
await h1.elementHandle()
37+
)
38+
await test.expect(color).toEqual('rgb(74, 74, 74)')
39+
})

programs/develop/install_scripts.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ html_plugin_files=(
99

1010
resolve_plugin_files=(
1111
"$(dirname "$0")/webpack/plugin-extension/feature-resolve/steps/resolver-loader.ts"
12-
"$(dirname "$0")/webpack/plugin-extension/feature-resolve/steps/resolver-module.ts"
12+
# Defined in line 32 since it needs to be executed in esm format
13+
# "$(dirname "$0")/webpack/plugin-extension/feature-resolve/steps/resolver-module.ts"
1314
)
1415

1516
scripts_plugin_files=(
@@ -28,6 +29,7 @@ minimum_files=(
2829
"$(dirname "$0")/webpack/plugin-reload/steps/setup-chromium-reload-client/minimum-chromium-file.ts"
2930
"$(dirname "$0")/webpack/plugin-reload/steps/setup-firefox-reload-client/minimum-firefox-file.ts"
3031
"$(dirname "$0")/webpack/plugin-extension/feature-html/steps/minimum-script-file.ts"
32+
"$(dirname "$0")/webpack/plugin-extension/feature-resolve/steps/resolver-module.ts"
3133
)
3234

3335
# Define the tsup function

programs/develop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"clean": "rm -rf dist",
2626
"watch": "tsup-node ./module.ts --format cjs --dts --target=node18 --watch",
2727
"compile": "tsup-node ./module.ts --format cjs --dts --target=node18 --minify && bash install_scripts.sh",
28-
"test": "jest --no-cache --testPathPattern='webpack/.*/__spec__/.*\\.spec\\.ts'",
28+
"test": "jest --no-cache --testPathPattern='webpack/.*/__spec__/.*\\.spec\\.*'",
2929
"test:coverage": "jest --no-cache --testPathPattern='webpack/.*/__spec__/.*\\.spec\\.ts' --coverage",
3030
"test:build": "jest ./build.spec.ts --no-cache"
3131
},

programs/develop/webpack/plugin-extension/feature-resolve/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class ResolvePlugin {
5656

5757
public apply(compiler: webpack.Compiler): void {
5858
new webpack.ProvidePlugin({
59-
r: [path.resolve(__dirname, './resolver-module.js'), 'default']
59+
r: [path.resolve(__dirname, './resolver-module.mjs'), 'default']
6060
}).apply(compiler)
6161

6262
// 1 - Add the resolver loader.

programs/develop/webpack/plugin-extension/feature-resolve/steps/resolver-loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function resolveLoader(
4848
})
4949

5050
if (new RegExp(options.test).test(this.resourcePath)) {
51-
const resolverName = 'resolver-module.js'
51+
const resolverName = 'resolver-module.mjs'
5252

5353
if (
5454
this.resourcePath.includes('node_modules') ||

0 commit comments

Comments
 (0)