bundle chunks
This commit is contained in:
+30
-34
@@ -44,11 +44,17 @@ module.exports = grunt => {
|
||||
|
||||
const originalLibDir = 'client/lib/original';
|
||||
|
||||
let bundleFileMap = {
|
||||
'client/lib/espo-libs.min.js': buildUtils.getPreparedBundleLibList(libs),
|
||||
'client/lib/espo-templates.min.js': 'client/lib/original/espo-templates.js',
|
||||
'client/lib/espo.min.js': originalLibDir + `/espo.js`,
|
||||
};
|
||||
let bundleFileMap = {'client/lib/espo-libs.min.js': buildUtils.getPreparedBundleLibList(libs)};
|
||||
|
||||
for (let name in bundleConfig.chunks) {
|
||||
let namePart = 'espo-' + name;
|
||||
|
||||
if (name === 'main') {
|
||||
namePart = 'espo';
|
||||
}
|
||||
|
||||
bundleFileMap[`client/lib/${namePart}.min.js`] = originalLibDir + `/${namePart}.js`
|
||||
}
|
||||
|
||||
let copyJsFileList = buildUtils.getCopyLibDataList(libs);
|
||||
|
||||
@@ -255,43 +261,34 @@ module.exports = grunt => {
|
||||
},
|
||||
});
|
||||
|
||||
grunt.registerTask('bundle-espo', () => {
|
||||
let chunkConfig = bundleConfig.chunks.main;
|
||||
|
||||
let contents = (new BundlerGeneral()).bundle({
|
||||
files: chunkConfig.files,
|
||||
patterns: chunkConfig.patterns,
|
||||
allPatterns: ['client/src/**/*.js'],
|
||||
libs: libs,
|
||||
});
|
||||
|
||||
contents += '\n' +
|
||||
(new LayoutTypeBundler()).bundle();
|
||||
|
||||
let file = originalLibDir + `/espo.js`;
|
||||
|
||||
const writeOriginalLib = (name, contents) => {
|
||||
if (!fs.existsSync(originalLibDir)) {
|
||||
fs.mkdirSync(originalLibDir);
|
||||
}
|
||||
|
||||
let file = originalLibDir + `/${name}.js`;
|
||||
|
||||
fs.writeFileSync(file, contents, 'utf8');
|
||||
});
|
||||
};
|
||||
|
||||
grunt.registerTask('bundle-templates', () => {
|
||||
let chunkConfig = bundleConfig.chunks.templates;
|
||||
grunt.registerTask('bundle', () => {
|
||||
let bundler = new BundlerGeneral(bundleConfig, libs);
|
||||
|
||||
let contents = (new BundlerGeneral()).bundle({
|
||||
templatePatterns: chunkConfig.templatePatterns,
|
||||
modulePaths: {'crm': 'client/modules/crm'},
|
||||
});
|
||||
let result = bundler.bundle();
|
||||
|
||||
let file = originalLibDir + `/espo-templates.js`;
|
||||
for (let name in result) {
|
||||
let contents = result[name];
|
||||
|
||||
if (!fs.existsSync(originalLibDir)) {
|
||||
fs.mkdirSync(originalLibDir);
|
||||
let key = 'espo-' + name;
|
||||
|
||||
if (name === 'main') {
|
||||
contents += '\n' + (new LayoutTypeBundler()).bundle();
|
||||
|
||||
key = 'espo';
|
||||
}
|
||||
|
||||
writeOriginalLib(key, contents);
|
||||
}
|
||||
|
||||
fs.writeFileSync(file, contents, 'utf8');
|
||||
});
|
||||
|
||||
grunt.registerTask('prepare-lib-original', () => {
|
||||
@@ -476,9 +473,8 @@ module.exports = grunt => {
|
||||
grunt.registerTask('internal', [
|
||||
'less',
|
||||
'cssmin',
|
||||
'bundle-espo',
|
||||
'bundle-templates',
|
||||
'prepare-lib-original',
|
||||
'bundle',
|
||||
'uglify:bundle',
|
||||
'copy:frontendLib',
|
||||
'prepare-lib',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{#if isNotEmpty}}
|
||||
|
||||
{{#if viewObject.isSvg}}
|
||||
{{#if isSvg}}
|
||||
<svg class="barcode"></svg>
|
||||
{{else}}
|
||||
<div class="barcode"></div>
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
this._cacheIsSet = false;
|
||||
this._responseCacheIsSet = false;
|
||||
this._internalModuleListIsSet = false;
|
||||
this._bundleFileMap = {};
|
||||
this._bundleMapping = {};
|
||||
/** @type Object.<string, string[]> */
|
||||
this._bundleDependenciesMap = {};
|
||||
|
||||
this._addLibsConfigCallCount = 0;
|
||||
this._addLibsConfigCallMaxCount = 2;
|
||||
@@ -500,6 +504,25 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (name in this._bundleMapping) {
|
||||
let bundleName = this._bundleMapping[name];
|
||||
|
||||
this._addBundle(bundleName).then(() => {
|
||||
let classObj = this._getClass(name);
|
||||
|
||||
if (!classObj) {
|
||||
let msg = `Could not obtain class '${name}' from bundle '${bundleName}'.`;
|
||||
console.error(msg);
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
callback(classObj);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
path = this._nameToPath(name);
|
||||
}
|
||||
|
||||
@@ -578,6 +601,65 @@
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} name
|
||||
* @return {Promise}
|
||||
*/
|
||||
_addBundle: function (name) {
|
||||
let dependencies = this._bundleDependenciesMap[name] || [];
|
||||
|
||||
if (!dependencies.length) {
|
||||
return this._addBundleInternal(name);
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
let list = dependencies.map(item => Espo.loader.requirePromise(item));
|
||||
|
||||
Promise.all(list)
|
||||
.then(() => {
|
||||
return this._addBundleInternal(name);
|
||||
})
|
||||
.then(() => resolve());
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} name
|
||||
* @return {Promise}
|
||||
*/
|
||||
_addBundleInternal: function (name) {
|
||||
let src = this._bundleFileMap[name];
|
||||
|
||||
if (!src) {
|
||||
throw new Error(`Unknown bundle '${name}'.`);
|
||||
}
|
||||
|
||||
if (this._cacheTimestamp) {
|
||||
let sep = (src.indexOf('?') > -1) ? '&' : '?';
|
||||
|
||||
src += sep + 'r=' + this._cacheTimestamp;
|
||||
}
|
||||
|
||||
src = this._basePath + src;
|
||||
|
||||
let scriptEl = document.createElement('script');
|
||||
|
||||
scriptEl.setAttribute('type', 'text/javascript')
|
||||
scriptEl.setAttribute('src', src);
|
||||
|
||||
scriptEl.addEventListener('error', event => {
|
||||
console.error(`Could not load bundle '${name}'.`, event);
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
document.head.appendChild(scriptEl);
|
||||
|
||||
scriptEl.addEventListener('load', () => resolve());
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -756,6 +838,32 @@
|
||||
return this._internalModuleMap[moduleName];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name A bundle name.
|
||||
* @param {string} file A bundle file.
|
||||
* @internal
|
||||
*/
|
||||
mapBundleFile: function (name, file) {
|
||||
this._bundleFileMap[name] = file;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name A bundle name.
|
||||
* @param {string} list Dependencies..
|
||||
* @internal
|
||||
*/
|
||||
mapBundleDependencies: function (name, list) {
|
||||
this._bundleDependenciesMap[name] = list;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object.<string, string>} mapping
|
||||
* @internal
|
||||
*/
|
||||
addBundleMapping: function (mapping) {
|
||||
Object.assign(this._bundleMapping, mapping);
|
||||
},
|
||||
|
||||
/**
|
||||
* Require a module or multiple modules.
|
||||
*
|
||||
@@ -871,6 +979,32 @@
|
||||
addLibsConfig: function (data) {
|
||||
loader.addLibsConfig(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name A bundle name.
|
||||
* @param {string} file A bundle file.
|
||||
* @internal
|
||||
*/
|
||||
mapBundleFile: function (name, file) {
|
||||
loader.mapBundleFile(name, file);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name A bundle name.
|
||||
* @param {string} list Dependencies..
|
||||
* @internal
|
||||
*/
|
||||
mapBundleDependencies: function (name, list) {
|
||||
loader.mapBundleDependencies(name, list);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object.<string, string>} mapping
|
||||
* @internal
|
||||
*/
|
||||
addBundleMapping: function (mapping) {
|
||||
loader.addBundleMapping(mapping);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,16 +26,16 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/fields/barcode',
|
||||
['views/fields/varchar', 'lib!JsBarcode', 'lib!qrcode'],
|
||||
function (Dep, JsBarcode, QRCode) {
|
||||
define('views/fields/barcode', ['views/fields/varchar'], function (Dep) {
|
||||
|
||||
let JsBarcode;
|
||||
let QRCode;
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
type: 'barcode',
|
||||
|
||||
listTemplate: 'fields/barcode/detail',
|
||||
|
||||
detailTemplate: 'fields/barcode/detail',
|
||||
|
||||
setup: function () {
|
||||
@@ -66,21 +66,44 @@ function (Dep, JsBarcode, QRCode) {
|
||||
|
||||
if (this.params.codeType !== 'QRcode') {
|
||||
this.isSvg = true;
|
||||
|
||||
this.wait(
|
||||
Espo.loader.requirePromise('lib!JsBarcode').then(lib => {
|
||||
JsBarcode = lib;
|
||||
|
||||
console.log(JsBarcode);
|
||||
})
|
||||
);
|
||||
}
|
||||
else {
|
||||
this.wait(
|
||||
Espo.loader.requirePromise('lib!qrcode').then(lib => {
|
||||
QRCode = lib;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
$(window).on('resize.' + this.cid, function () {
|
||||
$(window).on('resize.' + this.cid, () => {
|
||||
if (!this.isRendered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.controlWidth();
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
this.listenTo(this.recordHelper, 'panel-show', () => this.controlWidth());
|
||||
},
|
||||
|
||||
data: function () {
|
||||
let data = Dep.prototype.data.call(this);
|
||||
|
||||
data.isSvg = this.isSvg;
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
onRemove: function () {
|
||||
$(window).off('resize.' + this.cid);
|
||||
},
|
||||
@@ -122,15 +145,10 @@ function (Dep, JsBarcode, QRCode) {
|
||||
}
|
||||
else {
|
||||
// SVG may be not available yet (in webkit).
|
||||
setTimeout(
|
||||
function () {
|
||||
this.initBarcode(value);
|
||||
|
||||
this.controlWidth();
|
||||
}
|
||||
.bind(this),
|
||||
100
|
||||
);
|
||||
setTimeout(() => {
|
||||
this.initBarcode(value);
|
||||
this.controlWidth();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,20 +26,20 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/fields/colorpicker', ['views/fields/varchar', 'lib!Colorpicker'], function (Dep, Colorpicker) {
|
||||
define('views/fields/colorpicker', ['views/fields/varchar'], function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
type: 'varchar',
|
||||
|
||||
detailTemplate: 'fields/colorpicker/detail',
|
||||
|
||||
listTemplate: 'fields/colorpicker/detail',
|
||||
|
||||
editTemplate: 'fields/colorpicker/edit',
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
this.wait(Espo.loader.requirePromise('lib!Colorpicker'));
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/modals/image-crop', ['views/modal', 'lib!Cropper'], function (Dep, Cropper) {
|
||||
define('views/modals/image-crop', ['views/modal'], function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -40,7 +40,7 @@ define('views/modals/image-crop', ['views/modal', 'lib!Cropper'], function (Dep,
|
||||
},
|
||||
'click [data-action="zoomOut"]': function () {
|
||||
this.$img.cropper('zoom', -0.1);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
setup: function () {
|
||||
@@ -56,6 +56,8 @@ define('views/modals/image-crop', ['views/modal', 'lib!Cropper'], function (Dep,
|
||||
},
|
||||
];
|
||||
|
||||
this.wait(Espo.loader.requirePromise('lib!Cropper'));
|
||||
|
||||
this.on('remove', () => {
|
||||
if (this.$img.length) {
|
||||
this.$img.cropper('destroy');
|
||||
@@ -65,13 +67,13 @@ define('views/modals/image-crop', ['views/modal', 'lib!Cropper'], function (Dep,
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
var $img = this.$img = $('<img>')
|
||||
let $img = this.$img = $('<img>')
|
||||
.attr('src', this.options.contents)
|
||||
.addClass('hidden');
|
||||
|
||||
this.$el.find('.image-container').append($img);
|
||||
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
$img.cropper({
|
||||
aspectRatio: 1,
|
||||
movable: true,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/modals/image-preview', ['views/modal', 'lib!exif'], function (Dep) {
|
||||
define('views/modals/image-preview', ['views/modal'], function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -82,6 +82,8 @@ define('views/modals/image-preview', ['views/modal', 'lib!exif'], function (Dep)
|
||||
this.once('remove', () => {
|
||||
$(window).off('resize.image-review');
|
||||
});
|
||||
|
||||
this.wait(Espo.loader.requirePromise('lib!exif'));
|
||||
},
|
||||
|
||||
getImageUrl: function () {
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/scheduled-job/fields/scheduling',
|
||||
['views/fields/varchar', 'lib!cronstrue'], function (Dep, cronstrue) {
|
||||
define('views/scheduled-job/fields/scheduling', ['views/fields/varchar'], function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -35,9 +34,14 @@ define('views/scheduled-job/fields/scheduling',
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
if (this.isEditMode() || this.isDetailMode()) {
|
||||
this.listenTo(this.model, 'change:' + this.name, function () {
|
||||
this.showText();
|
||||
}, this);
|
||||
this.wait(
|
||||
Espo.loader.requirePromise('lib!cronstrue')
|
||||
.then(Cronstrue => {
|
||||
this.Cronstrue = Cronstrue;
|
||||
|
||||
this.listenTo(this.model, 'change:' + this.name, () => this.showText());
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,7 +49,8 @@ define('views/scheduled-job/fields/scheduling',
|
||||
Dep.prototype.afterRender.call(this);
|
||||
|
||||
if (this.isEditMode() || this.isDetailMode()) {
|
||||
var $text = this.$text = $('<div class="small text-success"/>');
|
||||
let $text = this.$text = $('<div class="small text-success"/>');
|
||||
|
||||
this.$el.append($text);
|
||||
this.showText();
|
||||
}
|
||||
@@ -56,6 +61,10 @@ define('views/scheduled-job/fields/scheduling',
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.Cronstrue) {
|
||||
return;
|
||||
}
|
||||
|
||||
var exp = this.model.get(this.name);
|
||||
|
||||
if (!exp) {
|
||||
@@ -71,7 +80,7 @@ define('views/scheduled-job/fields/scheduling',
|
||||
}
|
||||
|
||||
var locale = 'en';
|
||||
var localeList = Object.keys(cronstrue.default.locales);
|
||||
var localeList = Object.keys(this.Cronstrue.default.locales);
|
||||
var language = this.getLanguage().name;
|
||||
|
||||
if (~localeList.indexOf(language)) {
|
||||
@@ -82,7 +91,7 @@ define('views/scheduled-job/fields/scheduling',
|
||||
}
|
||||
|
||||
try {
|
||||
var text = cronstrue.toString(exp, {
|
||||
var text = this.Cronstrue.toString(exp, {
|
||||
use24HourTimeFormat: !this.getDateTime().hasMeridian(),
|
||||
locale: locale,
|
||||
});
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/user-security/modals/totp',
|
||||
['views/modal', 'model', 'lib!qrcode'],
|
||||
function (Dep, Model, QRCode) {
|
||||
define('views/user-security/modals/totp', ['views/modal', 'model'], function (Dep, Model) {
|
||||
|
||||
let QRCode;
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -114,6 +114,10 @@ define('views/user-security/modals/totp',
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
Espo.loader.requirePromise('lib!qrcode').then(lib => {
|
||||
QRCode = lib;
|
||||
})
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
|
||||
+111
-1
@@ -1,4 +1,15 @@
|
||||
{
|
||||
"order": [
|
||||
"main",
|
||||
"templates",
|
||||
"admin",
|
||||
"crm",
|
||||
"wysiwyg",
|
||||
"chart",
|
||||
"calendar",
|
||||
"timeline",
|
||||
"extra"
|
||||
],
|
||||
"chunks": {
|
||||
"main": {
|
||||
"files": [
|
||||
@@ -65,6 +76,105 @@
|
||||
"client/res/templates/fields/varchar/*.tpl",
|
||||
"client/res/templates/fields/wysiwyg/*.tpl"
|
||||
]
|
||||
},
|
||||
"admin": {
|
||||
"patterns": [
|
||||
"client/src/views/admin/**/*.js",
|
||||
"client/src/views/authentication-provider/**/*.js",
|
||||
"client/src/views/inbound-email/**/*.js",
|
||||
"client/src/views/templates/**/*.js",
|
||||
"client/src/views/role/**/*.js",
|
||||
"client/src/views/portal-role/**/*.js",
|
||||
"client/src/views/scheduled-job/**/*.js",
|
||||
"client/src/views/lead-capture/**/*.js",
|
||||
"client/src/views/lead-capture-log-record/**/*.js",
|
||||
"client/src/views/webhooks/**/*.js",
|
||||
"client/src/controllers/admin.js",
|
||||
"client/src/controllers/inbound-email.js",
|
||||
"client/src/controllers/layout-set.js",
|
||||
"client/src/controllers/api-user.js",
|
||||
"client/src/controllers/role.js",
|
||||
"client/src/controllers/portal-role.js"
|
||||
],
|
||||
"templatePatterns": [
|
||||
"client/res/templates/admin/**/*.tpl"
|
||||
]
|
||||
},
|
||||
"crm": {
|
||||
"patterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
],
|
||||
"templatePatterns": [
|
||||
"client/modules/crm/res/templates/**/*.tpl"
|
||||
],
|
||||
"allPatterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
]
|
||||
},
|
||||
"wysiwyg": {
|
||||
"patterns": [
|
||||
"client/src/**/*.js",
|
||||
"client/modules/crm/src/**/*.js"
|
||||
],
|
||||
"dependentOn": [
|
||||
"lib!Summernote"
|
||||
],
|
||||
"libs": ["Summernote"],
|
||||
"allPatterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
]
|
||||
},
|
||||
"chart": {
|
||||
"patterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
],
|
||||
"dependentOn": [
|
||||
"lib!Flotr",
|
||||
"lib!espo-funnel-chart"
|
||||
],
|
||||
"libs": ["Flotr", "espo-funnel-chart"],
|
||||
"allPatterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
]
|
||||
},
|
||||
"calendar": {
|
||||
"patterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
],
|
||||
"dependentOn": [
|
||||
"lib!full-calendar"
|
||||
],
|
||||
"libs": ["full-calendar"],
|
||||
"allPatterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
]
|
||||
},
|
||||
"timeline": {
|
||||
"patterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
],
|
||||
"dependentOn": [
|
||||
"lib!vis"
|
||||
],
|
||||
"libs": ["vis"],
|
||||
"allPatterns": [
|
||||
"client/modules/crm/src/**/*.js"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"patterns": [
|
||||
"client/src/**/*.js"
|
||||
],
|
||||
"templatePatterns": [
|
||||
"client/res/templates/**/*.tpl"
|
||||
],
|
||||
"noDuplicates": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"modulePaths": {
|
||||
"crm": "client/modules/crm"
|
||||
},
|
||||
"allPatterns": [
|
||||
"client/src/**/*.js"
|
||||
]
|
||||
}
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ let BuildUtils = {
|
||||
* src: string,
|
||||
* dest: string,
|
||||
* originalDest: string|null,
|
||||
* minify: boolean
|
||||
* }[]}
|
||||
* minify: boolean,
|
||||
* }[]}
|
||||
*/
|
||||
getCopyLibDataList: function (libs) {
|
||||
let list = [];
|
||||
|
||||
+202
-27
@@ -28,47 +28,222 @@
|
||||
|
||||
const Bundler = require("./bundler");
|
||||
const Precompiler = require('./template-precompiler');
|
||||
const fs = require('fs');
|
||||
|
||||
class BundlerGeneral {
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* files: string[],
|
||||
* patterns?: string[],
|
||||
* allPatterns?: string[],
|
||||
* libs: {
|
||||
* src?: string,
|
||||
* bundle?: boolean,
|
||||
* key?: string,
|
||||
* }[],
|
||||
* templatePatterns?: string[],
|
||||
* modulePaths?: Object.<string, string>,
|
||||
* }} params
|
||||
* @return {string}
|
||||
* chunks: Object.<string, {
|
||||
* files?: string[],
|
||||
* patterns?: string[],
|
||||
* allPatterns?: string[],
|
||||
* templatePatterns?: string[],
|
||||
* noDuplicates?: boolean,
|
||||
* dependentOn?: string[],
|
||||
* libs?: string[],
|
||||
* }>,
|
||||
* modulePaths?: Record.<string, string>,
|
||||
* allPatterns: string[],
|
||||
* order: string[],
|
||||
* }} config
|
||||
* @param {{
|
||||
* src?: string,
|
||||
* bundle?: boolean,
|
||||
* key?: string,
|
||||
* files?: {
|
||||
* src: string,
|
||||
* }[]
|
||||
* }[]} libs
|
||||
* @param {string} [filePattern]
|
||||
*/
|
||||
bundle(params) {
|
||||
let contents = '';
|
||||
constructor(config, libs, filePattern) {
|
||||
this.config = config;
|
||||
this.libs = libs;
|
||||
this.mainBundleFiles = [];
|
||||
this.filePattern = filePattern || 'client/lib/espo-{*}.min.js';
|
||||
}
|
||||
|
||||
if (params.patterns) {
|
||||
let chunks = (new Bundler()).bundle({
|
||||
files: params.files,
|
||||
patterns: params.patterns,
|
||||
allPatterns: params.allPatterns,
|
||||
libs: params.libs,
|
||||
/**
|
||||
* @return {Object.<string, string>}
|
||||
*/
|
||||
bundle() {
|
||||
let result = {};
|
||||
let mapping = {};
|
||||
let files = [];
|
||||
let modules = [];
|
||||
let templateFiles = [];
|
||||
let mainName = this.config.order[0];
|
||||
|
||||
/** @var {Object.<string, string[]>} */
|
||||
let notBundledMap = {};
|
||||
|
||||
this.config.order.forEach((name, i) => {
|
||||
let data = this.#bundleChunk(name, i === 0, {
|
||||
files: files,
|
||||
templateFiles: templateFiles,
|
||||
});
|
||||
|
||||
contents += chunks[0];
|
||||
files = files.concat(data.files);
|
||||
templateFiles = templateFiles.concat(data.templateFiles);
|
||||
modules = modules.concat(data.modules);
|
||||
notBundledMap[name] = data.notBundledModules;
|
||||
result[name] = data.contents;
|
||||
|
||||
if (i === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.modules.forEach(item => mapping[item] = name);
|
||||
|
||||
let bundleFile = this.filePattern.replace('{*}', name);
|
||||
|
||||
let libs = this.config.chunks[name].libs;
|
||||
|
||||
if (libs) {
|
||||
let part = JSON.stringify(libs.map(item => 'lib!' + item));
|
||||
|
||||
result[mainName] += `Espo.loader.mapBundleDependencies('${name}', ${part});\n`;
|
||||
}
|
||||
|
||||
result[mainName] += `Espo.loader.mapBundleFile('${name}', '${bundleFile}');\n`;
|
||||
});
|
||||
|
||||
let notBundledModules = [];
|
||||
|
||||
this.config.order.forEach(name => {
|
||||
notBundledMap[name]
|
||||
.filter(item => !modules.includes(item))
|
||||
.filter(item => !notBundledModules.includes(item))
|
||||
.forEach(item => notBundledModules.push(item));
|
||||
});
|
||||
|
||||
if (notBundledModules.length) {
|
||||
let part = notBundledModules
|
||||
.map(item => ' ' + item)
|
||||
.join('\n');
|
||||
|
||||
console.log(`\nNot bundled:\n${part}`);
|
||||
}
|
||||
|
||||
result[mainName] += `Espo.loader.addBundleMapping(${JSON.stringify(mapping)});`
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {boolean} isMain
|
||||
* @param {{files: [], templateFiles: []}} alreadyBundled
|
||||
* @return {{
|
||||
* contents: string,
|
||||
* modules: string[],
|
||||
* files: string[],
|
||||
* templateFiles: string[],
|
||||
* notBundledModules: string[],
|
||||
* }}
|
||||
*/
|
||||
#bundleChunk(name, isMain, alreadyBundled) {
|
||||
let contents = '';
|
||||
|
||||
let modules = [];
|
||||
|
||||
let params = this.config.chunks[name];
|
||||
|
||||
let patterns = params.patterns;
|
||||
let allPatterns = []
|
||||
.concat(this.config.allPatterns)
|
||||
.concat(params.allPatterns || []);
|
||||
|
||||
let bundledFiles = [];
|
||||
let bundledTemplateFiles = [];
|
||||
let notBundledModules = [];
|
||||
|
||||
if (params.patterns) {
|
||||
let bundler = (new Bundler(this.config.modulePaths));
|
||||
|
||||
// The main bundle is always loaded, duplicates are not needed.
|
||||
let ignoreFiles = [].concat(this.mainBundleFiles);
|
||||
|
||||
if (params.noDuplicates) {
|
||||
ignoreFiles = ignoreFiles.concat(alreadyBundled.files);
|
||||
}
|
||||
|
||||
let data = bundler.bundle({
|
||||
files: params.files,
|
||||
patterns: patterns,
|
||||
allPatterns: allPatterns,
|
||||
libs: this.libs,
|
||||
ignoreFiles: ignoreFiles,
|
||||
dependentOn: params.dependentOn,
|
||||
});
|
||||
|
||||
contents += data.contents;
|
||||
|
||||
if (isMain) {
|
||||
this.mainBundleFiles = data.files;
|
||||
}
|
||||
|
||||
modules = data.modules;
|
||||
bundledFiles = data.files;
|
||||
|
||||
/*if (params.libs) {
|
||||
contents = this.#bundleLibs(params.libs) + '\n' + contents;
|
||||
}*/
|
||||
|
||||
notBundledModules = data.notBundledModules;
|
||||
}
|
||||
|
||||
if (params.templatePatterns) {
|
||||
contents += '\n' +
|
||||
(new Precompiler()).precompile({
|
||||
patterns: params.templatePatterns,
|
||||
modulePaths: params.modulePaths,
|
||||
});
|
||||
let ignoreFiles = params.noDuplicates ? [].concat(alreadyBundled.templateFiles) : [];
|
||||
|
||||
let data = (new Precompiler()).precompile({
|
||||
patterns: params.templatePatterns,
|
||||
modulePaths: this.config.modulePaths,
|
||||
ignoreFiles: ignoreFiles,
|
||||
});
|
||||
|
||||
contents += '\n' + data.contents;
|
||||
bundledTemplateFiles = data.files;
|
||||
}
|
||||
|
||||
return contents;
|
||||
return {
|
||||
contents: contents,
|
||||
modules: modules,
|
||||
files: bundledFiles,
|
||||
templateFiles: bundledTemplateFiles,
|
||||
notBundledModules: notBundledModules,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} libs
|
||||
* @return {string}
|
||||
*/
|
||||
#bundleLibs(libs) {
|
||||
let files = [];
|
||||
|
||||
this.libs
|
||||
.filter(item => libs.includes(item.key))
|
||||
.forEach(item => {
|
||||
if (item.src) {
|
||||
files.push(item.src);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.files) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.files.forEach(item => {
|
||||
files.push(item.src);
|
||||
});
|
||||
});
|
||||
|
||||
let contents = files.map(file => fs.readFileSync(file, 'utf-8'));
|
||||
|
||||
return contents.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+123
-39
@@ -38,6 +38,13 @@ const {globSync} = require('glob');
|
||||
*/
|
||||
class Bundler {
|
||||
|
||||
/**
|
||||
* @param {Object.<string, string>} modulePaths
|
||||
*/
|
||||
constructor(modulePaths) {
|
||||
this.modulePaths = modulePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {string}
|
||||
@@ -48,55 +55,60 @@ class Bundler {
|
||||
* Bundles Espo js files into chunks.
|
||||
*
|
||||
* @param {{
|
||||
* files: string[],
|
||||
* files?: string[],
|
||||
* patterns: string[],
|
||||
* allPatterns: string[],
|
||||
* chunkNumber?: number,
|
||||
* allPatterns?: string[],
|
||||
* ignoreFiles?: string[],
|
||||
* dependentOn?: string[],
|
||||
* libs: {
|
||||
* src?: string,
|
||||
* bundle?: boolean,
|
||||
* key?: string,
|
||||
* }[],
|
||||
* }} params
|
||||
* @return {string[]}
|
||||
* @return {{
|
||||
* contents: string,
|
||||
* files: string[],
|
||||
* modules: string[],
|
||||
* notBundledModules: string[],
|
||||
* }}
|
||||
*/
|
||||
bundle(params) {
|
||||
let chunkNumber = params.chunkNumber || 1;
|
||||
|
||||
let files = []
|
||||
.concat(params.files)
|
||||
.concat(this.#obtainFiles(params.patterns, params.files));
|
||||
.concat(params.files || [])
|
||||
.concat(this.#obtainFiles(params.patterns, params.files))
|
||||
.filter(file => !params.ignoreFiles.includes(file));
|
||||
|
||||
let allFiles = this.#obtainFiles(params.allPatterns);
|
||||
let allFiles = this.#obtainFiles(params.allPatterns || params.patterns);
|
||||
|
||||
let ignoreLibs = params.libs
|
||||
.filter(item => item.key && !item.bundle)
|
||||
.map(item => 'lib!' + item.key);
|
||||
.map(item => 'lib!' + item.key)
|
||||
.filter(item => !(params.dependentOn || []).includes(item));
|
||||
|
||||
let sortedFiles = this.#sortFiles(files, allFiles, ignoreLibs);
|
||||
let notBundledModules = [];
|
||||
|
||||
let portions = [];
|
||||
let portionSize = Math.floor(sortedFiles.length / chunkNumber);
|
||||
let sortedFiles = this.#sortFiles(
|
||||
files,
|
||||
allFiles,
|
||||
ignoreLibs,
|
||||
params.ignoreFiles || [],
|
||||
notBundledModules,
|
||||
params.dependentOn || null,
|
||||
);
|
||||
|
||||
for (let i = 0; i < chunkNumber; i++) {
|
||||
let end = i === chunkNumber - 1 ?
|
||||
sortedFiles.length :
|
||||
(i + 1) * portionSize;
|
||||
let contents = '';
|
||||
|
||||
portions.push(sortedFiles.slice(i * portionSize, end));
|
||||
}
|
||||
sortedFiles.forEach(file => contents += this.#normalizeSourceFile(file));
|
||||
|
||||
let chunks = [];
|
||||
let modules = sortedFiles.map(file => this.#obtainModuleName(file));
|
||||
|
||||
portions.forEach(portion => {
|
||||
let chunk = '';
|
||||
|
||||
portion.forEach(file => chunk += this.normalizeSourceFile(file));
|
||||
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
return chunks;
|
||||
return {
|
||||
contents: contents,
|
||||
files: sortedFiles,
|
||||
modules: modules,
|
||||
notBundledModules: notBundledModules,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,17 +135,27 @@ class Bundler {
|
||||
* @param {string[]} files
|
||||
* @param {string[]} allFiles
|
||||
* @param {string[]} ignoreLibs
|
||||
* @param {string[]} ignoreFiles
|
||||
* @param {string[]} notBundledModules
|
||||
* @param {string[]|null} dependentOn
|
||||
* @return {string[]}
|
||||
*/
|
||||
#sortFiles(files, allFiles, ignoreLibs) {
|
||||
#sortFiles(
|
||||
files,
|
||||
allFiles,
|
||||
ignoreLibs,
|
||||
ignoreFiles,
|
||||
notBundledModules,
|
||||
dependentOn
|
||||
) {
|
||||
/** @var {Object.<string, string[]>} */
|
||||
let map = {};
|
||||
|
||||
let standalonePathList = [];
|
||||
|
||||
let modules = [];
|
||||
let moduleFileMap = {};
|
||||
|
||||
let ignoreModules = ignoreFiles.map(file => this.#obtainModuleName(file));
|
||||
|
||||
allFiles.forEach(file => {
|
||||
let data = this.#obtainModuleData(file);
|
||||
|
||||
@@ -170,12 +192,16 @@ class Bundler {
|
||||
});
|
||||
});
|
||||
|
||||
modules = modules.concat(depModules);
|
||||
modules = modules
|
||||
.concat(depModules)
|
||||
.filter(module => !ignoreModules.includes(module));
|
||||
|
||||
/** @var {string[]} */
|
||||
let discardedModules = [];
|
||||
/** @var {Object.<string, number>} */
|
||||
let depthMap = {};
|
||||
/** @var {string[]} */
|
||||
let pickedModules = [];
|
||||
|
||||
for (let name of modules) {
|
||||
this.#buildTreeItem(
|
||||
@@ -183,17 +209,29 @@ class Bundler {
|
||||
map,
|
||||
depthMap,
|
||||
ignoreLibs,
|
||||
discardedModules
|
||||
dependentOn,
|
||||
discardedModules,
|
||||
pickedModules
|
||||
);
|
||||
}
|
||||
|
||||
if (dependentOn) {
|
||||
modules = pickedModules;
|
||||
}
|
||||
|
||||
modules.sort((v1, v2) => {
|
||||
return depthMap[v2] - depthMap[v1];
|
||||
});
|
||||
|
||||
discardedModules.forEach(item => notBundledModules.push(item));
|
||||
|
||||
modules = modules.filter(item => !discardedModules.includes(item));
|
||||
|
||||
let modulePaths = modules.map(name => {
|
||||
if (!moduleFileMap[name]) {
|
||||
throw Error(`Can't obtain ${name}. Might be missing in allPatterns.`);
|
||||
}
|
||||
|
||||
return moduleFileMap[name];
|
||||
});
|
||||
|
||||
@@ -232,7 +270,9 @@ class Bundler {
|
||||
* @param {Object.<string, string[]>} map
|
||||
* @param {Object.<string, number>} depthMap
|
||||
* @param {string[]} ignoreLibs
|
||||
* @param {string[]} dependentOn
|
||||
* @param {string[]} discardedModules
|
||||
* @param {string[]} pickedModules
|
||||
* @param {number} [depth]
|
||||
* @param {string[]} [path]
|
||||
*/
|
||||
@@ -241,7 +281,9 @@ class Bundler {
|
||||
map,
|
||||
depthMap,
|
||||
ignoreLibs,
|
||||
dependentOn,
|
||||
discardedModules,
|
||||
pickedModules,
|
||||
depth,
|
||||
path
|
||||
) {
|
||||
@@ -271,6 +313,12 @@ class Bundler {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dependentOn && dependentOn.includes(depName)) {
|
||||
path
|
||||
.filter(item => !pickedModules.includes(item))
|
||||
.forEach(item => pickedModules.push(item));
|
||||
}
|
||||
}
|
||||
|
||||
deps.forEach(depName => {
|
||||
@@ -283,13 +331,31 @@ class Bundler {
|
||||
map,
|
||||
depthMap,
|
||||
ignoreLibs,
|
||||
dependentOn,
|
||||
discardedModules,
|
||||
pickedModules,
|
||||
depth + 1,
|
||||
path
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @return string
|
||||
*/
|
||||
#obtainModuleName(file) {
|
||||
for (let mod in this.modulePaths) {
|
||||
let part = this.modulePaths[mod] + '/src/';
|
||||
|
||||
if (file.indexOf(part) === 0) {
|
||||
return mod + ':' + file.substring(part.length, file.length - 3);
|
||||
}
|
||||
}
|
||||
|
||||
return file.slice(this.#getBathPath().length, -3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @return {{deps: string[], name: string}|null}
|
||||
@@ -315,7 +381,7 @@ class Bundler {
|
||||
return null;
|
||||
}
|
||||
|
||||
let moduleName = path.slice(this._getBathPath().length, -3);
|
||||
let moduleName = this.#obtainModuleName(path);
|
||||
|
||||
let deps = [];
|
||||
|
||||
@@ -344,7 +410,25 @@ class Bundler {
|
||||
* @return {boolean}
|
||||
*/
|
||||
#isClientJsFile(path) {
|
||||
return path.indexOf(this._getBathPath()) === 0 && path.slice(-3) === '.js';
|
||||
if (path.slice(-3) !== '.js') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let startParts = [this.#getBathPath()];
|
||||
|
||||
for (let mod in this.modulePaths) {
|
||||
let modPath = this.modulePaths[mod];
|
||||
|
||||
startParts.push(modPath);
|
||||
}
|
||||
|
||||
for (let starPart of startParts) {
|
||||
if (path.indexOf(starPart) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,9 +436,9 @@ class Bundler {
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
*/
|
||||
normalizeSourceFile(path) {
|
||||
#normalizeSourceFile(path) {
|
||||
let sourceCode = fs.readFileSync(path, 'utf-8');
|
||||
let basePath = this._getBathPath();
|
||||
let basePath = this.#getBathPath();
|
||||
|
||||
if (!this.#isClientJsFile(path)) {
|
||||
return sourceCode;
|
||||
@@ -401,7 +485,7 @@ class Bundler {
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
_getBathPath() {
|
||||
#getBathPath() {
|
||||
let path = this.basePath;
|
||||
|
||||
if (path.slice(-1) !== '/') {
|
||||
|
||||
+10
-4
@@ -32,6 +32,8 @@ const archiver = require('archiver');
|
||||
const process = require('process');
|
||||
const buildUtils = require('./build-utils');
|
||||
|
||||
const bundleConfig = require('../frontend/bundle-config.json');
|
||||
|
||||
const exec = cp.exec;
|
||||
|
||||
/**
|
||||
@@ -281,10 +283,14 @@ class Diff
|
||||
|
||||
fileList.push('client/lib/espo-libs.min.js');
|
||||
fileList.push('client/lib/espo-libs.min.js.map');
|
||||
fileList.push(`client/lib/espo.min.js`);
|
||||
fileList.push(`client/lib/espo.min.js.map`);
|
||||
fileList.push('client/lib/espo-templates.min.js');
|
||||
fileList.push('client/lib/espo-templates.min.js.map');
|
||||
|
||||
Object.keys(bundleConfig.chunks)
|
||||
.map(name => {
|
||||
let namePart = name === 'main' ? 'espo' : 'espo-' + name;
|
||||
|
||||
fileList.push(`client/lib/${namePart}.min.js`);
|
||||
fileList.push(`client/lib/${namePart}.min.js.map`);
|
||||
});
|
||||
|
||||
fs.readdirSync('client/css/espo/').forEach(file => {
|
||||
fileList.push('client/css/espo/' + file);
|
||||
|
||||
@@ -30,6 +30,7 @@ const fs = require('fs');
|
||||
const buildUtils = require('../build-utils');
|
||||
|
||||
const libs = require('./../../frontend/libs.json');
|
||||
const bundleConfig = require('../../frontend/bundle-config.json');
|
||||
|
||||
const libDir = './client/lib';
|
||||
const originalLibDir = './client/lib/original';
|
||||
@@ -40,10 +41,12 @@ const originalLibCrmDir = './client/modules/crm/lib/original';
|
||||
.filter(path => !fs.existsSync(path))
|
||||
.forEach(path => fs.mkdirSync(path));
|
||||
|
||||
let bundleFiles = [
|
||||
'espo-templates.js',
|
||||
'espo.js',
|
||||
];
|
||||
let bundleFiles = Object.keys(bundleConfig.chunks)
|
||||
.map(name => {
|
||||
let namePart = name === 'main' ? 'espo' : 'espo-' + name;
|
||||
|
||||
return namePart + '.js';
|
||||
});
|
||||
|
||||
fs.readdirSync(originalLibDir)
|
||||
.filter(file => !bundleFiles.includes(file))
|
||||
|
||||
@@ -37,9 +37,10 @@ class TemplatePrecompiler {
|
||||
/**
|
||||
* @param {{
|
||||
* patterns: string[],
|
||||
* modulePaths: Object.<string, string>,
|
||||
* modulePaths: Record.<string, string>,
|
||||
* ignoreFiles: string[],
|
||||
* }} params
|
||||
* @return {string}
|
||||
* @return {{contents: string, files: string[]}}
|
||||
*/
|
||||
precompile(params) {
|
||||
let files = [];
|
||||
@@ -52,10 +53,15 @@ class TemplatePrecompiler {
|
||||
});
|
||||
|
||||
let nameMap = {};
|
||||
let compiledFiles = [];
|
||||
|
||||
files.forEach(file => {
|
||||
let module = null;
|
||||
|
||||
if (params.ignoreFiles.includes(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let itemModule in params.modulePaths) {
|
||||
let path = params.modulePaths[itemModule];
|
||||
|
||||
@@ -79,6 +85,7 @@ class TemplatePrecompiler {
|
||||
}
|
||||
|
||||
nameMap[file] = name
|
||||
compiledFiles.push(file);
|
||||
});
|
||||
|
||||
let contents =
|
||||
@@ -96,7 +103,10 @@ class TemplatePrecompiler {
|
||||
|
||||
contents += `\n});`;
|
||||
|
||||
return contents;
|
||||
return {
|
||||
files: compiledFiles,
|
||||
contents: contents,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user