Skip to content
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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/actions/setup-pnpm-with-dependencies/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,26 @@ runs:
with:
path: './node_modules'
key: ${{ steps.node-version.outputs.cache-key }}

- name: Get Playwright Chromium version
id: get-playwright-version
run: echo "PLAYWRIGHT_VERSION=$(jq -r '.devDependencies["playwright-chromium"]' < package.json | sed 's/[^0-9.]//g' | sed 's/\./-/g')" >> $GITHUB_OUTPUT
shell: bash

- name: Set Playwright path
id: playwright-path
shell: bash
run: echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/playwright-bin" >> $GITHUB_ENV

- name: Cache Playwright's binary
id: playwright-cache
uses: actions/cache@v4
with:
key: ${{ runner.os }}-playwright-bin-v${{ steps.get-playwright-version.outputs.PLAYWRIGHT_VERSION }}
path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}

- name: Install Playwright
id: playwright-install
shell: bash
# does not need to explicitly set chromium after https://github.com/microsoft/playwright/issues/14862 is solved
run: pnpm playwright install chromium
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to pass the PLAYWRIGHT_BROWSERS_PATH here as an env variable

6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,11 @@ pnpm lint:fix
Unit tests are run with [Vitest](https://vitest.dev/).

```shell
# Run tests
pnpm test

# Run tests in the Vitest UI
pnpm test:ui
```

See the [Steps to Test Your Plugin](./playground/README.md) for more information on how to write tests.

### Build

Build for production and inspect the files in the `/dist` directory.
Expand Down
1 change: 1 addition & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default defineBuildConfig({
// Each separate plugin's entry file should be listed here
entries: [
'./src/plugin-example-one/index.ts',
'./src/plugin-dynamic-import-retry/index.ts',
],
// Generates .d.ts declaration file(s)
declaration: true,
Expand Down
29 changes: 26 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
"scripts": {
"lint": "eslint",
"lint:fix": "eslint --fix",
"test": "vitest run --passWithNoTests",
"test:ui": "vitest --ui --passWithNoTests",
"test": "vitest run -c vitest.config.e2e.ts",
"typecheck": "vue-tsc --noEmit",
"build": "unbuild",
"commit": "cz"
Expand All @@ -16,9 +15,15 @@
"./plugin-example-one": {
"import": "./dist/plugin-example-one/index.mjs",
"types": "./dist/plugin-example-one/index.d.ts"
},
"./plugin-dynamic-import-retry": {
"import": "./dist/plugin-dynamic-import-retry/index.mjs",
"types": "./dist/plugin-dynamic-import-retry/index.d.ts"
Justineo marked this conversation as resolved.
Show resolved Hide resolved
}
},
"files": ["dist"],
"files": [
"dist"
],
"author": "Kong, Inc.",
"license": "Apache-2.0",
"devDependencies": {
Expand All @@ -27,11 +32,21 @@
"@digitalroute/cz-conventional-changelog-for-jira": "^8.0.1",
"@evilmartians/lefthook": "^1.10.1",
"@kong/eslint-config-kong-ui": "^1.2.4",
"@rollup/plugin-dynamic-import-vars": "^2.1.5",
"@types/fs-extra": "^11.0.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vitest/ui": "^2.1.8",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.17.0",
"fs-extra": "^11.2.0",
"npm-run-all2": "^7.0.2",
"playwright-chromium": "^1.49.1",
"typescript": "^5.7.2",
"unbuild": "^3.2.0",
"vite": "^6.0.7",
"vitest": "^2.1.8",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vue-tsc": "^2.2.0"
},
"engines": {
Expand All @@ -52,5 +67,13 @@
"jiraPrepend": "[",
"jiraAppend": "]"
}
},
"dependencies": {
"@rollup/pluginutils": "^5.1.4",
"acorn-walk": "^8.3.4",
"magic-string": "^0.30.17"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0"
}
}
19 changes: 19 additions & 0 deletions playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## 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.

## Run playground project manually
```bash
pnpm vite dev ./playground/<plugin-name>/<project-name>
pnpm vite build ./playground/<plugin-name>/<project-name>
pnpm vite preview ./playground/<plugin-name>/<project-name>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect, test } from 'vitest'
import { build, preview } from 'vite'
import { resolve } from 'node:path'

import { browserLogs, page } from '../../vitestSetup'

import type { InlineConfig } from 'vite'
import { loadConfigFromFile } from 'vite'
import { beforeAll } from 'vitest'

let viteTestUrl: string

beforeAll(async () => {
const res = await loadConfigFromFile(
{
command: 'build',
mode: 'production',
},
undefined,
resolve(__dirname),
)
if (!res) throw new Error('Failed to load config')

const testConfig: InlineConfig = {
...res.config,
logLevel: 'silent',
configFile: false,
}

await build(testConfig)
const previewServer = await preview(testConfig)

viteTestUrl = previewServer.resolvedUrls!.local[0]
await page.goto(viteTestUrl)

return async () => {
previewServer.close()
}
})

test('should work with @rollup/plugin-dynamic-import-vars', async () => {
let loadCount = 0
await page.route(
(url) => url.pathname.includes('en-') && url.pathname.includes('.js'),
route => {
loadCount++
if (loadCount < 2) {
return route.fulfill({ status: 404, body: 'Not Found' })
}
return route.continue()
},
)
await page.click('#btn-vars')
await page.waitForTimeout(1000)
const log = browserLogs.find(log => log.includes('{title: Hello World}'))
expect(loadCount).toBe(2)
expect(log).toBeDefined()
})
12 changes: 12 additions & 0 deletions playground/dynamic-import-retry/dynamic-import-vars/index.html
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>Dynamic import vars</title>
</head>
<body>
<button id="btn-vars">vars</button>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
title: 'Hello World',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const btn = document.querySelector<HTMLButtonElement>('#btn-vars')!

btn.addEventListener('click', async () => {
const lang = 'en'
const res = await import(`./locales/${lang}.ts`)
console.log(res.default)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"declaration": false,
"declarationDir": null,
"types": ["vite/client"]
},
"include": ["vite.config.ts.bak"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig } from 'vite'
import { resolve } from 'node:path'
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars'

import { DynamicImportRetryPlugin } from '../../../src/plugin-dynamic-import-retry'

export default defineConfig({
root: resolve(__dirname),
build: {
minify: false,
outDir: 'dist',
target: 'esnext',
emptyOutDir: true,
rollupOptions: {
plugins: [
dynamicImportVars(),
DynamicImportRetryPlugin(),
],
},
},
})
13 changes: 13 additions & 0 deletions playground/dynamic-import-retry/transitive/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Transitive</title>
</head>
<body>
<button id="btn-simple">simple module</button>
<button id="btn-transitive">transitive module</button>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
13 changes: 13 additions & 0 deletions playground/dynamic-import-retry/transitive/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const btnA = document.querySelector<HTMLButtonElement>('#btn-simple')!
const btnB = document.querySelector<HTMLButtonElement>('#btn-transitive')!

btnA.addEventListener('click', async () => {
const { value } = await import('./simple')
console.log(value)
})

btnB.addEventListener('click', async () => {
const { value } = await import('./transitive')
console.log(value)
})

1 change: 1 addition & 0 deletions playground/dynamic-import-retry/transitive/src/simple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = 'a'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { value as valueA } from './simple'
export const value = valueA + 'b'
64 changes: 64 additions & 0 deletions playground/dynamic-import-retry/transitive/transitive.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { expect, test } from 'vitest'
import { build, preview } from 'vite'
import { resolve } from 'node:path'

import { browserErrors, page } from '../../vitestSetup'

import type { InlineConfig } from 'vite'
import { loadConfigFromFile } from 'vite'
import { beforeAll } from 'vitest'

let viteTestUrl: string

beforeAll(async () => {
const res = await loadConfigFromFile(
{
command: 'build',
mode: 'production',
},
undefined,
resolve(__dirname),
)
if (!res) throw new Error('Failed to load config')

const testConfig: InlineConfig = {
...res.config,
logLevel: 'silent',
configFile: false,
}

await build(testConfig)
const previewServer = await preview(testConfig)

viteTestUrl = previewServer.resolvedUrls!.local[0]
await page.goto(viteTestUrl)

return async () => {
previewServer.close()
}
})

test('should not work on transitive import', async () => {
let subModuleLoadCount = 0
let entryModuleLoadCount = 0
await page.route(
(url) => url.pathname.includes('simple') && url.pathname.includes('.js'),
route => {
subModuleLoadCount++
return route.fulfill({ status: 404, body: 'Not Found' })
},
)
await page.route(
(url) => url.pathname.includes('transitive') && url.pathname.includes('.js'),
route => {
entryModuleLoadCount++
return route.continue()
},
)
await page.click('#btn-transitive')
await page.waitForTimeout(2000)
expect(entryModuleLoadCount).toBe(4)
expect(subModuleLoadCount).toBe(1)
const e = browserErrors.find(e => e.message.includes('[dynamic-import-retry]') && e.message.includes('transitive'))
expect(e).toBeDefined()
})
11 changes: 11 additions & 0 deletions playground/dynamic-import-retry/transitive/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "dist",
"declaration": false,
"declarationDir": null,
"types": ["vite/client"]
},
"include": ["vite.config.ts.bak"]
}
17 changes: 17 additions & 0 deletions playground/dynamic-import-retry/transitive/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from 'vite'
import { resolve } from 'node:path'

import { DynamicImportRetryPlugin } from '../../../src/plugin-dynamic-import-retry'

export default defineConfig({
root: resolve(__dirname),
plugins: [
DynamicImportRetryPlugin(),
],
build: {
minify: false,
outDir: 'dist',
target: 'esnext',
emptyOutDir: true,
},
})
12 changes: 12 additions & 0 deletions playground/dynamic-import-retry/vue-async-component/index.html
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>
Loading