remove WorkerService (#4484)

* Use bare bones search worker using native Worker
* Remove worker service
* remove unused workers pulled into search bundle

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
David Tsay 2021-12-06 13:27:06 -08:00 committed by GitHub
parent 84e82d3bda
commit e1e2cf9be8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 147 additions and 693 deletions

View File

@ -1 +0,0 @@
Contains services which manage execution and flow control (e.g. for concurrency.)

View File

@ -1,46 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed 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.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/WorkerService"
], function (
WorkerService
) {
return {
name: "platform/execution",
definition: {
"extensions": {
"services": [
{
"key": "workerService",
"implementation": WorkerService,
"depends": [
"$window",
"workers[]"
]
}
]
}
}
};
});

View File

@ -1,92 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed 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.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* This bundle contains services for managing the flow of execution,
* such as support for running web workers on background threads.
* @namespace platform/execution
*/
define(
[],
function () {
/**
* Handles the execution of WebWorkers.
* @memberof platform/execution
* @constructor
*/
function WorkerService($window, workers) {
var workerUrls = {},
sharedWorkers = {};
function addWorker(worker) {
var key = worker.key;
if (!workerUrls[key]) {
if (worker.scriptUrl) {
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
worker.scriptUrl
].join("/");
} else if (worker.scriptText) {
var blob = new Blob(
[worker.scriptText],
{type: 'application/javascript'}
);
workerUrls[key] = URL.createObjectURL(blob);
}
sharedWorkers[key] = worker.shared;
}
}
(workers || []).forEach(addWorker);
this.workerUrls = workerUrls;
this.sharedWorkers = sharedWorkers;
this.Worker = $window.Worker;
this.SharedWorker = $window.SharedWorker;
}
/**
* Start running a new web worker. This will run a worker
* that has been registered under the `workers` category
* of extension.
*
* This will return either a Worker or a SharedWorker,
* depending on whether a `shared` flag has been specified
* on the the extension definition for the referenced worker.
*
* @param {string} key symbolic identifier for the worker
* @returns {Worker | SharedWorker} the running Worker
*/
WorkerService.prototype.run = function (key) {
var scriptUrl = this.workerUrls[key],
Worker = this.sharedWorkers[key]
? this.SharedWorker : this.Worker;
return scriptUrl && Worker && new Worker(scriptUrl);
};
return WorkerService;
}
);

View File

@ -1,105 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed 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.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../src/WorkerService"],
function (WorkerService) {
describe("The worker service", function () {
var mockWindow,
testWorkers,
mockWorker,
mockSharedWorker,
service;
beforeEach(function () {
mockWindow = jasmine.createSpyObj(
'$window',
['Worker', 'SharedWorker']
);
testWorkers = [
{
key: 'abc',
scriptUrl: 'c.js',
bundle: {
path: 'a',
sources: 'b'
}
},
{
key: 'xyz',
scriptUrl: 'z.js',
bundle: {
path: 'x',
sources: 'y'
}
},
{
key: 'xyz',
scriptUrl: 'bad.js',
bundle: {
path: 'bad',
sources: 'bad'
}
},
{
key: 'a-shared-worker',
shared: true,
scriptUrl: 'c.js',
bundle: {
path: 'a',
sources: 'b'
}
}
];
mockWorker = {};
mockSharedWorker = {};
mockWindow.Worker.and.returnValue(mockWorker);
mockWindow.SharedWorker.and.returnValue(mockSharedWorker);
service = new WorkerService(mockWindow, testWorkers);
});
it("instantiates workers at registered paths", function () {
expect(service.run('abc')).toBe(mockWorker);
expect(mockWindow.Worker).toHaveBeenCalledWith('a/b/c.js');
});
it("prefers the first worker when multiple keys are found", function () {
expect(service.run('xyz')).toBe(mockWorker);
expect(mockWindow.Worker).toHaveBeenCalledWith('x/y/z.js');
});
it("allows workers to be shared", function () {
expect(service.run('a-shared-worker')).toBe(mockSharedWorker);
expect(mockWindow.SharedWorker)
.toHaveBeenCalledWith('a/b/c.js');
});
it("returns undefined for unknown workers", function () {
expect(service.run('def')).toBeUndefined();
});
});
}
);

