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"}}