Skip to content

Commit

Permalink
Merge pull request #1346 from embroider-build/bugfix-webpack-vfs
Browse files Browse the repository at this point in the history
Bugfix: inconsistent handling of webpack virtual modules
  • Loading branch information
ef4 authored Feb 1, 2023
2 parents e1c5b45 + 460b954 commit 22d54eb
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 142 deletions.
74 changes: 63 additions & 11 deletions packages/core/src/module-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { emberVirtualPackages, emberVirtualPeerDeps, packageName as getPackageNa
import { dirname, resolve } from 'path';
import { PackageCache, Package, V2Package, explicitRelative } from '@embroider/shared-internals';
import { compile } from './js-handlebars';
import makeDebug from 'debug';
import assertNever from 'assert-never';

export interface Options {
renamePackages: {
Expand All @@ -24,16 +26,35 @@ export interface Options {
appRoot: string;
}

const externalPrefix = '/@embroider/external/';

export type Resolution =
| { result: 'continue' }
| { result: 'alias'; specifier: string; fromFile?: string }
| { result: 'rehome'; fromFile: string }
| { result: 'virtual'; filename: string; content: string };
| { result: 'virtual'; filename: string };

export class Resolver {
// Given a filename that was returned with result === 'virtual', this produces
// the corresponding contents. It's a static, stateless function because we
// recognize that that process that did resolution might not be the same one
// that loads the content.
static virtualContent(filename: string): string | undefined {
if (filename.startsWith(externalPrefix)) {
return externalShim({ moduleName: filename.slice(externalPrefix.length) });
}
return undefined;
}

constructor(private options: Options) {}

beforeResolve(specifier: string, fromFile: string): Resolution {
let resolution = this.internalBeforeResolve(specifier, fromFile);
debug('[%s] %s %s => %r', 'before', specifier, fromFile, resolution);
return resolution;
}

private internalBeforeResolve(specifier: string, fromFile: string): Resolution {
if (specifier === '@embroider/macros') {
// the macros package is always handled directly within babel (not
// necessarily as a real resolvable package), so we should not mess with it.
Expand All @@ -51,7 +72,9 @@ export class Resolver {
}

fallbackResolve(specifier: string, fromFile: string): Resolution {
return this.postHandleExternal(specifier, fromFile);
let resolution = this.postHandleExternal(specifier, fromFile);
debug('[%s] %s %s => %r', 'fallback', specifier, fromFile, resolution);
return resolution;
}

private owningPackage(fromFile: string): Package | undefined {
Expand Down Expand Up @@ -103,21 +126,28 @@ export class Resolver {
// packages get this help, v2 packages are natively supposed to make their
// own modules resolvable, and we want to push them all to do that
// correctly.
return specifier.replace(packageName, pkg.root);
return this.resolveWithinPackage(specifier, pkg);
}

let originalPkg = this.originalPackage(fromFile);
if (originalPkg && pkg.meta['auto-upgraded'] && originalPkg.name === packageName) {
// a file that was relocated into a package does a self-import of that
// package's name. This can happen when an addon (like ember-cli-mirage)
// emits files from its own treeForApp that contain imports of the app's own
// fully qualified name.
return specifier.replace(packageName, originalPkg.root);
// A file that was relocated out of a package is importing that package's
// name, it should find its own original copy.
return this.resolveWithinPackage(specifier, originalPkg);
}

return specifier;
}

private resolveWithinPackage(specifier: string, pkg: Package): string {
if ('exports' in pkg.packageJSON) {
// this is the easy case -- a package that uses exports can safely resolve
// its own name
return require.resolve(specifier, { paths: [pkg.root] });
}
return specifier.replace(pkg.name, pkg.root);
}

private preHandleExternal(specifier: string, fromFile: string): Resolution {
let pkg = this.owningPackage(fromFile);
if (!pkg || !pkg.isV2Ember()) {
Expand Down Expand Up @@ -237,7 +267,10 @@ export class Resolver {
if ((pkg.meta['auto-upgraded'] || packageName === pkg.name) && this.options.activeAddons[packageName]) {
return {
result: 'alias',
specifier: specifier.replace(packageName, this.options.activeAddons[packageName]),
specifier: this.resolveWithinPackage(
specifier,
PackageCache.shared('embroider-stage3', this.options.appRoot).get(this.options.activeAddons[packageName])
),
};
}

Expand Down Expand Up @@ -290,8 +323,7 @@ function reliablyResolvable(pkg: V2Package, packageName: string) {
function external(specifier: string): Resolution {
return {
result: 'virtual',
filename: specifier,
content: externalShim({ moduleName: specifier }),
filename: externalPrefix + specifier,
};
}

Expand All @@ -317,3 +349,23 @@ if (m.default && !m.__esModule) {
}
module.exports = m;
`) as (params: { moduleName: string }) => string;

const debug = makeDebug('embroider:resolver');
makeDebug.formatters.r = (r: Resolution) => {
switch (r.result) {
case 'alias':
if (r.fromFile) {
return `alias:${r.specifier} from ${r.fromFile}`;
} else {
return `alias:${r.specifier}`;
}
case 'rehome':
return `rehome:${r.fromFile}`;
case 'continue':
return 'continue';
case 'virtual':
return 'virtual';
default:
throw assertNever(r);
}
};
3 changes: 1 addition & 2 deletions packages/webpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@
"style-loader": "^2.0.0",
"supports-color": "^8.1.0",
"terser": "^5.7.0",
"thread-loader": "^3.0.4",
"webpack-virtual-modules": "^0.5.0"
"thread-loader": "^3.0.4"
},
"devDependencies": {
"@types/csso": "^3.5.1",
Expand Down
13 changes: 13 additions & 0 deletions packages/webpack/src/virtual-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Resolver } from '@embroider/core';
import { LoaderContext } from 'webpack';

export default function virtualLoader(this: LoaderContext<unknown>) {
let filename = this.loaders[this.loaderIndex].options;
if (typeof filename === 'string') {
let content = Resolver.virtualContent(filename);
if (content) {
return content;
}
}
throw new Error(`@embroider/webpack/src/virtual-loader received unexpected request: ${filename}`);
}
Loading

0 comments on commit 22d54eb

Please sign in to comment.