From b9faa5df476e38e9b43ba56608f65e22d5393460 Mon Sep 17 00:00:00 2001 From: gnbm Date: Sun, 19 Oct 2025 23:06:46 +0100 Subject: [PATCH 01/21] Fix Jest environment/runner TypeScript errors when building in Windows --- src/sys/node/logger/terminal-logger.ts | 15 +++++++++------ src/testing/jest/jest-28/jest-environment.ts | 14 ++++++-------- src/testing/jest/jest-28/jest-runner.ts | 15 +++++++++++---- src/testing/jest/jest-29/jest-environment.ts | 14 ++++++-------- src/testing/jest/jest-29/jest-runner.ts | 15 +++++++++++---- 5 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/sys/node/logger/terminal-logger.ts b/src/sys/node/logger/terminal-logger.ts index 1fbdce3483c..6851cbcfae5 100644 --- a/src/sys/node/logger/terminal-logger.ts +++ b/src/sys/node/logger/terminal-logger.ts @@ -95,8 +95,9 @@ export const createTerminalLogger = (loggerSys: TerminalLoggerSys): Logger => { const debug = (...msg: any[]) => { if (shouldLog(currentLogLevel, 'debug')) { - formatMemoryUsage(msg); const lines = wordWrap(msg, loggerSys.getColumns()); + // append memory usage after wrapping to preserve expected spacing semantics + lines[0] = `${lines[0]} ${getFormattedMemoryUsage()}`; debugPrefix(lines); console.log(lines.join('\n')); } @@ -115,8 +116,9 @@ export const createTerminalLogger = (loggerSys: TerminalLoggerSys): Logger => { if (debug) { if (shouldLog(currentLogLevel, 'debug')) { - formatMemoryUsage(msg); const lines = wordWrap(msg, loggerSys.getColumns()); + // append memory usage after wrapping to preserve expected spacing semantics + lines[0] = `${lines[0]} ${getFormattedMemoryUsage()}`; debugPrefix(lines); console.log(lines.join('\n')); queueWriteLog('D', [`${startMsg} ...`]); @@ -137,11 +139,12 @@ export const createTerminalLogger = (loggerSys: TerminalLoggerSys): Logger => { * * @param message an array to which the memory usage will be added */ - const formatMemoryUsage = (message: string[]) => { + const getFormattedMemoryUsage = (): string => { const mem = loggerSys.memoryUsage(); if (mem > 0) { - message.push(dim(` MEM: ${(mem / 1_000_000).toFixed(1)}MB`)); + return dim(` MEM: ${(mem / 1_000_000).toFixed(1)}MB`); } + return ''; }; const timespanFinish = ( @@ -167,9 +170,9 @@ export const createTerminalLogger = (loggerSys: TerminalLoggerSys): Logger => { if (debug) { if (shouldLog(currentLogLevel, 'debug')) { const m = [msg]; - formatMemoryUsage(m); - const lines = wordWrap(m, loggerSys.getColumns()); + // append memory usage after wrapping to preserve expected spacing semantics + lines[0] = `${lines[0]} ${getFormattedMemoryUsage()}`; debugPrefix(lines); console.log(lines.join('\n')); } diff --git a/src/testing/jest/jest-28/jest-environment.ts b/src/testing/jest/jest-28/jest-environment.ts index 4ac1530e380..f174092410c 100644 --- a/src/testing/jest/jest-28/jest-environment.ts +++ b/src/testing/jest/jest-28/jest-environment.ts @@ -1,22 +1,20 @@ -import type { Circus } from '@jest/types'; +import type { Circus, Global as JestGlobal } from '@jest/types'; import type { E2EProcessEnv, JestEnvironmentGlobal } from '@stencil/core/internal'; -import { TestEnvironment as NodeEnvironment } from 'jest-environment-node'; +import NodeEnvironment from 'jest-environment-node'; import { connectBrowser, disconnectBrowser, newBrowserPage } from '../../puppeteer/puppeteer-browser'; import { JestPuppeteerEnvironmentConstructor } from '../jest-apis'; export function createJestPuppeteerEnvironment(): JestPuppeteerEnvironmentConstructor { const JestEnvironment = class extends NodeEnvironment { - // TODO(STENCIL-1023): Remove this @ts-expect-error - // @ts-expect-error - Stencil's Jest environment adds additional properties to the Jest global, but does not extend it - global: JestEnvironmentGlobal; + override global: JestGlobal.Global & JestEnvironmentGlobal; browser: any = null; pages: any[] = []; testPath: string | null = null; - constructor(config: any, context: any) { - super(config, context); - this.testPath = context.testPath; + constructor(config: any, context?: any) { + super(config); + this.testPath = context?.testPath ?? null; } override async setup() { diff --git a/src/testing/jest/jest-28/jest-runner.ts b/src/testing/jest/jest-28/jest-runner.ts index a18ea5a30a8..4aef544ac90 100644 --- a/src/testing/jest/jest-28/jest-runner.ts +++ b/src/testing/jest/jest-28/jest-runner.ts @@ -1,6 +1,6 @@ import type { AggregatedResult } from '@jest/test-result'; import type * as d from '@stencil/core/internal'; -import { default as TestRunner } from 'jest-runner'; +import TestRunner from 'jest-runner'; import type { ConfigFlags } from '../../../cli/config-flags'; import { setScreenshotEmulateData } from '../../puppeteer/puppeteer-emulate'; @@ -53,7 +53,14 @@ export async function runJest(config: d.ValidatedConfig, env: d.E2EProcessEnv) { */ export function createTestRunner(): JestTestRunnerConstructor { class StencilTestRunner extends TestRunner { - override async runTests(tests: { context: any; path: string }[], watcher: any, options: any) { + override async runTests( + tests: { context: any; path: string }[], + watcher: any, + onStart: any, + onResult: any, + onFailure: any, + options: any, + ) { const env = process.env as d.E2EProcessEnv; // filter out only the tests the flags said we should run @@ -75,12 +82,12 @@ export function createTestRunner(): JestTestRunnerConstructor { setScreenshotEmulateData(emulateConfig, env); // run the test for each emulate config - await super.runTests(tests, watcher, options); + await super.runTests(tests, watcher, onStart, onResult, onFailure, options); } } else { // not doing e2e screenshot tests // so just run each test once - await super.runTests(tests, watcher, options); + await super.runTests(tests, watcher, onStart, onResult, onFailure, options); } } } diff --git a/src/testing/jest/jest-29/jest-environment.ts b/src/testing/jest/jest-29/jest-environment.ts index 4ac1530e380..f174092410c 100644 --- a/src/testing/jest/jest-29/jest-environment.ts +++ b/src/testing/jest/jest-29/jest-environment.ts @@ -1,22 +1,20 @@ -import type { Circus } from '@jest/types'; +import type { Circus, Global as JestGlobal } from '@jest/types'; import type { E2EProcessEnv, JestEnvironmentGlobal } from '@stencil/core/internal'; -import { TestEnvironment as NodeEnvironment } from 'jest-environment-node'; +import NodeEnvironment from 'jest-environment-node'; import { connectBrowser, disconnectBrowser, newBrowserPage } from '../../puppeteer/puppeteer-browser'; import { JestPuppeteerEnvironmentConstructor } from '../jest-apis'; export function createJestPuppeteerEnvironment(): JestPuppeteerEnvironmentConstructor { const JestEnvironment = class extends NodeEnvironment { - // TODO(STENCIL-1023): Remove this @ts-expect-error - // @ts-expect-error - Stencil's Jest environment adds additional properties to the Jest global, but does not extend it - global: JestEnvironmentGlobal; + override global: JestGlobal.Global & JestEnvironmentGlobal; browser: any = null; pages: any[] = []; testPath: string | null = null; - constructor(config: any, context: any) { - super(config, context); - this.testPath = context.testPath; + constructor(config: any, context?: any) { + super(config); + this.testPath = context?.testPath ?? null; } override async setup() { diff --git a/src/testing/jest/jest-29/jest-runner.ts b/src/testing/jest/jest-29/jest-runner.ts index a18ea5a30a8..4aef544ac90 100644 --- a/src/testing/jest/jest-29/jest-runner.ts +++ b/src/testing/jest/jest-29/jest-runner.ts @@ -1,6 +1,6 @@ import type { AggregatedResult } from '@jest/test-result'; import type * as d from '@stencil/core/internal'; -import { default as TestRunner } from 'jest-runner'; +import TestRunner from 'jest-runner'; import type { ConfigFlags } from '../../../cli/config-flags'; import { setScreenshotEmulateData } from '../../puppeteer/puppeteer-emulate'; @@ -53,7 +53,14 @@ export async function runJest(config: d.ValidatedConfig, env: d.E2EProcessEnv) { */ export function createTestRunner(): JestTestRunnerConstructor { class StencilTestRunner extends TestRunner { - override async runTests(tests: { context: any; path: string }[], watcher: any, options: any) { + override async runTests( + tests: { context: any; path: string }[], + watcher: any, + onStart: any, + onResult: any, + onFailure: any, + options: any, + ) { const env = process.env as d.E2EProcessEnv; // filter out only the tests the flags said we should run @@ -75,12 +82,12 @@ export function createTestRunner(): JestTestRunnerConstructor { setScreenshotEmulateData(emulateConfig, env); // run the test for each emulate config - await super.runTests(tests, watcher, options); + await super.runTests(tests, watcher, onStart, onResult, onFailure, options); } } else { // not doing e2e screenshot tests // so just run each test once - await super.runTests(tests, watcher, options); + await super.runTests(tests, watcher, onStart, onResult, onFailure, options); } } } From c7e28304db8d0395c19631e1c0254526736caba2 Mon Sep 17 00:00:00 2001 From: gnbm Date: Mon, 20 Oct 2025 00:06:26 +0100 Subject: [PATCH 02/21] Update node-sys.ts --- src/sys/node/node-sys.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/sys/node/node-sys.ts b/src/sys/node/node-sys.ts index 6bf87e0fb92..2e32bd4d4e6 100644 --- a/src/sys/node/node-sys.ts +++ b/src/sys/node/node-sys.ts @@ -667,8 +667,23 @@ export function createNodeSys(c: { process?: any; logger?: Logger } = {}): Compi 'workbox-build': { minVersion: '4.3.1', recommendedVersion: '4.3.1' }, }); - prcs.on('SIGINT', runInterruptsCallbacks); - prcs.on('exit', runInterruptsCallbacks); + // In test environments avoid attaching duplicate global process listeners + const isJest = !!process.env.JEST_WORKER_ID; + if (!isJest) { + prcs.on('SIGINT', runInterruptsCallbacks); + prcs.on('exit', runInterruptsCallbacks); + } else { + const sigintHandlers = prcs.listeners('SIGINT'); + const exitHandlers = prcs.listeners('exit'); + const alreadyHasSigint = sigintHandlers.includes(runInterruptsCallbacks as any); + const alreadyHasExit = exitHandlers.includes(runInterruptsCallbacks as any); + if (!alreadyHasSigint) { + prcs.on('SIGINT', runInterruptsCallbacks); + } + if (!alreadyHasExit) { + prcs.on('exit', runInterruptsCallbacks); + } + } return sys; } From dba304aa83007a02ff08f16227bf067c05cb59e3 Mon Sep 17 00:00:00 2001 From: gnbm Date: Mon, 20 Oct 2025 12:15:30 +0100 Subject: [PATCH 03/21] chore(test): migrate bundler test from Karma to Jest; add sub-config and ci jobs --- .github/workflows/ci.yml | 41 ++++ package.json | 2 + test/bundler/jest-dom-utils.ts | 87 ++++++++ test/bundler/jest.config.js | 21 ++ test/bundler/karma-stencil-utils.ts | 194 +----------------- test/bundler/karma.config.ts | 51 +---- test/bundler/package.json | 8 +- .../vite-bundle-test/vite-bundle.spec.ts | 12 +- 8 files changed, 162 insertions(+), 254 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 test/bundler/jest-dom-utils.ts create mode 100644 test/bundler/jest.config.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000000..2a3ff227057 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + pull_request: + push: + +jobs: + test-core: + name: Core Jest + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + - name: Install + run: npm ci + - name: Run Jest (core) + run: npm run test.jest + + test-bundler: + name: Bundler Jest + runs-on: ubuntu-latest + needs: test-core + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + - name: Install + run: npm ci + - name: Run Bundler tests + run: npm run test.bundler + + diff --git a/package.json b/package.json index ec68e81bbfa..0db170100d9 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,8 @@ "test.docs-build": "cd test && npm run build.docs-json && npm run build.docs-readme", "test.watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch", "test.watch-all": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watchAll --coverage", + "test.bundler": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c test/bundler/jest.config.js --passWithNoTests", + "ci.test": "npm run test.jest && npm run test.bundler", "tsc.prod": "tsc", "ts": "tsc --noEmit --project scripts/tsconfig.json && tsx" }, diff --git a/test/bundler/jest-dom-utils.ts b/test/bundler/jest-dom-utils.ts new file mode 100644 index 00000000000..d25e70c95cc --- /dev/null +++ b/test/bundler/jest-dom-utils.ts @@ -0,0 +1,87 @@ +import fs from 'fs'; +import path from 'path'; +import { pathToFileURL } from 'url'; + +export type DomTestUtilities = { + setupDom: (htmlPathFromRepoRoot: string) => Promise; + tearDownDom: () => void; +}; + +export function setupDomTests(document: Document): DomTestUtilities { + let testBed = document.getElementById('test-app'); + if (!testBed) { + testBed = document.createElement('div'); + testBed.id = 'test-app'; + document.body.appendChild(testBed); + } + + async function setupDom(htmlPathFromRepoRoot: string): Promise { + if (!testBed) { + throw new Error('The Stencil/Jest test bed could not be found.'); + } + const testElement = document.createElement('div'); + testElement.className = 'test-spec'; + testBed.appendChild(testElement); + + const absPath = path.resolve(process.cwd(), htmlPathFromRepoRoot.replace(/^\//, '')); + const html = fs.readFileSync(absPath, 'utf-8'); + testElement.innerHTML = html; + + // execute module scripts referenced by the built index.html so components register + const baseDir = path.dirname(absPath); + const scripts = Array.from(testElement.querySelectorAll('script')) as HTMLScriptElement[]; + for (const s of scripts) { + const type = (s.getAttribute('type') || '').toLowerCase(); + const src = s.getAttribute('src'); + if (type === 'module' && src && src.endsWith('.js')) { + const rel = src.startsWith('/') ? src.slice(1) : src; + const jsAbs = path.resolve(baseDir, rel); + const fileUrl = pathToFileURL(jsAbs); + // dynamic import to execute the built bundle + // eslint-disable-next-line no-await-in-loop + await import(fileUrl.href); + } + } + + // wait for app readiness similar to Karma helper + await new Promise((resolve) => { + const onAppLoad = () => { + window.removeEventListener('appload', onAppLoad); + resolve(); + }; + window.addEventListener('appload', onAppLoad); + // if app already loaded synchronously, resolve on next tick + setTimeout(() => resolve(), 0); + }); + + await allReady(); + return testElement; + } + + function tearDownDom(): void { + if (testBed) { + testBed.innerHTML = ''; + } + } + + async function allReady(): Promise { + const promises: Promise[] = []; + const waitForDidLoad = (elm: Element): void => { + if (elm != null && elm.nodeType === 1) { + for (let i = 0; i < elm.children.length; i++) { + const childElm = elm.children[i] as any; + if (childElm.tagName && childElm.tagName.includes('-') && typeof childElm.componentOnReady === 'function') { + promises.push(childElm.componentOnReady()); + } + waitForDidLoad(childElm); + } + } + }; + waitForDidLoad(window.document.documentElement); + await Promise.all(promises).catch(() => undefined); + } + + return { setupDom, tearDownDom }; +} + + diff --git a/test/bundler/jest.config.js b/test/bundler/jest.config.js new file mode 100644 index 00000000000..9c67f7f0a43 --- /dev/null +++ b/test/bundler/jest.config.js @@ -0,0 +1,21 @@ +const path = require('path'); +const base = require('../../jest.config.js'); + +const rootDir = path.resolve(__dirname, '../..'); +const modulePathIgnorePatterns = (base.modulePathIgnorePatterns || []).filter( + (p) => !/\\/test\//.test(p) +); + +module.exports = { + rootDir, + testEnvironment: base.testEnvironment || 'jsdom', + setupFilesAfterEnv: base.setupFilesAfterEnv || ['/testing/jest-setuptestframework.js'], + transform: base.transform, + moduleNameMapper: base.moduleNameMapper, + moduleFileExtensions: base.moduleFileExtensions, + testPathIgnorePatterns: base.testPathIgnorePatterns || [], + modulePathIgnorePatterns, + testRegex: '/test/bundler/.*\\.spec\\.ts$', +}; + + diff --git a/test/bundler/karma-stencil-utils.ts b/test/bundler/karma-stencil-utils.ts index bca9c3cab0c..ea254dd2e61 100644 --- a/test/bundler/karma-stencil-utils.ts +++ b/test/bundler/karma-stencil-utils.ts @@ -2,7 +2,8 @@ const path = require('path'); // we must use a relative path here instead of tsconfig#paths // see https://github.com/monounity/karma-typescript/issues/315 -import * as d from '../../internal'; +// Deprecated: replaced by jest-dom-utils.ts +export {}; /** * Utilities for creating a test bed to execute HTML rendering tests against @@ -26,193 +27,4 @@ type DomTestUtilities = { * @param document a `Document` compliant entity where tests may be rendered * @returns utilities to set up the DOM and tear it down within the test bed */ -export function setupDomTests(document: Document): DomTestUtilities { - /** - * All HTML will be rendered as a child of the test bed - get it from the current document (and create it, if it - * doesn't exist) so that it is available for all future tests. - */ - let testBed = document.getElementById('test-app'); - if (!testBed) { - testBed = document.createElement('div'); - testBed.id = 'test-app'; - document.body.appendChild(testBed); - } - - /** - * @see {@link DomTestUtilities#setupDom} - */ - function setupDom(url: string): Promise { - const testElement = document.createElement('div'); - testElement.className = 'test-spec'; - - if (!testBed) { - console.error('The Stencil/Karma test bed could not be found.'); - process.exit(1); - } - - testBed.appendChild(testElement); - - return renderTest(url, testElement); - } - - /** - * Render HTML for executing tests against. - * @param url the location on disk containing the HTML to load - * @param testElement a parent HTML element to place test code in - * @returns the fully rendered HTML to test against - */ - function renderTest(url: string, testElement: HTMLElement): Promise { - // 'base' is the directory that karma will serve all assets from - url = path.join('base', url); - - return new Promise((resolve, reject) => { - /** - * Callback to be invoked following the retrieval of the file containing the HTML to load - * @param this the `XMLHttpRequest` instance that requested the HTML - */ - const indexHtmlLoaded = function (this: XMLHttpRequest): void { - if (this.status !== 200) { - reject(`404: ${url}`); - return; - } - - testElement.innerHTML = this.responseText; - - /** - * Re-generate script tags that are embedded in the loaded HTML file. - * - * Doing so allows JS files to be loaded (via script tags), when the HTML is served, without having to configure - * Karma to load the JS explicitly. This is done by adding the host/port combination to existing `src` - * attributes. - * - * Before: - * ```html - * - * ``` - * - * After: - * ```html - * - * ``` - */ - const parseAndRebuildScriptTags = () => { - const tempScripts: NodeListOf = testElement.querySelectorAll('script'); - for (let i = 0; i < tempScripts.length; i++) { - const script: HTMLScriptElement = document.createElement('script'); - if (tempScripts[i].src) { - script.src = tempScripts[i].src; - } - if (tempScripts[i].hasAttribute('nomodule')) { - script.setAttribute('nomodule', ''); - } - if (tempScripts[i].hasAttribute('type')) { - const typeAttribute = tempScripts[i].getAttribute('type'); - if (typeof typeAttribute === 'string') { - // older DOM implementations would return an empty string to designate `null` - // here, we interpret the empty string to be a valid value - script.setAttribute('type', typeAttribute); - } - } - script.innerHTML = tempScripts[i].innerHTML; - - if (tempScripts[i].parentNode) { - // the scripts were found by querying a common parent node, which _should_ still exist - tempScripts[i].parentNode!.insertBefore(script, tempScripts[i]); - tempScripts[i].parentNode!.removeChild(tempScripts[i]); - } else { - // if for some reason the parent node no longer exists, something's manipulated it while we were parsing - // the script tags. this can lead to undesirable & hard to debug behavior, fail. - reject('the parent node for script tags no longer exists. exiting.'); - } - } - }; - - parseAndRebuildScriptTags(); - - /** - * Create a listener for Stencil's "appload" event to signal to the test framework the application and its - * children have finished loading - */ - const onAppLoad = () => { - window.removeEventListener('appload', onAppLoad); - allReady().then(() => { - resolve(testElement); - }); - }; - window.addEventListener('appload', onAppLoad); - }; - - /** - * Ensure that all `onComponentReady` functions on Stencil elements in the DOM have been called before rendering - * @returns an array of promises, one for each `onComponentReady` found on a Stencil component - */ - const allReady = (): Promise => { - const promises: Promise[] = []; - - /** - * Function that recursively traverses the DOM, looking for Stencil components. Any `componentOnReady` - * functions found on Stencil components are pushed to a buffer to be run after traversing the entire DOM. - * @param elm the current element being inspected - */ - const waitForDidLoad = (elm: Element): void => { - if (elm != null && elm.nodeType === 1) { - // the element exists and is an `ELEMENT_NODE` - for (let i = 0; i < elm.children.length; i++) { - const childElm = elm.children[i]; - if (childElm.tagName.includes('-') && isHtmlStencilElement(childElm)) { - promises.push(childElm.componentOnReady()); - } - waitForDidLoad(childElm); - } - } - }; - - // recursively walk the DOM to find all `onComponentReady` functions - waitForDidLoad(window.document.documentElement); - - return Promise.all(promises).catch((e) => console.error(e)); - }; - - try { - const testHtmlRequest = new XMLHttpRequest(); - testHtmlRequest.addEventListener('load', indexHtmlLoaded); - testHtmlRequest.addEventListener('error', (err) => { - console.error('error testHtmlRequest.addEventListener', err); - reject(err); - }); - testHtmlRequest.open('GET', url); - testHtmlRequest.send(); - } catch (e: unknown) { - console.error('catch error', e); - reject(e); - } - }); - } - - /** - * @see {@link DomTestUtilities#tearDownDom} - */ - function tearDownDom(): void { - if (testBed) { - testBed.innerHTML = ''; - } - } - - return { setupDom, tearDownDom }; -} - -/** - * Type guard to verify some entity is an instance of Stencil HTML Element - * @param elm the entity to test - * @returns `true` if the entity is a Stencil HTML Element, `false` otherwise - */ -function isHtmlStencilElement(elm: unknown): elm is d.HTMLStencilElement { - // `hasOwnProperty` does not act as a type guard/narrow `elm` in any way, so we use an assertion to verify that - // `onComponentReady` is a function - return ( - elm != null && - typeof elm === 'object' && - elm.hasOwnProperty('onComponentReady') && - typeof (elm as any).onComponentReady === 'function' - ); -} +// diff --git a/test/bundler/karma.config.ts b/test/bundler/karma.config.ts index 1053a7cdacc..5b7be1f5c4b 100644 --- a/test/bundler/karma.config.ts +++ b/test/bundler/karma.config.ts @@ -28,52 +28,5 @@ const localLaunchers = { * @param config the configuration object. this object will be updated/mutated with the settings necessary to run our * tests */ -module.exports = function (config: Config): void { - config.set({ - browsers: Object.keys(localLaunchers), - colors: true, - files: [ - // general utilities for running Stencil + Karma - 'karma-stencil-utils.ts', - - // use the application built by vite - { pattern: 'vite-bundle-test/dist/index.html', nocache: true }, - { - pattern: 'vite-bundle-test/dist/**/*.js', - // don't include these files via