diff --git a/.changeset/six-cobras-flash.md b/.changeset/six-cobras-flash.md new file mode 100644 index 000000000..92347c366 --- /dev/null +++ b/.changeset/six-cobras-flash.md @@ -0,0 +1,5 @@ +--- +"@crxjs/vite-plugin": patch +--- + +Test: infinite recursion on circular dependency diff --git a/.github/workflows/vite-plugin.yml b/.github/workflows/vite-plugin.yml index e6ecef6d8..8e928e58d 100644 --- a/.github/workflows/vite-plugin.yml +++ b/.github/workflows/vite-plugin.yml @@ -113,7 +113,7 @@ jobs: run: pnpm install --filter "vite-plugin" - name: Run e2e tests - uses: GabrielBB/xvfb-action@v1 + uses: xvfb-run -a npm test with: run: pnpm test:run:e2e working-directory: "packages/vite-plugin" @@ -122,7 +122,7 @@ jobs: if: failure() with: name: image-diff-${{ matrix.type }} - path: ${{ github.workspace }}/tests/**/__image_snapshots__/__diff_output__/*.png + path: ${{ github.workspace }}/packages/vite-plugin/tests/**/__image_snapshots__/__diff_output__/*.png # Runs unit and output tests unit: diff --git a/packages/vite-plugin/tests/out/with-circular-deps/__snapshots__/build.test.ts.snap b/packages/vite-plugin/tests/out/with-circular-deps/__snapshots__/build.test.ts.snap new file mode 100644 index 000000000..ca1ee4881 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/__snapshots__/build.test.ts.snap @@ -0,0 +1,318 @@ +// Vitest Snapshot v1 + +exports[`build fs output > _00 manifest.json 1`] = ` +Object { + "action": Object { + "default_popup": "index.html", + }, + "content_scripts": Array [ + Object { + "all_frames": true, + "js": Array [ + "assets/content.ts-loader.hash0.js", + ], + "matches": Array [ + "", + ], + "run_at": "document_start", + }, + ], + "manifest_version": 3, + "name": "CRXJS Vanilla JS Example", + "version": "1.0.0", + "web_accessible_resources": Array [ + Object { + "matches": Array [ + "", + ], + "resources": Array [ + "assets/content.ts.hash1.js", + "assets/module.hash2.js", + ], + "use_dynamic_url": true, + }, + ], +} +`; + +exports[`build fs output > _01 output files 1`] = ` +Array [ + "assets/content.ts-loader.hash0.js", + "assets/content.ts.hash1.js", + "assets/index.hash3.css", + "assets/index.html.hash4.js", + "assets/module.hash2.js", + "assets/typescript.hash5.svg", + "index.html", + "manifest.json", + "vite.svg", +] +`; + +exports[`build fs output > assets/content.ts.hash1.js 1`] = ` +"const scriptRel = \\"modulepreload\\"; +const assetsURL = function(dep) { + return \\"/\\" + dep; +}; +const seen = {}; +const __vitePreload = function preload(baseModule, deps, importerUrl) { + if (!deps || deps.length === 0) { + return baseModule(); + } + return Promise.all(deps.map((dep) => { + dep = assetsURL(dep); + if (dep in seen) + return; + seen[dep] = true; + const isCss = dep.endsWith(\\".css\\"); + const cssSelector = isCss ? '[rel=\\"stylesheet\\"]' : \\"\\"; + if (document.querySelector(\`link[href=\\"\${dep}\\"]\${cssSelector}\`)) { + return; + } + const link = document.createElement(\\"link\\"); + link.rel = isCss ? \\"stylesheet\\" : scriptRel; + if (!isCss) { + link.as = \\"script\\"; + link.crossOrigin = \\"\\"; + } + link.href = dep; + document.head.appendChild(link); + if (isCss) { + return new Promise((res, rej) => { + link.addEventListener(\\"load\\", res); + link.addEventListener(\\"error\\", () => rej(new Error(\`Unable to preload CSS for \${dep}\`))); + }); + } + })).then(() => baseModule()); +}; +const sharedUtil = () => { + console.log(\\"util\\"); +}; +sharedUtil(); +__vitePreload(() => import(\\"./module.hash2.js\\"), true ? [] : void 0); +export { + sharedUtil as s +}; +// # sourceMappingURL=data:application/json;base64, +" +`; + +exports[`build fs output > assets/content.ts-loader.hash0.js 1`] = ` +"(function () { + 'use strict'; + + const injectTime = performance.now(); + (async () => { + const { onExecute } = await import( + /* @vite-ignore */ + chrome.runtime.getURL(\\"assets/content.ts.hash1.js\\") + ); + onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } }); + })().catch(console.error); + +})(); +" +`; + +exports[`build fs output > assets/index.hash3.css 1`] = ` +":root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} +" +`; + +exports[`build fs output > assets/index.html.hash4.js 1`] = ` +"(function polyfill() { + const relList = document.createElement(\\"link\\").relList; + if (relList && relList.supports && relList.supports(\\"modulepreload\\")) { + return; + } + for (const link of document.querySelectorAll('link[rel=\\"modulepreload\\"]')) { + processPreload(link); + } + new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type !== \\"childList\\") { + continue; + } + for (const node of mutation.addedNodes) { + if (node.tagName === \\"LINK\\" && node.rel === \\"modulepreload\\") + processPreload(node); + } + } + }).observe(document, { childList: true, subtree: true }); + function getFetchOpts(script) { + const fetchOpts = {}; + if (script.integrity) + fetchOpts.integrity = script.integrity; + if (script.referrerpolicy) + fetchOpts.referrerPolicy = script.referrerpolicy; + if (script.crossorigin === \\"use-credentials\\") + fetchOpts.credentials = \\"include\\"; + else if (script.crossorigin === \\"anonymous\\") + fetchOpts.credentials = \\"omit\\"; + else + fetchOpts.credentials = \\"same-origin\\"; + return fetchOpts; + } + function processPreload(link) { + if (link.ep) + return; + link.ep = true; + const fetchOpts = getFetchOpts(link); + fetch(link.href, fetchOpts); + } +})(); +const style = \\"\\"; +const typescriptLogo = \\"/assets/typescript.hash5.svg\\"; +const viteLogo = \\"/vite.svg\\"; +function setupCounter(element) { + let counter = 0; + const setCounter = (count) => { + counter = count; + element.innerHTML = \`count is \${counter}\`; + }; + element.addEventListener(\\"click\\", () => setCounter(counter + 1)); + setCounter(0); +} +document.querySelector(\\"#app\\").innerHTML = \` +
+ + \\"Vite + + + + +

Vite + TypeScript

+
+ +
+

+ Click on the Vite and TypeScript logos to learn more +

+
+\`; +setupCounter(document.querySelector(\\"#counter\\")); +// # sourceMappingURL=data:application/json;charset=utf-8;base64, +" +`; + +exports[`build fs output > assets/module.hash2.js 1`] = ` +"import { s as sharedUtil } from \\"./content.ts.hash1.js\\"; +sharedUtil(); +// # sourceMappingURL=data:application/json;charset=utf-8;base64, +" +`; + +exports[`build fs output > index.html 1`] = ` +" + + + + + + Vite + TS + + + + +
+ + + +" +`; diff --git a/packages/vite-plugin/tests/out/with-circular-deps/__snapshots__/serve.test.ts.snap b/packages/vite-plugin/tests/out/with-circular-deps/__snapshots__/serve.test.ts.snap new file mode 100644 index 000000000..b7c6c05cc --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/__snapshots__/serve.test.ts.snap @@ -0,0 +1,149 @@ +// Vitest Snapshot v1 + +exports[`serve fs output > _00 manifest.json 1`] = ` +Object { + "action": Object { + "default_popup": "index.html", + }, + "background": Object { + "service_worker": "service-worker-loader.js", + "type": "module", + }, + "content_scripts": Array [ + Object { + "all_frames": true, + "js": Array [ + "src/content.ts-loader.js", + ], + "matches": Array [ + "", + ], + "run_at": "document_start", + }, + ], + "manifest_version": 3, + "name": "CRXJS Vanilla JS Example", + "version": "1.0.0", + "web_accessible_resources": Array [ + Object { + "matches": Array [ + "", + ], + "resources": Array [ + "*", + "**/*", + ], + "use_dynamic_url": true, + }, + ], +} +`; + +exports[`serve fs output > _01 output files 1`] = ` +Array [ + "assets/precontroller.hash0.js", + "index.html", + "manifest.json", + "service-worker-loader.js", + "src/content.ts-loader.js", + "src/content.ts.js", + "src/module.ts.js", + "src/utils.ts.js", + "vendor/crx-client-port.js", + "vendor/vite-client.js", + "vendor/vite-dist-client-env.mjs.js", + "vendor/webcomponents-custom-elements.js", + "vite.svg", +] +`; + +exports[`serve fs output > _02 optimized deps 1`] = ` +Set { + "src/content.ts", + "index.html", +} +`; + +exports[`serve fs output > assets/precontroller.hash0.js 1`] = ` +"const id = setInterval(() => location.reload(), 100); +setTimeout(() => clearInterval(id), 5e3); +" +`; + +exports[`serve fs output > index.html 1`] = ` +" + + + Waiting for the extension service worker... + + + +

Waiting for service worker

+ +

+ If you see this message, it means the service worker has not loaded fully. +

+ +

This page is never added in production.

+ + +" +`; + +exports[`serve fs output > service-worker-loader.js 1`] = ` +"import 'http://localhost:3000/@vite/env'; +import 'http://localhost:3000/@crx/client-worker'; +" +`; + +exports[`serve fs output > src/content.ts.js 1`] = ` +"import { sharedUtil } from \\"/src/utils.ts.js\\"; +sharedUtil(); +import(\\"/src/module.ts.js\\"); + +// # sourceMappingURL=data:application/json;charset=utf-8;base64, +" +`; + +exports[`serve fs output > src/content.ts-loader.js 1`] = ` +"(function () { + 'use strict'; + + const injectTime = performance.now(); + (async () => { + if (\\"\\") + await import( + /* @vite-ignore */ + chrome.runtime.getURL(\\"\\") + ); + await import( + /* @vite-ignore */ + chrome.runtime.getURL(\\"vendor/vite-client.js\\") + ); + const { onExecute } = await import( + /* @vite-ignore */ + chrome.runtime.getURL(\\"src/content.ts.js\\") + ); + onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } }); + })().catch(console.error); + +})(); +" +`; + +exports[`serve fs output > src/module.ts.js 1`] = ` +"import { sharedUtil } from \\"/src/utils.ts.js\\"; +sharedUtil(); + +// # sourceMappingURL=data:application/json;charset=utf-8;base64, +" +`; + +exports[`serve fs output > src/utils.ts.js 1`] = ` +"export const sharedUtil = () => { + console.log(\\"util\\"); +}; + +// # sourceMappingURL=data:application/json;charset=utf-8;base64, +" +`; diff --git a/packages/vite-plugin/tests/out/with-circular-deps/build.test.ts b/packages/vite-plugin/tests/out/with-circular-deps/build.test.ts new file mode 100644 index 000000000..84bcc380a --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/build.test.ts @@ -0,0 +1,8 @@ +import { build } from 'tests/runners' +import { testOutput } from 'tests/testOutput' +import { test } from 'vitest' + +test('build fs output', async () => { + const result = await build(__dirname) + await testOutput(result) +}) diff --git a/packages/vite-plugin/tests/out/with-circular-deps/index.html b/packages/vite-plugin/tests/out/with-circular-deps/index.html new file mode 100644 index 000000000..f86e483c9 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + TS + + +
+ + + diff --git a/packages/vite-plugin/tests/out/with-circular-deps/manifest.json b/packages/vite-plugin/tests/out/with-circular-deps/manifest.json new file mode 100644 index 000000000..80d0c2838 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/manifest.json @@ -0,0 +1,16 @@ +{ + "manifest_version": 3, + "name": "CRXJS Vanilla JS Example", + "version": "1.0.0", + "action": { + "default_popup": "index.html" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["src/content.ts"], + "run_at": "document_start", + "all_frames": true + } + ] +} \ No newline at end of file diff --git a/packages/vite-plugin/tests/out/with-circular-deps/public/vite.svg b/packages/vite-plugin/tests/out/with-circular-deps/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vite-plugin/tests/out/with-circular-deps/serve.test.ts b/packages/vite-plugin/tests/out/with-circular-deps/serve.test.ts new file mode 100644 index 000000000..394772d33 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/serve.test.ts @@ -0,0 +1,16 @@ +import { serve } from 'tests/runners' +import { testOutput } from 'tests/testOutput' +import { afterAll, test } from 'vitest' + +let result: Awaited> | undefined + +afterAll(async () => { + try { + await result?.server.close() + } catch (error) {} +}) + +test('serve fs output', async () => { + result = await serve(__dirname) + await testOutput(result) +}) diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/content.ts b/packages/vite-plugin/tests/out/with-circular-deps/src/content.ts new file mode 100644 index 000000000..0753b9842 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/content.ts @@ -0,0 +1,5 @@ +import { sharedUtil } from "./utils"; + +sharedUtil(); + +import('./module'); \ No newline at end of file diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/counter.ts b/packages/vite-plugin/tests/out/with-circular-deps/src/counter.ts new file mode 100644 index 000000000..09e5afd2d --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/counter.ts @@ -0,0 +1,9 @@ +export function setupCounter(element: HTMLButtonElement) { + let counter = 0 + const setCounter = (count: number) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/main.ts b/packages/vite-plugin/tests/out/with-circular-deps/src/main.ts new file mode 100644 index 000000000..2f852a7cf --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/main.ts @@ -0,0 +1,24 @@ +import './style.css' +import typescriptLogo from './typescript.svg' +import viteLogo from '/vite.svg' +import { setupCounter } from './counter' + +document.querySelector('#app')!.innerHTML = ` +
+ + + + + + +

Vite + TypeScript

+
+ +
+

+ Click on the Vite and TypeScript logos to learn more +

+
+` + +setupCounter(document.querySelector('#counter')!) diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/module.ts b/packages/vite-plugin/tests/out/with-circular-deps/src/module.ts new file mode 100644 index 000000000..f6ba181d9 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/module.ts @@ -0,0 +1,3 @@ +import { sharedUtil } from "./utils"; + +sharedUtil(); \ No newline at end of file diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/style.css b/packages/vite-plugin/tests/out/with-circular-deps/src/style.css new file mode 100644 index 000000000..b528b6cc2 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/style.css @@ -0,0 +1,97 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/typescript.svg b/packages/vite-plugin/tests/out/with-circular-deps/src/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/utils.ts b/packages/vite-plugin/tests/out/with-circular-deps/src/utils.ts new file mode 100644 index 000000000..0acb614df --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/utils.ts @@ -0,0 +1,3 @@ +export const sharedUtil = () => { + console.log('util'); +}; \ No newline at end of file diff --git a/packages/vite-plugin/tests/out/with-circular-deps/src/vite-env.d.ts b/packages/vite-plugin/tests/out/with-circular-deps/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/vite-plugin/tests/out/with-circular-deps/vite.config.ts b/packages/vite-plugin/tests/out/with-circular-deps/vite.config.ts new file mode 100644 index 000000000..17c4d0892 --- /dev/null +++ b/packages/vite-plugin/tests/out/with-circular-deps/vite.config.ts @@ -0,0 +1,21 @@ +import { crx } from '../../plugin-testOptionsProvider' +import { defineConfig } from 'vite' +import manifest from './manifest.json' + +export default defineConfig({ + build: { + sourcemap: 'inline', + minify: false, + rollupOptions: { + output: { + // the hash randomly changes between environments + assetFileNames: 'assets/[name].hash[hash].[ext]', + chunkFileNames: 'assets/[name].hash[hash].js', + entryFileNames: 'assets/[name].hash[hash].js', + }, + }, + }, + clearScreen: false, + logLevel: 'error', + plugins: [crx({ manifest })], +})