From e8d907680b8e235f54508bc416448b5bf8285397 Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Thu, 23 Apr 2020 09:13:00 -0600 Subject: [PATCH] Use runtime publicPath for KP plugin bundles (#64226) --- .../basic_optimization.test.ts.snap | 4 +- .../src/worker/webpack.config.ts | 11 ++- packages/kbn-ui-shared-deps/index.d.ts | 5 ++ packages/kbn-ui-shared-deps/index.js | 1 + .../kbn-ui-shared-deps/public_path_loader.js | 23 ++++++ packages/kbn-ui-shared-deps/webpack.config.js | 12 ++- src/legacy/core_plugins/tests_bundle/index.js | 13 +++ .../ui/ui_render/bootstrap/template.js.hbs | 1 + src/legacy/ui/ui_render/ui_render_mixin.js | 14 ++++ src/optimize/bundles_route/bundles_route.js | 79 +++++++++++-------- .../bundles_route/dynamic_asset_response.js | 9 ++- tasks/config/karma.js | 2 + 12 files changed, 134 insertions(+), 40 deletions(-) create mode 100644 packages/kbn-ui-shared-deps/public_path_loader.js diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 4b4bb1282d939..fe0f75c05c646 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -57,6 +57,6 @@ OptimizerConfig { } `; -exports[`builds expected bundles, saves bundle counts to metadata: bar bundle 1`] = `"var __kbnBundles__=typeof __kbnBundles__===\\"object\\"?__kbnBundles__:{};__kbnBundles__[\\"plugin/bar\\"]=function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i `/${bundle.type}:${bundle.id}/${Path.relative( bundle.sourceRoot, @@ -144,6 +144,13 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { ], rules: [ + { + include: Path.join(bundle.contextDir, bundle.entry), + loader: UiSharedDeps.publicPathLoader, + options: { + key: bundle.id, + }, + }, { test: /\.css$/, include: /node_modules/, diff --git a/packages/kbn-ui-shared-deps/index.d.ts b/packages/kbn-ui-shared-deps/index.d.ts index dec519da69641..b829c87d91c4a 100644 --- a/packages/kbn-ui-shared-deps/index.d.ts +++ b/packages/kbn-ui-shared-deps/index.d.ts @@ -53,3 +53,8 @@ export const lightCssDistFilename: string; export const externals: { [key: string]: string; }; + +/** + * Webpack loader for configuring the public path lookup from `window.__kbnPublicPath__`. + */ +export const publicPathLoader: string; diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 666ec7a46ff06..42ed08259ac8f 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -64,3 +64,4 @@ exports.externals = { 'elasticsearch-browser': '__kbnSharedDeps__.ElasticsearchBrowser', 'elasticsearch-browser/elasticsearch': '__kbnSharedDeps__.ElasticsearchBrowser', }; +exports.publicPathLoader = require.resolve('./public_path_loader'); diff --git a/packages/kbn-ui-shared-deps/public_path_loader.js b/packages/kbn-ui-shared-deps/public_path_loader.js new file mode 100644 index 0000000000000..6b7a27c9ca52b --- /dev/null +++ b/packages/kbn-ui-shared-deps/public_path_loader.js @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = function(source) { + const options = this.query; + return `__webpack_public_path__ = window.__kbnPublicPath__['${options.key}'];${source}`; +}; diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index a875274544905..bf63c57765859 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -46,7 +46,6 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ path: UiSharedDeps.distDir, filename: '[name].js', sourceMapFilename: '[file].map', - publicPath: '__REPLACE_WITH_PUBLIC_PATH__', devtoolModuleFilenameTemplate: info => `kbn-ui-shared-deps/${Path.relative(REPO_ROOT, info.absoluteResourcePath)}`, library: '__kbnSharedDeps__', @@ -55,6 +54,17 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ module: { noParse: [MOMENT_SRC], rules: [ + { + include: [require.resolve('./entry.js')], + use: [ + { + loader: UiSharedDeps.publicPathLoader, + options: { + key: 'kbn-ui-shared-deps', + }, + }, + ], + }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], diff --git a/src/legacy/core_plugins/tests_bundle/index.js b/src/legacy/core_plugins/tests_bundle/index.js index 5e78047088d2a..e1966a9e8b266 100644 --- a/src/legacy/core_plugins/tests_bundle/index.js +++ b/src/legacy/core_plugins/tests_bundle/index.js @@ -148,6 +148,19 @@ export default kibana => { .type('text/css'); }, }); + + // Sets global variables normally set by the bootstrap.js script + kbnServer.server.route({ + path: '/test_bundle/karma/globals.js', + method: 'GET', + async handler(req, h) { + const basePath = config.get('server.basePath'); + + const file = `window.__kbnPublicPath__ = { 'kbn-ui-shared-deps': "${basePath}/bundles/kbn-ui-shared-deps/" };`; + + return h.response(file).header('content-type', 'application/json'); + }, + }); }, __globalImportAliases__: { diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index 7250fa4fc9eca..8a71c6ccb1506 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -1,6 +1,7 @@ var kbnCsp = JSON.parse(document.querySelector('kbn-csp').getAttribute('data')); window.__kbnStrictCsp__ = kbnCsp.strictCsp; window.__kbnDarkMode__ = {{darkMode}}; +window.__kbnPublicPath__ = {{publicPathMap}}; if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { var legacyBrowserError = document.getElementById('kbn_legacy_browser_error'); diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 1e84405dd5153..801eecf5b608b 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -153,11 +153,25 @@ export function uiRenderMixin(kbnServer, server, config) { `${regularBundlePath}/plugin/kibanaReact/kibanaReact.plugin.js`, ]; + const uiPluginIds = [...kbnServer.newPlatform.__internals.uiPlugins.public.keys()]; + + // These paths should align with the bundle routes configured in + // src/optimize/bundles_route/bundles_route.js + const publicPathMap = JSON.stringify({ + core: `${regularBundlePath}/core/`, + 'kbn-ui-shared-deps': `${regularBundlePath}/kbn-ui-shared-deps/`, + ...uiPluginIds.reduce( + (acc, pluginId) => ({ ...acc, [pluginId]: `${regularBundlePath}/plugin/${pluginId}/` }), + {} + ), + }); + const bootstrap = new AppBootstrap({ templateData: { darkMode, jsDependencyPaths, styleSheetPaths, + publicPathMap, entryBundlePath: isCore ? `${regularBundlePath}/core/core.entry.js` : `${regularBundlePath}/${app.getId()}.bundle.js`, diff --git a/src/optimize/bundles_route/bundles_route.js b/src/optimize/bundles_route/bundles_route.js index 530dabb9a46a3..4030988c8552c 100644 --- a/src/optimize/bundles_route/bundles_route.js +++ b/src/optimize/bundles_route/bundles_route.js @@ -72,43 +72,57 @@ export function createBundlesRoute({ } return [ - buildRouteForBundles( - `${basePublicPath}/bundles/kbn-ui-shared-deps/`, - '/bundles/kbn-ui-shared-deps/', - UiSharedDeps.distDir, - fileHashCache - ), + buildRouteForBundles({ + publicPath: `${basePublicPath}/bundles/kbn-ui-shared-deps/`, + routePath: '/bundles/kbn-ui-shared-deps/', + bundlesPath: UiSharedDeps.distDir, + fileHashCache, + replacePublicPath: false, + }), ...npUiPluginPublicDirs.map(({ id, path }) => - buildRouteForBundles( - `${basePublicPath}/bundles/plugin/${id}/`, - `/bundles/plugin/${id}/`, - path, - fileHashCache - ) - ), - buildRouteForBundles( - `${basePublicPath}/bundles/core/`, - `/bundles/core/`, - fromRoot(join('src', 'core', 'target', 'public')), - fileHashCache - ), - buildRouteForBundles( - `${basePublicPath}/bundles/`, - '/bundles/', - regularBundlesPath, - fileHashCache + buildRouteForBundles({ + publicPath: `${basePublicPath}/bundles/plugin/${id}/`, + routePath: `/bundles/plugin/${id}/`, + bundlesPath: path, + fileHashCache, + replacePublicPath: false, + }) ), - buildRouteForBundles( - `${basePublicPath}/built_assets/dlls/`, - '/built_assets/dlls/', - dllBundlesPath, - fileHashCache - ), - buildRouteForBundles(`${basePublicPath}/`, '/built_assets/css/', builtCssPath, fileHashCache), + buildRouteForBundles({ + publicPath: `${basePublicPath}/bundles/core/`, + routePath: `/bundles/core/`, + bundlesPath: fromRoot(join('src', 'core', 'target', 'public')), + fileHashCache, + replacePublicPath: false, + }), + buildRouteForBundles({ + publicPath: `${basePublicPath}/bundles/`, + routePath: '/bundles/', + bundlesPath: regularBundlesPath, + fileHashCache, + }), + buildRouteForBundles({ + publicPath: `${basePublicPath}/built_assets/dlls/`, + routePath: '/built_assets/dlls/', + bundlesPath: dllBundlesPath, + fileHashCache, + }), + buildRouteForBundles({ + publicPath: `${basePublicPath}/`, + routePath: '/built_assets/css/', + bundlesPath: builtCssPath, + fileHashCache, + }), ]; } -function buildRouteForBundles(publicPath, routePath, bundlesPath, fileHashCache) { +function buildRouteForBundles({ + publicPath, + routePath, + bundlesPath, + fileHashCache, + replacePublicPath = true, +}) { return { method: 'GET', path: `${routePath}{path*}`, @@ -129,6 +143,7 @@ function buildRouteForBundles(publicPath, routePath, bundlesPath, fileHashCache) bundlesPath, fileHashCache, publicPath, + replacePublicPath, }); }, }, diff --git a/src/optimize/bundles_route/dynamic_asset_response.js b/src/optimize/bundles_route/dynamic_asset_response.js index 7af780a79e430..80c49a26270fd 100644 --- a/src/optimize/bundles_route/dynamic_asset_response.js +++ b/src/optimize/bundles_route/dynamic_asset_response.js @@ -52,7 +52,7 @@ import { replacePlaceholder } from '../public_path_placeholder'; * @property {LruCache} options.fileHashCache */ export async function createDynamicAssetResponse(options) { - const { request, h, bundlesPath, publicPath, fileHashCache } = options; + const { request, h, bundlesPath, publicPath, fileHashCache, replacePublicPath } = options; let fd; try { @@ -78,11 +78,14 @@ export async function createDynamicAssetResponse(options) { }); fd = null; // read stream is now responsible for fd + const content = replacePublicPath ? replacePlaceholder(read, publicPath) : read; + const etag = replacePublicPath ? `${hash}-${publicPath}` : hash; + return h - .response(replacePlaceholder(read, publicPath)) + .response(content) .takeover() .code(200) - .etag(`${hash}-${publicPath}`) + .etag(etag) .header('cache-control', 'must-revalidate') .type(request.server.mime.path(path).type); } catch (error) { diff --git a/tasks/config/karma.js b/tasks/config/karma.js index 4e106ef3e039a..1ec7c831b4864 100644 --- a/tasks/config/karma.js +++ b/tasks/config/karma.js @@ -53,6 +53,8 @@ module.exports = function(grunt) { function getKarmaFiles(shardNum) { return [ 'http://localhost:5610/test_bundle/built_css.css', + // Sets global variables normally set by the bootstrap.js script + 'http://localhost:5610/test_bundle/karma/globals.js', ...UiSharedDeps.jsDepFilenames.map( chunkFilename => `http://localhost:5610/bundles/kbn-ui-shared-deps/${chunkFilename}`