View File

@ -27,9 +27,7 @@ define([
"./src/services/SearchAggregator", "./src/services/SearchAggregator",
"./res/templates/search-item.html", "./res/templates/search-item.html",
"./res/templates/search.html", "./res/templates/search.html",
"./res/templates/search-menu.html", "./res/templates/search-menu.html"
"raw-loader!./src/services/GenericSearchWorker.js",
"raw-loader!./src/services/BareBonesSearchWorker.js"
], function ( ], function (
SearchController, SearchController,
SearchMenuController, SearchMenuController,
@ -37,9 +35,7 @@ define([
SearchAggregator, SearchAggregator,
searchItemTemplate, searchItemTemplate,
searchTemplate, searchTemplate,
searchMenuTemplate, searchMenuTemplate
searchWorkerText,
BareBonesSearchWorkerText
) { ) {
return { return {
@ -55,45 +51,6 @@ define([
"ROOT" "ROOT"
], ],
"priority": "fallback" "priority": "fallback"
},
{
"key": "USE_LEGACY_INDEXER",
"value": false,
"priority": 2
}
],
"controllers": [
{
"key": "SearchController",
"implementation": SearchController,
"depends": [
"$scope",
"searchService"
]
},
{
"key": "SearchMenuController",
"implementation": SearchMenuController,
"depends": [
"$scope",
"types[]"
]
}
],
"representations": [
{
"key": "search-item",
"template": searchItemTemplate
}
],
"templates": [
{
"key": "search",
"template": searchTemplate
},
{
"key": "search-menu",
"template": searchMenuTemplate
} }
], ],
"components": [ "components": [
@ -105,10 +62,8 @@ define([
"$q", "$q",
"$log", "$log",
"objectService", "objectService",
"workerService",
"topic", "topic",
"GENERIC_SEARCH_ROOTS", "GENERIC_SEARCH_ROOTS",
"USE_LEGACY_INDEXER",
"openmct" "openmct"
] ]
}, },
@ -121,16 +76,6 @@ define([
"objectService" "objectService"
] ]
} }
],
"workers": [
{
"key": "bareBonesSearchWorker",
"scriptText": BareBonesSearchWorkerText
},
{
"key": "genericSearchWorker",
"scriptText": searchWorkerText
}
] ]
} }
} }

View File

