-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: dynamic import retry plugin [KM-865] #2
base: main
Are you sure you want to change the base?
Changes from all commits
9ddda00
96fd000
5d1fbb5
5042a73
73dc74e
b87ef41
5afe05b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,3 +20,6 @@ dist | |
|
||
# Local History | ||
.history | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe some additional playwright artifact paths will need to be added |
||
playground-temp | ||
temp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
## Directory Overview | ||
|
||
This directory serves as a testing environment for plugins. Each subdirectory corresponds to a specific plugin, identified by its name. | ||
|
||
* `vitestGlobalSetup.ts`: This is the global setup file for Vitest, executed once before all tests. It initializes a headless browser environment. | ||
* `vitestSetup.ts`: This is the per-test setup file for Vitest, executed before each individual test. It provides references to the browser instance and the page context for use during testing. | ||
|
||
## Steps to Test Your Plugin | ||
1. Create a new subdirectory within the playground directory, using your plugin’s name as the folder name. | ||
2. Within this folder, set up one or more Vite projects for testing. | ||
3. Write test files with the `.spec.ts` extension to validate your plugin’s functionality. | ||
4. If browser-based testing is required, you can import `page` or `browser` from the `vitestSetup.ts` file to interact with the headless browser environment. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { beforeAll, expect, test } from 'vitest' | ||
import { resolve } from 'node:path' | ||
import type { InlineConfig } from 'vite' | ||
import { build, preview } from 'vite' | ||
|
||
import { page, browserErrors, browserLogs } from '../vitestSetup' | ||
|
||
const root = resolve(__dirname, 'vue-router') | ||
|
||
let viteTestUrl: string | ||
|
||
beforeAll(async () => { | ||
const testConfig: InlineConfig = { | ||
configFile: resolve(root, 'vite.config.ts'), | ||
} | ||
|
||
await build(testConfig) | ||
const previewServer = await preview(testConfig) | ||
|
||
viteTestUrl = previewServer.resolvedUrls!.local[0] | ||
await page.goto(viteTestUrl) | ||
|
||
return async () => { | ||
previewServer.close() | ||
} | ||
}) | ||
|
||
test('should dynamic import module successful', async () => { | ||
await page.click('#nav-about') | ||
await page.waitForTimeout(100) | ||
expect(await page.textContent('h2')).toMatch('AboutView') | ||
}) | ||
|
||
const resources = ['css', 'js'] | ||
|
||
for (const resourceType of resources) { | ||
const filter = (url: URL) => url.pathname.includes('AboutView') && url.pathname.includes(`.${resourceType}`) | ||
|
||
test('should recover by retrying import ' + resourceType, async () => { | ||
await page.goto(viteTestUrl) | ||
|
||
let retries = 0 | ||
await page.route(filter, route => { | ||
retries++ | ||
if (retries < 3) { | ||
return route.fulfill({ status: 404, body: 'Not Found' }) | ||
} | ||
return route.continue() | ||
}) | ||
await page.click('#nav-about') | ||
await page.waitForTimeout(1000) | ||
expect(await page.textContent('h2')).toMatch('AboutView') | ||
expect(retries).toBe(3) | ||
}) | ||
|
||
test('should has an error when reach max attempts on loading ' + resourceType, async () => { | ||
await page.goto(viteTestUrl) | ||
|
||
await page.route(filter, route => { | ||
return route.fulfill({ status: 404, body: 'Not Found' }) | ||
}) | ||
await page.click('#nav-about') | ||
await page.waitForTimeout(1500) | ||
|
||
if (resourceType === 'js') { | ||
expect(await page.textContent('h2')).toMatch('HomeView') | ||
} | ||
|
||
if (resourceType === 'css') { | ||
const e = browserErrors.find(e => e.message.includes('[preload-css-retried]')) | ||
expect(e).toBeDefined() | ||
} else { | ||
const e = browserLogs.find(l => l.includes('[dynamic-import-retry]')) | ||
expect(e).toBeDefined() | ||
} | ||
}) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite + Vue + TS</title> | ||
</head> | ||
<body> | ||
<div id="app"></div> | ||
<script type="module" src="/src/main.ts"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<script setup lang="ts"> | ||
import { computed } from 'vue' | ||
import { useRoute, useRouter } from 'vue-router' | ||
|
||
const router = useRouter() | ||
const route = useRoute() | ||
|
||
const search = computed({ | ||
get() { | ||
return route.query.search ?? '' | ||
}, | ||
set(search) { | ||
router.replace({ query: { search } }) | ||
}, | ||
}) | ||
</script> | ||
|
||
<template> | ||
<h2>AboutView</h2> | ||
<label> | ||
Search: <input | ||
v-model.trim="search" | ||
maxlength="20" | ||
> | ||
</label> | ||
</template> | ||
|
||
<style scoped> | ||
h2 { | ||
color: blue; | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<template> | ||
<h1>Hello App!</h1> | ||
<nav> | ||
<RouterLink | ||
id="nav-home" | ||
to="/" | ||
> | ||
Go to Home | ||
</RouterLink> | ||
<RouterLink | ||
id="nav-about" | ||
to="/about" | ||
> | ||
Go to About | ||
</RouterLink> | ||
</nav> | ||
<main> | ||
<RouterView /> | ||
</main> | ||
</template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<script lang="ts" setup> | ||
import { useRouter } from 'vue-router' | ||
const router = useRouter() | ||
const goToAbout = () => router.push('/about') | ||
</script> | ||
|
||
<template> | ||
<h2>HomeView</h2> | ||
<button @click="goToAbout"> | ||
Go to About | ||
</button> | ||
</template> | ||
|
||
<style scoped> | ||
h2 { | ||
color: red; | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { createApp } from 'vue' | ||
import App from './App.vue' | ||
|
||
import { createRouter, createWebHistory } from 'vue-router' | ||
|
||
const router = createRouter({ | ||
history: createWebHistory(), | ||
routes: [ | ||
{ path: '/', component: () => import('./HomeView.vue') }, | ||
{ path: '/about', component: () => import('./AboutView.vue') }, | ||
], | ||
}) | ||
|
||
createApp(App) | ||
.use(router) | ||
.mount('#app') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/// <reference types="vite/client" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we can remove this file and reference the types in the tsConfig |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,10 @@ | ||||||
{ | ||||||
"extends": "../../../tsconfig.json", | ||||||
"compilerOptions": { | ||||||
"baseUrl": ".", | ||||||
"outDir": "dist", | ||||||
"declaration": false, | ||||||
"declarationDir":null | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
}, | ||||||
"include": ["vite.config.ts"] | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { defineConfig } from 'vite' | ||
import vue from '@vitejs/plugin-vue' | ||
import { resolve } from 'node:path' | ||
|
||
import { DynamicImportRetryPlugin } from '../../../src/plugin-dynamic-import-retry' | ||
|
||
// https://vite.dev/config/ | ||
export default defineConfig({ | ||
logLevel: 'silent', | ||
root: resolve(__dirname), | ||
plugins: [ | ||
vue(), | ||
DynamicImportRetryPlugin(), | ||
], | ||
build: { | ||
minify: false, | ||
outDir: 'dist', | ||
target: 'esnext', | ||
emptyOutDir: false, | ||
}, | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import os from 'node:os' | ||
import path from 'node:path' | ||
import fs from 'fs-extra' | ||
import type { BrowserServer } from 'playwright-chromium' | ||
import { chromium } from 'playwright-chromium' | ||
|
||
const DIR = path.join(os.tmpdir(), 'vitest_playwright_global_setup') | ||
|
||
let browserServer: BrowserServer | undefined | ||
|
||
export async function setup(): Promise<void> { | ||
adamdehaven marked this conversation as resolved.
Show resolved
Hide resolved
|
||
browserServer = await chromium.launchServer({ | ||
headless: !process.env.VITE_DEBUG_SERVE, | ||
args: process.env.CI | ||
? ['--no-sandbox', '--disable-setuid-sandbox'] | ||
: undefined, | ||
}) | ||
|
||
await fs.mkdirp(DIR) | ||
await fs.writeFile(path.join(DIR, 'wsEndpoint'), browserServer.wsEndpoint()) | ||
} | ||
|
||
export async function teardown(): Promise<void> { | ||
await browserServer?.close() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we tie the cache key here to a dynamic string that includes the playwright dependency version from package.json?