diff --git a/Gruntfile.js b/Gruntfile.js index 8a28bea404..641b1b753e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -33,16 +33,20 @@ const fs = require('fs'); const cp = require('child_process'); const path = require('path'); const buildUtils = require('./js/build-utils'); +const Bundler = require("./js/bundler"); +const bundleConfig = require("./frontend/bundle-config.json"); +const libs = require("./frontend/libs.json"); module.exports = grunt => { const pkg = grunt.file.readJSON('package.json'); - const bundleConfig = require('./frontend/bundle-config.json'); - const libs = require('./frontend/libs.json'); const originalLibDir = 'client/lib/original'; - let bundleFileMap = {'client/lib/espo-libs.min.js': buildUtils.getPreparedBundleLibList(libs)}; + let bundleFileMap = { + 'client/lib/espo-libs.min.js': buildUtils.getPreparedBundleLibList(libs), + 'client/lib/espo-templates.min.js': 'client/lib/original/espo-templates.js', + }; for (let i = 0; i < bundleConfig.chunkNumber; i++) { let bundleFile = originalLibDir + `/espo-${i}.js`; @@ -256,8 +260,6 @@ module.exports = grunt => { }); grunt.registerTask('espo-bundle', () => { - const Bundler = require('./js/bundler'); - let chunks = (new Bundler()).bundle({ files: bundleConfig.files, patterns: bundleConfig.patterns, @@ -277,6 +279,23 @@ module.exports = grunt => { }); }); + grunt.registerTask('template-precompile', () => { + const Precompiler = require('./js/template-precompiler'); + + let contents = (new Precompiler()).precompile({ + patterns: bundleConfig.templatePatterns, + modulePaths: {'crm': 'client/modules/crm'}, + }); + + if (!fs.existsSync(originalLibDir)) { + fs.mkdirSync(originalLibDir); + } + + let file = originalLibDir + `/espo-templates.js`; + + fs.writeFileSync(file, contents, 'utf8'); + }); + grunt.registerTask('prepare-lib-original', () => { // Even though `npm ci` runs the same script, 'clean:start' deletes files. cp.execSync("node js/scripts/prepare-lib-original"); @@ -460,6 +479,7 @@ module.exports = grunt => { 'less', 'cssmin', 'espo-bundle', + 'template-precompile', 'prepare-lib-original', 'uglify:bundle', 'copy:frontendLib', diff --git a/application/Espo/Resources/metadata/app/client.json b/application/Espo/Resources/metadata/app/client.json index b429acd6b2..8e5d10d463 100644 --- a/application/Espo/Resources/metadata/app/client.json +++ b/application/Espo/Resources/metadata/app/client.json @@ -4,7 +4,8 @@ "client/lib/espo-0.min.js", "client/lib/espo-1.min.js", "client/lib/espo-2.min.js", - "client/lib/espo-3.min.js" + "client/lib/espo-3.min.js", + "client/lib/espo-templates.min.js" ], "developerModeScriptList": [ "client/src/loader.js", diff --git a/client/cfg/pre-load.json b/client/cfg/pre-load.json index 2395ba2607..ba92f760da 100644 --- a/client/cfg/pre-load.json +++ b/client/cfg/pre-load.json @@ -1,23 +1,10 @@ { - "templates": [ - "dashlet", - "detail", - "edit", - "header", - "list", - "login", - "modals/edit", - "modals/select-records", - "site/header", - "site/navbar" - ], + "templates": [], "layoutTypes": [ "columns-2", "record", "list-row", "list-row-expanded" ], - "classes": [ - - ] + "classes": [] } diff --git a/client/src/app.js b/client/src/app.js index 18e03e41f1..b034eaf109 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -896,6 +896,7 @@ function ( }, }, }, + preCompiledTemplates: Espo.preCompiledTemplates || {}, }); }, diff --git a/frontend/bundle-config.json b/frontend/bundle-config.json index 348cf75674..24118a5414 100644 --- a/frontend/bundle-config.json +++ b/frontend/bundle-config.json @@ -25,5 +25,36 @@ "client/src/collections/*.js", "client/src/acl/*.js" ], + "templatePatterns": [ + "client/res/templates/*.tpl", + "client/res/templates/site/**/*.tpl", + "client/res/templates/record/**/*.tpl", + "client/res/templates/note/*.tpl", + "client/res/templates/stream/*.tpl", + "client/res/templates/global-search/*.tpl", + "client/res/templates/fields/address/*.tpl", + "client/res/templates/fields/array/*.tpl", + "client/res/templates/fields/attachments-multiple/*.tpl", + "client/res/templates/fields/base/*.tpl", + "client/res/templates/fields/bool/*.tpl", + "client/res/templates/fields/currency/*.tpl", + "client/res/templates/fields/date/*.tpl", + "client/res/templates/fields/datetime/*.tpl", + "client/res/templates/fields/datetime-short/*.tpl", + "client/res/templates/fields/duration/*.tpl", + "client/res/templates/fields/email/*.tpl", + "client/res/templates/fields/enum/*.tpl", + "client/res/templates/fields/file/*.tpl", + "client/res/templates/fields/float/*.tpl", + "client/res/templates/fields/int/*.tpl", + "client/res/templates/fields/multi-enum/*.tpl", + "client/res/templates/fields/person-name/*.tpl", + "client/res/templates/fields/text/*.tpl", + "client/res/templates/fields/url/*.tpl", + "client/res/templates/fields/user/*.tpl", + "client/res/templates/fields/user-with-avatar/*.tpl", + "client/res/templates/fields/varchar/*.tpl", + "client/res/templates/fields/wysiwyg/*.tpl" + ], "chunkNumber": 4 } diff --git a/js/diff.js b/js/diff.js index fd0c05c2ad..7d39e6fbfa 100644 --- a/js/diff.js +++ b/js/diff.js @@ -289,6 +289,9 @@ class Diff fileList.push(`client/lib/espo-${i}.min.js.map`); } + fileList.push('client/lib/espo-templates.min.js'); + fileList.push('client/lib/espo-templates.min.js.map'); + //fileList.push('client/lib/original/espo.js'); fs.readdirSync('client/css/espo/').forEach(file => { diff --git a/js/scripts/prepare-lib-original.js b/js/scripts/prepare-lib-original.js index 268b4c2acd..5edfe4b6ad 100644 --- a/js/scripts/prepare-lib-original.js +++ b/js/scripts/prepare-lib-original.js @@ -41,7 +41,8 @@ const originalLibCrmDir = './client/modules/crm/lib/original'; .filter(path => !fs.existsSync(path)) .forEach(path => fs.mkdirSync(path)); -let bundleFiles = []; +let bundleFiles = ['espo-templates.js']; + for (let i = 0; i < bundleConfig.chunkNumber; i++) { bundleFiles.push(`espo-${i}.js`) } diff --git a/js/template-precompiler.js b/js/template-precompiler.js new file mode 100644 index 0000000000..0011008f47 --- /dev/null +++ b/js/template-precompiler.js @@ -0,0 +1,101 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko + * Website: https://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU General Public License version 3. + * + * In accordance with Section 7(b) of the GNU General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +const fs = require('fs'); +const {globSync} = require('glob'); +const Handlebars = require('handlebars'); + +class TemplatePrecompiler { + + defaultPath = 'client'; + + /** + * @param {{ + * patterns: string[], + * modulePaths: Object., + * }} params + * @return {string} + */ + precompile(params) { + let files = []; + + params.patterns.forEach(pattern => { + let itemFiles = globSync(pattern) + .map(file => file.replaceAll('\\', '/')); + + files = files.concat(itemFiles); + }); + + let nameMap = {}; + + files.forEach(file => { + let module = null; + + for (let itemModule in params.modulePaths) { + let path = params.modulePaths[itemModule]; + + if (file.indexOf(path) === 0) { + module = itemModule; + + break; + } + } + + let path = module ? + params.modulePaths[module] : + this.defaultPath; + + path += '/res/templates/'; + + let name = file.substring(path.length).slice(0, -4); + + if (module) { + name = module + ':' + name; + } + + nameMap[file] = name + }); + + let contents = 'Espo.preCompiledTemplates = {\n'; + + for (let file in nameMap) { + let name = nameMap[file]; + + let templateContent = fs.readFileSync(file, 'utf8'); + let compiled = Handlebars.precompile(templateContent); + + contents += `'${name}': Handlebars.template(\n${compiled}\n),\n`; + } + + contents += `\n}`; + + return contents; + } +} + +module.exports = TemplatePrecompiler;