[REF] web: make boot.js independent of libraries
This is a first step towards lazy loading the majority of the JS: the boot JS code should not depend on any libraries (underscore.js and jQuery). Even without lazy loading in mind, this would be a nice-to-have. Part of https://github.com/odoo/odoo/pull/32181 task-1961045
This commit is contained in:
+276
-236
@@ -1,10 +1,7 @@
|
||||
/*---------------------------------------------------------
|
||||
* OpenERP Web Boostrap Code
|
||||
*---------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* @name openerp
|
||||
* @namespace openerp
|
||||
*------------------------------------------------------------------------------
|
||||
* Odoo Web Boostrap Code
|
||||
*------------------------------------------------------------------------------
|
||||
*
|
||||
* Each module can return a promise. In that case, the module is marked as loaded
|
||||
* only when the promise is resolved, and its value is equal to the resolved value.
|
||||
@@ -26,274 +23,317 @@
|
||||
* Debug:
|
||||
* Non loaded or failed module informations for debugging
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var jobUID = Date.now();
|
||||
|
||||
var jobs = [];
|
||||
var factories = Object.create(null);
|
||||
var job_names = [];
|
||||
var job_deps = [];
|
||||
var job_promise = [];
|
||||
|
||||
var jobDeps = [];
|
||||
var jobPromises = [];
|
||||
|
||||
var services = Object.create({});
|
||||
|
||||
var commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg;
|
||||
var cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g;
|
||||
|
||||
var debug = ($.deparam($.param.querystring()).debug !== undefined);
|
||||
var debug = /\bdebug\b/.test(window.location.search);
|
||||
|
||||
if (!window.odoo) {
|
||||
window.odoo = {};
|
||||
}
|
||||
var odoo = window.odoo;
|
||||
|
||||
var odoo = window.odoo = window.odoo || {};
|
||||
var didLogInfoResolve;
|
||||
var didLogInfoPromise = new Promise(function (resolve) {
|
||||
didLogInfoResolve = resolve;
|
||||
});
|
||||
|
||||
_.extend(odoo, {
|
||||
testing: typeof QUnit === "object",
|
||||
debug: debug,
|
||||
remaining_jobs: jobs,
|
||||
|
||||
__DEBUG__: {
|
||||
didLogInfo: didLogInfoPromise,
|
||||
get_dependencies: function (name, transitive) {
|
||||
var deps = name instanceof Array ? name : [name],
|
||||
changed;
|
||||
do {
|
||||
changed = false;
|
||||
_.each(job_deps, function (dep) {
|
||||
if (_.contains(deps, dep.to) && (!_.contains(deps, dep.from))) {
|
||||
deps.push(dep.from);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
} while (changed && transitive);
|
||||
return deps;
|
||||
},
|
||||
get_dependents: function (name) {
|
||||
return _.pluck(_.where(job_deps, { from: name }), 'to');
|
||||
},
|
||||
get_waited_jobs: function () {
|
||||
return _.uniq(_.map(jobs, function (job) { return job.name; }));
|
||||
},
|
||||
get_missing_jobs: function () {
|
||||
var self = this;
|
||||
var waited = this.get_waited_jobs();
|
||||
var missing = [];
|
||||
_.each(waited, function (job) {
|
||||
_.each(self.get_dependencies(job), function (job) {
|
||||
if (!(job in self.services)) {
|
||||
missing.push(job);
|
||||
}
|
||||
});
|
||||
odoo.testing = typeof QUnit === 'object';
|
||||
odoo.debug = debug;
|
||||
odoo.remainingJobs = jobs;
|
||||
odoo.__DEBUG__ = {
|
||||
didLogInfo: didLogInfoPromise,
|
||||
getDependencies: function (name, transitive) {
|
||||
var deps = name instanceof Array ? name : [name];
|
||||
var changed;
|
||||
do {
|
||||
changed = false;
|
||||
jobDeps.forEach(function (dep) {
|
||||
if (deps.indexOf(dep.to) >= 0 && deps.indexOf(dep.from) < 0) {
|
||||
deps.push(dep.from);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
return _.filter(_.difference(_.uniq(missing), waited), function (job) { return !job.error; });
|
||||
},
|
||||
get_failed_jobs: function () {
|
||||
return _.filter(jobs, function (job) { return !!job.error; });
|
||||
},
|
||||
factories: factories,
|
||||
services: services,
|
||||
} while (changed && transitive);
|
||||
return deps;
|
||||
},
|
||||
define: function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var name = typeof args[0] === 'string' ? args.shift() : _.uniqueId('__job');
|
||||
var factory = args[args.length - 1];
|
||||
var deps;
|
||||
if (args[0] instanceof Array) {
|
||||
deps = args[0];
|
||||
} else {
|
||||
deps = [];
|
||||
factory.toString()
|
||||
.replace(commentRegExp, '')
|
||||
.replace(cjsRequireRegExp, function (match, dep) {
|
||||
deps.push(dep);
|
||||
});
|
||||
}
|
||||
|
||||
if (odoo.debug) {
|
||||
if (!(deps instanceof Array)) {
|
||||
throw new Error('Dependencies should be defined by an array', deps);
|
||||
}
|
||||
if (typeof factory !== 'function') {
|
||||
throw new Error('Factory should be defined by a function', factory);
|
||||
}
|
||||
if (typeof name !== 'string') {
|
||||
throw new Error("Invalid name definition (should be a string", name);
|
||||
}
|
||||
if (name in factories) {
|
||||
throw new Error("Service " + name + " already defined");
|
||||
}
|
||||
}
|
||||
|
||||
factory.deps = deps;
|
||||
factories[name] = factory;
|
||||
|
||||
jobs.push({
|
||||
name: name,
|
||||
factory: factory,
|
||||
deps: deps,
|
||||
getDependents: function (name) {
|
||||
return jobDeps.filter(function (dep) {
|
||||
return dep.from === name;
|
||||
}).map(function (dep) {
|
||||
return dep.to;
|
||||
});
|
||||
|
||||
job_names.push(name);
|
||||
_.each(deps, function (dep) {
|
||||
job_deps.push({ from: dep, to: name });
|
||||
});
|
||||
|
||||
this.process_jobs(jobs, services);
|
||||
},
|
||||
log: function () {
|
||||
getWaitedJobs: function () {
|
||||
return jobs.map(function (job) {
|
||||
return job.name;
|
||||
}).filter(function (item, index, self) { // uniq
|
||||
return self.indexOf(item) === index;
|
||||
});
|
||||
},
|
||||
getMissingJobs: function () {
|
||||
var self = this;
|
||||
var waited = this.getWaitedJobs();
|
||||
var missing = [];
|
||||
var failed = [];
|
||||
|
||||
if (jobs.length) {
|
||||
var debug_jobs = {};
|
||||
var rejected = [];
|
||||
var rejected_linked = [];
|
||||
var job;
|
||||
var jobdep;
|
||||
|
||||
for (var k = 0; k < jobs.length; k++) {
|
||||
debug_jobs[jobs[k].name] = job = {
|
||||
dependencies: jobs[k].deps,
|
||||
dependents: odoo.__DEBUG__.get_dependents(jobs[k].name),
|
||||
name: jobs[k].name
|
||||
};
|
||||
if (jobs[k].error) {
|
||||
job.error = jobs[k].error;
|
||||
waited.forEach(function (job) {
|
||||
self.getDependencies(job).forEach(function (job) {
|
||||
if (!(job in self.services)) {
|
||||
missing.push(job);
|
||||
}
|
||||
if (jobs[k].rejected) {
|
||||
job.rejected = jobs[k].rejected;
|
||||
rejected.push(job.name);
|
||||
}
|
||||
var deps = odoo.__DEBUG__.get_dependencies(job.name);
|
||||
for (var i = 0; i < deps.length; i++) {
|
||||
if (job.name !== deps[i] && !(deps[i] in services)) {
|
||||
jobdep = debug_jobs[deps[i]] || (deps[i] in factories && _.find(jobs, function (job) { return job.name === deps[i]; }));
|
||||
if (jobdep && jobdep.rejected) {
|
||||
if (!job.rejected) {
|
||||
job.rejected = [];
|
||||
rejected_linked.push(job.name);
|
||||
});
|
||||
});
|
||||
return missing.filter(function (item, index, self) {
|
||||
return self.indexOf(item) === index;
|
||||
}).filter(function (item) {
|
||||
return waited.indexOf(item) < 0;
|
||||
}).filter(function (job) {
|
||||
return !job.error;
|
||||
});
|
||||
},
|
||||
getFailedJobs: function () {
|
||||
return jobs.filter(function (job) {
|
||||
return !!job.error;
|
||||
});
|
||||
},
|
||||
factories: factories,
|
||||
services: services,
|
||||
};
|
||||
odoo.define = function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var name = typeof args[0] === 'string' ? args.shift() : ('__odoo_job' + (jobUID++));
|
||||
var factory = args[args.length - 1];
|
||||
var deps;
|
||||
if (args[0] instanceof Array) {
|
||||
deps = args[0];
|
||||
} else {
|
||||
deps = [];
|
||||
factory.toString()
|
||||
.replace(commentRegExp, '')
|
||||
.replace(cjsRequireRegExp, function (match, dep) {
|
||||
deps.push(dep);
|
||||
});
|
||||
}
|
||||
|
||||
if (odoo.debug) {
|
||||
if (!(deps instanceof Array)) {
|
||||
throw new Error('Dependencies should be defined by an array', deps);
|
||||
}
|
||||
if (typeof factory !== 'function') {
|
||||
throw new Error('Factory should be defined by a function', factory);
|
||||
}
|
||||
if (typeof name !== 'string') {
|
||||
throw new Error("Invalid name definition (should be a string", name);
|
||||
}
|
||||
if (name in factories) {
|
||||
throw new Error("Service " + name + " already defined");
|
||||
}
|
||||
}
|
||||
|
||||
factory.deps = deps;
|
||||
factories[name] = factory;
|
||||
|
||||
jobs.push({
|
||||
name: name,
|
||||
factory: factory,
|
||||
deps: deps,
|
||||
});
|
||||
|
||||
deps.forEach(function (dep) {
|
||||
jobDeps.push({from: dep, to: name});
|
||||
});
|
||||
|
||||
this.processJobs(jobs, services);
|
||||
};
|
||||
odoo.log = function () {
|
||||
var missing = [];
|
||||
var failed = [];
|
||||
|
||||
if (jobs.length) {
|
||||
var debugJobs = {};
|
||||
var rejected = [];
|
||||
var rejectedLinked = [];
|
||||
var job;
|
||||
var jobdep;
|
||||
|
||||
for (var k = 0; k < jobs.length; k++) {
|
||||
debugJobs[jobs[k].name] = job = {
|
||||
dependencies: jobs[k].deps,
|
||||
dependents: odoo.__DEBUG__.getDependents(jobs[k].name),
|
||||
name: jobs[k].name
|
||||
};
|
||||
if (jobs[k].error) {
|
||||
job.error = jobs[k].error;
|
||||
}
|
||||
if (jobs[k].rejected) {
|
||||
job.rejected = jobs[k].rejected;
|
||||
rejected.push(job.name);
|
||||
}
|
||||
var deps = odoo.__DEBUG__.getDependencies(job.name);
|
||||
for (var i = 0; i < deps.length; i++) {
|
||||
if (job.name !== deps[i] && !(deps[i] in services)) {
|
||||
jobdep = debugJobs[deps[i]];
|
||||
if (!jobdep && deps[i] in factories) {
|
||||
for (var j = 0; j < jobs.length; j++) {
|
||||
if (jobs[j].name === deps[i]) {
|
||||
jobdep = jobs[j];
|
||||
break;
|
||||
}
|
||||
job.rejected.push(deps[i]);
|
||||
} else {
|
||||
if (!job.missing) {
|
||||
job.missing = [];
|
||||
}
|
||||
job.missing.push(deps[i]);
|
||||
}
|
||||
}
|
||||
if (jobdep && jobdep.rejected) {
|
||||
if (!job.rejected) {
|
||||
job.rejected = [];
|
||||
rejectedLinked.push(job.name);
|
||||
}
|
||||
job.rejected.push(deps[i]);
|
||||
} else {
|
||||
if (!job.missing) {
|
||||
job.missing = [];
|
||||
}
|
||||
job.missing.push(deps[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
missing = odoo.__DEBUG__.get_missing_jobs();
|
||||
failed = odoo.__DEBUG__.get_failed_jobs();
|
||||
var unloaded = _.filter(debug_jobs, function (job) { return job.missing; });
|
||||
|
||||
if (odoo.debug || !_.isEmpty(failed) || !_.isEmpty(unloaded)) {
|
||||
var log = console[_.isEmpty(failed) || _.isEmpty(unloaded) ? 'info' : 'error'].bind(console);
|
||||
log((_.isEmpty(failed) ? (_.isEmpty(unloaded) ? 'info' : 'warning') : 'error') + ': Some modules could not be started');
|
||||
if (missing.length) {
|
||||
log('Missing dependencies: ', missing);
|
||||
}
|
||||
if (!_.isEmpty(failed)) {
|
||||
log('Failed modules: ', _.pluck(failed, 'name'));
|
||||
}
|
||||
if (!_.isEmpty(rejected)) {
|
||||
log('Rejected modules: ', rejected);
|
||||
}
|
||||
if (!_.isEmpty(rejected_linked)) {
|
||||
log('Rejected linked modules: ', rejected_linked);
|
||||
}
|
||||
if (!_.isEmpty(unloaded)) {
|
||||
log('Non loaded modules: ', _.pluck(unloaded, 'name'));
|
||||
}
|
||||
if (odoo.debug && !_.isEmpty(debug_jobs)) {
|
||||
log('Debug: ', debug_jobs);
|
||||
}
|
||||
}
|
||||
}
|
||||
odoo.__DEBUG__.js_modules = {
|
||||
missing: missing,
|
||||
failed: _.pluck(failed, 'name'),
|
||||
};
|
||||
didLogInfoResolve();
|
||||
},
|
||||
process_jobs: function (jobs, services) {
|
||||
var job;
|
||||
|
||||
function process_job(job) {
|
||||
var require = make_require(job);
|
||||
|
||||
var job_exec;
|
||||
var def = new Promise(function (resolve) {
|
||||
try {
|
||||
job_exec = job.factory.call(null, require);
|
||||
jobs.splice(jobs.indexOf(job), 1);
|
||||
} catch (e) {
|
||||
job.error = e;
|
||||
console.error('Error while loading ' + job.name);
|
||||
console.error(e.stack);
|
||||
}
|
||||
if (!job.error) {
|
||||
Promise.resolve(job_exec).then(
|
||||
function (data) {
|
||||
services[job.name] = data;
|
||||
resolve();
|
||||
odoo.process_jobs(jobs, services);
|
||||
}).guardedCatch(function (e) {
|
||||
job.rejected = e || true;
|
||||
jobs.push(job);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
}
|
||||
missing = odoo.__DEBUG__.getMissingJobs();
|
||||
failed = odoo.__DEBUG__.getFailedJobs();
|
||||
var unloaded = Object.keys(debugJobs) // Object.values is not supported
|
||||
.map(function (key) {
|
||||
return debugJobs[key];
|
||||
}).filter(function (job) {
|
||||
return job.missing;
|
||||
});
|
||||
job_promise.push(def);
|
||||
}
|
||||
|
||||
function is_ready(job) {
|
||||
return !job.error && !job.rejected && _.every(job.factory.deps, function (name) { return name in services; });
|
||||
}
|
||||
|
||||
function make_require(job) {
|
||||
var deps = _.pick(services, job.deps);
|
||||
|
||||
function require(name) {
|
||||
if (!(name in deps)) {
|
||||
console.error('Undefined dependency: ', name);
|
||||
} else {
|
||||
require.__require_calls++;
|
||||
}
|
||||
return deps[name];
|
||||
if (odoo.debug || failed.length || unloaded.length) {
|
||||
var log = window.console[!failed.length || !unloaded.length ? 'info' : 'error'].bind(window.console);
|
||||
log((failed.length ? 'error' : (unloaded.length ? 'warning' : 'info')) + ': Some modules could not be started');
|
||||
if (missing.length) {
|
||||
log('Missing dependencies: ', missing);
|
||||
}
|
||||
if (failed.length) {
|
||||
log('Failed modules: ', failed.map(function (fail) {
|
||||
return fail.name;
|
||||
}));
|
||||
}
|
||||
if (rejected.length) {
|
||||
log('Rejected modules: ', rejected);
|
||||
}
|
||||
if (rejectedLinked.length) {
|
||||
log('Rejected linked modules: ', rejectedLinked);
|
||||
}
|
||||
if (unloaded.length) {
|
||||
log('Non loaded modules: ', unloaded.map(function (unload) {
|
||||
return unload.name;
|
||||
}));
|
||||
}
|
||||
if (odoo.debug && Object.keys(debugJobs).length) {
|
||||
log('Debug: ', debugJobs);
|
||||
}
|
||||
|
||||
require.__require_calls = 0;
|
||||
return require;
|
||||
}
|
||||
|
||||
while (jobs.length && (job = _.find(jobs, is_ready))) {
|
||||
process_job(job);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
});
|
||||
odoo.__DEBUG__.jsModules = {
|
||||
missing: missing,
|
||||
failed: failed.map(function (fail) {
|
||||
return fail.name;
|
||||
}),
|
||||
};
|
||||
didLogInfoResolve();
|
||||
};
|
||||
odoo.processJobs = function (jobs, services) {
|
||||
var job;
|
||||
|
||||
function processJob(job) {
|
||||
var require = makeRequire(job);
|
||||
|
||||
// automatically log errors detected when loading modules
|
||||
var log_when_loaded = function () {
|
||||
_.delay(function () {
|
||||
var len = job_promise.length;
|
||||
Promise.all(job_promise).then(function () {
|
||||
if (len === job_promise.length) {
|
||||
var jobExec;
|
||||
var def = new Promise(function (resolve) {
|
||||
try {
|
||||
jobExec = job.factory.call(null, require);
|
||||
jobs.splice(jobs.indexOf(job), 1);
|
||||
} catch (e) {
|
||||
job.error = e;
|
||||
console.error('Error while loading ' + job.name);
|
||||
console.error(e.stack);
|
||||
}
|
||||
if (!job.error) {
|
||||
Promise.resolve(jobExec).then(
|
||||
function (data) {
|
||||
services[job.name] = data;
|
||||
resolve();
|
||||
odoo.processJobs(jobs, services);
|
||||
}).guardedCatch(function (e) {
|
||||
job.rejected = e || true;
|
||||
jobs.push(job);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
jobPromises.push(def);
|
||||
}
|
||||
|
||||
function isReady(job) {
|
||||
return !job.error && !job.rejected && job.factory.deps.every(function (name) {
|
||||
return name in services;
|
||||
});
|
||||
}
|
||||
|
||||
function makeRequire(job) {
|
||||
var deps = {};
|
||||
Object.keys(services).filter(function (item) {
|
||||
return job.deps.indexOf(item) >= 0;
|
||||
}).forEach(function (key) {
|
||||
deps[key] = services[key];
|
||||
});
|
||||
|
||||
return function require(name) {
|
||||
if (!(name in deps)) {
|
||||
console.error('Undefined dependency: ', name);
|
||||
}
|
||||
return deps[name];
|
||||
};
|
||||
}
|
||||
|
||||
while (jobs.length) {
|
||||
job = undefined;
|
||||
for (var i = 0; i < jobs.length; i++) {
|
||||
if (isReady(jobs[i])) {
|
||||
job = jobs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!job) {
|
||||
break;
|
||||
}
|
||||
processJob(job);
|
||||
}
|
||||
|
||||
return services;
|
||||
};
|
||||
|
||||
// Automatically log errors detected when loading modules
|
||||
window.addEventListener('load', function logWhenLoaded() {
|
||||
setTimeout(function () {
|
||||
var len = jobPromises.length;
|
||||
Promise.all(jobPromises).then(function () {
|
||||
if (len === jobPromises.length) {
|
||||
odoo.log();
|
||||
} else {
|
||||
log_when_loaded();
|
||||
logWhenLoaded();
|
||||
}
|
||||
});
|
||||
}, 6000);
|
||||
};
|
||||
$(log_when_loaded);
|
||||
|
||||
}, 9999);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
(function() {
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
odoo.__DEBUG__.didLogInfo.then(function() {
|
||||
|
||||
var modulesInfo = odoo.__DEBUG__.js_modules;
|
||||
odoo.__DEBUG__.didLogInfo.then(function () {
|
||||
var modulesInfo = odoo.__DEBUG__.jsModules;
|
||||
|
||||
QUnit.module('Odoo JS Modules');
|
||||
|
||||
QUnit.test('all modules are properly loaded', function(assert) {
|
||||
QUnit.test('all modules are properly loaded', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
assert.deepEqual(modulesInfo.missing, [],
|
||||
@@ -15,7 +14,5 @@ odoo.__DEBUG__.didLogInfo.then(function() {
|
||||
assert.deepEqual(modulesInfo.failed, [],
|
||||
"no js module should have failed");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user