Skip to content

Commit

Permalink
Merge pull request #1924 from embroider-build/virtual-test-entrypoint
Browse files Browse the repository at this point in the history
virtualise test entrypoint
  • Loading branch information
ef4 authored May 17, 2024
2 parents 7d1419e + 244ab55 commit 6b85eb1
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 162 deletions.
169 changes: 10 additions & 159 deletions packages/compat/src/compat-app-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
Resolver,
locateEmbroiderWorkingDir,
} from '@embroider/core';
import { resolve as resolvePath, posix } from 'path';
import { resolve as resolvePath } from 'path';
import type { JSDOM } from 'jsdom';
import type Options from './options';
import type { CompatResolverOptions } from './resolver-transform';
Expand Down Expand Up @@ -341,7 +341,7 @@ export class CompatAppBuilder {
return portable;
}

private insertEmberApp(asset: ParsedEmberAsset, appFiles: AppFiles[], prepared: Map<string, InternalAsset>) {
private insertEmberApp(asset: ParsedEmberAsset) {
let html = asset.html;

if (this.fastbootConfig) {
Expand Down Expand Up @@ -377,9 +377,7 @@ export class CompatAppBuilder {
}

// Test-related assets happen below this point

let testJS = this.testJSEntrypoint(appFiles, prepared);
html.insertScriptTag(html.testJavascript, testJS.relativePath, { type: 'module' });
html.insertScriptTag(html.javascript, '@embroider/core/test-entrypoint', { type: 'module' });
}

// recurse to find all active addons that don't cross an engine boundary.
Expand Down Expand Up @@ -529,7 +527,7 @@ export class CompatAppBuilder {
}
}

private prepareAsset(asset: Asset, appFiles: AppFiles[], prepared: Map<string, InternalAsset>) {
private prepareAsset(asset: Asset, prepared: Map<string, InternalAsset>) {
if (asset.kind === 'ember') {
let prior = this.assets.get(asset.relativePath);
let parsed: ParsedEmberAsset;
Expand All @@ -540,17 +538,17 @@ export class CompatAppBuilder {
} else {
parsed = new ParsedEmberAsset(asset);
}
this.insertEmberApp(parsed, appFiles, prepared);
this.insertEmberApp(parsed);
prepared.set(asset.relativePath, new BuiltEmberAsset(parsed));
} else {
prepared.set(asset.relativePath, asset);
}
}

private prepareAssets(requestedAssets: Asset[], appFiles: AppFiles[]): Map<string, InternalAsset> {
private prepareAssets(requestedAssets: Asset[]): Map<string, InternalAsset> {
let prepared: Map<string, InternalAsset> = new Map();
for (let asset of requestedAssets) {
this.prepareAsset(asset, appFiles, prepared);
this.prepareAsset(asset, prepared);
}
return prepared;
}
Expand Down Expand Up @@ -587,8 +585,8 @@ export class CompatAppBuilder {
writeFileSync(destination, asset.source, 'utf8');
}

private async updateAssets(requestedAssets: Asset[], appFiles: AppFiles[]): Promise<void> {
let assets = this.prepareAssets(requestedAssets, appFiles);
private async updateAssets(requestedAssets: Asset[]): Promise<void> {
let assets = this.prepareAssets(requestedAssets);
for (let asset of assets.values()) {
if (this.assetIsValid(asset, this.assets.get(asset.relativePath))) {
continue;
Expand Down Expand Up @@ -656,7 +654,7 @@ export class CompatAppBuilder {
let appFiles = this.updateAppJS(inputPaths.appJS);
let assets = this.gatherAssets(inputPaths);

await this.updateAssets(assets, appFiles);
await this.updateAssets(assets);

let assetPaths = assets.map(asset => asset.relativePath);

Expand Down Expand Up @@ -801,49 +799,6 @@ export class CompatAppBuilder {
private addAppBoot(appBoot?: string) {
writeFileSync(join(locateEmbroiderWorkingDir(this.compatApp.root), 'ember-app-boot.js'), appBoot ?? '');
}

private importPaths({ engine }: AppFiles, engineRelativePath: string) {
let noHBS = engineRelativePath.replace(this.resolvableExtensionsPattern, '').replace(/\.hbs$/, '');
return {
runtime: `${engine.modulePrefix}/${noHBS}`,
buildtime: posix.join(engine.package.name, engineRelativePath),
};
}

private testJSEntrypoint(appFiles: AppFiles[], prepared: Map<string, InternalAsset>): InternalAsset {
let asset = prepared.get(`assets/test.js`);
if (asset) {
return asset;
}

// We're only building tests from the first engine (the app). This is the
// normal thing to do -- tests from engines don't automatically roll up into
// the app.
let engine = appFiles[0];

const myName = 'assets/test.js';

let amdModules: { runtime: string; buildtime: string }[] = [];

for (let relativePath of engine.tests) {
amdModules.push(this.importPaths(engine, relativePath));
}

let source = entryTemplate({
amdModules,
testSuffix: true,
// this is a backward-compatibility feature: addons can force inclusion of test support modules.
defineModulesFrom: './-embroider-implicit-test-modules.js',
});

asset = {
kind: 'in-memory',
source,
relativePath: myName,
};
prepared.set(asset.relativePath, asset);
return asset;
}
}

function defaultAddonPackageRules(): PackageRules[] {
Expand All @@ -858,110 +813,6 @@ function defaultAddonPackageRules(): PackageRules[] {
.reduce((a, b) => a.concat(b), []);
}

const entryTemplate = jsHandlebarsCompile(`
import { importSync as i, macroCondition, getGlobalConfig } from '@embroider/macros';
let w = window;
let d = w.define;
{{#if styles}}
if (macroCondition(!getGlobalConfig().fastboot?.isRunning)) {
{{#each styles as |stylePath| ~}}
i("{{js-string-escape stylePath.path}}");
{{/each}}
}
{{/if}}
{{#if defineModulesFrom ~}}
import implicitModules from "{{js-string-escape defineModulesFrom}}";
for(const [name, module] of Object.entries(implicitModules)) {
d(name, function() { return module });
}
{{/if}}
import "ember-testing";
import "@embroider/core/entrypoint";
{{#each amdModules as |amdModule| ~}}
d("{{js-string-escape amdModule.runtime}}", function(){ return i("{{js-string-escape amdModule.buildtime}}");});
{{/each}}
{{#if fastbootOnlyAmdModules}}
if (macroCondition(getGlobalConfig().fastboot?.isRunning)) {
let fastbootModules = {};
{{#each fastbootOnlyAmdModules as |amdModule| ~}}
fastbootModules["{{js-string-escape amdModule.runtime}}"] = import("{{js-string-escape amdModule.buildtime}}");
{{/each}}
const resolvedValues = await Promise.all(Object.values(fastbootModules));
Object.keys(fastbootModules).forEach((k, i) => {
d(k, function(){ return resolvedValues[i];});
})
}
{{/if}}
{{#if lazyRoutes}}
w._embroiderRouteBundles_ = [
{{#each lazyRoutes as |route|}}
{
names: {{json-stringify route.names}},
load: function() {
return import("{{js-string-escape route.path}}");
}
},
{{/each}}
]
{{/if}}
{{#if lazyEngines}}
w._embroiderEngineBundles_ = [
{{#each lazyEngines as |engine|}}
{
names: {{json-stringify engine.names}},
load: function() {
return import("{{js-string-escape engine.path}}");
}
},
{{/each}}
]
{{/if}}
{{#if autoRun ~}}
if (!runningTests) {
i("{{js-string-escape mainModule}}").default.create({{json-stringify appConfig}});
}
{{else if appBoot ~}}
{{ appBoot }}
{{/if}}
{{#if testSuffix ~}}
{{!- TODO: both of these suffixes should get dynamically generated so they incorporate
any content-for added by addons. -}}
{{!- this is the traditional tests-suffix.js -}}
i('../tests/test-helper');
EmberENV.TESTS_FILE_LOADED = true;
{{/if}}
`) as (params: {
amdModules: { runtime: string; buildtime: string }[];
fastbootOnlyAmdModules?: { runtime: string; buildtime: string }[];
defineModulesFrom?: string;
eagerModules?: string[];
autoRun?: boolean;
appBoot?: string;
mainModule?: string;
appConfig?: unknown;
testSuffix?: boolean;
lazyRoutes?: { names: string[]; path: string }[];
lazyEngines?: { names: string[]; path: string }[];
styles?: { path: string }[];
}) => string;

function stringOrBufferEqual(a: string | Buffer, b: string | Buffer): boolean {
if (typeof a === 'string' && typeof b === 'string') {
return a === b;
Expand Down
35 changes: 32 additions & 3 deletions packages/core/src/module-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export class Resolver {
request = this.handleVendorStyles(request);
request = this.handleTestSupportStyles(request);
request = this.handleEntrypoint(request);
request = this.handleTestEntrypoint(request);
request = this.handleRouteEntrypoint(request);
request = this.handleRenaming(request);
request = this.handleVendor(request);
Expand Down Expand Up @@ -435,9 +436,6 @@ export class Resolver {
if (isTerminal(request)) {
return request;
}
// TODO: also handle targeting from the outside (for engines) like:
// request.specifier === 'my-package-name/-embroider-entrypoint.js'
// just like implicit-modules does.

//TODO move the extra forwardslash handling out into the vite plugin
const candidates = ['@embroider/core/entrypoint', '/@embroider/core/entrypoint', './@embroider/core/entrypoint'];
Expand Down Expand Up @@ -472,6 +470,37 @@ export class Resolver {
return logTransition('entrypoint', request, request.virtualize(resolve(pkg.root, '-embroider-entrypoint.js')));
}

private handleTestEntrypoint<R extends ModuleRequest>(request: R): R {
if (isTerminal(request)) {
return request;
}

//TODO move the extra forwardslash handling out into the vite plugin
const candidates = [
'@embroider/core/test-entrypoint',
'/@embroider/core/test-entrypoint',
'./@embroider/core/test-entrypoint',
];

if (!candidates.some(c => request.specifier === c)) {
return request;
}

const pkg = this.packageCache.ownerOfFile(request.fromFile);

if (!pkg?.isV2Ember() || !pkg.isV2App()) {
throw new Error(
`bug: found test entrypoint import from somewhere other than the top-level app engine: ${request.fromFile}`
);
}

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

private handleRouteEntrypoint<R extends ModuleRequest>(request: R): 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 @@ -8,6 +8,7 @@ import { decodeVirtualVendor, renderVendor } from './virtual-vendor';
import { decodeVirtualVendorStyles, renderVendorStyles } from './virtual-vendor-styles';

import { decodeEntrypoint, renderEntrypoint } from './virtual-entrypoint';
import { decodeTestEntrypoint, renderTestEntrypoint } from './virtual-test-entrypoint';
import { decodeRouteEntrypoint, renderRouteEntrypoint } from './virtual-route-entrypoint';

const externalESPrefix = '/@embroider/ext-es/';
Expand All @@ -33,6 +34,11 @@ export function virtualContent(filename: string, resolver: Resolver): VirtualCon
return renderEntrypoint(resolver, entrypoint);
}

let testEntrypoint = decodeTestEntrypoint(filename);
if (testEntrypoint) {
return renderTestEntrypoint(resolver, testEntrypoint);
}

let routeEntrypoint = decodeRouteEntrypoint(filename);
if (routeEntrypoint) {
return renderRouteEntrypoint(resolver, routeEntrypoint);
Expand Down
Loading

0 comments on commit 6b85eb1

Please sign in to comment.