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

Module resolver: virtualize test-support.js #1807

Merged
merged 6 commits into from
Apr 4, 2024
51 changes: 2 additions & 49 deletions packages/compat/src/compat-app-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,32 +361,6 @@ export class CompatAppBuilder {
});
}

if (type === 'implicit-test-scripts') {
// this is the traditional test-support-suffix.js
result.push({
kind: 'in-memory',
relativePath: '_testing_suffix_.js',
source: `
var runningTests=true;
if (typeof Testem !== 'undefined' && (typeof QUnit !== 'undefined' || typeof Mocha !== 'undefined')) {
Testem.hookIntoTestFramework();
}`,
});

// whether or not anybody was actually using @embroider/macros
// explicitly as an addon, we ensure its test-support file is always
// present.
if (!result.find(s => s.kind === 'on-disk' && s.sourcePath.endsWith('embroider-macros-test-support.js'))) {
result.unshift({
kind: 'on-disk',
sourcePath: require.resolve('@embroider/macros/src/vendor/embroider-macros-test-support'),
mtime: 0,
size: 0,
relativePath: 'embroider-macros-test-support.js',
});
}
}

return result;
}

Expand Down Expand Up @@ -556,10 +530,8 @@ export class CompatAppBuilder {
let testJS = this.testJSEntrypoint(appFiles, prepared);
html.insertScriptTag(html.testJavascript, testJS.relativePath, { type: 'module' });

let implicitTestScriptsAsset = this.implicitTestScriptsAsset(prepared, parentEngine);
if (implicitTestScriptsAsset) {
html.insertScriptTag(html.implicitTestScripts, implicitTestScriptsAsset.relativePath);
}
// virtual test-support.js
html.insertScriptTag(html.implicitTestScripts, '@embroider/core/test-support.js');

let implicitTestStylesAsset = this.implicitTestStylesAsset(prepared, parentEngine);
if (implicitTestStylesAsset) {
Expand Down Expand Up @@ -596,25 +568,6 @@ export class CompatAppBuilder {
return asset;
}

private implicitTestScriptsAsset(
prepared: Map<string, InternalAsset>,
application: AppFiles
): InternalAsset | undefined {
let testSupportJS = prepared.get('assets/test-support.js');
if (!testSupportJS) {
let implicitTestScripts = this.impliedAssets('implicit-test-scripts', application);
if (implicitTestScripts.length > 0) {
testSupportJS = new ConcatenatedAsset(
'assets/test-support.js',
implicitTestScripts,
this.resolvableExtensionsPattern
);
prepared.set(testSupportJS.relativePath, testSupportJS);
}
}
return testSupportJS;
}

private implicitTestStylesAsset(
prepared: Map<string, InternalAsset>,
application: AppFiles
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/module-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export class Resolver {
request = this.handleFastbootSwitch(request);
request = await this.handleGlobalsCompat(request);
request = this.handleImplicitModules(request);
request = this.handleImplicitTestScripts(request);
request = this.handleRenaming(request);
// we expect the specifier to be app relative at this point - must be after handleRenaming
request = this.generateFastbootSwitch(request);
Expand Down Expand Up @@ -421,6 +422,32 @@ export class Resolver {
}
}

private handleImplicitTestScripts<R extends ModuleRequest>(request: R): R {
//TODO move the extra forwardslash handling out into the vite plugin
const candidates = [
'@embroider/core/test-support.js',
'/@embroider/core/test-support.js',
'./@embroider/core/test-support.js',
];

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/vendor. If you think something should be fixed in Embroider, please open an issue on https://github.com/embroider-build/embroider/issues.`
);
}

if (!pkg?.isV2Ember()) {
throw new Error(`bug: an import of ${request.specifier} in non-ember package at ${request.fromFile}`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove this check, the check up above for being the first engine is sufficient.


return logTransition('test-support', request, request.virtualize(resolve(pkg.root, '-embroider-test-support.js')));
}

private async handleGlobalsCompat<R extends ModuleRequest>(request: R): Promise<R> {
if (isTerminal(request)) {
return request;
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/virtual-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { dirname, basename, resolve, posix, sep, join } from 'path';
import type { Resolver, AddonPackage, Package } from '.';
import { explicitRelative, extensionsPattern } from '.';
import { compile } from './js-handlebars';
import { decodeImplicitTestScripts, renderImplicitTestScripts } from './virtual-test-support';

const externalESPrefix = '/@embroider/ext-es/';
const externalCJSPrefix = '/@embroider/ext-cjs/';
Expand Down Expand Up @@ -40,6 +41,11 @@ export function virtualContent(filename: string, resolver: Resolver): VirtualCon
return renderImplicitModules(im, resolver);
}

let isImplicitTestScripts = decodeImplicitTestScripts(filename);
if (isImplicitTestScripts) {
return renderImplicitTestScripts(filename, resolver);
}

throw new Error(`not an @embroider/core virtual file: ${filename}`);
}

Expand Down
88 changes: 88 additions & 0 deletions packages/core/src/virtual-test-support.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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 decodeImplicitTestScripts(filename: string): boolean {
return filename.endsWith('-embroider-test-support.js');
}

export function renderImplicitTestScripts(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: getTestSupport(owner, resolver), watches: [] };
}

function getTestSupport(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 generateTestSupport(engine);
}

function generateTestSupport(engine: Engine): string {
// Add classic addons test-support
let result: string[] = impliedAddonTestSupport(engine);
let hasEmbroiderMacrosTestSupport = result.find(sourcePath =>
sourcePath.endsWith('embroider-macros-test-support.js')
);
result = result.map((sourcePath: string): string => {
let source = readFileSync(sourcePath);
return `${source}`;
});

// Add _testing_suffix_.js
result.push(`
var runningTests=true;
if (typeof Testem !== 'undefined' && (typeof QUnit !== 'undefined' || typeof Mocha !== 'undefined')) {
Testem.hookIntoTestFramework();
}`);

// whether or not anybody was actually using @embroider/macros explicitly
// as an addon, we ensure its test-support file is always present.
if (!hasEmbroiderMacrosTestSupport) {
result.unshift(`${readFileSync(require.resolve('@embroider/macros/src/vendor/embroider-macros-test-support'))}`);
}

return result.join('') as string;
}

function impliedAddonTestSupport(engine: Engine): string[] {
let result: Array<string> = [];
for (let addon of sortBy(Array.from(engine.addons.keys()), pkg => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can delete the sortBy. It was only being used by implicit-scripts not implicit-test-scripts, and now that this function only handles implicit-test-scripts this part should never have an effect.

switch (pkg.name) {
case 'loader.js':
return 0;
case 'ember-source':
return 10;
default:
return 1000;
}
})) {
let implicitScripts = addon.meta['implicit-test-scripts'];
if (implicitScripts) {
let options = { basedir: addon.root };
for (let mod of implicitScripts) {
result.push(resolve.sync(mod, options));
}
}
}
return result;
}
11 changes: 11 additions & 0 deletions packages/vite/src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { virtualContent, ResolverLoader } from '@embroider/core';
import { RollupModuleRequest, virtualPrefix } from './request';
import assertNever from 'assert-never';
import makeDebug from 'debug';
import { resolve } from 'path';

const debug = makeDebug('embroider:vite');

Expand Down Expand Up @@ -59,5 +60,15 @@ export function resolver(): Plugin {
return src;
}
},
buildEnd() {
this.emitFile({
type: 'asset',
fileName: '@embroider/core/test-support.js',
source: virtualContent(
resolve(resolverLoader.resolver.options.engines[0].root, '-embroider-test-support.js'),
resolverLoader.resolver
).src,
});
},
};
}
Loading