From e0f26bb9f27e9339a5ea4e3c1a3aa272f5113b61 Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 16 Jun 2020 00:18:02 -0700 Subject: [PATCH 1/4] [BundleRefPlugin] resolve imports to files too (cherry picked from commit 2bb6ac1a796c6b8661d9ca6c33bef9e23d7a0764) (cherry picked from commit 14955039ba9a2cb40197f0fdcf8fd0cc4be3337f) --- .../src/worker/bundle_refs_plugin.ts | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts index 6defcaa787b7d..501e14f4704d4 100644 --- a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts +++ b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts @@ -70,9 +70,11 @@ function hookIntoCompiler( } export class BundleRefsPlugin { - private resolvedRequestCache = new Map>(); + private readonly resolvedRefEntryCache = new Map>(); + private readonly resolvedRequestCache = new Map>(); + private readonly ignorePrefix = Path.resolve(this.bundle.contextDir) + Path.sep; - constructor(private readonly bundle: Bundle, public readonly bundleRefs: BundleRefs) {} + constructor(private readonly bundle: Bundle, private readonly bundleRefs: BundleRefs) {} apply(compiler: webpack.Compiler) { hookIntoCompiler(compiler, async (context, request) => { @@ -83,8 +85,26 @@ export class BundleRefsPlugin { }); } - private cachedResolveRequest(context: string, request: string) { - const absoluteRequest = Path.resolve(context, request); + private cachedResolveRefEntry(ref: BundleRef) { + const cached = this.resolvedRefEntryCache.get(ref); + + if (cached) { + return cached; + } + + const absoluteRequest = Path.resolve(ref.contextDir, ref.entry); + const promise = this.cachedResolveRequest(absoluteRequest).then((resolved) => { + if (!resolved) { + throw new Error(`Unable to resolve request [${ref.entry}] relative to [${ref.contextDir}]`); + } + + return resolved; + }); + this.resolvedRefEntryCache.set(ref, promise); + return promise; + } + + private cachedResolveRequest(absoluteRequest: string) { const cached = this.resolvedRequestCache.get(absoluteRequest); if (cached) { @@ -102,6 +122,7 @@ export class BundleRefsPlugin { return absoluteRequest; } + // look for an index file in directories if (stats?.isDirectory()) { for (const ext of RESOLVE_EXTENSIONS) { const indexPath = Path.resolve(absoluteRequest, `index${ext}`); @@ -112,6 +133,15 @@ export class BundleRefsPlugin { } } + // look for a file with one of the supported extensions + for (const ext of RESOLVE_EXTENSIONS) { + const filePath = `${absoluteRequest}${ext}`; + const fileStats = await safeStat(filePath); + if (fileStats?.isFile()) { + return filePath; + } + } + return; } @@ -132,7 +162,12 @@ export class BundleRefsPlugin { return; } - const resolved = await this.cachedResolveRequest(context, request); + const absoluteRequest = Path.resolve(context, request); + if (absoluteRequest.startsWith(this.ignorePrefix)) { + return; + } + + const resolved = await this.cachedResolveRequest(absoluteRequest); if (!resolved) { return; } @@ -143,23 +178,17 @@ export class BundleRefsPlugin { return; } - let matchingRef: BundleRef | undefined; for (const ref of eligibleRefs) { - const resolvedEntry = await this.cachedResolveRequest(ref.contextDir, ref.entry); + const resolvedEntry = await this.cachedResolveRefEntry(ref); if (resolved === resolvedEntry) { - matchingRef = ref; - break; + return ref; } } - if (!matchingRef) { - const bundleId = Array.from(new Set(eligibleRefs.map((r) => r.bundleId))).join(', '); - const publicDir = eligibleRefs.map((r) => r.entry).join(', '); - throw new Error( - `import [${request}] references a non-public export of the [${bundleId}] bundle and must point to one of the public directories: [${publicDir}]` - ); - } - - return matchingRef; + const bundleId = Array.from(new Set(eligibleRefs.map((r) => r.bundleId))).join(', '); + const publicDir = eligibleRefs.map((r) => r.entry).join(', '); + throw new Error( + `import [${request}] references a non-public export of the [${bundleId}] bundle and must point to one of the public directories: [${publicDir}]` + ); } } From 40ad2525c4df8bb3c6ba5cd0dc3a8cec0a441fa5 Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 16 Jun 2020 00:48:27 -0700 Subject: [PATCH 2/4] add required extraPublicDirs to embeddableExamples --- examples/embeddable_examples/kibana.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json index b3ee0de096989..486c6322fad93 100644 --- a/examples/embeddable_examples/kibana.json +++ b/examples/embeddable_examples/kibana.json @@ -6,5 +6,5 @@ "ui": true, "requiredPlugins": ["embeddable"], "optionalPlugins": [], - "extraPublicDirs": ["public/todo", "public/hello_world"] + "extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"] } From dc3df6b4d6f6bc157220d52f0e03314b972cd6b9 Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 16 Jun 2020 01:03:09 -0700 Subject: [PATCH 3/4] clear the resolution caches whenever a compilation starts --- .../src/worker/bundle_refs_plugin.ts | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts index 501e14f4704d4..4b6313b6ba6bd 100644 --- a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts +++ b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts @@ -40,35 +40,6 @@ interface RequestData { type Callback = (error?: any, result?: T) => void; type ModuleFactory = (data: RequestData, callback: Callback) => void; -/** - * Isolate the weired type juggling we have to do to add a hook to the webpack compiler - */ -function hookIntoCompiler( - compiler: webpack.Compiler, - handler: (context: string, request: string) => Promise -) { - compiler.hooks.compile.tap('BundleRefsPlugin', (compilationParams: any) => { - compilationParams.normalModuleFactory.hooks.factory.tap( - 'BundleRefsPlugin/normalModuleFactory/factory', - (wrappedFactory: ModuleFactory): ModuleFactory => (data, callback) => { - const context = data.context; - const dep = data.dependencies[0]; - - handler(context, dep.request).then( - (result) => { - if (!result) { - wrappedFactory(data, callback); - } else { - callback(undefined, result); - } - }, - (error) => callback(error) - ); - } - ); - }); -} - export class BundleRefsPlugin { private readonly resolvedRefEntryCache = new Map>(); private readonly resolvedRequestCache = new Map>(); @@ -76,12 +47,39 @@ export class BundleRefsPlugin { constructor(private readonly bundle: Bundle, private readonly bundleRefs: BundleRefs) {} - apply(compiler: webpack.Compiler) { - hookIntoCompiler(compiler, async (context, request) => { - const ref = await this.resolveRef(context, request); - if (ref) { - return new BundleRefModule(ref.exportId); - } + /** + * Called by webpack when the plugin is passed in the webpack config + */ + public apply(compiler: webpack.Compiler) { + // called whenever the compiler starts to compile, passed the params + // that will be used to create the compilation + compiler.hooks.compile.tap('BundleRefsPlugin', (compilationParams: any) => { + // clear caches because a new compilation is starting, meaning that files have + // changed and we should re-run resolutions + this.resolvedRefEntryCache.clear(); + this.resolvedRequestCache.clear(); + + // hook into the creation of NormalModule instances in webpack, if the import + // statement leading to the creation of the module is pointing to a bundleRef + // entry then create a BundleRefModule instead of a NormalModule. + compilationParams.normalModuleFactory.hooks.factory.tap( + 'BundleRefsPlugin/normalModuleFactory/factory', + (wrappedFactory: ModuleFactory): ModuleFactory => (data, callback) => { + const context = data.context; + const dep = data.dependencies[0]; + + this.resolveRef(context, dep.request).then( + (ref) => { + if (!ref) { + wrappedFactory(data, callback); + } else { + callback(undefined, new BundleRefModule(ref.exportId)); + } + }, + (error) => callback(error) + ); + } + ); }); } From 345724f728674813d2cca30c3df0006ea815888b Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 16 Jun 2020 11:29:52 -0700 Subject: [PATCH 4/4] return BundleRefModule directly --- .../kbn-optimizer/src/worker/bundle_refs_plugin.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts index 4b6313b6ba6bd..9c4d5ed7f8a98 100644 --- a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts +++ b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts @@ -68,12 +68,12 @@ export class BundleRefsPlugin { const context = data.context; const dep = data.dependencies[0]; - this.resolveRef(context, dep.request).then( - (ref) => { - if (!ref) { + this.maybeReplaceImport(context, dep.request).then( + (module) => { + if (!module) { wrappedFactory(data, callback); } else { - callback(undefined, new BundleRefModule(ref.exportId)); + callback(undefined, module); } }, (error) => callback(error) @@ -149,7 +149,7 @@ export class BundleRefsPlugin { * then an error is thrown. If the request does not resolve to a bundleRef then * undefined is returned. Otherwise it returns the referenced bundleRef. */ - private async resolveRef(context: string, request: string) { + private async maybeReplaceImport(context: string, request: string) { // ignore imports that have loaders defined or are not relative seeming if (request.includes('!') || !request.startsWith('.')) { return; @@ -179,7 +179,7 @@ export class BundleRefsPlugin { for (const ref of eligibleRefs) { const resolvedEntry = await this.cachedResolveRefEntry(ref); if (resolved === resolvedEntry) { - return ref; + return new BundleRefModule(ref.exportId); } }