@ -25,10 +25,12 @@
*/ */
define([ define([
'objectUtils', 'objectUtils',
'lodash' 'lodash',
'raw-loader!./BareBonesSearchWorker.js'
], function ( ], function (
objectUtils, objectUtils,
_ _,
BareBonesSearchWorkerText
) { ) {
/** /**
@ -39,12 +41,13 @@ define([
* @param $q Angular's $q, for promise consolidation. * @param $q Angular's $q, for promise consolidation.
* @param $log Anglar's $log, for logging. * @param $log Anglar's $log, for logging.
* @param {ObjectService} objectService the object service. * @param {ObjectService} objectService the object service.
* @param {WorkerService} workerService the workerService.
* @param {TopicService} topic the topic service. * @param {TopicService} topic the topic service.
* @param {Array} ROOTS An array of object Ids to begin indexing. * @param {Array} ROOTS An array of object Ids to begin indexing.
*/ */
function GenericSearchProvider($q, $log, objectService, workerService, topic, ROOTS, USE_LEGACY_INDEXER, openmct) {
var provider = this; function GenericSearchProvider($q, $log, objectService, topic, ROOTS, openmct) {
let provider = this;
this.$q = $q; this.$q = $q;
this.$log = $log; this.$log = $log;
this.objectService = objectService; this.objectService = objectService;
@ -57,9 +60,7 @@ define([
this.pendingQueries = {}; this.pendingQueries = {};
this.USE_LEGACY_INDEXER = USE_LEGACY_INDEXER; this.worker = this.startWorker();
this.worker = this.startWorker(workerService);
this.indexOnMutation(topic); this.indexOnMutation(topic);
ROOTS.forEach(function indexRoot(rootId) { ROOTS.forEach(function indexRoot(rootId) {
@ -97,24 +98,23 @@ define([
* Creates a search worker and attaches handlers. * Creates a search worker and attaches handlers.
* *
* @private * @private
* @param workerService
* @returns worker the created search worker. * @returns worker the created search worker.
*/ */
GenericSearchProvider.prototype.startWorker = function (workerService) { GenericSearchProvider.prototype.startWorker = function () {
var provider = this, let provider = this;
worker;
if (this.USE_LEGACY_INDEXER) { const blob = new Blob(
worker = workerService.run('genericSearchWorker'); [BareBonesSearchWorkerText],
} else { {type: 'application/javascript'}
worker = workerService.run('bareBonesSearchWorker'); );
} const objectUrl = URL.createObjectURL(blob);
const searchWorker = new Worker(objectUrl);
worker.addEventListener('message', function (messageEvent) { searchWorker.addEventListener('message', function (messageEvent) {
provider.onWorkerMessage(messageEvent); provider.onWorkerMessage(messageEvent);
}); });
return worker; return searchWorker;
}; };
/** /**

View File

@ -1,162 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed 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.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Module defining GenericSearchWorker. Created by shale on 07/21/2015.
*/
(function () {
// An array of objects composed of domain object IDs and models
// {id: domainObject's ID, model: domainObject's model}
var indexedItems = [],
TERM_SPLITTER = /[ _*]/;
function indexItem(id, model) {
var vector = {
name: model.name
};
vector.cleanName = model.name.trim();
vector.lowerCaseName = vector.cleanName.toLocaleLowerCase();
vector.terms = vector.lowerCaseName.split(TERM_SPLITTER);
indexedItems.push({
id: id,
vector: vector,
model: model,
type: model.type
});
}
// Helper function for search()
function convertToTerms(input) {
var query = {
exactInput: input
};
query.inputClean = input.trim();
query.inputLowerCase = query.inputClean.toLocaleLowerCase();
query.terms = query.inputLowerCase.split(TERM_SPLITTER);
query.exactTerms = query.inputClean.split(TERM_SPLITTER);
return query;
}
/**
* Gets search results from the indexedItems based on provided search
* input. Returns matching results from indexedItems
*
* @param data An object which contains:
* * input: The original string which we are searching with
* * maxResults: The maximum number of search results desired
* * queryId: an id identifying this query, will be returned.
*/
function search(data) {
// This results dictionary will have domain object ID keys which
// point to the value the domain object's score.
var results,
input = data.input,
query = convertToTerms(input),
message = {
request: 'search',
results: {},
total: 0,
queryId: data.queryId
},
matches = {};
if (!query.inputClean) {
// No search terms, no results;
return message;
}
// Two phases: find matches, then score matches.
// Idea being that match finding should be fast, so that future scoring
// operations process fewer objects.
query.terms.forEach(function findMatchingItems(term) {
indexedItems
.filter(function matchesItem(item) {
return item.vector.lowerCaseName.indexOf(term) !== -1;
})
.forEach(function trackMatch(matchedItem) {
if (!matches[matchedItem.id]) {
matches[matchedItem.id] = {
matchCount: 0,
item: matchedItem
};
}
matches[matchedItem.id].matchCount += 1;
});
});
// Then, score matching items.
results = Object
.keys(matches)
.map(function asMatches(matchId) {
return matches[matchId];
})
.map(function prioritizeExactMatches(match) {
if (match.item.vector.name === query.exactInput) {
match.matchCount += 100;
} else if (match.item.vector.lowerCaseName
=== query.inputLowerCase) {
match.matchCount += 50;
}
return match;
})
.map(function prioritizeCompleteTermMatches(match) {
match.item.vector.terms.forEach(function (term) {
if (query.terms.indexOf(term) !== -1) {
match.matchCount += 0.5;
}
});
return match;
})
.sort(function compare(a, b) {
if (a.matchCount > b.matchCount) {
return -1;
}
if (a.matchCount < b.matchCount) {
return 1;
}
return 0;
});
message.total = results.length;
message.results = results
.slice(0, data.maxResults);
return message;
}
self.onmessage = function (event) {
if (event.data.request === 'index') {
indexItem(event.data.id, event.data.model);
} else if (event.data.request === 'search') {
self.postMessage(search(event.data));
}
};
}());

View File

@ -0,0 +1,126 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed 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.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* SearchSpec. Created by shale on 07/31/2015.
*/
define([
"raw-loader!../../src/services/BareBonesSearchWorker.js"
], function (
BareBonesSearchWorkerText
) {
describe('BareBonesSearchWorker', function () {
let blob;
let objectUrl;
let worker;
let objectX;
let objectY;
let objectZ;
let itemsToIndex;
beforeEach(function () {
blob = new Blob(
[BareBonesSearchWorkerText],
{type: 'application/javascript'}
);
objectUrl = URL.createObjectURL(blob);
worker = new Worker(objectUrl);
objectX = {
id: 'x',
model: {name: 'object xx'}
};
objectY = {
id: 'y',
model: {name: 'object yy'}
};
objectZ = {
id: 'z',
model: {name: 'object zz'}
};
itemsToIndex = [
objectX,
objectY,
objectZ
];
itemsToIndex.forEach(function (item) {
worker.postMessage({
request: 'index',
id: item.id,
model: item.model
});
});
});
afterEach(function () {
blob = undefined;
objectUrl = undefined;
worker.terminate();
});
it('returns search results for partial term matches', function (done) {
worker.addEventListener('message', function (message) {
let data = message.data;
expect(data.request).toBe('search');
expect(data.total).toBe(3);
expect(data.queryId).toBe(123);
expect(data.results.length).toBe(3);
expect(data.results[0].id).toBe('x');
expect(data.results[0].name).toEqual(objectX.model.name);
expect(data.results[1].id).toBe('y');
expect(data.results[1].name).toEqual(objectY.model.name);
expect(data.results[2].id).toBe('z');
expect(data.results[2].name).toEqual(objectZ.model.name);
done();
});
worker.postMessage({
request: 'search',
input: 'obj',
maxResults: 100,
queryId: 123
});
});
it('can find partial term matches', function (done) {
worker.addEventListener('message', function (message) {
let data = message.data;
expect(data.queryId).toBe(345);
expect(data.results.length).toBe(1);
expect(data.results[0].id).toBe('x');
done();
});
worker.postMessage({
request: 'search',
input: 'x',
maxResults: 100,
queryId: 345
});
});
});
});

View File

@ -1,209 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed 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.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* SearchSpec. Created by shale on 07/31/2015.
*/
define([
"raw-loader!../../src/services/GenericSearchWorker.js"
], function (
GenericSearchWorkerText
) {
var WORKER_FILE = URL.createObjectURL(new Blob(
[GenericSearchWorkerText],
{type: 'application/javascript'}
));
describe('GenericSearchWorker', function () {
// If this test fails, make sure this path is correct
var worker,
objectX,
objectY,
objectZ,
itemsToIndex;
beforeEach(function () {
worker = new Worker(WORKER_FILE);
objectX = {
id: 'x',
model: {name: 'object xx'}
};
objectY = {
id: 'y',
model: {name: 'object yy'}
};
objectZ = {
id: 'z',
model: {name: 'object zz'}
};
itemsToIndex = [
objectX,
objectY,
objectZ
];
itemsToIndex.forEach(function (item) {
worker.postMessage({
request: 'index',
id: item.id,
model: item.model
});
});
});
afterEach(function () {
worker.terminate();
});
it('returns search results for partial term matches', function (done) {
worker.addEventListener('message', function (message) {
var data = message.data;
expect(data.request).toBe('search');
expect(data.total).toBe(3);
expect(data.queryId).toBe(123);
expect(data.results.length).toBe(3);
expect(data.results[0].item.id).toBe('x');
expect(data.results[0].item.model).toEqual(objectX.model);
expect(data.results[0].matchCount).toBe(1);
expect(data.results[1].item.id).toBe('y');
expect(data.results[1].item.model).toEqual(objectY.model);
expect(data.results[1].matchCount).toBe(1);
expect(data.results[2].item.id).toBe('z');
expect(data.results[2].item.model).toEqual(objectZ.model);
expect(data.results[2].matchCount).toBe(1);
done();
});
worker.postMessage({
request: 'search',
input: 'obj',
maxResults: 100,
queryId: 123
});
});
it('scores exact term matches higher', function (done) {
worker.addEventListener('message', function (message) {
expect(message.data.queryId).toBe(234);
expect(message.data.results.length).toBe(3);
expect(message.data.results[0].item.id).toBe('x');
expect(message.data.results[0].matchCount).toBe(1.5);
done();
});
worker.postMessage({
request: 'search',
input: 'object',
maxResults: 100,
queryId: 234
});
});
it('can find partial term matches', function (done) {
worker.addEventListener('message', function (message) {
expect(message.data.queryId).toBe(345);
expect(message.data.results.length).toBe(1);
expect(message.data.results[0].item.id).toBe('x');
expect(message.data.results[0].matchCount).toBe(1);
done();
});
worker.postMessage({
request: 'search',
input: 'x',
maxResults: 100,
queryId: 345
});
});
it('matches individual terms', function (done) {
worker.addEventListener('message', function (message) {
var data = message.data;
expect(data.queryId).toBe(456);
expect(data.results.length).toBe(3);
expect(data.results[0].item.id).toBe('x');
expect(data.results[0].matchCount).toBe(1);
expect(data.results[1].item.id).toBe('y');
expect(data.results[1].matchCount).toBe(1);
expect(data.results[2].item.id).toBe('z');
expect(data.results[1].matchCount).toBe(1);
done();
});
worker.postMessage({
request: 'search',
input: 'x y z',
maxResults: 100,
queryId: 456
});
});
it('scores exact matches highest', function (done) {
worker.addEventListener('message', function (message) {
var data = message.data;
expect(data.queryId).toBe(567);
expect(data.results.length).toBe(3);
expect(data.results[0].item.id).toBe('x');
expect(data.results[0].matchCount).toBe(103);
expect(data.results[1].matchCount).toBe(1.5);
expect(data.results[2].matchCount).toBe(1.5);
done();
});
worker.postMessage({
request: 'search',
input: 'object xx',
maxResults: 100,
queryId: 567
});
});
it('scores multiple term match above single match', function () {
worker.addEventListener('message', function (message) {
var data = message.data;
expect(data.queryId).toBe(678);
expect(data.results.length).toBe(3);
expect(data.results[0].item.id).toBe('x');
expect(data.results[0].matchCount).toBe(2);
expect(data.results[1].matchCount).toBe(1);
expect(data.results[2].matchCount).toBe(1);
});
worker.postMessage({
request: 'search',
input: 'obj x',
maxResults: 100,
queryId: 678
});
});
});
});

View File

@ -33,7 +33,6 @@ const DEFAULTS = [
'platform/commonUI/mobile', 'platform/commonUI/mobile',
'platform/commonUI/notification', 'platform/commonUI/notification',
'platform/containment', 'platform/containment',
'platform/execution',
'platform/exporters', 'platform/exporters',
'platform/telemetry', 'platform/telemetry',
'platform/forms', 'platform/forms',
@ -72,7 +71,6 @@ define([
'../platform/containment/bundle', '../platform/containment/bundle',
'../platform/core/bundle', '../platform/core/bundle',
'../platform/entanglement/bundle', '../platform/entanglement/bundle',
'../platform/execution/bundle',
'../platform/exporters/bundle', '../platform/exporters/bundle',
'../platform/features/static-markup/bundle', '../platform/features/static-markup/bundle',
'../platform/forms/bundle', '../platform/forms/bundle',