[uiExports] migrate uiApp "uses" to explicit imports in apps (#17828)

* [uiExports] migrate uiApp "uses" to explicit imports in apps

* [uiApp] update tests for getModules() method

* [optimize/uiExports] improve naming and comments

* [uiExports] sort imports so they load in the same order as before

* [testHarness] load hacks when testing in the browser

* [x-pack/uiExports] use new uiExports modules

* [testHarness] describe why we import uiExports/hacks

* [optimize] remove needless [].concat()

* [optimize/createUiExportsModule] string.includes > string.indexOf

* [uiExports/createUiExportsModule] remove needless capture of module exports
This commit is contained in:
Spencer 2018-05-03 12:18:50 -07:00 committed by GitHub
parent 6201d1a6c7
commit e1a2fcbd96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 125 additions and 111 deletions

View file

@ -65,7 +65,7 @@ module.exports = {
// instructs import/no-extraneous-dependencies to treat modules
// in plugins/ or ui/ namespace as "core modules" so they don't
// trigger failures for not being listed in package.json
'import/core-modules': ['plugins', 'ui'],
'import/core-modules': ['plugins', 'ui', 'uiExports'],
'import/resolver': {
'@kbn/eslint-import-resolver-kibana': {

View file

@ -202,6 +202,7 @@
"ui-select": "0.19.6",
"url-loader": "0.5.9",
"uuid": "3.0.1",
"val-loader": "^1.1.0",
"validate-npm-package-name": "2.2.2",
"vega-lib": "^3.3.1",
"vega-lite": "^2.4.0",

View file

@ -35,6 +35,16 @@ function initContext(file, config) {
exports.resolve = function resolveKibanaPath(importRequest, file, config) {
config = config || {};
// these modules are simulated by webpack, so there is no
// path to resolve to and no reason to do any more work
if (importRequest.startsWith('uiExports/')) {
return {
found: true,
path: null,
};
}
const { webpackConfig, aliasEntries } = initContext(file, config);
let isPathRequest = getIsPathRequest(importRequest);

View file

@ -48,22 +48,6 @@ export default function (kibana) {
listed: false,
description: 'the kibana you know and love',
main: 'plugins/kibana/kibana',
uses: [
'home',
'visTypes',
'visResponseHandlers',
'visRequestHandlers',
'visEditorTypes',
'savedObjectTypes',
'spyModes',
'fieldFormats',
'fieldFormatEditors',
'navbarExtensions',
'managementSections',
'devTools',
'docViews',
'embeddableFactories',
],
},
links: [

View file

@ -5,6 +5,22 @@ import chrome from 'ui/chrome';
import routes from 'ui/routes';
import { uiModules } from 'ui/modules';
// import the uiExports that we want to "use"
import 'uiExports/home';
import 'uiExports/visTypes';
import 'uiExports/visResponseHandlers';
import 'uiExports/visRequestHandlers';
import 'uiExports/visEditorTypes';
import 'uiExports/savedObjectTypes';
import 'uiExports/spyModes';
import 'uiExports/fieldFormats';
import 'uiExports/fieldFormatEditors';
import 'uiExports/navbarExtensions';
import 'uiExports/managementSections';
import 'uiExports/devTools';
import 'uiExports/docViews';
import 'uiExports/embeddableFactories';
import 'ui/autoload/all';
import './home';
import './discover';

View file

@ -8,10 +8,6 @@ export default function (kibana) {
description: 'Time series expressions for everything',
icon: 'plugins/timelion/icon.svg',
main: 'plugins/timelion/app',
uses: [
'fieldFormats',
'savedObjectTypes'
]
},
hacks: [
'plugins/timelion/lib/panel_registry',

View file

@ -7,6 +7,10 @@ import { notify, fatalError, toastNotifications } from 'ui/notify';
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { recentlyAccessed } from 'ui/persisted_log';
// import the uiExports that we want to "use"
import 'uiExports/fieldFormats';
import 'uiExports/savedObjectTypes';
require('ui/autoload/all');
require('plugins/timelion/directives/cells/cells');
require('plugins/timelion/directives/fixed_element');

View file

@ -154,6 +154,30 @@ export default class BaseOptimizer {
new webpack.NoEmitOnErrorsPlugin(),
// replace imports for `uiExports/*` modules with a synthetic module
// created by create_ui_exports_module.js
new webpack.NormalModuleReplacementPlugin(/^uiExports\//, (resource) => {
// the map of uiExport types to module ids
const extensions = this.uiBundles.getAppExtensions();
// everything following the first / in the request is
// treated as a type of appExtension
const type = resource.request.slice(resource.request.indexOf('/') + 1);
resource.request = [
// the "val-loader" is used to execute create_ui_exports_module
// and use its return value as the source for the module in the
// bundle. This allows us to bypass writing to the file system
require.resolve('val-loader'),
'!',
require.resolve('./create_ui_exports_module'),
'?',
// this JSON is parsed by create_ui_exports_module and determines
// what require() calls it will execute within the bundle
JSON.stringify({ type, modules: extensions[type] || [] })
].join('');
}),
...this.uiBundles.getWebpackPluginProviders()
.map(provider => provider(webpack)),
],

View file

@ -0,0 +1,21 @@
// We normalize all path separators to `/` in generated files
function normalizePath(path) {
return path.replace(/[\\\/]+/g, '/');
}
export default function () {
if (!module.id.includes('?')) {
throw new Error('create_ui_exports_module loaded without JSON args in module.id');
}
const { type, modules } = JSON.parse(module.id.slice(module.id.indexOf('?') + 1));
const comment = `// dynamically generated to load ${type} uiExports from plugins`;
const requires = modules
.sort((a, b) => a.localeCompare(b))
.map(m => `require('${normalizePath(m)}')`)
.join('\n ');
return {
code: `${comment}\n${requires}\n`
};
}

View file

@ -54,6 +54,12 @@ translationsApi(chrome, internals);
const waitForBootstrap = new Promise(resolve => {
chrome.bootstrap = function () {
// import chrome nav controls and hacks now so that they are executed after
// everything else, can safely import the chrome, and interact with services
// and such setup by all other modules
require('uiExports/chromeNavControls');
require('uiExports/hacks');
chrome.setupAngular();
angular.bootstrap(document.body, ['kibana']);
resolve();

View file

@ -67,5 +67,8 @@ afterEach(function () {
// Kick off mocha, called at the end of test entry files
export function bootstrap() {
// load the hacks since we aren't actually bootstrapping the
// chrome, which is where the hacks would normally be loaded
require('uiExports/hacks');
chrome.setupAngular();
}

View file

@ -1,12 +1,9 @@
import sinon from 'sinon';
import expect from 'expect.js';
import Chance from 'chance';
import { UiApp } from '../ui_app';
import { UiNavLink } from '../../ui_nav_links';
const chance = new Chance();
function createStubUiAppSpec(extraParams) {
return {
id: 'uiapp-test',
@ -18,11 +15,6 @@ function createStubUiAppSpec(extraParams) {
linkToLastSubUrl: true,
hidden: false,
listed: false,
uses: [
'visTypes',
'chromeNavControls',
'hacks',
],
...extraParams
};
}
@ -30,13 +22,6 @@ function createStubUiAppSpec(extraParams) {
function createStubKbnServer() {
return {
plugins: [],
uiExports: {
appExtensions: {
hacks: [
'plugins/foo/hack'
]
}
},
config: {
get: sinon.stub()
.withArgs('server.basePath')
@ -129,7 +114,6 @@ describe('ui apps / UiApp', () => {
it('includes main and hack modules', () => {
expect(app.getModules()).to.eql([
'main.js',
'plugins/foo/hack'
]);
});
@ -305,34 +289,5 @@ describe('ui apps / UiApp', () => {
const app = createUiApp({ id: 'foo', main: 'bar' });
expect(app.getModules()).to.eql(['bar']);
});
it('returns appExtensions for used types only, in alphabetical order, starting with main module', () => {
const kbnServer = createStubKbnServer();
kbnServer.uiExports.appExtensions = {
abc: chance.shuffle([
'a',
'b',
'c',
]),
def: chance.shuffle([
'd',
'e',
'f',
])
};
const appExtensionType = chance.shuffle(Object.keys(kbnServer.uiExports.appExtensions))[0];
const appSpec = {
id: 'foo',
main: 'bar',
uses: [appExtensionType],
};
const app = createUiApp(appSpec, kbnServer);
expect(app.getModules()).to.eql([
'bar',
...appExtensionType.split(''),
]);
});
});
});

View file

@ -14,7 +14,6 @@ export class UiApp {
linkToLastSubUrl,
listed,
url = `/app/${id}`,
uses = []
} = spec;
if (!id) {
@ -38,18 +37,6 @@ export class UiApp {
throw new Error(`Unknown plugin id "${this._pluginId}"`);
}
const { appExtensions = [] } = kbnServer.uiExports;
this._modules = [].concat(
this._main || [],
uses
// flatten appExtensions for used types
.reduce((acc, type) => acc.concat(appExtensions[type] || []), [])
// de-dupe app extension module ids
.reduce((acc, item) => !item || acc.includes(item) ? acc : acc.concat(item), [])
// sort app extension module ids alphabetically
.sort((a, b) => a.localeCompare(b))
);
if (!this.isHidden()) {
// unless an app is hidden it gets a navlink, but we only respond to `getNavLink()`
// if the app is also listed. This means that all apps in the kibanaPayload will
@ -93,7 +80,7 @@ export class UiApp {
}
getModules() {
return this._modules;
return this._main ? [this._main] : [];
}
_getPlugin() {

View file

@ -44,6 +44,8 @@ export class UiBundlesController {
matchBase: true
});
this._appExtensions = uiExports.appExtensions || {};
this._webpackAliases = {
...getWebpackAliases(pluginSpecs),
...uiExports.webpackAliases
@ -103,6 +105,10 @@ export class UiBundlesController {
return this._webpackAliases;
}
getAppExtensions() {
return this._appExtensions;
}
isDevMode() {
return this._env === 'development';
}

View file

@ -1,5 +1,3 @@
import { uniq } from 'lodash';
import { flatConcatAtType } from './reduce';
import { alias, mapSpec, wrap } from './modify_reduce';
@ -16,13 +14,18 @@ function applySpecDefaults(spec, type, pluginSpec) {
linkToLastSubUrl = true,
listed = !hidden,
url = `/app/${id}`,
uses = [],
} = spec;
if (spec.injectVars) {
throw new Error(`[plugin:${pluginId}] uiExports.app.injectVars has been removed. Use server.injectUiAppVars('${id}', () => { ... })`);
}
if (spec.uses) {
throw new Error(
`[plugin:${pluginId}] uiExports.app.uses has been removed. Import these uiExport types with "import 'uiExports/{type}'"`
);
}
return {
pluginId,
id,
@ -35,11 +38,6 @@ function applySpecDefaults(spec, type, pluginSpec) {
linkToLastSubUrl,
listed,
url,
uses: uniq([
...uses,
'chromeNavControls',
'hacks',
]),
};
}

View file

@ -38,18 +38,6 @@ export function dashboardMode(kibana) {
hidden: true,
description: 'view dashboards',
main: 'plugins/dashboard_mode/dashboard_viewer',
uses: [
'visTypes',
'visResponseHandlers',
'visRequestHandlers',
'visEditorTypes',
'savedObjectTypes',
'embeddableFactories',
'spyModes',
'navbarExtensions',
'docViews',
'fieldFormats'
],
links: [
{
id: 'kibana:dashboard',

View file

@ -13,6 +13,18 @@ import chrome from 'ui/chrome';
import routes from 'ui/routes';
import { uiModules } from 'ui/modules';
// import the uiExports that we want to "use"
import 'uiExports/visTypes';
import 'uiExports/visResponseHandlers';
import 'uiExports/visRequestHandlers';
import 'uiExports/visEditorTypes';
import 'uiExports/savedObjectTypes';
import 'uiExports/embeddableFactories';
import 'uiExports/spyModes';
import 'uiExports/navbarExtensions';
import 'uiExports/docViews';
import 'uiExports/fieldFormats';
import _ from 'lodash';
import 'ui/autoload/all';
import 'plugins/kibana/dashboard';

View file

@ -23,10 +23,6 @@ export function graph(kibana) {
icon: 'plugins/graph/icon.png',
description: 'Graph exploration',
main: 'plugins/graph/app',
uses: [
'fieldFormats',
'savedObjectTypes',
]
},
hacks: ['plugins/graph/hacks/toggle_app_link_in_nav'],
home: ['plugins/graph/register_feature'],

View file

@ -8,6 +8,10 @@ import d3 from 'd3';
import 'ace';
import rison from 'rison-node';
// import the uiExports that we want to "use"
import 'uiExports/fieldFormats';
import 'uiExports/savedObjectTypes';
import 'ui/autoload/all';
import 'ui/directives/saved_object_finder';
import chrome from 'ui/chrome';

View file

@ -34,14 +34,9 @@ export const ml = (kibana) => {
description: 'Machine Learning for the Elastic Stack',
icon: 'plugins/ml/ml.svg',
main: 'plugins/ml/app',
uses: [
'fieldFormats',
'savedObjectTypes',
]
},
hacks: ['plugins/ml/hacks/toggle_app_link_in_nav'],
home: ['plugins/ml/register_feature']
},

View file

@ -5,6 +5,9 @@
*/
// import the uiExports that we want to "use"
import 'uiExports/fieldFormats';
import 'uiExports/savedObjectTypes';
import 'ui/courier';
import 'ui-bootstrap';
@ -47,4 +50,3 @@ uiRoutes
.otherwise({
redirectTo: '/jobs'
});

View file

@ -7881,7 +7881,7 @@ loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
loader-utils@^1.0.2, loader-utils@^1.1.0:
loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
dependencies:
@ -12836,6 +12836,12 @@ uuid@^3.0.0, uuid@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
val-loader@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/val-loader/-/val-loader-1.1.0.tgz#ed91537424d62a4ded98e846ccf07367756bf506"
dependencies:
loader-utils "^1.0.0"
validate-npm-package-license@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338"