es modules first migration

This commit is contained in:
Yuri Kuznetsov
2023-06-08 13:31:27 +03:00
parent 4e040700ab
commit c6b7337317
194 changed files with 50583 additions and 47583 deletions
@@ -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,
+113 -33
View File
@@ -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;
+1 -1
View File
@@ -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;
+36
View File
@@ -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();
+169
View File
@@ -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;