From e5da90dcf4eceab7736903fed9fcefc7ec86513d Mon Sep 17 00:00:00 2001 From: Patrick Pircher Date: Tue, 5 Nov 2024 14:36:52 +0100 Subject: [PATCH 1/2] fix custom base --- packages/compat/src/compat-app.ts | 6 +- packages/vite/src/assets.ts | 5 +- packages/vite/src/scripts.ts | 16 +- test-packages/support/testem-proxy.ts | 7 +- tests/app-template/index.html | 2 +- tests/app-template/tests/index.html | 2 +- tests/fixtures/macro-test/tests/index.html | 2 +- tests/fixtures/preprocess-addon/index.js | 2 +- .../compat-addon-classic-features-test.ts | 2 +- tests/scenarios/preprocess-test.ts | 4 +- tests/scenarios/vite-app-test.ts | 2 +- tests/scenarios/vite-internals-test.ts | 146 ++++++++++++++++++ tests/ts-app-template/index.html | 2 +- tests/ts-app-template/tests/index.html | 2 +- 14 files changed, 180 insertions(+), 20 deletions(-) diff --git a/packages/compat/src/compat-app.ts b/packages/compat/src/compat-app.ts index 4102c8b19..b111cfab7 100644 --- a/packages/compat/src/compat-app.ts +++ b/packages/compat/src/compat-app.ts @@ -459,7 +459,7 @@ export default class CompatApp { // internal implementation detail, and respecting outputPaths here is // unnecessary complexity. The corresponding code that adjusts the HTML // is in updateHTML in app.ts. - outputPaths: { app: `/assets/${this.name}.css` }, + outputPaths: { app: `/@embroider/virtual/app.css` }, registry: this.legacyEmberAppInstance.registry, minifyCSS: this.legacyEmberAppInstance.options.minifyCSS.options, }; @@ -473,10 +473,10 @@ export default class CompatApp { version: 2, 'public-assets': {}, }; - let assetPath = join(outputPath, 'assets'); + let assetPath = join(outputPath, '@embroider', 'virtual'); if (pathExistsSync(assetPath)) { for (let file of walkSync(assetPath, { directories: false })) { - addonMeta['public-assets']![`./assets/${file}`] = `/assets/${file}`; + addonMeta['public-assets']![`./@embroider/virtual/${file}`] = `/@embroider/virtual/${file}`; } } let meta: PackageInfo = { diff --git a/packages/vite/src/assets.ts b/packages/vite/src/assets.ts index c9a963555..8319b2471 100644 --- a/packages/vite/src/assets.ts +++ b/packages/vite/src/assets.ts @@ -43,8 +43,9 @@ export function assets(): Plugin { configureServer(server) { return () => { server.middlewares.use((req, res, next) => { - if (req.originalUrl && req.originalUrl.length > 1) { - const assetUrl = findPublicAsset(req.originalUrl.split('?')[0], resolverLoader.resolver); + const originalUrl = req.originalUrl!.slice((server.config.base.length || 1) - 1); + if (originalUrl && originalUrl.length > 1) { + const assetUrl = findPublicAsset(originalUrl.split('?')[0], resolverLoader.resolver); if (assetUrl) { return send(req, assetUrl).pipe(res as unknown as NodeJS.WritableStream); } diff --git a/packages/vite/src/scripts.ts b/packages/vite/src/scripts.ts index de6e991b6..dd19d03fa 100644 --- a/packages/vite/src/scripts.ts +++ b/packages/vite/src/scripts.ts @@ -26,11 +26,14 @@ export function scripts(params?: { include?: string[]; exclude?: string[] }): Pl } }); + let config: any = null; + return { name: 'embroider-scripts', enforce: 'pre', configResolved(resolvedConfig) { + config = resolvedConfig; optimizer = new ScriptOptimizer(resolvedConfig.root); }, @@ -47,7 +50,7 @@ export function scripts(params?: { include?: string[]; exclude?: string[] }): Pl // we don't do anything in `vite dev`, we only need to work in `vite // build` if (!context.server) { - return optimizer.transformHTML(htmlIn); + return optimizer.transformHTML(htmlIn, config.base); } }, }; @@ -123,16 +126,25 @@ class ScriptOptimizer { return fileParts.join('.'); } - transformHTML(htmlIn: string) { + transformHTML(htmlIn: string, baseUrl: string) { if (this.transformState?.htmlIn !== htmlIn) { let parsed = new JSDOM(htmlIn); let scriptTags = [...parsed.window.document.querySelectorAll('script')] as HTMLScriptElement[]; + let linkTags = [...parsed.window.document.querySelectorAll('link')] as HTMLLinkElement[]; + for (const linkTag of linkTags) { + if (linkTag.href.startsWith('/@embroider/virtual')) { + linkTag.href = baseUrl + linkTag.href.slice(1); + } + } for (let scriptTag of scriptTags) { if (scriptTag.type !== 'module') { let fingerprinted = this.emitted.get(scriptTag.src); if (fingerprinted) { scriptTag.src = fingerprinted; } + if (scriptTag.src.startsWith('/@embroider/virtual')) { + scriptTag.src = baseUrl + scriptTag.src.slice(1); + } } } let htmlOut = parsed.serialize(); diff --git a/test-packages/support/testem-proxy.ts b/test-packages/support/testem-proxy.ts index 13507afb3..324ce58a6 100644 --- a/test-packages/support/testem-proxy.ts +++ b/test-packages/support/testem-proxy.ts @@ -10,7 +10,7 @@ import type { Application } from 'express'; "/tests/index.html" URL. */ -export function testemProxy(targetURL: string) { +export function testemProxy(targetURL: string, base = '/') { return function testemProxyHandler(app: Application) { const proxy = httpProxy.createProxyServer({ changeOrigin: true, @@ -23,10 +23,11 @@ export function testemProxy(targetURL: string) { app.all('*', (req, res, next) => { let url = req.url; - if (url === '/testem.js' || url.startsWith('/testem/')) { + if (url === `${base}testem.js` || url.startsWith('/testem/')) { + req.url = req.url.replace(base, '/'); return next(); } - let m = /^(\/\d+)\/tests\/index.html/.exec(url); + let m = /^(\/\d+).*\/tests\/index.html/.exec(url); if (m) { url = url.slice(m[1].length); } diff --git a/tests/app-template/index.html b/tests/app-template/index.html index b385800fa..41c4e8eab 100644 --- a/tests/app-template/index.html +++ b/tests/app-template/index.html @@ -9,7 +9,7 @@ {{content-for "head"}} - + {{content-for "head-footer"}} diff --git a/tests/app-template/tests/index.html b/tests/app-template/tests/index.html index 13780e80d..42ff7bd6a 100644 --- a/tests/app-template/tests/index.html +++ b/tests/app-template/tests/index.html @@ -9,7 +9,7 @@ {{content-for "head"}} {{content-for "test-head"}} - + {{content-for "head-footer"}} {{content-for "test-head-footer"}} diff --git a/tests/fixtures/macro-test/tests/index.html b/tests/fixtures/macro-test/tests/index.html index f6a21fea2..ecb008bea 100644 --- a/tests/fixtures/macro-test/tests/index.html +++ b/tests/fixtures/macro-test/tests/index.html @@ -11,7 +11,7 @@ {{content-for "test-head"}} - + {{content-for "head-footer"}} diff --git a/tests/fixtures/preprocess-addon/index.js b/tests/fixtures/preprocess-addon/index.js index a23cc849a..36065fcf7 100644 --- a/tests/fixtures/preprocess-addon/index.js +++ b/tests/fixtures/preprocess-addon/index.js @@ -34,7 +34,7 @@ module.exports = { let relativePathWithPrefix = `/${relativePath}`; if (relativePathWithPrefix === `${inputPath}/app.css`) { - return join(outputPath, 'app-template.css'); + return join(outputPath, '../@embroider/virtual/app.css'); } return join(outputPath, relativePathWithPrefix.replace(inputPath, '')); diff --git a/tests/scenarios/compat-addon-classic-features-test.ts b/tests/scenarios/compat-addon-classic-features-test.ts index c00634731..1355c2385 100644 --- a/tests/scenarios/compat-addon-classic-features-test.ts +++ b/tests/scenarios/compat-addon-classic-features-test.ts @@ -72,7 +72,7 @@ appScenarios {{content-for "head"}} - + {{content-for "head-footer"}} diff --git a/tests/scenarios/preprocess-test.ts b/tests/scenarios/preprocess-test.ts index babdb3a99..0c610fcfd 100644 --- a/tests/scenarios/preprocess-test.ts +++ b/tests/scenarios/preprocess-test.ts @@ -39,7 +39,7 @@ appScenarios test(`css is transformed: build mode`, async function (assert) { let result = await app.execute(`pnpm build`); assert.strictEqual(result.exitCode, 0, result.output); - let text = readFileSync(join(app.dir, `dist/assets/app-template.css`), 'utf8'); + let text = readFileSync(join(app.dir, `dist/@embroider/virtual/app.css`), 'utf8'); assert.strictEqual(text, 'body { background: red; }'); }); @@ -47,7 +47,7 @@ appScenarios const server = CommandWatcher.launch('vite', ['--clearScreen', 'false'], { cwd: app.dir }); try { const [, url] = await server.waitFor(/Local:\s+(https?:\/\/.*)\//g); - let response = await fetch(`${url}/assets/app-template.css`); + let response = await fetch(`${url}/@embroider/virtual/app.css`); let text = await response.text(); assert.strictEqual(text, 'body { background: red; }'); } finally { diff --git a/tests/scenarios/vite-app-test.ts b/tests/scenarios/vite-app-test.ts index 5c3d61a89..384bfd28b 100644 --- a/tests/scenarios/vite-app-test.ts +++ b/tests/scenarios/vite-app-test.ts @@ -307,7 +307,7 @@ appScenarios assert.ok(distFiles.includes('assets'), 'should have created assets folder'); assert.ok(distFiles.includes('robots.txt'), 'should have copied app assets'); - const assetFiles = readdirSync(join(app.dir, 'dist', 'assets')); + const assetFiles = readdirSync(join(app.dir, 'dist', '@embroider', 'virtual')); assert.ok(assetFiles.length > 1, 'should have created asset files'); }); }); diff --git a/tests/scenarios/vite-internals-test.ts b/tests/scenarios/vite-internals-test.ts index e95d63c19..dc5295856 100644 --- a/tests/scenarios/vite-internals-test.ts +++ b/tests/scenarios/vite-internals-test.ts @@ -4,6 +4,8 @@ import QUnit from 'qunit'; import fetch from 'node-fetch'; import CommandWatcher from './helpers/command-watcher'; import { setupAuditTest } from '@embroider/test-support/audit-assertions'; +import { mkdirSync, moveSync, readFileSync, writeFileSync } from 'fs-extra'; +import { resolve } from 'path'; const { module: Qmodule, test } = QUnit; @@ -354,3 +356,147 @@ tsAppScenarios buildViteInternalsTest(false, app); }) .forEachScenario(runViteInternalsTest); + +tsAppScenarios + .map('vite-with-base-internals', app => { + // These are for a custom testem setup that will let us do runtime tests + // inside `vite dev` rather than only against the output of `vite build`. + // + // Most apps should run their CI against `vite build`, as that's closer to + // production. And they can do development tests directly in brower against + // `vite dev` at `/tests/index.html`. We're doing `vite dev` in CI here + // because we're testing the development experience itself. + app.linkDevDependency('testem', { baseDir: __dirname }); + app.linkDevDependency('@embroider/test-support', { baseDir: __dirname }); + + app.linkDevDependency('ember-page-title', { baseDir: __dirname }); + app.linkDevDependency('ember-welcome-page', { baseDir: __dirname }); + const customBase = '/sub-dir/'; + app.mergeFiles({ + 'testem-dev.js': ` + 'use strict'; + + module.exports = { + test_page: '${customBase}tests/index.html?hidepassed', + disable_watching: true, + launch_in_ci: ['Chrome'], + launch_in_dev: ['Chrome'], + browser_start_timeout: 120, + browser_args: { + Chrome: { + ci: [ + // --no-sandbox is needed when running Chrome inside a container + process.env.CI ? '--no-sandbox' : null, + '--headless', + '--disable-dev-shm-usage', + '--disable-software-rasterizer', + '--mute-audio', + '--remote-debugging-port=0', + '--window-size=1440,900', + ].filter(Boolean), + }, + }, + middleware: [ + require('@embroider/test-support/testem-proxy').testemProxy('http://localhost:4200', '${customBase}') + ], + }; + `, + + config: { + 'environment.js': ` + 'use strict'; + + module.exports = function (environment) { + const ENV = { + modulePrefix: 'ts-app-template', + environment, + rootURL: '${customBase}', + locationType: 'history', + EmberENV: { + EXTEND_PROTOTYPES: false, + FEATURES: { + // Here you can enable experimental features on an ember canary build + // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true + }, + }, + + APP: { + // Here you can pass flags/options to your application instance + // when it is created + }, + }; + + if (environment === 'development') { + // ENV.APP.LOG_RESOLVER = true; + // ENV.APP.LOG_ACTIVE_GENERATION = true; + // ENV.APP.LOG_TRANSITIONS = true; + // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; + // ENV.APP.LOG_VIEW_LOOKUPS = true; + } + + if (environment === 'test') { + // Testem prefers this... + ENV.locationType = 'none'; + + // keep test console output quieter + ENV.APP.LOG_ACTIVE_GENERATION = false; + ENV.APP.LOG_VIEW_LOOKUPS = false; + + ENV.APP.rootElement = '#ember-testing'; + ENV.APP.autoboot = false; + } + + if (environment === 'production') { + // here you can enable a production-specific feature + } + + return ENV; + }; + `, + }, + }); + }) + .forEachScenario(runViteInternalsTestWithBase); + +function runViteInternalsTestWithBase(scenario: Scenario) { + Qmodule(scenario.name, function (hooks) { + let app: PreparedApp; + let server: CommandWatcher; + + hooks.before(async () => { + app = await scenario.prepare(); + }); + + Qmodule('vite dev', function (hooks) { + hooks.before(async () => { + server = CommandWatcher.launch('vite', ['--clearScreen', 'false', '--base', '/sub-dir/'], { cwd: app.dir }); + const [, appURL] = await server.waitFor(/Local:\s+(https?:\/\/.*)\//g); + let testem = readFileSync(resolve(app.dir, 'testem-dev.js')).toString(); + testem = testem.replace('http://localhost:4200', appURL.replace('/sub-dir', '')); + writeFileSync(resolve(app.dir, 'testem-dev.js'), testem); + }); + + hooks.after(async () => { + await server?.shutdown(); + }); + + test('run test suite against vite dev', async function (assert) { + let result = await app.execute('pnpm testem --file testem-dev.js ci'); + assert.equal(result.exitCode, 0, result.output); + }); + }); + + Qmodule('vite build', function (hooks) { + hooks.before(async () => { + await app.execute('pnpm vite build --mode test --base /sub-dir/'); + mkdirSync(resolve(app.dir, './custom-base/sub-dir'), { recursive: true }); + moveSync(resolve(app.dir, './dist'), resolve(app.dir, './custom-base/sub-dir'), { overwrite: true }); + }); + + test('run test suite against vite dist with sub-dir', async function (assert) { + let result = await app.execute('ember test --path custom-base/sub-dir'); + assert.equal(result.exitCode, 0, result.output); + }); + }); + }); +} diff --git a/tests/ts-app-template/index.html b/tests/ts-app-template/index.html index 96cf4c8ea..717db3655 100644 --- a/tests/ts-app-template/index.html +++ b/tests/ts-app-template/index.html @@ -9,7 +9,7 @@ {{content-for "head"}} - + {{content-for "head-footer"}} diff --git a/tests/ts-app-template/tests/index.html b/tests/ts-app-template/tests/index.html index 3b15ea8e8..f56430667 100644 --- a/tests/ts-app-template/tests/index.html +++ b/tests/ts-app-template/tests/index.html @@ -10,7 +10,7 @@ {{content-for "test-head"}} - + {{content-for "head-footer"}} From 79811e7664758a91b6338e3c47ee9ad15eaeac9e Mon Sep 17 00:00:00 2001 From: Patrick Pircher Date: Tue, 10 Dec 2024 17:41:34 +0100 Subject: [PATCH 2/2] fix lint --- packages/vite/src/esbuild-request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/esbuild-request.ts b/packages/vite/src/esbuild-request.ts index 9aca75759..e17c3d7bb 100644 --- a/packages/vite/src/esbuild-request.ts +++ b/packages/vite/src/esbuild-request.ts @@ -112,7 +112,7 @@ export class EsBuildRequestAdapter implements RequestAdapter