Move from watching optimizer to live optimizer
This commit is contained in:
parent
1f95e662df
commit
70ef0125b9
|
@ -17,7 +17,7 @@ module.exports = function (config) {
|
|||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'http://localhost:5601/bundles/tests.bundle.js',
|
||||
'http://localhost:5601/bundles/tests.bundle.style.css'
|
||||
'http://localhost:5601/bundles/tests.style.css'
|
||||
],
|
||||
|
||||
proxies: {
|
||||
|
|
86
src/cli/cluster/ClusterManager.js
Normal file
86
src/cli/cluster/ClusterManager.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
let cluster = require('cluster');
|
||||
let { join } = require('path');
|
||||
let { compact, invoke, bindAll, once } = require('lodash');
|
||||
|
||||
let Log = require('../Log');
|
||||
let Worker = require('./Worker');
|
||||
|
||||
module.exports = class ClusterManager {
|
||||
constructor(opts) {
|
||||
this.log = new Log(opts.quiet, opts.silent);
|
||||
this.addedCount = 0;
|
||||
|
||||
this.workers = [
|
||||
new Worker({
|
||||
type: 'optmzr',
|
||||
title: 'optimizer',
|
||||
log: this.log,
|
||||
argv: compact([
|
||||
(opts.quiet || opts.silent || opts.verbose) ? null : '--quiet',
|
||||
'--plugins.initialize=false',
|
||||
'--server.autoListen=false'
|
||||
]),
|
||||
watch: false
|
||||
}),
|
||||
|
||||
new Worker({
|
||||
type: 'server',
|
||||
log: this.log
|
||||
})
|
||||
];
|
||||
|
||||
bindAll(this, 'onWatcherAdd', 'onWatcherError', 'onWatcherChange');
|
||||
|
||||
if (opts.watch) this.setupWatching();
|
||||
else this.startCluster();
|
||||
}
|
||||
|
||||
startCluster() {
|
||||
invoke(this.workers, 'start');
|
||||
}
|
||||
|
||||
setupWatching() {
|
||||
var chokidar = require('chokidar');
|
||||
let utils = require('requirefrom')('src/utils');
|
||||
let fromRoot = utils('fromRoot');
|
||||
|
||||
this.watcher = chokidar.watch([
|
||||
'src/cli',
|
||||
'src/optimize',
|
||||
'src/plugins',
|
||||
'src/server',
|
||||
'src/ui',
|
||||
'src/utils',
|
||||
'config',
|
||||
'installedPlugins'
|
||||
], {
|
||||
cwd: fromRoot('.'),
|
||||
ignored: /[\\\/](node_modules|bower_components|public)[\\\/]/,
|
||||
});
|
||||
|
||||
this.watcher.on('add', this.onWatcherAdd);
|
||||
this.watcher.on('error', this.onWatcherError);
|
||||
|
||||
this.watcher.on('ready', once(() => {
|
||||
// start sending changes to workers
|
||||
this.watcher.removeListener('add', this.onWatcherAdd);
|
||||
this.watcher.on('all', this.onWatcherChange);
|
||||
|
||||
this.log.good('Watching for changes', `(${this.addedCount} files)`);
|
||||
this.startCluster();
|
||||
}));
|
||||
}
|
||||
|
||||
onWatcherAdd() {
|
||||
this.addedCount += 1;
|
||||
}
|
||||
|
||||
onWatcherChange(e, path) {
|
||||
invoke(this.workers, 'onChange', path);
|
||||
}
|
||||
|
||||
onWatcherError(err) {
|
||||
this.log.bad('Failed to watch files!\n', err.stack);
|
||||
process.exit(1); // eslint-disable-line no-process-exit
|
||||
}
|
||||
};
|
|
@ -22,7 +22,7 @@ module.exports = class Worker extends EventEmitter {
|
|||
this.log = opts.log;
|
||||
this.type = opts.type;
|
||||
this.title = opts.title || opts.type;
|
||||
this.filters = opts.filters;
|
||||
this.watch = (opts.watch !== false);
|
||||
this.changes = [];
|
||||
|
||||
let argv = _.union(baseArgv, opts.argv || []);
|
||||
|
@ -53,15 +53,7 @@ module.exports = class Worker extends EventEmitter {
|
|||
}
|
||||
|
||||
onChange(path) {
|
||||
var valid = true;
|
||||
|
||||
if (this.filters) {
|
||||
valid = _.any(this.filters, function (filter) {
|
||||
return filter.test(path);
|
||||
});
|
||||
}
|
||||
|
||||
if (!valid) return;
|
||||
if (!this.watch) return;
|
||||
this.changes.push(path);
|
||||
this.start();
|
||||
}
|
|
@ -3,10 +3,8 @@ let { isWorker } = require('cluster');
|
|||
let { resolve } = require('path');
|
||||
|
||||
let cwd = process.cwd();
|
||||
let readYamlConfig = require('./readYamlConfig');
|
||||
let src = require('requirefrom')('src');
|
||||
let fromRoot = src('utils/fromRoot');
|
||||
let KbnServer = src('server/KbnServer');
|
||||
|
||||
let pathCollector = function () {
|
||||
let paths = [];
|
||||
|
@ -52,19 +50,25 @@ module.exports = function (program) {
|
|||
.option('--plugins <path>', 'an alias for --plugin-dir', pluginDirCollector)
|
||||
.option('--dev', 'Run the server with development mode defaults')
|
||||
.option('--no-watch', 'Prevent watching, use with --dev to prevent server restarts')
|
||||
.option('--no-lazy', 'Prevent lazy optimization of applications, only works with --dev')
|
||||
.action(function (opts) {
|
||||
if (opts.dev && opts.watch && !isWorker) {
|
||||
// stop processing the action and handoff to watch cluster manager
|
||||
return require('../watch/watch')(opts);
|
||||
if (opts.dev && !isWorker) {
|
||||
// stop processing the action and handoff to cluster manager
|
||||
let ClusterManager = require('../cluster/ClusterManager');
|
||||
new ClusterManager(opts);
|
||||
return;
|
||||
}
|
||||
|
||||
let readYamlConfig = require('./readYamlConfig');
|
||||
let KbnServer = src('server/KbnServer');
|
||||
|
||||
let settings = readYamlConfig(opts.config || fromRoot('config/kibana.yml'));
|
||||
let set = _.partial(_.set, settings);
|
||||
let get = _.partial(_.get, settings);
|
||||
|
||||
if (opts.dev) {
|
||||
set('env', 'development');
|
||||
set('optimize.watch', opts.watch);
|
||||
set('optimize.lazy', opts.lazy);
|
||||
}
|
||||
|
||||
if (opts.elasticsearch) set('elasticsearch.url', opts.elasticsearch);
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
let cluster = require('cluster');
|
||||
let { join } = require('path');
|
||||
let _ = require('lodash');
|
||||
var chokidar = require('chokidar');
|
||||
|
||||
let utils = require('requirefrom')('src/utils');
|
||||
let fromRoot = utils('fromRoot');
|
||||
let Log = require('../Log');
|
||||
let Worker = require('./Worker');
|
||||
|
||||
module.exports = function (opts) {
|
||||
|
||||
let watcher = chokidar.watch([
|
||||
'src/cli',
|
||||
'src/fixtures',
|
||||
'src/server',
|
||||
'src/utils',
|
||||
'src/plugins',
|
||||
'config',
|
||||
], {
|
||||
cwd: fromRoot('.'),
|
||||
ignore: 'src/plugins/*/public/**/*'
|
||||
});
|
||||
|
||||
let log = new Log(opts.quiet, opts.silent);
|
||||
let customLogging = opts.quiet || opts.silent || opts.verbose;
|
||||
|
||||
let workers = [
|
||||
new Worker({
|
||||
type: 'optmzr',
|
||||
title: 'optimizer',
|
||||
log: log,
|
||||
argv: _.compact([
|
||||
customLogging ? null : '--quiet',
|
||||
'--plugins.initialize=false',
|
||||
'--server.autoListen=false',
|
||||
'--optimize._workerRole=send'
|
||||
]),
|
||||
filters: [
|
||||
/src\/server\/(optimize|ui|plugins)\//,
|
||||
/src\/plugins\/[^\/]+\/[^\/]+\.js$/,
|
||||
/src\/cli\//
|
||||
]
|
||||
}),
|
||||
|
||||
new Worker({
|
||||
type: 'server',
|
||||
log: log,
|
||||
argv: [
|
||||
'--optimize._workerRole=receive'
|
||||
]
|
||||
})
|
||||
];
|
||||
|
||||
workers.forEach(function (worker) {
|
||||
worker.on('broadcast', function (msg) {
|
||||
workers.forEach(function (to) {
|
||||
if (to !== worker && to.fork) to.fork.send(msg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var addedCount = 0;
|
||||
function onAddBeforeReady() {
|
||||
addedCount += 1;
|
||||
}
|
||||
|
||||
function onReady() {
|
||||
// start sending changes to workers
|
||||
watcher.removeListener('add', onAddBeforeReady);
|
||||
watcher.on('all', onAnyChangeAfterReady);
|
||||
|
||||
log.good('Watching for changes', `(${addedCount} files)`);
|
||||
_.invoke(workers, 'start');
|
||||
}
|
||||
|
||||
function onAnyChangeAfterReady(e, path) {
|
||||
_.invoke(workers, 'onChange', path);
|
||||
}
|
||||
|
||||
function onError(err) {
|
||||
log.bad('Failed to watch files!\n', err.stack);
|
||||
process.exit(1); // eslint-disable-line no-process-exit
|
||||
}
|
||||
|
||||
watcher.on('add', onAddBeforeReady);
|
||||
watcher.on('ready', onReady);
|
||||
watcher.on('error', onError);
|
||||
};
|
|
@ -1,4 +1,3 @@
|
|||
let { EventEmitter } = require('events');
|
||||
let { inherits } = require('util');
|
||||
let _ = require('lodash');
|
||||
let { join } = require('path');
|
||||
|
@ -9,24 +8,17 @@ let ExtractTextPlugin = require('extract-text-webpack-plugin');
|
|||
|
||||
let utils = require('requirefrom')('src/utils');
|
||||
let fromRoot = utils('fromRoot');
|
||||
let OptmzBundles = require('./OptmzBundles');
|
||||
let OptmzUiModules = require('./OptmzUiModules');
|
||||
let babelOptions = require('./babelOptions');
|
||||
|
||||
let kbnTag = `Kibana ${ utils('packageJson').version }`;
|
||||
|
||||
class BaseOptimizer extends EventEmitter {
|
||||
class BaseOptimizer {
|
||||
constructor(opts) {
|
||||
super();
|
||||
|
||||
this.env = opts.env;
|
||||
this.bundles = opts.bundles;
|
||||
this.sourceMaps = opts.sourceMaps || false;
|
||||
this.modules = new OptmzUiModules(opts.plugins);
|
||||
this.bundles = new OptmzBundles(
|
||||
opts,
|
||||
`${kbnTag} ${this.constructor.name} ${ this.sourceMaps ? ' (with source maps)' : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
_.bindAll(this, 'getConfig');
|
||||
async initCompiler() {
|
||||
return this.compiler || (this.compiler = webpack(this.getConfig()));
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
|
@ -34,12 +26,12 @@ class BaseOptimizer extends EventEmitter {
|
|||
|
||||
return {
|
||||
context: fromRoot('.'),
|
||||
entry: this.bundles.getEntriesConfig(),
|
||||
entry: this.bundles.toWebpackEntries(),
|
||||
|
||||
devtool: this.sourceMaps ? '#source-map' : false,
|
||||
|
||||
output: {
|
||||
path: this.bundles.dir,
|
||||
path: this.env.workingDir,
|
||||
filename: '[name].bundle.js',
|
||||
sourceMapFilename: '[file].map',
|
||||
publicPath: '/bundles/',
|
||||
|
@ -87,8 +79,8 @@ class BaseOptimizer extends EventEmitter {
|
|||
nonStandard: true
|
||||
}, babelOptions)
|
||||
}
|
||||
].concat(this.modules.loaders),
|
||||
noParse: this.modules.noParse,
|
||||
].concat(this.env.loaders),
|
||||
noParse: this.env.noParse,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
|
@ -97,7 +89,7 @@ class BaseOptimizer extends EventEmitter {
|
|||
modulesDirectories: ['node_modules'],
|
||||
loaderPostfixes: ['-loader', ''],
|
||||
root: fromRoot('.'),
|
||||
alias: this.modules.aliases
|
||||
alias: this.env.aliases
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
let _ = require('lodash');
|
||||
let webpack = require('webpack');
|
||||
|
||||
let BaseOptimizer = require('./BaseOptimizer');
|
||||
|
||||
module.exports = class CachedOptimizer extends BaseOptimizer {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
_.bindAll(this, 'init', 'setupCompiler', 'run');
|
||||
}
|
||||
|
||||
init(autoRun) {
|
||||
return this.bundles.ensureAllEntriesExist().then(autoRun ? this.run : this.setupCompiler);
|
||||
}
|
||||
|
||||
setupCompiler(autoRun) {
|
||||
this.entries = this.bundles.getMissingEntries();
|
||||
if (!this.entries.length) return;
|
||||
|
||||
this.compilerConfig = this.getConfig();
|
||||
this.compiler = webpack(this.compilerConfig);
|
||||
|
||||
if (autoRun) this.run();
|
||||
}
|
||||
|
||||
run() {
|
||||
if (!this.compiler) {
|
||||
return this.setupCompiler(true);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
let entries = self.entries;
|
||||
|
||||
self.emit('build-start', entries);
|
||||
self.compiler.run(function (err, stats) {
|
||||
if (err) {
|
||||
self.emit('error', entries, stats, err);
|
||||
}
|
||||
else if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
self.emit('error', entries, stats, new Error('Optimization must not produce errors or warnings'));
|
||||
}
|
||||
else {
|
||||
self.emit('done', entries, stats);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
27
src/optimize/FsOptimizer.js
Normal file
27
src/optimize/FsOptimizer.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
let { fromNode } = require('bluebird');
|
||||
let BaseOptimizer = require('./BaseOptimizer');
|
||||
|
||||
module.exports = class FsOptimizer extends BaseOptimizer {
|
||||
async init() {
|
||||
await this.bundles.writeEntryFiles();
|
||||
await this.bundles.filterCachedBundles();
|
||||
}
|
||||
|
||||
async run() {
|
||||
await this.initCompiler();
|
||||
await fromNode(cb => {
|
||||
this.compiler.run((err, stats) => {
|
||||
if (err || !stats) return cb(err);
|
||||
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
let err = new Error('Optimization must not produce errors or warnings');
|
||||
err.stats = stats;
|
||||
return cb(err);
|
||||
}
|
||||
else {
|
||||
cb(null, stats);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,53 +1,102 @@
|
|||
let _ = require('lodash');
|
||||
let { contains } = require('lodash');
|
||||
let { join } = require('path');
|
||||
let { promisify } = require('bluebird');
|
||||
let { fromNode } = require('bluebird');
|
||||
let webpack = require('webpack');
|
||||
let Boom = require('boom');
|
||||
let MemoryFileSystem = require('memory-fs');
|
||||
let BaseOptimizer = require('./BaseOptimizer');
|
||||
|
||||
module.exports = class LiveOptimizer extends BaseOptimizer {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
|
||||
this.compilerConfig = this.getConfig();
|
||||
// this.compilerConfig.profile = true;
|
||||
|
||||
this.compiler = webpack(this.compilerConfig);
|
||||
async init() {
|
||||
await this.bundles.writeEntryFiles();
|
||||
await this.initCompiler();
|
||||
this.outFs = this.compiler.outputFileSystem = new MemoryFileSystem();
|
||||
|
||||
_.bindAll(this, 'get', 'init');
|
||||
|
||||
this.compile = promisify(this.compiler.run, this.compiler);
|
||||
}
|
||||
|
||||
init() {
|
||||
return this.bundles.ensureAllEntriesExist(true);
|
||||
}
|
||||
async compile() {
|
||||
let stats = await fromNode(cb => this.compiler.run(cb));
|
||||
|
||||
get(id) {
|
||||
let self = this;
|
||||
let fs = self.outFs;
|
||||
let filename = join(self.compiler.outputPath, `${id}.bundle.js`);
|
||||
let mapFilename = join(self.compiler.outputPath, `${id}.bundle.js.map`);
|
||||
let styleFilename = join(self.compiler.outputPath, `${id}.style.css`);
|
||||
|
||||
if (!self.active) {
|
||||
self.active = self.compile().finally(function () {
|
||||
self.active = null;
|
||||
});
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
let err = new Error('optimization failure');
|
||||
err.stats = stats;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return self.active.then(function (stats) {
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
console.log(stats.toString({ colors: true }));
|
||||
return null;
|
||||
return stats;
|
||||
}
|
||||
|
||||
async start() {
|
||||
let prom = null;
|
||||
|
||||
try {
|
||||
prom = this.current = this.compile();
|
||||
return await prom;
|
||||
}
|
||||
catch (e) {
|
||||
if (e.stats && this.current === prom) {
|
||||
console.log(e.stats.toString({ colors: true }));
|
||||
}
|
||||
|
||||
return {
|
||||
bundle: fs.readFileSync(filename),
|
||||
sourceMap: self.sourceMaps ? fs.readFileSync(mapFilename) : false,
|
||||
style: fs.readFileSync(styleFilename)
|
||||
};
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
this.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a file from the in-memory bundles, paths just like those
|
||||
* produces by the FsOptimizer are used to access files. The first time
|
||||
* a file is requested it is marked as "sent". If that same file is requested
|
||||
* a second time then we will rerun the compiler.
|
||||
*
|
||||
* !!!ONLY ONE BROWSER TAB SHOULD ACCESS THE IN-MEMORY OPTIMIZERS FILES AT A TIME!!!
|
||||
*
|
||||
* @param {[type]} relativePath [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
async get(relativePath) {
|
||||
try {
|
||||
|
||||
let fs = this.outFs;
|
||||
let path = join(this.compiler.outputPath, relativePath);
|
||||
let restart = contains(this.sent, path);
|
||||
|
||||
if (!this.sent || restart) {
|
||||
this.sent = [];
|
||||
this.current = null;
|
||||
}
|
||||
|
||||
await (this.current || this.start());
|
||||
let content = fs.readFileSync(path); // may throw "Path doesn't exist"
|
||||
this.sent.push(path);
|
||||
return content || '';
|
||||
}
|
||||
catch (error) {
|
||||
if (error && error.message.match(/Path doesn't exist/)) {
|
||||
error = Boom.notFound();
|
||||
}
|
||||
|
||||
console.log(error.stack);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
bindToServer(server) {
|
||||
server.route({
|
||||
path: '/bundles/{path*}',
|
||||
method: 'GET',
|
||||
handler: async (request, reply) => {
|
||||
let path = request.params.path;
|
||||
let mimeType = server.mime.path(path).type;
|
||||
|
||||
try {
|
||||
let content = await this.get(path);
|
||||
return reply(content).type(mimeType);
|
||||
} catch (error) {
|
||||
return reply(Boom.wrap(error, 500));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
let _ = require('lodash');
|
||||
let { join } = require('path');
|
||||
let { resolve, promisify } = require('bluebird');
|
||||
let rimraf = promisify(require('rimraf'));
|
||||
let mkdirp = promisify(require('mkdirp'));
|
||||
let stat = promisify(require('fs').stat);
|
||||
let read = promisify(require('fs').readFile);
|
||||
let write = promisify(require('fs').writeFile);
|
||||
let unlink = promisify(require('fs').unlink);
|
||||
let readdir = promisify(require('fs').readdir);
|
||||
let readSync = require('fs').readFileSync;
|
||||
|
||||
let entryFileTemplate = _.template(readSync(join(__dirname, 'entry.js.tmpl')));
|
||||
|
||||
class OptmzBundles {
|
||||
constructor(opts, optimizerTagline) {
|
||||
this.dir = _.get(opts, 'bundleDir');
|
||||
if (!_.isString(this.dir)) {
|
||||
throw new TypeError('Optimizer requires a working directory');
|
||||
}
|
||||
|
||||
this.sourceMaps = _.get(opts, 'sourceMaps');
|
||||
this.entries = _.get(opts, 'entries', []).map(function (spec) {
|
||||
let entry = {
|
||||
id: spec.id,
|
||||
deps: _.get(spec, 'deps', []),
|
||||
modules: _.get(spec, 'modules', []),
|
||||
|
||||
path: join(this.dir, spec.id + '.entry.js'),
|
||||
bundlePath: join(this.dir, spec.id + '.bundle.js'),
|
||||
|
||||
exists: undefined,
|
||||
content: undefined
|
||||
};
|
||||
|
||||
entry.content = _.get(spec, 'template', entryFileTemplate)({
|
||||
entry: entry,
|
||||
optimizerTagline: optimizerTagline
|
||||
});
|
||||
|
||||
return entry;
|
||||
}, this);
|
||||
|
||||
|
||||
_.bindAll(this, [
|
||||
'ensureDir',
|
||||
'ensureAllEntriesExist',
|
||||
'checkIfEntryExists',
|
||||
'writeEntryFile',
|
||||
'clean',
|
||||
'getMissingEntries',
|
||||
'getEntriesConfig'
|
||||
]);
|
||||
}
|
||||
|
||||
ensureDir() {
|
||||
return mkdirp(this.dir);
|
||||
}
|
||||
|
||||
ensureAllEntriesExist(overwrite) {
|
||||
return this.ensureDir()
|
||||
.return(this.entries)
|
||||
.map(overwrite ? this.checkIfEntryExists : _.noop)
|
||||
.return(this.entries)
|
||||
.map(this.writeEntryFile)
|
||||
.return(undefined);
|
||||
}
|
||||
|
||||
checkIfEntryExists(entry) {
|
||||
return resolve([
|
||||
read(entry.path),
|
||||
stat(entry.bundlePath)
|
||||
])
|
||||
.settle()
|
||||
.spread(function (readEntry, statBundle) {
|
||||
let existingEntry = readEntry.isFulfilled() && readEntry.value().toString('utf8');
|
||||
let bundleExists = statBundle.isFulfilled() && !statBundle.value().isDirectory();
|
||||
entry.exists = existingEntry && bundleExists && (existingEntry === entry.content);
|
||||
});
|
||||
}
|
||||
|
||||
writeEntryFile(entry) {
|
||||
return this.clean([entry.path, entry.bundlePath]).then(function () {
|
||||
entry.exists = false;
|
||||
return write(entry.path, entry.content, { encoding: 'utf8' });
|
||||
});
|
||||
}
|
||||
|
||||
// unlinks files, swallows missing file errors
|
||||
clean(paths) {
|
||||
return resolve(
|
||||
_.flatten([paths]).map(function (path) {
|
||||
return rimraf(path);
|
||||
})
|
||||
)
|
||||
.settle()
|
||||
.return(undefined);
|
||||
}
|
||||
|
||||
getMissingEntries() {
|
||||
return _.reject(this.entries, 'exists');
|
||||
}
|
||||
|
||||
getEntriesConfig() {
|
||||
return _.transform(this.getMissingEntries(), function (map, entry) {
|
||||
map[entry.id] = entry.path;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OptmzBundles;
|
|
@ -1,98 +0,0 @@
|
|||
var _ = require('lodash');
|
||||
var fromRoot = require('../utils/fromRoot');
|
||||
|
||||
var asRegExp = _.flow(
|
||||
_.escapeRegExp,
|
||||
function (path) {
|
||||
return path + '(?:\\.js)?$';
|
||||
},
|
||||
RegExp
|
||||
);
|
||||
|
||||
function OptmzUiExports(plugins) {
|
||||
// regular expressions which will prevent webpack from parsing the file
|
||||
var noParse = this.noParse = [];
|
||||
|
||||
// webpack aliases, like require paths, mapping a prefix to a directory
|
||||
var aliases = this.aliases = {
|
||||
ui: fromRoot('src/ui/public'),
|
||||
testHarness: fromRoot('src/testHarness/public')
|
||||
};
|
||||
|
||||
// webpack loaders map loader configuration to regexps
|
||||
var loaders = this.loaders = [];
|
||||
|
||||
var claimedModuleIds = {};
|
||||
_.each(plugins, function (plugin) {
|
||||
var exports = plugin.uiExportsSpecs;
|
||||
|
||||
// add an alias for this plugins public directory
|
||||
if (plugin.publicDir) {
|
||||
aliases[`plugins/${plugin.id}`] = plugin.publicDir;
|
||||
}
|
||||
|
||||
// consume the plugin's "modules" exports
|
||||
_.forOwn(exports.modules, function (spec, id) {
|
||||
if (claimedModuleIds[id]) {
|
||||
throw new TypeError(`Plugin ${plugin.id} attempted to override export "${id}" from ${claimedModuleIds[id]}`);
|
||||
} else {
|
||||
claimedModuleIds[id] = plugin.id;
|
||||
}
|
||||
|
||||
// configurable via spec
|
||||
var path;
|
||||
var parse = true;
|
||||
var imports = null;
|
||||
var exports = null;
|
||||
var expose = null;
|
||||
|
||||
// basic style, just a path
|
||||
if (_.isString(spec)) path = spec;
|
||||
|
||||
if (_.isArray(spec)) {
|
||||
path = spec[0];
|
||||
imports = spec[1];
|
||||
exports = spec[2];
|
||||
}
|
||||
|
||||
if (_.isPlainObject(spec)) {
|
||||
path = spec.path;
|
||||
parse = _.get(spec, 'parse', parse);
|
||||
imports = _.get(spec, 'imports', imports);
|
||||
exports = _.get(spec, 'exports', exports);
|
||||
expose = _.get(spec, 'expose', expose);
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
throw new TypeError('Invalid spec definition, unable to identify path');
|
||||
}
|
||||
|
||||
aliases[id] = path;
|
||||
|
||||
var loader = [];
|
||||
if (imports) {
|
||||
loader.push(`imports?${imports}`);
|
||||
}
|
||||
|
||||
if (exports) loader.push(`exports?${exports}`);
|
||||
if (expose) loader.push(`expose?${expose}`);
|
||||
if (loader.length) loaders.push({ test: asRegExp(path), loader: loader.join('!') });
|
||||
|
||||
if (!parse) noParse.push(asRegExp(path));
|
||||
});
|
||||
|
||||
// consume the plugin's "loaders" exports
|
||||
_.each(exports.loaders, function (loader) {
|
||||
loaders.push(loader);
|
||||
});
|
||||
|
||||
|
||||
// consume the plugin's "noParse" exports
|
||||
_.each(exports.noParse, function (regExp) {
|
||||
noParse.push(regExp);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = OptmzUiExports;
|
|
@ -1,118 +0,0 @@
|
|||
let _ = require('lodash');
|
||||
let webpack = require('webpack');
|
||||
|
||||
let BaseOptimizer = require('./BaseOptimizer');
|
||||
|
||||
const STATUS_BUNDLE_INVALID = 'bundle invalid';
|
||||
const STATUS_BUNDLING = 'optimizing';
|
||||
const STATUS_REBUNDLING = 'bundle invalid during optimizing';
|
||||
const STATUS_ERROR = 'error';
|
||||
const STATUS_DONE = 'done';
|
||||
|
||||
class WatchingOptimizer extends BaseOptimizer {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
|
||||
this.bundleStatus = null;
|
||||
_.bindAll(this, 'init', 'setupCompiler', 'onBundlesInvalid', 'setStatus', 'enable', 'disable');
|
||||
|
||||
this.run = this.enable; // enable makes a bit more sense here, but alias for consistency with CachedOptimizer
|
||||
}
|
||||
|
||||
init(autoEnable) {
|
||||
return this.bundles.ensureAllEntriesExist(true).then(autoEnable ? this.enable : this.setupCompiler);
|
||||
}
|
||||
|
||||
setupCompiler(autoEnable) {
|
||||
if (!_.size(this.bundles.entries)) return;
|
||||
|
||||
this.compilerConfig = this.getConfig();
|
||||
this.compiler = webpack(this.compilerConfig);
|
||||
this.compiler.plugin('watch-run', _.partial(this.setStatus, STATUS_BUNDLING));
|
||||
this.compiler.plugin('invalid', this.onBundlesInvalid);
|
||||
this.compiler.plugin('failed', _.partial(this.setStatus, STATUS_ERROR));
|
||||
this.compiler.plugin('done', _.partial(this.setStatus, STATUS_DONE));
|
||||
|
||||
if (autoEnable) this.enable();
|
||||
}
|
||||
|
||||
onBundlesInvalid() {
|
||||
switch (this.bundleStatus || null) {
|
||||
case STATUS_BUNDLING:
|
||||
case STATUS_REBUNDLING:
|
||||
// if the source changed during building, we immediately rebuild
|
||||
return this.setStatus(STATUS_REBUNDLING);
|
||||
case null:
|
||||
// the bundle has to be something before that something can be invalid
|
||||
return;
|
||||
default:
|
||||
return this.setStatus(STATUS_BUNDLE_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
setStatus(status) {
|
||||
let self = this;
|
||||
let entries = self.bundles.entries;
|
||||
let stats;
|
||||
let error;
|
||||
let shouldBeFinal = false;
|
||||
|
||||
switch (status) {
|
||||
case 'done':
|
||||
stats = self.watcher.stats;
|
||||
error = null;
|
||||
shouldBeFinal = true;
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
error = new Error('Optimization must not produce errors or warnings');
|
||||
status = 'error';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
stats = self.watcher.stats;
|
||||
error = self.watcher.error;
|
||||
}
|
||||
|
||||
let apply = function () {
|
||||
clearTimeout(self.tentativeStatusChange);
|
||||
self.tentativeStatusChange = null;
|
||||
self.emit(self.bundleStatus = status, entries, stats, error);
|
||||
};
|
||||
|
||||
if (shouldBeFinal) {
|
||||
// this looks race-y, but it's how webpack does it: https://goo.gl/ShVo2o
|
||||
self.tentativeStatusChange = setTimeout(apply, 0);
|
||||
} else {
|
||||
apply();
|
||||
}
|
||||
|
||||
// webpack allows some plugins to be async, we don't want to hold up webpack,
|
||||
// so just always callback if we get a cb();
|
||||
let cb = _.last(arguments);
|
||||
if (typeof cb === 'function') cb();
|
||||
}
|
||||
|
||||
enable() {
|
||||
if (!this.compiler) {
|
||||
return this.setupCompiler(true);
|
||||
}
|
||||
|
||||
if (this.watcher) {
|
||||
throw new Error('WatchingOptimizer already watching!');
|
||||
}
|
||||
|
||||
this.watcher = this.compiler.watch({}, _.noop);
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (!this.compiler) return;
|
||||
if (!this.watcher) return;
|
||||
|
||||
this.watcher.close();
|
||||
this.watcher = null;
|
||||
this.compiler = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WatchingOptimizer;
|
|
@ -1,37 +1,36 @@
|
|||
let _ = require('lodash');
|
||||
let { once, template } = require('lodash');
|
||||
let { resolve } = require('path');
|
||||
let { readFileSync } = require('fs');
|
||||
|
||||
let src = require('requirefrom')('src');
|
||||
let fromRoot = src('utils/fromRoot');
|
||||
let pathContains = src('utils/pathContains');
|
||||
let LiveOptimizer = src('optimize/LiveOptimizer');
|
||||
|
||||
let id = 'tests';
|
||||
let globAll = require('./globAll');
|
||||
let testEntryFileTemplate = _.template(readFileSync(resolve(__dirname, './testBundleEntry.js.tmpl')));
|
||||
let testEntryFileTemplate = template(readFileSync(resolve(__dirname, './testBundleEntry.js.tmpl')));
|
||||
|
||||
class TestBundler {
|
||||
constructor(kbnServer) {
|
||||
this.kbnServer = kbnServer;
|
||||
this.init = _.once(this.init);
|
||||
_.bindAll(this, ['init', 'findTestFiles', 'setupOptimizer', 'render']);
|
||||
this.hapi = kbnServer.server;
|
||||
this.plugins = kbnServer.plugins;
|
||||
this.bundleDir = kbnServer.config.get('optimize.bundleDir');
|
||||
this.init = once(this.init);
|
||||
}
|
||||
|
||||
init() {
|
||||
return this.findTestFiles().then(this.setupOptimizer);
|
||||
async init() {
|
||||
await this.findTestFiles();
|
||||
await this.setupOptimizer();
|
||||
}
|
||||
|
||||
findTestFiles() {
|
||||
return globAll(fromRoot('src'), [
|
||||
'**/public/**/__tests__/**/*.js'
|
||||
async findTestFiles() {
|
||||
await globAll(fromRoot('.'), [
|
||||
'src/**/public/**/__tests__/**/*.js',
|
||||
'installedPlugins/**/public/**/__tests__/**/*.js'
|
||||
]);
|
||||
}
|
||||
|
||||
setupOptimizer(testFiles) {
|
||||
let plugins = this.kbnServer.plugins;
|
||||
let bundleDir = this.kbnServer.config.get('optimize.bundleDir');
|
||||
|
||||
async setupOptimizer(testFiles) {
|
||||
let deps = [];
|
||||
let modules = [];
|
||||
|
||||
|
@ -39,7 +38,7 @@ class TestBundler {
|
|||
modules = modules.concat(testFiles);
|
||||
}
|
||||
|
||||
plugins.forEach(function (plugin) {
|
||||
this.plugins.forEach(function (plugin) {
|
||||
if (!plugin.app) return;
|
||||
modules = modules.concat(plugin.app.getModules());
|
||||
deps = deps.concat(plugin.app.getRelatedPlugins());
|
||||
|
@ -47,7 +46,7 @@ class TestBundler {
|
|||
|
||||
this.optimizer = new LiveOptimizer({
|
||||
sourceMaps: true,
|
||||
bundleDir: bundleDir,
|
||||
bundleDir: this.bundleDir,
|
||||
entries: [
|
||||
{
|
||||
id: id,
|
||||
|
@ -56,22 +55,12 @@ class TestBundler {
|
|||
template: testEntryFileTemplate
|
||||
}
|
||||
],
|
||||
plugins: plugins
|
||||
plugins: this.plugins
|
||||
});
|
||||
|
||||
return this.optimizer.init();
|
||||
}
|
||||
await this.optimizer.init();
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
let first = !this.optimizer;
|
||||
let server = this.kbnServer.server;
|
||||
|
||||
return self.init()
|
||||
.then(function () {
|
||||
server.log(['optimize', 'testHarness', first ? 'info' : 'debug'], 'Test harness built, compiling test bundle');
|
||||
return self.optimizer.get(id);
|
||||
});
|
||||
this.optimizer.bindToServer(this.hapi);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
39
src/optimize/dist/dist.js
vendored
Normal file
39
src/optimize/dist/dist.js
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* Optimize source code in a way that is suitable for distribution
|
||||
*/
|
||||
module.exports = async (kbnServer, server, config) => {
|
||||
let FsOptimizer = require('../FsOptimizer');
|
||||
|
||||
let optimizer = new FsOptimizer({
|
||||
env: kbnServer.bundles.env,
|
||||
bundles: kbnServer.bundles,
|
||||
sourceMaps: config.get('optimize.sourceMaps')
|
||||
});
|
||||
|
||||
server.exposeStaticDir('/bundles/{path*}', kbnServer.bundles.env.workingDir);
|
||||
|
||||
try {
|
||||
await optimizer.init();
|
||||
let bundleIds = optimizer.bundles.getIds();
|
||||
|
||||
if (bundleIds.length) {
|
||||
server.log(
|
||||
['warning', 'optimize'],
|
||||
`Optimizing bundles for ${bundleIds.join(', ')}. This may take a few minutes.`
|
||||
);
|
||||
} else {
|
||||
server.log(
|
||||
['debug', 'optimize'],
|
||||
`All bundles are cached and ready to go!`
|
||||
);
|
||||
}
|
||||
|
||||
await optimizer.run();
|
||||
} catch (e) {
|
||||
if (e.stats) {
|
||||
server.log(['error'], e.stats.toString({ colors: true }));
|
||||
}
|
||||
server.log(['fatal'], e);
|
||||
process.exit(1); // eslint-disable-line no-process-exit
|
||||
}
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
/**
|
||||
* Optimized application entry file
|
||||
*
|
||||
* This is programatically created and updated, do not modify
|
||||
*
|
||||
* built using: <%= optimizerTagline %>
|
||||
* includes code from:
|
||||
<%
|
||||
entry.deps.sort().forEach(function (plugin) {
|
||||
print(` * - ${plugin}\n`);
|
||||
})
|
||||
%> *
|
||||
*/
|
||||
|
||||
require('ui/chrome');
|
||||
<%
|
||||
entry.modules.forEach(function (id) {
|
||||
if (id !== 'ui/chrome') print(`require('${id}');\n`);
|
||||
});
|
||||
%>require('ui/chrome').bootstrap(/* xoxo */);
|
|
@ -1,115 +1,7 @@
|
|||
module.exports = function (kbnServer, server, config) {
|
||||
if (!config.get('optimize.enabled')) return;
|
||||
|
||||
var _ = require('lodash');
|
||||
var { resolve } = require('path');
|
||||
var fromRoot = require('../utils/fromRoot');
|
||||
|
||||
var CachedOptimizer = require('./CachedOptimizer');
|
||||
var WatchingOptimizer = require('./WatchingOptimizer');
|
||||
|
||||
var bundleDir = resolve(config.get('optimize.bundleDir'));
|
||||
var status = kbnServer.status.create('optimize');
|
||||
|
||||
server.exposeStaticDir('/bundles/{path*}', bundleDir);
|
||||
|
||||
function logStats(tag, stats) {
|
||||
if (config.get('logging.json')) {
|
||||
server.log(['optimize', tag], _.pick(stats.toJson(), 'errors', 'warnings'));
|
||||
} else {
|
||||
server.log(['optimize', tag], `\n${ stats.toString({ colors: true }) }`);
|
||||
}
|
||||
}
|
||||
|
||||
function describeEntries(entries) {
|
||||
let ids = _.pluck(entries, 'id').join('", "');
|
||||
return `application${ entries.length === 1 ? '' : 's'} "${ids}"`;
|
||||
}
|
||||
|
||||
function onMessage(handle, filter) {
|
||||
filter = filter || _.constant(true);
|
||||
process.on('message', function (msg) {
|
||||
var optimizeMsg = msg && msg.optimizeMsg;
|
||||
if (!optimizeMsg || !filter(optimizeMsg)) return;
|
||||
handle(optimizeMsg);
|
||||
});
|
||||
}
|
||||
|
||||
var role = config.get('optimize._workerRole');
|
||||
if (role === 'receive') {
|
||||
// query for initial status
|
||||
process.send(['WORKER_BROADCAST', { optimizeMsg: '?' }]);
|
||||
onMessage(function (wrkrStatus) {
|
||||
status[wrkrStatus.state](wrkrStatus.message);
|
||||
});
|
||||
}
|
||||
|
||||
if (role === 'send') {
|
||||
let send = function () {
|
||||
process.send(['WORKER_BROADCAST', { optimizeMsg: status }]);
|
||||
};
|
||||
|
||||
status.on('change', send);
|
||||
onMessage(send, _.partial(_.eq, '?'));
|
||||
send();
|
||||
}
|
||||
|
||||
let watching = config.get('optimize.watch');
|
||||
let Optimizer = watching ? WatchingOptimizer : CachedOptimizer;
|
||||
let optmzr = kbnServer.optimizer = new Optimizer({
|
||||
sourceMaps: config.get('optimize.sourceMaps'),
|
||||
bundleDir: bundleDir,
|
||||
entries: _.map(kbnServer.uiExports.allApps(), function (app) {
|
||||
return {
|
||||
id: app.id,
|
||||
deps: app.getRelatedPlugins(),
|
||||
modules: app.getModules()
|
||||
};
|
||||
}),
|
||||
plugins: kbnServer.plugins
|
||||
});
|
||||
|
||||
server.on('close', _.bindKey(optmzr.disable || _.noop, optmzr));
|
||||
|
||||
kbnServer.mixin(require('./browserTests'))
|
||||
.then(function () {
|
||||
|
||||
if (role === 'receive') return;
|
||||
|
||||
optmzr.on('bundle invalid', function () {
|
||||
status.yellow('Source file change detected, reoptimizing source files');
|
||||
});
|
||||
|
||||
optmzr.on('done', function (entries, stats) {
|
||||
logStats('debug', stats);
|
||||
status.green(`Optimization of ${describeEntries(entries)} complete`);
|
||||
});
|
||||
|
||||
optmzr.on('error', function (entries, stats, err) {
|
||||
if (stats) logStats('fatal', stats);
|
||||
status.red('Optimization failure! ' + err.message);
|
||||
});
|
||||
|
||||
return optmzr.init()
|
||||
.then(function () {
|
||||
let entries = optmzr.bundles.getMissingEntries();
|
||||
if (!entries.length) {
|
||||
if (watching) {
|
||||
status.red('No optimizable applications found');
|
||||
} else {
|
||||
status.green('Reusing previously cached application source files');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (watching) {
|
||||
status.yellow(`Optimizing and watching all application source files`);
|
||||
} else {
|
||||
status.yellow(`Optimizing and caching ${describeEntries(entries)}`);
|
||||
}
|
||||
|
||||
optmzr.run();
|
||||
return null;
|
||||
});
|
||||
});
|
||||
let lazy = config.get('optimize.lazy');
|
||||
let strategy = lazy ? require('./lazy/lazy') : require('./dist/dist');
|
||||
return kbnServer.mixin(strategy);
|
||||
};
|
||||
|
|
22
src/optimize/lazy/LazyServer.js
Normal file
22
src/optimize/lazy/LazyServer.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
let { Server } = require('hapi');
|
||||
let { fromNode } = require('bluebird');
|
||||
let Boom = require('boom');
|
||||
|
||||
|
||||
module.exports = class LazyServer {
|
||||
constructor(host, port, optimizer) {
|
||||
this.optimizer = optimizer;
|
||||
this.server = new Server({ minimal: true });
|
||||
this.server.connection({
|
||||
host: host,
|
||||
port: port
|
||||
});
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.optimizer.init();
|
||||
this.optimizer.bindToServer(this.server);
|
||||
await fromNode(cb => this.server.start(cb));
|
||||
}
|
||||
};
|
35
src/optimize/lazy/lazy.js
Normal file
35
src/optimize/lazy/lazy.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
module.exports = async (kbnServer, server, config) => {
|
||||
|
||||
let { isWorker } = require('cluster');
|
||||
|
||||
if (!isWorker) {
|
||||
throw new Error(`lazy optimization is only available in "watch" mode.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* When running in lazy mode two workers/threads run in one
|
||||
* of the modes: 'optmzr' or 'server'
|
||||
*
|
||||
* optmzr: this thread runs the LiveOptimizer and the LazyServer
|
||||
* which serves the LiveOptimizer's output and blocks requests
|
||||
* while the optimizer is running
|
||||
*
|
||||
* server: this thread runs the entire kibana server and proxies
|
||||
* all requests for /bundles/* to the optmzr
|
||||
*
|
||||
* @param {string} process.env.kbnWorkerType
|
||||
*/
|
||||
switch (process.env.kbnWorkerType) {
|
||||
case 'optmzr':
|
||||
await kbnServer.mixin(require('./optmzrRole'));
|
||||
break;
|
||||
|
||||
case 'server':
|
||||
await kbnServer.mixin(require('./proxyRole'));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`unkown kbnWorkerType "${process.env.kbnWorkerType}"`);
|
||||
}
|
||||
|
||||
};
|
19
src/optimize/lazy/optmzrRole.js
Normal file
19
src/optimize/lazy/optmzrRole.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
module.exports = async (kbnServer, kibanaHapiServer, config) => {
|
||||
|
||||
let src = require('requirefrom')('src');
|
||||
let fromRoot = src('utils/fromRoot');
|
||||
let LazyServer = require('./LazyServer');
|
||||
let LiveOptimizer = require('../LiveOptimizer');
|
||||
|
||||
let server = new LazyServer(
|
||||
config.get('optimize.lazyHost'),
|
||||
config.get('optimize.lazyPort'),
|
||||
new LiveOptimizer({
|
||||
env: kbnServer.bundles.env,
|
||||
bundles: kbnServer.bundles,
|
||||
sourceMaps: config.get('optimize.sourceMaps')
|
||||
})
|
||||
);
|
||||
|
||||
await server.init();
|
||||
};
|
16
src/optimize/lazy/proxyRole.js
Normal file
16
src/optimize/lazy/proxyRole.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
module.exports = (kbnServer, server, config) => {
|
||||
|
||||
server.route({
|
||||
path: '/bundles/{path*}',
|
||||
method: 'GET',
|
||||
handler: {
|
||||
proxy: {
|
||||
host: config.get('optimize.lazyHost'),
|
||||
port: config.get('optimize.lazyPort'),
|
||||
passThrough: true,
|
||||
xforward: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
|
@ -16,7 +16,7 @@ require('ui/chrome')
|
|||
'logo': 'url(' + kibanaLogoUrl + ') left no-repeat',
|
||||
'smallLogo': 'url(' + kibanaLogoUrl + ') left no-repeat'
|
||||
})
|
||||
.setNavBackground('#222222')
|
||||
.setNavBackground('black')
|
||||
.setTabDefaults({
|
||||
resetWhenActive: true,
|
||||
trackLastPath: true,
|
||||
|
|
|
@ -22,14 +22,25 @@ module.exports = class KbnServer extends EventEmitter {
|
|||
require('./config/setup'),
|
||||
require('./http'),
|
||||
require('./logging'),
|
||||
require('./status'), // sets this.status
|
||||
require('./plugins'), // sets this.plugins
|
||||
require('./status'),
|
||||
|
||||
// find plugins and set this.plugins
|
||||
require('./plugins/scan'),
|
||||
|
||||
// tell the config we are done loading plugins
|
||||
require('./config/complete'),
|
||||
|
||||
require('../ui'), // sets this.uiExports
|
||||
// setup this.uiExports and this.bundles
|
||||
require('../ui'),
|
||||
|
||||
// ensure that all bundles are built, or that the
|
||||
// lazy bundle server is running
|
||||
require('../optimize'),
|
||||
|
||||
function () {
|
||||
// finally, initialize the plugins
|
||||
require('./plugins/initialize'),
|
||||
|
||||
() => {
|
||||
if (this.config.get('server.autoListen')) {
|
||||
this.listen();
|
||||
}
|
||||
|
|
|
@ -75,8 +75,14 @@ module.exports = Joi.object({
|
|||
bundleDir: Joi.string().default(fromRoot('optimize/bundles')),
|
||||
viewCaching: Joi.boolean().default(Joi.ref('$prod')),
|
||||
watch: Joi.boolean().default(Joi.ref('$dev')),
|
||||
lazy: Joi.boolean().when('watch', {
|
||||
is: true,
|
||||
then: Joi.default(true),
|
||||
otherwise: Joi.default(false)
|
||||
}),
|
||||
lazyPort: Joi.number().default(5602),
|
||||
lazyHost: Joi.string().hostname().default('0.0.0.0'),
|
||||
sourceMaps: Joi.boolean().default(Joi.ref('$dev')),
|
||||
_workerRole: Joi.valid('send', 'receive', null).default(null)
|
||||
}).default()
|
||||
|
||||
}).default();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
let _ = require('lodash');
|
||||
let Joi = require('joi');
|
||||
let Promise = require('bluebird');
|
||||
let { attempt, fromNode } = require('bluebird');
|
||||
let { resolve } = require('path');
|
||||
let { inherits } = require('util');
|
||||
|
||||
|
@ -33,63 +33,59 @@ module.exports = class Plugin {
|
|||
};
|
||||
}
|
||||
|
||||
init() {
|
||||
let self = this;
|
||||
|
||||
let id = self.id;
|
||||
let version = self.version;
|
||||
let kbnStatus = self.kbnServer.status;
|
||||
let server = self.kbnServer.server;
|
||||
let config = self.kbnServer.config;
|
||||
async init() {
|
||||
let id = this.id;
|
||||
let version = this.version;
|
||||
let kbnStatus = this.kbnServer.status;
|
||||
let server = this.kbnServer.server;
|
||||
let config = this.kbnServer.config;
|
||||
|
||||
server.log(['plugins', 'debug'], {
|
||||
tmpl: 'Initializing plugin <%= plugin.id %>',
|
||||
plugin: self
|
||||
plugin: this
|
||||
});
|
||||
|
||||
self.status = kbnStatus.create(`plugin:${self.id}`);
|
||||
return Promise.try(function () {
|
||||
return self.getConfigSchema(Joi);
|
||||
})
|
||||
.then(function (schema) {
|
||||
if (schema) config.extendSchema(id, schema);
|
||||
else config.extendSchema(id, defaultConfigSchema);
|
||||
})
|
||||
.then(function () {
|
||||
if (config.get([id, 'enabled'])) {
|
||||
return self.externalCondition(config);
|
||||
}
|
||||
})
|
||||
.then(function (enabled) {
|
||||
if (!enabled) {
|
||||
// Only change the plugin status if it wasn't set by the externalCondition
|
||||
if (self.status.state === 'uninitialized') {
|
||||
self.status.disabled();
|
||||
}
|
||||
return;
|
||||
if (this.publicDir) {
|
||||
server.exposeStaticDir(`/plugins/${id}/{path*}`, this.publicDir);
|
||||
}
|
||||
|
||||
this.status = kbnStatus.create(`plugin:${this.id}`);
|
||||
|
||||
// setup plugin config
|
||||
let schema = await this.getConfigSchema(Joi);
|
||||
config.extendSchema(id, schema || defaultConfigSchema);
|
||||
|
||||
// ensure that the plugin is enabled
|
||||
let enabled = config.get([id, 'enabled']) && this.externalCondition(config);
|
||||
if (!enabled) {
|
||||
// Only change the plugin status if it wasn't set by the externalCondition
|
||||
if (this.status.state === 'uninitialized') {
|
||||
this.status.disabled();
|
||||
}
|
||||
|
||||
let register = function (server, options, next) {
|
||||
server.expose('status', self.status);
|
||||
Promise.try(self.externalInit, [server, options], self).nodeify(next);
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
register.attributes = { name: id, version: version };
|
||||
// setup the hapi register function and get on with it
|
||||
let register = (server, options, next) => {
|
||||
server.expose('status', this.status);
|
||||
attempt(this.externalInit, [server, options], this).nodeify(next);
|
||||
};
|
||||
|
||||
return Promise.fromNode(function (cb) {
|
||||
server.register({
|
||||
register: register,
|
||||
options: config.has(id) ? config.get(id) : null
|
||||
}, cb);
|
||||
})
|
||||
.then(function () {
|
||||
// Only change the plugin status to green if the
|
||||
// intial status has not been updated
|
||||
if (self.status.state === 'uninitialized') {
|
||||
self.status.green('Ready');
|
||||
}
|
||||
});
|
||||
register.attributes = { name: id, version: version };
|
||||
|
||||
await fromNode(cb => {
|
||||
server.register({
|
||||
register: register,
|
||||
options: config.has(id) ? config.get(id) : null
|
||||
}, cb);
|
||||
});
|
||||
|
||||
// Only change the plugin status to green if the
|
||||
// intial status has not been changed
|
||||
if (this.status.state === 'uninitialized') {
|
||||
this.status.green('Ready');
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
|
|
@ -10,18 +10,17 @@ module.exports = class Plugins extends Array {
|
|||
}
|
||||
|
||||
new(path) {
|
||||
var self = this;
|
||||
var api = new PluginApi(this.kbnServer, path);
|
||||
let output = [].concat(require(path)(api) || []);
|
||||
|
||||
[].concat(require(path)(api) || [])
|
||||
.forEach(function (out) {
|
||||
if (out instanceof api.Plugin) {
|
||||
self._byId = null;
|
||||
self.push(out);
|
||||
for (let product of output) {
|
||||
if (product instanceof api.Plugin) {
|
||||
this._byId = null;
|
||||
this.push(product);
|
||||
} else {
|
||||
throw new TypeError('unexpected plugin export ' + inspect(out));
|
||||
throw new TypeError('unexpected plugin export ' + inspect(product));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get byId() {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
module.exports = function (kbnServer, server, config) {
|
||||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var Boom = require('boom');
|
||||
var { join } = require('path');
|
||||
|
||||
server.exposeStaticDir('/plugins/{id}/{path*}', function (req) {
|
||||
var id = req.params.id;
|
||||
var plugin = kbnServer.plugins.byId[id];
|
||||
return (plugin && plugin.publicDir) ? plugin.publicDir : Boom.notFound();
|
||||
});
|
||||
|
||||
server.method('kbnPluginById', function (id, next) {
|
||||
if (kbnServer.plugins.byId[id]) {
|
||||
next(null, kbnServer.plugins.byId[id]);
|
||||
} else {
|
||||
next(Boom.notFound(`no plugin with the id "${id}"`));
|
||||
}
|
||||
});
|
||||
|
||||
return kbnServer.mixin(
|
||||
require('./scan'),
|
||||
require('./load')
|
||||
);
|
||||
};
|
|
@ -1,11 +1,6 @@
|
|||
module.exports = function (kbnServer, server, config) {
|
||||
let _ = require('lodash');
|
||||
let resolve = require('bluebird').resolve;
|
||||
let all = require('bluebird').all;
|
||||
let attempt = require('bluebird').attempt;
|
||||
|
||||
var Plugins = require('./Plugins');
|
||||
var plugins = kbnServer.plugins = new Plugins(kbnServer);
|
||||
|
||||
let path = [];
|
||||
let step = function (id, block) {
|
||||
|
@ -23,26 +18,23 @@ module.exports = function (kbnServer, server, config) {
|
|||
});
|
||||
};
|
||||
|
||||
return resolve(kbnServer.pluginPaths)
|
||||
.map(function (path) {
|
||||
return plugins.new(path);
|
||||
})
|
||||
return resolve(kbnServer.plugins)
|
||||
.then(function () {
|
||||
if (!config.get('plugins.initialize')) {
|
||||
server.log(['info'], 'Plugin initialization disabled.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return _.toArray(plugins);
|
||||
return _.toArray(kbnServer.plugins);
|
||||
})
|
||||
.each(function loadReqsAndInit(plugin) {
|
||||
return step(plugin.id, function () {
|
||||
return resolve(plugin.requiredIds).map(function (reqId) {
|
||||
if (!plugins.byId[reqId]) {
|
||||
if (!kbnServer.plugins.byId[reqId]) {
|
||||
throw new Error(`Unmet requirement "${reqId}" for plugin "${plugin.id}"`);
|
||||
}
|
||||
|
||||
return loadReqsAndInit(plugins.byId[reqId]);
|
||||
return loadReqsAndInit(kbnServer.plugins.byId[reqId]);
|
||||
})
|
||||
.then(function () {
|
||||
return plugin.init();
|
|
@ -1,63 +1,62 @@
|
|||
module.exports = function (kbnServer, server, config) {
|
||||
module.exports = async (kbnServer, server, config) => {
|
||||
let _ = require('lodash');
|
||||
let Promise = require('bluebird');
|
||||
let readdir = Promise.promisify(require('fs').readdir);
|
||||
let stat = Promise.promisify(require('fs').stat);
|
||||
let resolve = require('path').resolve;
|
||||
let { fromNode } = require('bluebird');
|
||||
let { readdir, stat } = require('fs');
|
||||
let { resolve } = require('path');
|
||||
|
||||
let src = require('requirefrom')('src');
|
||||
let { each } = src('utils/async');
|
||||
|
||||
var Plugins = require('./Plugins');
|
||||
var plugins = kbnServer.plugins = new Plugins(kbnServer);
|
||||
|
||||
let scanDirs = [].concat(config.get('plugins.scanDirs') || []);
|
||||
let absolutePaths = [].concat(config.get('plugins.paths') || []);
|
||||
let pluginPaths = [].concat(config.get('plugins.paths') || []);
|
||||
|
||||
let debug = _.bindKey(server, 'log', ['plugins', 'debug']);
|
||||
let warning = _.bindKey(server, 'log', ['plugins', 'warning']);
|
||||
|
||||
return Promise.map(scanDirs, function (dir) {
|
||||
// scan all scanDirs to find pluginPaths
|
||||
await each(scanDirs, async dir => {
|
||||
debug({ tmpl: 'Scanning `<%= dir %>` for plugins', dir: dir });
|
||||
|
||||
return readdir(dir)
|
||||
.catch(function (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
let filenames = null;
|
||||
|
||||
try {
|
||||
filenames = await fromNode(cb => readdir(dir, cb));
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
|
||||
filenames = [];
|
||||
warning({
|
||||
tmpl: '<%= err.code %>: Unable to scan non-existent directory for plugins "<%= dir %>"',
|
||||
err: err,
|
||||
dir: dir
|
||||
});
|
||||
|
||||
return [];
|
||||
})
|
||||
.map(function (file) {
|
||||
if (file === '.' || file === '..') return false;
|
||||
let path = resolve(dir, file);
|
||||
|
||||
return stat(path).then(function (stat) {
|
||||
return stat.isDirectory() ? path : false;
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function (dirs) {
|
||||
return _([dirs, absolutePaths])
|
||||
.flattenDeep()
|
||||
.compact()
|
||||
.uniq()
|
||||
.value();
|
||||
})
|
||||
.filter(function (dir) {
|
||||
let path;
|
||||
try { path = require.resolve(dir); }
|
||||
catch (e) { path = false; }
|
||||
|
||||
if (!path) {
|
||||
warning({ tmpl: 'Skipping non-plugin directory at <%= dir %>', dir: dir });
|
||||
return false;
|
||||
} else {
|
||||
require(path);
|
||||
debug({ tmpl: 'Found plugin at <%= dir %>', dir: dir });
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.then(function (pluginPaths) {
|
||||
kbnServer.pluginPaths = pluginPaths;
|
||||
|
||||
await each(filenames, async name => {
|
||||
if (name[0] === '.') return;
|
||||
|
||||
let path = resolve(dir, name);
|
||||
let stats = await fromNode(cb => stat(path, cb));
|
||||
if (stats.isDirectory()) {
|
||||
pluginPaths.push(path);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
for (let path of pluginPaths) {
|
||||
let modulePath;
|
||||
try {
|
||||
modulePath = require.resolve(path);
|
||||
} catch (e) {
|
||||
warning({ tmpl: 'Skipping non-plugin directory at <%= path %>', path: path });
|
||||
continue;
|
||||
}
|
||||
|
||||
require(modulePath);
|
||||
plugins.new(path);
|
||||
debug({ tmpl: 'Found plugin at <%= modulePath %>', path: modulePath });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@ module.exports = function (kbnServer) {
|
|||
});
|
||||
|
||||
server.decorate('reply', 'renderStatusPage', function () {
|
||||
var app = _.get(kbnServer, 'uiExports.apps.hidden.byId.statusPage');
|
||||
var app = _.get(kbnServer, 'apps.hidden.byId.statusPage');
|
||||
var resp = app ? this.renderApp(app) : this(kbnServer.status.toString());
|
||||
resp.code(kbnServer.status.isGreen() ? 200 : 503);
|
||||
return resp;
|
||||
|
|
|
@ -19,7 +19,6 @@ class UiApp {
|
|||
this.hidden = this.spec.hidden;
|
||||
this.autoloadOverrides = this.spec.autoload;
|
||||
this.templateName = this.spec.templateName || 'uiApp';
|
||||
this.requireOptimizeGreen = this.spec.requireOptimizeGreen !== false;
|
||||
this.getModules = _.once(this.getModules);
|
||||
}
|
||||
|
||||
|
@ -34,18 +33,6 @@ class UiApp {
|
|||
.value();
|
||||
}
|
||||
|
||||
getRelatedPlugins() {
|
||||
var pluginsById = this.uiExports.kbnServer.plugins.byId;
|
||||
return _.transform(this.getModules(), function (plugins, id) {
|
||||
var matches = id.match(/^plugins\/([^\/]+)(?:\/|$)/);
|
||||
if (!matches) return;
|
||||
|
||||
var plugin = pluginsById[matches[1]];
|
||||
if (_.includes(plugins, plugin)) return;
|
||||
plugins.push(plugin);
|
||||
}, []);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return _.pick(this, ['id', 'title', 'description', 'icon', 'main']);
|
||||
}
|
||||
|
|
69
src/ui/UiBundle.js
Normal file
69
src/ui/UiBundle.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
|
||||
let { join } = require('path');
|
||||
let { promisify } = require('bluebird');
|
||||
let read = promisify(require('fs').readFile);
|
||||
let write = promisify(require('fs').writeFile);
|
||||
let unlink = promisify(require('fs').unlink);
|
||||
let stat = promisify(require('fs').stat);
|
||||
|
||||
module.exports = class UiBundle {
|
||||
constructor(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
this.env = opts.env;
|
||||
this.id = opts.id;
|
||||
this.plugins = opts.plugins;
|
||||
this.modules = opts.modules;
|
||||
this.template = opts.template;
|
||||
|
||||
let pathBase = join(this.env.workingDir, this.id);
|
||||
this.entryPath = `${pathBase}.entry.js`;
|
||||
this.outputPath = `${pathBase}.bundle.js`;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
return this.template({
|
||||
env: this.env,
|
||||
bundle: this
|
||||
});
|
||||
}
|
||||
|
||||
async readEntryFile() {
|
||||
try {
|
||||
let content = await read(this.entryPath);
|
||||
return content.toString('utf8');
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async writeEntryFile() {
|
||||
return await write(this.entryPath, this.renderContent(), { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
async clearBundleFile() {
|
||||
try { await unlink(this.outputPath); }
|
||||
catch (e) { return null; }
|
||||
}
|
||||
|
||||
async checkForExistingOutput() {
|
||||
try {
|
||||
await stat(this.outputPath);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
plugins: this.plugins,
|
||||
modules: this.modules,
|
||||
entryPath: this.entryPath,
|
||||
outputPath: this.outputPath
|
||||
};
|
||||
}
|
||||
};
|
68
src/ui/UiBundleCollection.js
Normal file
68
src/ui/UiBundleCollection.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
let { pull, transform, pluck } = require('lodash');
|
||||
let { join } = require('path');
|
||||
let { resolve, promisify } = require('bluebird');
|
||||
let rimraf = promisify(require('rimraf'));
|
||||
let mkdirp = promisify(require('mkdirp'));
|
||||
let unlink = promisify(require('fs').unlink);
|
||||
let readdir = promisify(require('fs').readdir);
|
||||
let readSync = require('fs').readFileSync;
|
||||
|
||||
let UiBundle = require('./UiBundle');
|
||||
let appEntryTemplate = require('./appEntryTemplate');
|
||||
|
||||
class UiBundleCollection {
|
||||
constructor(bundlerEnv) {
|
||||
this.each = [];
|
||||
this.env = bundlerEnv;
|
||||
}
|
||||
|
||||
addApp(app) {
|
||||
this.each.push(new UiBundle({
|
||||
id: app.id,
|
||||
modules: app.getModules(),
|
||||
template: appEntryTemplate,
|
||||
env: this.env
|
||||
}));
|
||||
}
|
||||
|
||||
async ensureDir() {
|
||||
await mkdirp(this.env.workingDir);
|
||||
}
|
||||
|
||||
async writeEntryFiles() {
|
||||
await this.ensureDir();
|
||||
|
||||
for (let bundle of this.each) {
|
||||
let existing = await bundle.readEntryFile();
|
||||
let expected = bundle.renderContent();
|
||||
|
||||
if (existing !== expected) {
|
||||
await bundle.writeEntryFile();
|
||||
await bundle.clearBundleFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async filterCachedBundles() {
|
||||
for (let bundle of this.each.slice()) {
|
||||
let exists = await bundle.checkForExistingOutput();
|
||||
if (exists) pull(this.each, bundle);
|
||||
}
|
||||
}
|
||||
|
||||
toWebpackEntries() {
|
||||
return transform(this.each, function (entries, bundle) {
|
||||
entries[bundle.id] = bundle.entryPath;
|
||||
}, {});
|
||||
}
|
||||
|
||||
getIds() {
|
||||
return pluck(this.each, 'id');
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.each;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UiBundleCollection;
|
147
src/ui/UiBundlerEnv.js
Normal file
147
src/ui/UiBundlerEnv.js
Normal file
|
@ -0,0 +1,147 @@
|
|||
let { includes, flow, escapeRegExp } = require('lodash');
|
||||
let { isString, isArray, isPlainObject, get } = require('lodash');
|
||||
let { keys } = require('lodash');
|
||||
let fromRoot = require('../utils/fromRoot');
|
||||
|
||||
let asRegExp = flow(
|
||||
escapeRegExp,
|
||||
function (path) {
|
||||
return path + '(?:\\.js)?$';
|
||||
},
|
||||
RegExp
|
||||
);
|
||||
|
||||
let arr = v => [].concat(v || []);
|
||||
|
||||
module.exports = class UiBundlerEnv {
|
||||
constructor(workingDir) {
|
||||
|
||||
// the location that bundle entry files and all compiles files will
|
||||
// be written
|
||||
this.workingDir = workingDir;
|
||||
|
||||
// the context that the bundler is running in, this is not officially
|
||||
// used for anything but it is serialized into the entry file to ensure
|
||||
// that they are invalidated when the context changes
|
||||
this.context = {};
|
||||
|
||||
// the plugins that are used to build this environment
|
||||
// are tracked and embedded into the entry file so that when the
|
||||
// environment changes we can rebuild the bundles
|
||||
this.pluginInfo = [];
|
||||
|
||||
// regular expressions which will prevent webpack from parsing the file
|
||||
this.noParse = [];
|
||||
|
||||
// webpack aliases, like require paths, mapping a prefix to a directory
|
||||
this.aliases = {
|
||||
ui: fromRoot('src/ui/public'),
|
||||
testHarness: fromRoot('src/testHarness/public')
|
||||
};
|
||||
|
||||
// map of which plugins created which aliases
|
||||
this.aliasOwners = {};
|
||||
|
||||
// webpack loaders map loader configuration to regexps
|
||||
this.loaders = [];
|
||||
}
|
||||
|
||||
consumePlugin(plugin) {
|
||||
let tag = `${plugin.id}@${plugin.version}`;
|
||||
if (includes(this.pluginInfo, tag)) return;
|
||||
|
||||
if (plugin.publicDir) {
|
||||
this.aliases[`plugins/${plugin.id}`] = plugin.publicDir;
|
||||
}
|
||||
|
||||
this.pluginInfo.push(tag);
|
||||
}
|
||||
|
||||
exportConsumer(type) {
|
||||
switch (type) {
|
||||
case 'loaders':
|
||||
return (plugin, spec) => {
|
||||
for (let loader of arr(spec)) this.addLoader(loader);
|
||||
};
|
||||
|
||||
case 'noParse':
|
||||
return (plugin, spec) => {
|
||||
for (let re of arr(spec)) this.addNoParse(re);
|
||||
};
|
||||
|
||||
case 'modules':
|
||||
return (plugin, spec) => {
|
||||
for (let id of keys(spec)) this.addModule(id, spec[id], plugin.id);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
addContext(key, val) {
|
||||
this.context[key] = val;
|
||||
}
|
||||
|
||||
addLoader(loader) {
|
||||
this.loaders.push(loader);
|
||||
}
|
||||
|
||||
addNoParse(regExp) {
|
||||
this.noParse.push(regExp);
|
||||
}
|
||||
|
||||
addModule(id, spec, pluginId) {
|
||||
this.claim(id, pluginId);
|
||||
|
||||
// configurable via spec
|
||||
let path;
|
||||
let parse = true;
|
||||
let imports = null;
|
||||
let exports = null;
|
||||
let expose = null;
|
||||
|
||||
// basic style, just a path
|
||||
if (isString(spec)) path = spec;
|
||||
|
||||
if (isArray(spec)) {
|
||||
path = spec[0];
|
||||
imports = spec[1];
|
||||
exports = spec[2];
|
||||
}
|
||||
|
||||
if (isPlainObject(spec)) {
|
||||
path = spec.path;
|
||||
parse = get(spec, 'parse', parse);
|
||||
imports = get(spec, 'imports', imports);
|
||||
exports = get(spec, 'exports', exports);
|
||||
expose = get(spec, 'expose', expose);
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
throw new TypeError('Invalid spec definition, unable to identify path');
|
||||
}
|
||||
|
||||
this.aliases[id] = path;
|
||||
|
||||
let loader = [];
|
||||
if (imports) {
|
||||
loader.push(`imports?${imports}`);
|
||||
}
|
||||
|
||||
if (exports) loader.push(`exports?${exports}`);
|
||||
if (expose) loader.push(`expose?${expose}`);
|
||||
if (loader.length) this.loaders.push({ test: asRegExp(path), loader: loader.join('!') });
|
||||
if (!parse) this.noParse.push(asRegExp(path));
|
||||
}
|
||||
|
||||
claim(id, pluginId) {
|
||||
let owner = pluginId ? `Plugin ${pluginId}` : 'Kibana Server';
|
||||
|
||||
// TODO(spalger): we could do a lot more to detect colliding module defs
|
||||
var existingOwner = this.aliasOwners[id] || this.aliasOwners[`${id}$`];
|
||||
|
||||
if (existingOwner) {
|
||||
throw new TypeError(`${owner} attempted to override export "${id}" from ${existingOwner}`);
|
||||
}
|
||||
|
||||
this.aliasOwners[id] = owner;
|
||||
}
|
||||
};
|
|
@ -5,57 +5,59 @@ var UiApps = require('./UiApps');
|
|||
|
||||
class UiExports {
|
||||
constructor(kbnServer) {
|
||||
this.kbnServer = kbnServer;
|
||||
this.apps = new UiApps(this);
|
||||
this.aliases = {};
|
||||
this.exportConsumer = _.memoize(this.exportConsumer);
|
||||
|
||||
kbnServer.plugins.forEach(_.bindKey(this, 'consumePlugin'));
|
||||
this.consumers = [];
|
||||
}
|
||||
|
||||
consumePlugin(plugin) {
|
||||
var self = this;
|
||||
var types = _.keys(plugin.uiExportsSpecs);
|
||||
|
||||
if (!types) return false;
|
||||
|
||||
var unkown = _.reject(types, self.exportConsumer, self);
|
||||
var unkown = _.reject(types, this.exportConsumer, this);
|
||||
if (unkown.length) {
|
||||
throw new Error('unknown export types ' + unkown.join(', ') + ' in plugin ' + plugin.id);
|
||||
}
|
||||
|
||||
types.forEach(function (type) {
|
||||
self.exportConsumer(type)(plugin, plugin.uiExportsSpecs[type]);
|
||||
for (let consumer of this.consumers) {
|
||||
consumer.consumePlugin && consumer.consumePlugin(plugin);
|
||||
}
|
||||
|
||||
types.forEach((type) => {
|
||||
this.exportConsumer(type)(plugin, plugin.uiExportsSpecs[type]);
|
||||
});
|
||||
}
|
||||
|
||||
addConsumer(consumer) {
|
||||
this.consumers.push(consumer);
|
||||
}
|
||||
|
||||
exportConsumer(type) {
|
||||
var self = this;
|
||||
for (let consumer of this.consumers) {
|
||||
if (!consumer.exportConsumer) continue;
|
||||
let fn = consumer.exportConsumer(type);
|
||||
if (fn) return fn;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'app':
|
||||
return function (plugin, spec) {
|
||||
return (plugin, spec) => {
|
||||
spec = _.defaults({}, spec, { id: plugin.id });
|
||||
plugin.app = self.apps.new(spec);
|
||||
plugin.app = this.apps.new(spec);
|
||||
};
|
||||
|
||||
case 'visTypes':
|
||||
case 'fieldFormats':
|
||||
case 'spyModes':
|
||||
return function (plugin, spec) {
|
||||
self.aliases[type] = _.union(self.aliases[type] || [], spec);
|
||||
};
|
||||
|
||||
case 'modules':
|
||||
case 'loaders':
|
||||
case 'noParse':
|
||||
return function (plugin, spec) {
|
||||
plugin.uiExportsSpecs[type] = spec;
|
||||
return (plugin, spec) => {
|
||||
this.aliases[type] = _.union(this.aliases[type] || [], spec);
|
||||
};
|
||||
|
||||
case 'aliases':
|
||||
return function (plugin, specs) {
|
||||
_.forOwn(specs, function (spec, adhocType) {
|
||||
self.aliases[adhocType] = _.union(self.aliases[adhocType] || [], spec);
|
||||
return (plugin, specs) => {
|
||||
_.forOwn(specs, (spec, adhocType) => {
|
||||
this.aliases[adhocType] = _.union(this.aliases[adhocType] || [], spec);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -81,9 +83,17 @@ class UiExports {
|
|||
.value();
|
||||
}
|
||||
|
||||
allApps() {
|
||||
getAllApps() {
|
||||
return _.union(this.apps, this.apps.hidden);
|
||||
}
|
||||
|
||||
getApp(id) {
|
||||
return this.apps.byId[id];
|
||||
}
|
||||
|
||||
getHiddenApp(id) {
|
||||
return this.apps.hidden.byId[id];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UiExports;
|
||||
|
|
33
src/ui/appEntryTemplate.js
Normal file
33
src/ui/appEntryTemplate.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
module.exports = require('lodash').template(
|
||||
`
|
||||
/**
|
||||
* Optimized application entry file
|
||||
*
|
||||
* This is programatically created and updated, do not modify
|
||||
*
|
||||
* context: <%= JSON.stringify(env.context) %>
|
||||
<%
|
||||
|
||||
env.pluginInfo.sort().forEach(function (plugin) {
|
||||
print(\` * - \${plugin}\n\`);
|
||||
});
|
||||
|
||||
%> *
|
||||
*/
|
||||
|
||||
require('ui/chrome');
|
||||
<%
|
||||
|
||||
bundle.modules.forEach(function (id) {
|
||||
|
||||
if (id !== 'ui/chrome') {
|
||||
print(\`require('\${id}');\n\`);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
%>
|
||||
require('ui/chrome').bootstrap(/* xoxo */);
|
||||
`
|
||||
);
|
|
@ -3,11 +3,29 @@ module.exports = function (kbnServer, server, config) {
|
|||
let Boom = require('boom');
|
||||
let formatUrl = require('url').format;
|
||||
let { join, resolve } = require('path');
|
||||
|
||||
let UiExports = require('./UiExports');
|
||||
let UiBundleCollection = require('./UiBundleCollection');
|
||||
let UiBundlerEnv = require('./UiBundlerEnv');
|
||||
|
||||
let uiExports = kbnServer.uiExports = new UiExports(kbnServer);
|
||||
let apps = uiExports.apps;
|
||||
let hiddenApps = uiExports.apps.hidden;
|
||||
|
||||
let bundlerEnv = new UiBundlerEnv(config.get('optimize.bundleDir'));
|
||||
bundlerEnv.addContext('env', config.get('env.name'));
|
||||
bundlerEnv.addContext('sourceMaps', config.get('optimize.sourceMaps'));
|
||||
bundlerEnv.addContext('kbnVersion', config.get('pkg.version'));
|
||||
bundlerEnv.addContext('buildNum', config.get('pkg.buildNum'));
|
||||
uiExports.addConsumer(bundlerEnv);
|
||||
|
||||
for (let plugin of kbnServer.plugins) {
|
||||
uiExports.consumePlugin(plugin);
|
||||
}
|
||||
|
||||
let bundles = kbnServer.bundles = new UiBundleCollection(bundlerEnv);
|
||||
|
||||
for (let app of uiExports.getAllApps()) {
|
||||
bundles.addApp(app);
|
||||
}
|
||||
|
||||
// render all views from the ui/views directory
|
||||
server.setupViews(resolve(__dirname, 'views'));
|
||||
|
@ -17,7 +35,7 @@ module.exports = function (kbnServer, server, config) {
|
|||
path: '/apps',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
let switcher = hiddenApps.byId.switcher;
|
||||
let switcher = uiExports.getHiddenApp('switcher');
|
||||
if (!switcher) return reply(Boom.notFound('app switcher not installed'));
|
||||
return reply.renderApp(switcher);
|
||||
}
|
||||
|
@ -28,7 +46,7 @@ module.exports = function (kbnServer, server, config) {
|
|||
path: '/api/apps',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
return reply(apps);
|
||||
return reply(uiExports.apps);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -37,7 +55,7 @@ module.exports = function (kbnServer, server, config) {
|
|||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
let id = req.params.id;
|
||||
let app = apps.byId[id];
|
||||
let app = uiExports.apps.byId[id];
|
||||
if (!app) return reply(Boom.notFound('Unkown app ' + id));
|
||||
|
||||
if (kbnServer.status.isGreen()) {
|
||||
|
@ -49,27 +67,9 @@ module.exports = function (kbnServer, server, config) {
|
|||
});
|
||||
|
||||
server.decorate('reply', 'renderApp', function (app) {
|
||||
if (app.requireOptimizeGreen) {
|
||||
let optimizeStatus = kbnServer.status.get('optimize');
|
||||
switch (optimizeStatus && optimizeStatus.state) {
|
||||
case 'yellow':
|
||||
return this(`
|
||||
<html>
|
||||
<head><meta http-equiv="refresh" content="1"></head>
|
||||
<body>${optimizeStatus.message}</body>
|
||||
</html>
|
||||
`).code(503);
|
||||
|
||||
case 'red':
|
||||
return this(`
|
||||
<html><body>${optimizeStatus.message}</body></html>
|
||||
`).code(500);
|
||||
}
|
||||
}
|
||||
|
||||
let payload = {
|
||||
app: app,
|
||||
appCount: apps.length,
|
||||
appCount: uiExports.apps.length,
|
||||
version: kbnServer.version,
|
||||
buildSha: _.get(kbnServer, 'build.sha', '@@buildSha'),
|
||||
buildNumber: _.get(kbnServer, 'build.number', '@@buildNum'),
|
||||
|
|
9
src/utils/async.js
Normal file
9
src/utils/async.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
let { all } = require('bluebird');
|
||||
|
||||
exports.each = async (arr, fn) => {
|
||||
await all(arr.map(fn)).return(undefined);
|
||||
};
|
||||
|
||||
exports.map = async (arr, fn) => {
|
||||
await all(arr.map(fn));
|
||||
};
|
Loading…
Reference in a new issue