diff --git a/packages/compat/src/compat-app-builder.ts b/packages/compat/src/compat-app-builder.ts index 5d3a45bcf..b80ce80ec 100644 --- a/packages/compat/src/compat-app-builder.ts +++ b/packages/compat/src/compat-app-builder.ts @@ -531,10 +531,7 @@ export class CompatAppBuilder { // virtual test-support.js html.insertScriptTag(html.implicitTestScripts, '@embroider/core/test-support.js'); - let implicitTestStylesAsset = this.implicitTestStylesAsset(prepared, parentEngine); - if (implicitTestStylesAsset) { - html.insertStyleLink(html.implicitTestStyles, implicitTestStylesAsset.relativePath); - } + html.insertStyleLink(html.implicitTestStyles, '@embroider/core/test-support.css'); } private implicitScriptsAsset( @@ -553,21 +550,6 @@ export class CompatAppBuilder { return asset; } - private implicitTestStylesAsset( - prepared: Map, - application: AppFiles - ): InternalAsset | undefined { - let asset = prepared.get('assets/test-support.css'); - if (!asset) { - let implicitTestStyles = this.impliedAssets('implicit-test-styles', application); - if (implicitTestStyles.length > 0) { - asset = new ConcatenatedAsset('assets/test-support.css', implicitTestStyles, this.resolvableExtensionsPattern); - prepared.set(asset.relativePath, asset); - } - } - return asset; - } - // recurse to find all active addons that don't cross an engine boundary. // Inner engines themselves will be returned, but not those engines' children. // The output set's insertion order is the proper ember-cli compatible diff --git a/packages/core/src/module-resolver.ts b/packages/core/src/module-resolver.ts index 7a6823b46..2e510a6ed 100644 --- a/packages/core/src/module-resolver.ts +++ b/packages/core/src/module-resolver.ts @@ -195,6 +195,7 @@ export class Resolver { request = this.handleImplicitModules(request); request = this.handleImplicitTestScripts(request); request = this.handleVendorStyles(request); + request = this.handleTestSupportStyles(request); request = this.handleRenaming(request); // we expect the specifier to be app relative at this point - must be after handleRenaming request = this.generateFastbootSwitch(request); @@ -445,6 +446,32 @@ export class Resolver { return logTransition('test-support', request, request.virtualize(resolve(pkg.root, '-embroider-test-support.js'))); } + private handleTestSupportStyles(request: R): R { + //TODO move the extra forwardslash handling out into the vite plugin + const candidates = [ + '@embroider/core/test-support.css', + '/@embroider/core/test-support.css', + './@embroider/core/test-support.css', + ]; + + if (!candidates.includes(request.specifier)) { + return request; + } + + let pkg = this.packageCache.ownerOfFile(request.fromFile); + if (pkg?.root !== this.options.engines[0].root) { + throw new Error( + `bug: found an import of ${request.specifier} in ${request.fromFile}, but this is not the top-level Ember app. The top-level Ember app is the only one that has support for @embroider/core/test-support.css. If you think something should be fixed in Embroider, please open an issue on https://github.com/embroider-build/embroider/issues.` + ); + } + + return logTransition( + 'test-support-styles', + request, + request.virtualize(resolve(pkg.root, '-embroider-test-support-styles.css')) + ); + } + private async handleGlobalsCompat(request: R): Promise { if (isTerminal(request)) { return request; diff --git a/packages/core/src/virtual-content.ts b/packages/core/src/virtual-content.ts index d82f20f40..62940b884 100644 --- a/packages/core/src/virtual-content.ts +++ b/packages/core/src/virtual-content.ts @@ -3,7 +3,7 @@ import type { Resolver, AddonPackage, Package } from '.'; import { explicitRelative, extensionsPattern } from '.'; import { compile } from './js-handlebars'; import { decodeImplicitTestScripts, renderImplicitTestScripts } from './virtual-test-support'; - +import { decodeTestSupportStyles, renderTestSupportStyles } from './virtual-test-support-styles'; import { decodeVirtualVendorStyles, renderVendorStyles } from './virtual-vendor-styles'; const externalESPrefix = '/@embroider/ext-es/'; @@ -53,6 +53,11 @@ export function virtualContent(filename: string, resolver: Resolver): VirtualCon return renderVendorStyles(filename, resolver); } + let isTestSupportStyles = decodeTestSupportStyles(filename); + if (isTestSupportStyles) { + return renderTestSupportStyles(filename, resolver); + } + throw new Error(`not an @embroider/core virtual file: ${filename}`); } diff --git a/packages/core/src/virtual-test-support-styles.ts b/packages/core/src/virtual-test-support-styles.ts new file mode 100644 index 000000000..0afd88b16 --- /dev/null +++ b/packages/core/src/virtual-test-support-styles.ts @@ -0,0 +1,70 @@ +import type { Package } from '@embroider/shared-internals'; +import type { V2AddonPackage } from '@embroider/shared-internals/src/package'; +import { readFileSync } from 'fs'; +import { sortBy } from 'lodash'; +import resolve from 'resolve'; +import type { Engine } from './app-files'; +import type { Resolver } from './module-resolver'; +import type { VirtualContentResult } from './virtual-content'; + +export function decodeTestSupportStyles(filename: string): boolean { + return filename.endsWith('-embroider-test-support-styles.css'); +} + +export function renderTestSupportStyles(filename: string, resolver: Resolver): VirtualContentResult { + const owner = resolver.packageCache.ownerOfFile(filename); + if (!owner) { + throw new Error(`Failed to find a valid owner for ${filename}`); + } + return { src: getTestSupportStyles(owner, resolver), watches: [] }; +} + +function getTestSupportStyles(owner: Package, resolver: Resolver): string { + let engineConfig = resolver.owningEngine(owner); + let engine: Engine = { + package: owner, + addons: new Map( + engineConfig.activeAddons.map(addon => [ + resolver.packageCache.get(addon.root) as V2AddonPackage, + addon.canResolveFromFile, + ]) + ), + isApp: true, + modulePrefix: resolver.options.modulePrefix, + appRelativePath: 'NOT_USED_DELETE_ME', + }; + + return generateTestSupportStyles(engine); +} + +function generateTestSupportStyles(engine: Engine): string { + let result: string[] = impliedAddonTestSupportStyles(engine).map((sourcePath: string): string => { + let source = readFileSync(sourcePath); + return `${source}`; + }); + + return result.join('') as string; +} + +function impliedAddonTestSupportStyles(engine: Engine): string[] { + let result: Array = []; + for (let addon of sortBy(Array.from(engine.addons.keys()), pkg => { + switch (pkg.name) { + case 'loader.js': + return 0; + case 'ember-source': + return 10; + default: + return 1000; + } + })) { + let implicitStyles = addon.meta['implicit-test-styles']; + if (implicitStyles) { + let options = { basedir: addon.root }; + for (let mod of implicitStyles) { + result.push(resolve.sync(mod, options)); + } + } + } + return result; +}