From e4bd88e4a855f369738e41b9120ab483b5868657 Mon Sep 17 00:00:00 2001 From: Julien Ripouteau Date: Sat, 30 Mar 2024 15:44:17 +0100 Subject: [PATCH] fix: handle cors issue --- configure.ts | 10 ++++ package.json | 4 ++ providers/vite_provider.ts | 11 +--- src/client/config.ts | 7 +++ src/{middleware => }/vite_middleware.ts | 14 ++++- tests/backend/middleware.spec.ts | 76 +++++++++++++++++++++++++ tests/backend/provider.spec.ts | 2 +- tests/client/config.spec.ts | 10 ++++ tests/configure.spec.ts | 6 +- 9 files changed, 128 insertions(+), 12 deletions(-) rename src/{middleware => }/vite_middleware.ts (74%) create mode 100644 tests/backend/middleware.spec.ts diff --git a/configure.ts b/configure.ts index e423f4c..3786808 100644 --- a/configure.ts +++ b/configure.ts @@ -25,11 +25,21 @@ export async function configure(command: Configure) { await codemods.makeUsingStub(stubsRoot, 'vite.config.stub', {}) await codemods.makeUsingStub(stubsRoot, 'js_entrypoint.stub', {}) + /** + * Update RC file + */ await codemods.updateRcFile((rcFile) => { rcFile.addProvider('@adonisjs/vite/vite_provider') rcFile.addMetaFile('public/**', false) }) + /** + * Add server middleware + */ + await codemods.registerMiddleware('server', [ + { path: '@adonisjs/vite/vite_middleware', position: 'after' }, + ]) + /** * Prompt when `install` or `--no-install` flags are * not used diff --git a/package.json b/package.json index d4ba809..96fb9a4 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ ".": "./build/index.js", "./vite_provider": "./build/providers/vite_provider.js", "./plugins/edge": "./build/src/plugins/edge.js", + "./vite_middleware": "./build/src/vite_middleware.js", "./build_hook": "./build/src/hooks/build_hook.js", "./services/main": "./build/services/vite.js", "./client": "./build/src/client/main.js", @@ -52,6 +53,7 @@ "@japa/snapshot": "^2.0.4", "@swc/core": "^1.4.2", "@types/node": "^20.11.20", + "@types/supertest": "^6.0.2", "c8": "^9.1.0", "copyfiles": "^2.4.1", "del-cli": "^5.1.0", @@ -59,6 +61,7 @@ "eslint": "^8.57.0", "np": "^10.0.0", "prettier": "^3.2.5", + "supertest": "^6.3.4", "ts-node": "^10.9.2", "tsup": "^8.0.2", "typescript": "~5.3.3", @@ -126,6 +129,7 @@ "entry": [ "./src/hooks/build_hook.ts", "./providers/vite_provider.ts", + "./src/vite_middleware.ts", "./src/plugins/edge.ts", "./src/client/main.ts", "./services/vite.ts", diff --git a/providers/vite_provider.ts b/providers/vite_provider.ts index e036d9b..f238d21 100644 --- a/providers/vite_provider.ts +++ b/providers/vite_provider.ts @@ -12,7 +12,7 @@ import type { cspKeywords as ShieldCSPKeywords } from '@adonisjs/shield' import { Vite } from '../src/vite.js' import type { ViteOptions } from '../src/types.js' -import ViteMiddleware from '../src/middleware/vite_middleware.js' +import ViteMiddleware from '../src/vite_middleware.js' declare module '@adonisjs/core/types' { interface ContainerBindings { @@ -80,7 +80,7 @@ export default class ViteProvider { const vite = new Vite(this.#shouldRunVite, config) this.app.container.bind('vite', () => vite) - this.app.container.bind(ViteMiddleware, () => new ViteMiddleware(vite)) + this.app.container.singleton(ViteMiddleware, () => new ViteMiddleware(vite)) } /** @@ -92,13 +92,8 @@ export default class ViteProvider { if (!this.#shouldRunVite) return - const [vite, server] = await Promise.all([ - this.app.container.make('vite'), - this.app.container.make('server'), - ]) - + const vite = await this.app.container.make('vite') await vite.createDevServer() - server.use([() => import('../src/middleware/vite_middleware.js')]) } /** diff --git a/src/client/config.ts b/src/client/config.ts index b21a90d..d9aa08e 100644 --- a/src/client/config.ts +++ b/src/client/config.ts @@ -41,6 +41,13 @@ export function configHook( publicDir: userConfig.publicDir ?? false, base: resolveBase(userConfig, options, command), + /** + * Disable the vite dev server cors handling. Otherwise, it will + * override the cors settings defined by @adonisjs/cors + * https://github.com/adonisjs/vite/issues/13 + */ + server: { cors: userConfig.server?.cors ?? false }, + build: { assetsDir: '', emptyOutDir: true, diff --git a/src/middleware/vite_middleware.ts b/src/vite_middleware.ts similarity index 74% rename from src/middleware/vite_middleware.ts rename to src/vite_middleware.ts index be81283..977fee9 100644 --- a/src/middleware/vite_middleware.ts +++ b/src/vite_middleware.ts @@ -11,7 +11,7 @@ import type { ViteDevServer } from 'vite' import type { HttpContext } from '@adonisjs/core/http' import type { NextFn } from '@adonisjs/core/types/http' -import type { Vite } from '../vite.js' +import type { Vite } from './vite.js' /** * Since Vite dev server is integrated within the AdonisJS process, this @@ -29,7 +29,17 @@ export default class ViteMiddleware { } async handle({ request, response }: HttpContext, next: NextFn) { - return await new Promise((resolve) => { + if (!this.#devServer) return next() + + /** + * @adonisjs/cors should handle the CORS instead of Vite + */ + if (this.#devServer.config.server.cors === false) response.relayHeaders() + + /** + * Proxy the request to the vite dev server + */ + await new Promise((resolve) => { this.#devServer.middlewares.handle(request.request, response.response, () => { return resolve(next()) }) diff --git a/tests/backend/middleware.spec.ts b/tests/backend/middleware.spec.ts new file mode 100644 index 0000000..0a73a6c --- /dev/null +++ b/tests/backend/middleware.spec.ts @@ -0,0 +1,76 @@ +/* + * @adonisjs/vite + * + * (c) AdonisJS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import supertest from 'supertest' +import { test } from '@japa/runner' +import { createServer } from 'node:http' +import { RequestFactory, ResponseFactory, HttpContextFactory } from '@adonisjs/core/factories/http' + +import { Vite } from '../../index.js' +import { createVite } from './helpers.js' +import adonisjs from '../../src/client/main.js' +import ViteMiddleware from '../../src/vite_middleware.js' + +test.group('Vite Middleware', () => { + test('if route is handled by vite, relay cors headers', async ({ assert, fs }) => { + await fs.create('resources/js/app.ts', 'console.log("Hello world")') + + const vite = await createVite( + { buildDirectory: 'foo', manifestFile: 'bar.json' }, + { plugins: [adonisjs({ entrypoints: ['./resources/js/app.ts'] })] } + ) + + const server = createServer(async (req, res) => { + const middleware = new ViteMiddleware(vite) + + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + + response.header('access-control-allow-origin', 'http://test-origin.com') + + await middleware.handle(ctx, () => {}) + + ctx.response.finish() + }) + + const res = await supertest(server).get('/resources/js/app.ts') + assert.equal(res.headers['access-control-allow-origin'], 'http://test-origin.com') + + const resOptions = await supertest(server).options('/resources/js/app.ts') + assert.equal(resOptions.headers['access-control-allow-origin'], 'http://test-origin.com') + }) + + test('if vite dev server is not available, call next middleware', async ({ assert }) => { + class FakeVite extends Vite { + getDevServer() { + return undefined + } + } + + const vite = new FakeVite(false, { buildDirectory: 'foo', manifestFile: 'bar.json' }) + + const server = createServer(async (req, res) => { + const middleware = new ViteMiddleware(vite) + + const request = new RequestFactory().merge({ req, res }).create() + const response = new ResponseFactory().merge({ req, res }).create() + const ctx = new HttpContextFactory().merge({ request, response }).create() + + await middleware.handle(ctx, () => { + ctx.response.status(200).send('handled by next middleware') + }) + + ctx.response.finish() + }) + + const res = await supertest(server).get('/resources/js/app.ts') + assert.equal(res.text, 'handled by next middleware') + }) +}) diff --git a/tests/backend/provider.spec.ts b/tests/backend/provider.spec.ts index 43e8b32..e85113c 100644 --- a/tests/backend/provider.spec.ts +++ b/tests/backend/provider.spec.ts @@ -12,7 +12,7 @@ import { setTimeout } from 'node:timers/promises' import { IgnitorFactory } from '@adonisjs/core/factories' import { defineConfig } from '../../index.js' -import ViteMiddleware from '../../src/middleware/vite_middleware.js' +import ViteMiddleware from '../../src/vite_middleware.js' const BASE_URL = new URL('./tmp/', import.meta.url) const IMPORTER = (filePath: string) => { diff --git a/tests/client/config.spec.ts b/tests/client/config.spec.ts index a95848c..1d20024 100644 --- a/tests/client/config.spec.ts +++ b/tests/client/config.spec.ts @@ -48,4 +48,14 @@ test.group('Vite plugin', () => { const config = plugin!.config!({}, { command: 'build' }) assert.deepEqual(config.base, 'https://cdn.com/') }) + + test('disable vite dev server cors handling', async ({ assert }) => { + const plugin = adonisjs({ + entrypoints: ['./resources/js/app.ts'], + })[1] as Plugin + + // @ts-ignore + const config = plugin!.config!({}, { command: 'serve' }) + assert.deepEqual(config.server?.cors, false) + }) }) diff --git a/tests/configure.spec.ts b/tests/configure.spec.ts index a0c7218..aa4f57a 100644 --- a/tests/configure.spec.ts +++ b/tests/configure.spec.ts @@ -16,7 +16,7 @@ import { BASE_URL } from './backend/helpers.js' test.group('Configure', (group) => { group.each.disableTimeout() - test('create config file and register provider', async ({ assert, fs }) => { + test('create config file and register provider and middleware', async ({ assert, fs }) => { const ignitor = new IgnitorFactory() .withCoreProviders() .withCoreConfig() @@ -33,6 +33,7 @@ test.group('Configure', (group) => { await fs.create('.env', '') await fs.createJson('tsconfig.json', {}) await fs.create('adonisrc.ts', `export default defineConfig({})`) + await fs.create('start/kernel.ts', `server.use([])`) const app = ignitor.createApp('web') await app.init() @@ -51,6 +52,7 @@ test.group('Configure', (group) => { await assert.fileContains('vite.config.ts', `import adonisjs from '@adonisjs/vite/client'`) await assert.fileContains('adonisrc.ts', `pattern: 'public/**'`) await assert.fileContains('adonisrc.ts', `reloadServer: false`) + await assert.fileContains('start/kernel.ts', '@adonisjs/vite/vite_middleware') }) test('install package when --install flag is used', async ({ assert, fs }) => { @@ -71,6 +73,7 @@ test.group('Configure', (group) => { await fs.createJson('tsconfig.json', {}) await fs.createJson('package.json', {}) await fs.create('adonisrc.ts', `export default defineConfig({})`) + await fs.create('start/kernel.ts', `server.use([])`) const app = ignitor.createApp('web') await app.init() @@ -101,6 +104,7 @@ test.group('Configure', (group) => { await fs.createJson('tsconfig.json', {}) await fs.createJson('package.json', {}) await fs.create('adonisrc.ts', `export default defineConfig({})`) + await fs.create('start/kernel.ts', `server.use([])`) const app = ignitor.createApp('web') await app.init()