es modules first migration
This commit is contained in:
@@ -33,6 +33,8 @@ class BundlerGeneral {
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* basePath?: string,
|
||||
* transpiledPath?: string,
|
||||
* chunks: Object.<string, {
|
||||
* files?: string[],
|
||||
* patterns?: string[],
|
||||
@@ -93,6 +95,7 @@ class BundlerGeneral {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
data.modules.forEach(item => mapping[item] = name);
|
||||
|
||||
let bundleFile = this.filePattern.replace('{*}', name);
|
||||
@@ -159,7 +162,11 @@ class BundlerGeneral {
|
||||
let notBundledModules = [];
|
||||
|
||||
if (params.patterns) {
|
||||
let bundler = (new Bundler(this.config.modulePaths));
|
||||
let bundler = new Bundler(
|
||||
this.config.modulePaths,
|
||||
this.config.basePath,
|
||||
this.config.transpiledPath
|
||||
);
|
||||
|
||||
// The main bundle is always loaded, duplicates are not needed.
|
||||
let ignoreFiles = [].concat(this.mainBundleFiles);
|
||||
@@ -169,6 +176,7 @@ class BundlerGeneral {
|
||||
}
|
||||
|
||||
let data = bundler.bundle({
|
||||
name: name,
|
||||
files: params.files,
|
||||
patterns: patterns,
|
||||
lookupPatterns: lookupPatterns,
|
||||
@@ -40,21 +40,22 @@ class Bundler {
|
||||
|
||||
/**
|
||||
* @param {Object.<string, string>} modulePaths
|
||||
* @param {string} [basePath]
|
||||
* @param {string} [transpiledPath]
|
||||
*/
|
||||
constructor(modulePaths) {
|
||||
constructor(modulePaths, basePath, transpiledPath) {
|
||||
this.modulePaths = modulePaths;
|
||||
}
|
||||
this.basePath = basePath ?? 'client';
|
||||
this.transpiledPath = transpiledPath ?? 'client/lib/transpiled';
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
basePath = 'client/src'
|
||||
this.srcPath = this.basePath + '/src';
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundles Espo js files into chunks.
|
||||
*
|
||||
* @param {{
|
||||
* name: string,
|
||||
* files?: string[],
|
||||
* patterns: string[],
|
||||
* lookupPatterns?: string[],
|
||||
@@ -75,8 +76,9 @@ class Bundler {
|
||||
*/
|
||||
bundle(params) {
|
||||
let files = []
|
||||
.concat(params.files || [])
|
||||
.concat(this.#normalizePaths(params.files || []))
|
||||
.concat(this.#obtainFiles(params.patterns, params.files))
|
||||
// @todo Check if working.
|
||||
.filter(file => !params.ignoreFiles.includes(file));
|
||||
|
||||
let allFiles = this.#obtainFiles(params.lookupPatterns || params.patterns);
|
||||
@@ -89,6 +91,7 @@ class Bundler {
|
||||
let notBundledModules = [];
|
||||
|
||||
let sortedFiles = this.#sortFiles(
|
||||
params.name,
|
||||
files,
|
||||
allFiles,
|
||||
ignoreLibs,
|
||||
@@ -99,7 +102,8 @@ class Bundler {
|
||||
|
||||
let contents = '';
|
||||
|
||||
sortedFiles.forEach(file => contents += this.#normalizeSourceFile(file));
|
||||
this.#mapToTraspiledFiles(sortedFiles)
|
||||
.forEach(file => contents += this.#normalizeSourceFile(file) + '\n');
|
||||
|
||||
let modules = sortedFiles.map(file => this.#obtainModuleName(file));
|
||||
|
||||
@@ -111,6 +115,16 @@ class Bundler {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} files
|
||||
* @return {string[]}
|
||||
*/
|
||||
#mapToTraspiledFiles(files) {
|
||||
return files.map(file => {
|
||||
return this.transpiledPath + '/' + file.slice(this.basePath.length + 1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {string[]} [ignoreFiles]
|
||||
@@ -120,7 +134,7 @@ class Bundler {
|
||||
let files = [];
|
||||
ignoreFiles = ignoreFiles || [];
|
||||
|
||||
patterns.forEach(pattern => {
|
||||
this.#normalizePaths(patterns).forEach(pattern => {
|
||||
let itemFiles = globSync(pattern, {})
|
||||
.map(file => file.replaceAll('\\', '/'))
|
||||
.filter(file => !ignoreFiles.includes(file));
|
||||
@@ -132,6 +146,15 @@ class Bundler {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @return {string[]}
|
||||
*/
|
||||
#normalizePaths(patterns) {
|
||||
return patterns.map(item => this.basePath + '/' + item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string[]} files
|
||||
* @param {string[]} allFiles
|
||||
* @param {string[]} ignoreLibs
|
||||
@@ -141,6 +164,7 @@ class Bundler {
|
||||
* @return {string[]}
|
||||
*/
|
||||
#sortFiles(
|
||||
name,
|
||||
files,
|
||||
allFiles,
|
||||
ignoreLibs,
|
||||
@@ -203,9 +227,9 @@ class Bundler {
|
||||
/** @var {string[]} */
|
||||
let pickedModules = [];
|
||||
|
||||
for (let name of modules) {
|
||||
for (let module of modules) {
|
||||
this.#buildTreeItem(
|
||||
name,
|
||||
module,
|
||||
map,
|
||||
depthMap,
|
||||
ignoreLibs,
|
||||
@@ -266,7 +290,7 @@ class Bundler {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} module
|
||||
* @param {Object.<string, string[]>} map
|
||||
* @param {Object.<string, number>} depthMap
|
||||
* @param {string[]} ignoreLibs
|
||||
@@ -277,7 +301,7 @@ class Bundler {
|
||||
* @param {string[]} [path]
|
||||
*/
|
||||
#buildTreeItem(
|
||||
name,
|
||||
module,
|
||||
map,
|
||||
depthMap,
|
||||
ignoreLibs,
|
||||
@@ -288,17 +312,17 @@ class Bundler {
|
||||
path
|
||||
) {
|
||||
/** @var {string[]} */
|
||||
let deps = map[name] || [];
|
||||
let deps = map[module] || [];
|
||||
depth = depth || 0;
|
||||
path = [].concat(path || []);
|
||||
|
||||
path.push(name);
|
||||
path.push(module);
|
||||
|
||||
if (!(name in depthMap)) {
|
||||
depthMap[name] = depth;
|
||||
if (!(module in depthMap)) {
|
||||
depthMap[module] = depth;
|
||||
}
|
||||
else if (depth > depthMap[name]) {
|
||||
depthMap[name] = depth;
|
||||
else if (depth > depthMap[module]) {
|
||||
depthMap[module] = depth;
|
||||
}
|
||||
|
||||
if (deps.length === 0) {
|
||||
@@ -346,14 +370,14 @@ class Bundler {
|
||||
*/
|
||||
#obtainModuleName(file) {
|
||||
for (let mod in this.modulePaths) {
|
||||
let part = this.modulePaths[mod] + '/src/';
|
||||
let part = this.basePath + '/' + 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);
|
||||
return file.slice(this.#getSrcPath().length, -3);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,9 +389,13 @@ class Bundler {
|
||||
return null;
|
||||
}
|
||||
|
||||
let moduleName = this.#obtainModuleName(path);
|
||||
|
||||
const sourceCode = fs.readFileSync(path, 'utf-8');
|
||||
|
||||
let tsSourceFile = typescript.createSourceFile(
|
||||
path,
|
||||
fs.readFileSync(path, 'utf-8'),
|
||||
sourceCode,
|
||||
typescript.ScriptTarget.Latest
|
||||
);
|
||||
|
||||
@@ -378,10 +406,22 @@ class Bundler {
|
||||
!rootStatement.expression.expression ||
|
||||
rootStatement.expression.expression.escapedText !== 'define'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (!sourceCode.includes('export ')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let moduleName = this.#obtainModuleName(path);
|
||||
if (!sourceCode.includes('import ')) {
|
||||
return {
|
||||
name: moduleName,
|
||||
deps: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: moduleName,
|
||||
deps: this.#obtainModuleDeps(tsSourceFile),
|
||||
};
|
||||
}
|
||||
|
||||
let deps = [];
|
||||
|
||||
@@ -405,6 +445,27 @@ class Bundler {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sourceFile
|
||||
* @return {string[]}
|
||||
*/
|
||||
#obtainModuleDeps(sourceFile) {
|
||||
return sourceFile.statements
|
||||
.filter(item => item.importClause)
|
||||
.filter(item => item.importClause && item.moduleSpecifier)
|
||||
.map(item => item.moduleSpecifier.text)
|
||||
.map(item => {
|
||||
if (!item.startsWith('modules/')) {
|
||||
return item;
|
||||
}
|
||||
|
||||
let mod = item.split('/')[1];
|
||||
let part = item.split('/').slice(2).join('/');
|
||||
|
||||
return mod + ':' + part;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @return {boolean}
|
||||
@@ -414,10 +475,10 @@ class Bundler {
|
||||
return false;
|
||||
}
|
||||
|
||||
let startParts = [this.#getBathPath()];
|
||||
let startParts = [this.#getSrcPath()];
|
||||
|
||||
for (let mod in this.modulePaths) {
|
||||
let modPath = this.modulePaths[mod];
|
||||
let modPath = this.basePath + '/' + this.modulePaths[mod] + '/src/';
|
||||
|
||||
startParts.push(modPath);
|
||||
}
|
||||
@@ -438,13 +499,19 @@ class Bundler {
|
||||
*/
|
||||
#normalizeSourceFile(path) {
|
||||
let sourceCode = fs.readFileSync(path, 'utf-8');
|
||||
let basePath = this.#getBathPath();
|
||||
let srcPath = this.#getSrcPath();
|
||||
|
||||
sourceCode = this.#stripSourceMappingUrl(sourceCode);
|
||||
|
||||
if (!this.#isClientJsFile(path)) {
|
||||
return sourceCode;
|
||||
}
|
||||
|
||||
let moduleName = path.slice(basePath.length, -3);
|
||||
if (!sourceCode.includes('define')) {
|
||||
return sourceCode;
|
||||
}
|
||||
|
||||
let moduleName = path.slice(srcPath.length, -3);
|
||||
|
||||
let tsSourceFile = typescript.createSourceFile(
|
||||
path,
|
||||
@@ -482,11 +549,24 @@ class Bundler {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} contents
|
||||
* @return {string}
|
||||
*/
|
||||
#getBathPath() {
|
||||
let path = this.basePath;
|
||||
#stripSourceMappingUrl(contents) {
|
||||
let re = /^\/\/# sourceMappingURL.*/gm;
|
||||
|
||||
if (!contents.match(re)) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
return contents.replaceAll(re, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
#getSrcPath() {
|
||||
let path = this.srcPath;
|
||||
|
||||
if (path.slice(-1) !== '/') {
|
||||
path += '/';
|
||||
@@ -43,9 +43,11 @@ class TemplatePrecompiler {
|
||||
* @return {{contents: string, files: string[]}}
|
||||
*/
|
||||
precompile(params) {
|
||||
const baseBase = 'client';
|
||||
|
||||
let files = [];
|
||||
|
||||
params.patterns.forEach(pattern => {
|
||||
this.#normalizePaths(params.patterns).forEach(pattern => {
|
||||
let itemFiles = globSync(pattern)
|
||||
.map(file => file.replaceAll('\\', '/'));
|
||||
|
||||
@@ -63,7 +65,7 @@ class TemplatePrecompiler {
|
||||
}
|
||||
|
||||
for (let itemModule in params.modulePaths) {
|
||||
let path = params.modulePaths[itemModule];
|
||||
let path = baseBase + '/' + params.modulePaths[itemModule];
|
||||
|
||||
if (file.indexOf(path) === 0) {
|
||||
module = itemModule;
|
||||
@@ -73,7 +75,7 @@ class TemplatePrecompiler {
|
||||
}
|
||||
|
||||
let path = module ?
|
||||
params.modulePaths[module] :
|
||||
baseBase + '/' + params.modulePaths[module] :
|
||||
this.defaultPath;
|
||||
|
||||
path += '/res/templates/';
|
||||
@@ -108,6 +110,14 @@ class TemplatePrecompiler {
|
||||
contents: contents,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @return {string[]}
|
||||
*/
|
||||
#normalizePaths(patterns) {
|
||||
return patterns.map(item => this.defaultPath + '/' + item);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplatePrecompiler;
|
||||
@@ -62,7 +62,7 @@ let stripSourceMappingUrl = path => {
|
||||
/** @var {string} */
|
||||
let originalContents = fs.readFileSync(path, {encoding: 'utf-8'});
|
||||
|
||||
let re = /\/\/# sourceMappingURL.*/g;
|
||||
let re = /^\/\/# sourceMappingURL.*/gm;
|
||||
|
||||
if (!originalContents.match(re)) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/************************************************************************
|
||||
* 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 Transpiler = require('../../js/transpiler');
|
||||
|
||||
(new Transpiler({})).process();
|
||||
|
||||
(new Transpiler({
|
||||
mod: 'crm',
|
||||
path: 'client/modules/crm',
|
||||
})).process();
|
||||
@@ -0,0 +1,169 @@
|
||||
/************************************************************************
|
||||
* 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 babelCore = require("@babel/core");
|
||||
const fs = require('fs');
|
||||
const {globSync} = require('glob');
|
||||
|
||||
class Transpiler {
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* path?: string,
|
||||
* destDir?: string,
|
||||
* mod?: string,
|
||||
* }} config
|
||||
*/
|
||||
constructor(config) {
|
||||
this.path = (config.path ?? 'client') + '/src';
|
||||
this.destDir = config.destDir || 'client/lib/transpiled';
|
||||
this.mod = config.mod;
|
||||
|
||||
this.contentsCache = {};
|
||||
}
|
||||
|
||||
process() {
|
||||
let allFiles = globSync(this.path + '/**/*.js')
|
||||
.map(file => file.replaceAll('\\', '/'));
|
||||
|
||||
let files = allFiles.filter(file => this.#isToBeTranspiled(file));
|
||||
let otherFiles = allFiles.filter(file => !files.includes(file));
|
||||
|
||||
files.forEach(file => this.#processFile(file));
|
||||
otherFiles.forEach(file => this.#copyFile(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
*/
|
||||
#processFile(file) {
|
||||
const module = this.#obtainModuleName(file);
|
||||
|
||||
const result = babelCore.transformSync(this.#getContents(file), {
|
||||
plugins: ['@babel/plugin-transform-modules-amd'],
|
||||
moduleId: module,
|
||||
sourceMaps: true,
|
||||
});
|
||||
|
||||
let dir = this.#obtainTargetDir(module);
|
||||
|
||||
fs.mkdirSync(dir, {recursive: true});
|
||||
|
||||
let part = module;
|
||||
|
||||
if (part.includes(':')) {
|
||||
part = part.split(':')[1];
|
||||
}
|
||||
|
||||
let filePart = part.split('/').slice(-1)[0] + '.js';
|
||||
let destFile = dir + filePart;
|
||||
|
||||
let resultContent = result.code + `\n//# sourceMappingURL=${filePart}.map ;`;
|
||||
|
||||
fs.writeFileSync(destFile, resultContent, 'utf-8');
|
||||
fs.writeFileSync(destFile + '.map', result.map.toString(), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
*/
|
||||
#copyFile(file) {
|
||||
let module = this.#obtainModuleName(file);
|
||||
let dir = this.#obtainTargetDir(module);
|
||||
|
||||
fs.mkdirSync(dir, {recursive: true});
|
||||
|
||||
let destFile = dir + file.split('/').slice(-1)[0];
|
||||
|
||||
fs.mkdirSync(dir, {recursive: true});
|
||||
fs.copyFileSync(file, destFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} module
|
||||
* @return {string}
|
||||
*/
|
||||
#obtainTargetDir(module) {
|
||||
let destDir = this.destDir;
|
||||
|
||||
let part = 'src';
|
||||
let path = module;
|
||||
|
||||
if (module.includes(':')) {
|
||||
let [mod, itemPath] = module.split(':');
|
||||
|
||||
part = 'modules/' + mod + '/' + part;
|
||||
|
||||
path = itemPath;
|
||||
}
|
||||
|
||||
destDir += '/' + part + '/' + path.split('/').slice(0, -1).join('/');
|
||||
|
||||
if (destDir.slice(-1) !== '/') {
|
||||
destDir += '/';
|
||||
}
|
||||
|
||||
return destDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @return {boolean}
|
||||
*/
|
||||
#isToBeTranspiled(file) {
|
||||
let contents = this.#getContents(file);
|
||||
|
||||
return !contents.includes("\ndefine(") && contents.includes("\nexport ");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @return {string}
|
||||
*/
|
||||
#getContents(file) {
|
||||
if (!(file in this.contentsCache)) {
|
||||
this.contentsCache[file] = fs.readFileSync(file, 'utf-8');
|
||||
}
|
||||
|
||||
return this.contentsCache[file];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @return string
|
||||
*/
|
||||
#obtainModuleName(file) {
|
||||
if (this.mod) {
|
||||
return this.mod + ':' + file.slice(this.path.length + 1, -3);
|
||||
}
|
||||
|
||||
return file.slice(this.path.length + 1, -3);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Transpiler;
|
||||
Reference in New Issue
Block a user