From 31c4623377ee8e87c1f22b7f05f6fe2786f7f14d Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Thu, 25 Apr 2024 02:24:24 -0300 Subject: [PATCH 01/12] wip --- lib/actions/loadFilesContent.js | 25 ++- lib/actions/loadFilesInfo.js | 17 ++ lib/actions/processChildrenWidget.js | 2 +- lib/actions/transformSchemaToWidget.js | 247 +++++++++++++++++++-- lib/alem-vm/components/AppIndexer.jsx | 36 +-- lib/compiler.js | 4 + lib/parsers/extractTopLevelDeclarations.js | 130 +++++++++++ lib/parsers/transformVariableInCode.js | 41 ++++ package.json | 2 +- 9 files changed, 473 insertions(+), 31 deletions(-) create mode 100644 lib/parsers/extractTopLevelDeclarations.js create mode 100644 lib/parsers/transformVariableInCode.js diff --git a/lib/actions/loadFilesContent.js b/lib/actions/loadFilesContent.js index c0e7e2a..4473624 100644 --- a/lib/actions/loadFilesContent.js +++ b/lib/actions/loadFilesContent.js @@ -1,3 +1,4 @@ +const parseAlemFeatures = require("../config/parseAlemFeatures"); const { scapeBacktick } = require("../helpers"); const transformSchemaToWidget = require("./transformSchemaToWidget"); @@ -27,13 +28,33 @@ const loadComponentCodesObjectByFileSchemas = ( additionalFileSchemas, ); + // Processa também os módulos + let modulesCodes = ""; + completeFileSchemas.fileSchemas.forEach((fileSchema) => { - if (fileSchema.widgetName && !fileSchema.isModule) { + // Prepare Widgets (stateful components) + if (fileSchema.widgetName && !fileSchema.isStateless) { componentsCodes += ` ${fileSchema.widgetName}: \`${scapeBacktick(fileSchema.finalFileBundle)}\`, `; } + // Prepare modules + if (fileSchema.isModule) { + let modulesValues = "{"; + const valuesEntries = Object.entries(fileSchema.moduleProps.values); + valuesEntries.forEach((entrie) => { + modulesValues += ` + ${entrie[0]}: ${scapeBacktick(entrie[1])}, + `; + }); + modulesValues += "}"; + + modulesCodes += ` + "${fileSchema.moduleProps.name}": ${parseAlemFeatures(modulesValues)}, + `; + } + if (fileSchema.widgetName === "App") { appComponentFinalBundle = fileSchema.finalFileBundle; } @@ -42,6 +63,8 @@ const loadComponentCodesObjectByFileSchemas = ( return { /** Código final de todos os componentes do projeto */ componentsCodes, + /** Código final de todos os módulos. Eles serão inseridos no escopo global e disponível para todos os subcomponents */ + modulesCodes, /** Código final do componente App de entrada do projet apenas. (src/index.tsx | .jsx) */ appComponentFinalBundle, /** Esquema final de todos os arquivos processados */ diff --git a/lib/actions/loadFilesInfo.js b/lib/actions/loadFilesInfo.js index aae1dce..18f881a 100644 --- a/lib/actions/loadFilesInfo.js +++ b/lib/actions/loadFilesInfo.js @@ -15,6 +15,7 @@ const { removeImports } = require("../parse"); const filesContentCache = require("../config/filesContentCache"); const replaceRegexWithReferences = require("../parsers/regex-parser/convertRegexToStringLiteral"); const regexObjects = require("../parsers/regex-parser/regexObjects"); +// const extractTopLevelDeclarations = require("../parsers/extractTopLevelDeclarations"); /** * Transform statefull components references to JSX (this applies for stateful and stateless components) * Troca referencias de stateful components para JSX. Accesse o arquivo "transformComponentReferenceToJSX" para saber mais. @@ -140,6 +141,8 @@ const processFileSchema = (filePath, processOnlyThisFile) => { nextFilesToLoad: [], toImport: [], content: fileContent, + isModule: false, + moduleProps: {}, }; fileImportsPath.forEach((importPath) => { // console.log("Check import Path:", importPath); @@ -179,6 +182,18 @@ const processFileSchema = (filePath, processOnlyThisFile) => { processedFiles.push(importedFileContentPath); } + + // Lógica de módulos + // isModule = Arquivos que estão na pasta "src/modules". Estes são inseridos no state global para serem acessados por todos + // os componentes, salvando assim bastante espaço do bundle final. + // const isModule = filePath.includes("src/modules/"); + // currentFileSchema.isModule = isModule; + // if (isModule) { + // currentFileSchema.moduleProps = { + // name: filePath, + // values: extractTopLevelDeclarations(fileContent), + // }; + // } }); // Transform statefull components references to JSX @@ -222,6 +237,8 @@ const loadFilesInfo = (entryFile) => { filePath: item.filePath, toImport: item.toImport, content: item.content, + // isModule: item.isModule, + // moduleProps: item.moduleProps, }; delete newItem.filesToImport; return newItem; diff --git a/lib/actions/processChildrenWidget.js b/lib/actions/processChildrenWidget.js index 3ba1d6b..e1a1590 100644 --- a/lib/actions/processChildrenWidget.js +++ b/lib/actions/processChildrenWidget.js @@ -47,7 +47,7 @@ const processChildrenWidget = (htmlContent, fileSchemas) => { // Processa o arquivo como Widget apenas se for achado na lista de schemas e // for um componente stateful - if (componentSchema && !componentSchema.isModule) { + if (componentSchema && !componentSchema.isStateless) { const extractPropsResult = extractPropsFromJSX(htmlElement); let childProps = extractPropsResult.keyValueProps; const childSpreads = extractPropsResult.spreads; diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index 47bc537..c53796c 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -31,6 +31,7 @@ const analyzeFunctionSignature = require("../parsers/analyzeFunctionSignature"); const removeFunctionParams = require("../parsers/removeFunctionParams"); const transformAsyncAwait = require("../parsers/transformAsyncAwait"); const compilerOptions = require("./compilerOptions"); +const extractTopLevelDeclarations = require("../parsers/extractTopLevelDeclarations"); let processError = null; @@ -98,10 +99,9 @@ const processSchema = (fileSchema) => { fileSchema.filePath.includes("src\\index.tsx") || fileSchema.filePath.includes("src\\index.jsx"); - // isModule = Arquivo sem controle de estado - let isModule = !hasWidgetPropsCheck(fileSchema.content) && !isIndex; - - fileSchema.isModule = isModule; + // isStateless = Arquivo sem controle de estado + let isStateless = !hasWidgetPropsCheck(fileSchema.content) && !isIndex; + fileSchema.isStateless = isStateless; // ITEM 2 const transpileTypescript = @@ -119,6 +119,20 @@ const processSchema = (fileSchema) => { transpileTypescript, ); + // isModule = Arquivos que estão na pasta "src/modules". Estes são inseridos no state global para serem acessados por todos + // os componentes, salvando assim bastante espaço do bundle final. + const isModule = fileSchema.filePath.includes("src/modules/"); + fileSchema.isModule = isModule; + if (isModule) { + fileSchema.moduleProps = { + name: fileSchema.filePath, + values: extractTopLevelDeclarations( + fileSchema.pureJsContent, + fileSchema.filePath, + ), + }; + } + // ITEM 1 let componentImports = getFileImportsElements(jsContent); componentImports = removeProhibitedMethods(componentImports); @@ -212,7 +226,7 @@ const processSchema = (fileSchema) => { if ( (getFirstLineContent(jsContent).includes("<") || getFirstLineContent(jsContent).endsWith("(")) && - !isModule + !isStateless ) { // Aqui eu uso o conteúdo original bruto mesmo. O parser consegue dividir os elementos // o elemento [0] vai ser o inicio da funcao @@ -235,7 +249,7 @@ const processSchema = (fileSchema) => { } // Módulos (arquivos stateles) não devem ter seu conteúdo modificado - if (!isModule) { + if (!isStateless) { // Remove os parametros da função jsContent = removeFunctionParams(jsContent); } @@ -243,7 +257,7 @@ const processSchema = (fileSchema) => { // Módulos devem ter seu jsContent inteiro, sem remover ou alterar sua estrutura. // Isso é porque eles são helpers que retornam JSX. Arquivos que não lidam com JSX // também podem ser módulos - if (!isModule) { + if (!isStateless) { // ITEM 4 (remover primeira linha) jsContent = removeLineFromText(jsContent, 1); @@ -261,7 +275,7 @@ const processSchema = (fileSchema) => { // ITEM 6 // ATENCAO: esta copiando tipos de props tbm do typescript - if (!isModule) { + if (!isStateless) { const componentParams = analyzeFunctionSignature(fileSchema.pureJsContent); if (componentParams.capturedParams) { @@ -312,7 +326,6 @@ const swapComponentsForStatelessFiles = (fileSchemas, fileSchema) => { */ const pastedFiles = []; - // let fileBundle = fileSchema.finalFileBundle; let fileBundle = fileSchema.jsContent; Object.keys(fileSchema.componentImportItems).forEach((importItem) => { @@ -352,7 +365,7 @@ const swapComponentsForStatelessFiles = (fileSchemas, fileSchema) => { importItemWidget.widgetName || getComponentName(importItemWidget.finalFileBundle); - if (importItemWidgetComponentName && !importItemWidget.isModule) { + if (importItemWidgetComponentName && !importItemWidget.isStateless) { // Processa todos os componentes filhos // Cada elemento do mesmo tipo é um filho diferente que foi adicionado ao elemento pai // const importItemElements = @@ -448,6 +461,59 @@ const swapComponentsForStatelessFiles = (fileSchemas, fileSchema) => { return fileSchema; }; +const prepareListOfModulesToInject = (fileSchemas, fileSchema) => { + const pastedFiles = []; + + // console.log("====== CHECAGEM DE INJECAO ====== "); + // console.log("Arquivo sendo processado:", fileSchema.filePath); + // console.log("Imports do arquivo:", fileSchema.componentImportItems); + + // Checa se o item faz parte de componentes + Object.keys(fileSchema.componentImportItems).forEach((importItem) => { + // Se for item não widget, inclui no topo do bundle do arquivo + + // Se for um arquivo disponível, segue (null quer dizer que é um import de alguma lib nao local no src) + const importItemFileSource = fileSchema.componentImportItems[importItem]; // src/path/to/file.tsx | null + // Nao deve processar (copiar e colar) o conteúdo do arquivo mais de uma vez + if (importItemFileSource && !pastedFiles.includes(importItemFileSource)) { + // Adiciona na lista de items ja processados + pastedFiles.push(importItemFileSource); + + // Import File Schema + let importItemFileContent = fileSchemas.find( + (importFileSchema) => + importFileSchema.filePath === importItemFileSource, + ); + + // Também não pode ser um módulo + if (importItemFileContent.isStateless && importItemFileContent.isModule) { + // Modulos + + // Insere os modulos dependentes + if (!fileSchema.toBeInjectedModules) { + fileSchema.toBeInjectedModules = []; + } + + // Adiciona somente se ainda nao tiver o item na lista de modulos + if ( + !fileSchema.toBeInjectedModules.includes( + importItemFileContent.filePath, + ) + ) { + fileSchema.toBeInjectedModules.push( + importItemFileContent.filePath, + ...(importItemFileContent.toBeInjectedModules + ? importItemFileContent.toBeInjectedModules + : []), + ); + } + } + } + }); + + return fileSchema; +}; + // Arquivos sinalizados que devem ser processados pelo "injectFilesDependencies" posteriormente devido // a falta de outro arquivo que ainda não tenha sido processado // const pending_injectFilesDependencies = []; @@ -489,8 +555,10 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { importFileSchema.filePath === importItemFileSource, ); + // TODO: Porque tem uma funcao para injecao de dependencias apenas de arquivos stateless? // NAO WIDGETS | MODULOS (arquivos que contenham "module" no nome): tem o conteúdo de seu arquivo copiado e colado no corpo do arquivo sendo processado atual - if (importItemFileContent.isModule) { + // Também não pode ser um módulo + if (importItemFileContent.isStateless) { // Funcao recursiva aqui para fazer com que arquivos ainda não processados pelo injection, sejam primeiro if (!importItemFileContent.injectFilesDependencies_Done) { // Faz o processo primeiro no arquivo dependente @@ -502,6 +570,31 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { ); } + // if (importItemFileContent.isModule) { + // // Modulos + + // // Insere os modulos dependentes + // if (!fileSchema.toBeInjectedModules) { + // fileSchema.toBeInjectedModules = []; + // } + + // // Adiciona somente se ainda nao tiver o item na lista de modulos + // if ( + // !fileSchema.toBeInjectedModules.includes( + // importItemFileContent.filePath, + // ) + // ) { + // fileSchema.toBeInjectedModules.push( + // importItemFileContent.filePath, + // ...(importItemFileContent.toBeInjectedModules + // ? importItemFileContent.toBeInjectedModules + // : []), + // ); + // } + // } + // else { + // // Nao modulos + // Insere os arquivos dependentes que já foram inseridos no bundle. Isso evita duplicatas de conteúdo if (!fileSchema.toBeInjectedFiles) { fileSchema.toBeInjectedFiles = []; @@ -519,6 +612,7 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { : []), ); } + // } } } }); @@ -529,6 +623,52 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { return fileSchema; }; +const foo = (fileSchemas, fileSchema) => { + let fileBundle = fileSchema.finalFileBundle; + let wasChanged = false; + + // Se nao tiver nada a ser injetado, somente retorna o file Schema sem alterações + if (!fileSchema.toBeInjectedModules) { + return fileSchema; + } + + // Checa se o item faz parte de componentes + fileSchema.toBeInjectedModules.forEach((fileItemPath) => { + // Pega o esquema do arquivo + let fileItemSchema = fileSchemas.find( + (importFileSchema) => importFileSchema.filePath === fileItemPath, + ); + + // Insere apenas as referencias das declarações do módulo + const moduleValuesKeys = Object.keys(fileItemSchema.moduleProps.values); + + moduleValuesKeys.forEach((propKey) => { + // Deve trocar o conteúdo somente se for um conteúdo sendo importado dentro do arquivo principal + const importReference = fileSchema.componentImportItems[propKey]; + // Existe o import no arquivo principal && o diretório do import pertence ao "fileItemSchema.filePath"? + const isImported = + (importReference && importReference === fileItemSchema.filePath) || + false; + + if (isImported) { + wasChanged = true; + + fileBundle = ` + const ${propKey} = props.alem.modulesCode["${fileItemSchema.moduleProps.name}"].${propKey}; + ${fileBundle} + `; + } + }); + + if (wasChanged) { + fileSchema.finalFileBundle = fileBundle; + fileSchema.jsContent = fileBundle; + } + }); + + return fileSchema; +}; + /** * Coloca o conteúdo dos arquivos nao .ts e .jsx de dependencia dentro do bundle de cada arquivo do schema global * Esse é um processo que ocorre para todos os arquivos, mas somente copia e cola o conteudo para arquivos nao JSX. @@ -559,16 +699,49 @@ const injectFilesDependencies = (fileSchemas, fileSchema) => { (importFileSchema) => importFileSchema.filePath === fileItemPath, ); - fileBundle = ` + // if (fileItemSchema.isModule) { + // console.log(fileSchema.filePath, "--->", fileItemSchema.filePath); + + // const moduleValuesKeys = Object.keys(fileItemSchema.moduleProps.values); + // moduleValuesKeys.forEach((propKey) => { + // // Deve trocar o conteúdo somente se for um conteúdo sendo importado dentro do arquivo principal + // const importReference = fileSchema.componentImportItems[propKey]; + // // Existe o import no arquivo principal && o diretório do import pertence ao "fileItemSchema.filePath"? + // const isImported = + // (importReference && importReference === fileItemSchema.filePath) || + // false; + + // // console.log("IS IMPOTED:", isImported, propKey); + // if (isImported) { + // // Insere apenas a referencia da dependencia + // fileBundle = ` + // const ${propKey} = props.alem.modulesCode.["${fileItemSchema.moduleProps.name}"].${propKey}; + // ${fileBundle} + // `; + // } + // }); + // } else { + // // Injeta o conteúdo literalmente + // fileBundle = ` + // ${fileItemSchema.jsContent} + // ${fileBundle} + // `; + // } + + if (!fileItemSchema.isModule) { + // Injeta o conteúdo literalmente + fileBundle = ` ${fileItemSchema.jsContent} ${fileBundle} `; - injectedFiles.push(fileItemPath); - fileSchema.finalFileBundle = fileBundle; - fileSchema.injectedFiles = injectedFiles; + injectedFiles.push(fileItemPath); + } }); + fileSchema.finalFileBundle = fileBundle; + fileSchema.injectedFiles = injectedFiles; + return fileSchema; }; @@ -649,12 +822,45 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { }); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + fileSchemas[fileSchemaIndex] = prepareListOfModulesToInject( + fileSchemas, + fileSchema, + ); + + // if (fileSchema.filePath.includes("src/Main.tsx")) { + // console.log("========== AAAA"); + // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); + // } + }); + + // FOO + // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" + fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + fileSchemas[fileSchemaIndex] = foo(fileSchemas, fileSchema); + // if (fileSchema.filePath.includes("src/Main.tsx")) { + // console.log("========== AAAA"); + // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); + // } + }); + + fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + // if (fileSchema.filePath.includes("src/Main.tsx")) { + // console.log("========== BBBB"); + // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); + // } // Processa arquivos que contenham elementos html mas são stateless // console.log("Current file:", fileSchema.filePath); + // ATENCAO: essa funcao esta usando "fileSchema.jsContent" ao invéz de "fileSchema.finalFileBundle" + // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" fileSchemas[fileSchemaIndex] = swapComponentsForStatelessFiles( fileSchemas, fileSchema, ); + + // if (fileSchema.filePath.includes("src/Main.tsx")) { + // console.log("========== AAAA"); + // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); + // } }); // Prepara lista de arquivos a serem injetados dentro de cada arquivo @@ -663,8 +869,18 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { fileSchemas, fileSchema, ); + + // if (fileSchema.filePath.includes("src/Main.tsx")) { + // console.log("========== AAAA"); + // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); + // } }); + // FOO + // fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + // fileSchemas[fileSchemaIndex] = foo(fileSchemas, fileSchema); + // }); + // Copia e cola o conteúdo de arquivos não .tsx | .jsx para dentro dos arquivos que dependem deles fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = injectFilesDependencies( @@ -675,6 +891,7 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { // Faz transformação de async/await para o formato promisify fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + // Transform async/await (experimental) fileSchema.finalFileBundle = transformAsyncAwait( fileSchema.finalFileBundle, ); diff --git a/lib/alem-vm/components/AppIndexer.jsx b/lib/alem-vm/components/AppIndexer.jsx index 5f87d95..426e4a7 100644 --- a/lib/alem-vm/components/AppIndexer.jsx +++ b/lib/alem-vm/components/AppIndexer.jsx @@ -3,24 +3,34 @@ const AlemApp = useMemo(() => { return ""; } - const Container = styled.div` - display: flex; - margin-top: 48%; - justify-content: center; - width: 100%; - `; + // Modules, Codes Wrapper, Recursos que dependem das propriedades globais devem ser colocadas em segunda camada, senão props retorna + // sem os dados do além. + // Todas as propriedades que dependem de algum recurso do Além deve ser colocado nessa segunda camada, por exemplo: + // "props.alem.isDevelopment", "props.alem.getAlemEvironment()", etc + // TODO: colocar isso em um arquivo? + // TODO: 2 - colocar condicional para: se usar modulos, faz esse processo de usar o layer2, senao, carrega diretamente o APP como antes + const widgetLayer2code = ` + const props = { + ...props, + // ==================================== Modules Code ==================================== + alem: { + ...props.alem, + modulesCode: { + MODULES_CODE: {}, + }, + } + } - const Loading = () => ( - -
- - ); + return ( + + ) + `; return ( } - code={props.alem.componentsCode.App} + loading=" " + code={widgetLayer2code} props={{ alem: props.alem }} /> diff --git a/lib/compiler.js b/lib/compiler.js index 7c25dd4..83dec49 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -57,6 +57,7 @@ function run_final_process(filesInfo, opts) { } let widgetsCodes = finishedSchemaProcessForWidgets.componentsCodes; + const modulesCodes = finishedSchemaProcessForWidgets.modulesCodes; // Alem VM -> Header contents let bundleContent = alemFiles.loadHeaderFilesContent(); @@ -74,6 +75,9 @@ function run_final_process(filesInfo, opts) { // Adiciona o bundle do componente App dentro do Indexador: bundleContent += alemFiles.loadIndexerContent(); + // Insere os códigos dos Módulos nas props globais + bundleContent = bundleContent.replace("MODULES_CODE: {},", modulesCodes); + // Inject CDN Libraries // file name: modules.json // Reason to not use: it's not possible to clone functions, use window or documents diff --git a/lib/parsers/extractTopLevelDeclarations.js b/lib/parsers/extractTopLevelDeclarations.js new file mode 100644 index 0000000..5afbfd6 --- /dev/null +++ b/lib/parsers/extractTopLevelDeclarations.js @@ -0,0 +1,130 @@ +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const generate = require("@babel/generator").default; +const t = require("@babel/types"); +const transformVariableInCode = require("./transformVariableInCode"); + +function transformFunctionDeclarations(code) { + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"], + }); + + const declarations = {}; + + traverse(ast, { + FunctionDeclaration(path) { + const { id, params, body, async } = path.node; + if (id) { + const arrowFunction = t.arrowFunctionExpression(params, body, async); + path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator(t.identifier(id.name), arrowFunction), + ]), + ); + } + }, + VariableDeclaration(path) { + if (path.parent.type === "Program") { + path.node.declarations.forEach((declaration) => { + if (declaration.id.type === "Identifier") { + const key = declaration.id.name; + const value = declaration.init + ? generate(declaration.init, { concise: false }).code + : "undefined"; + declarations[key] = value; + } + }); + } + }, + }); + + return declarations; +} + +/** + * Usado para extrair as funçoes no escopo principal e retornar um objeto deles. + * Declarações "function" são automaticamente convertidas para "arrow functions". Todas as declarações + * são usadas, mesmo as que nao tem "export", já que elas podem depender de outras declarações dentro do arquivo + * + * Exemplo, dado um arquivo com esse conteúdo: + * ` + * const Foo = () => {console.log('foo');}; + * function Bar(age) {console.log('bar', age);}; + * var My = () => { + * console.log('ola'); + * } + * + * const age = 2; + * const contract = "foobar"; + * ` + * Tem Esse retorno: + * { + * Foo: "() => { console.log('foo'); }", + * Bar: "function Bar(age) { console.log('bar', age); }", + * My: "() => { console.log('ola'); }", + * age: '2', + * contract: '"foobar"' + * } + * + * @param {*} code + * @returns + */ +function extractTopLevelDeclarations(code, modulePath) { + const transformedCode = transformFunctionDeclarations(code); + const declarations = transformedCode; + + // console.log("\n"); + // console.log("FILE:", modulePath); + const referenceKeys = Object.keys(declarations); + referenceKeys.forEach((declarationReference) => { + // console.log("AAAA", declarationReference); + referenceKeys.forEach((refKey) => { + // console.log("+ --->", refKey); + const currentValue = declarations[refKey]; + if (currentValue.includes(declarationReference)) { + const replaced = transformVariableInCode( + currentValue, + declarationReference, + ":::VAR_REF:::", + ); + + declarations[refKey] = replaced.replaceAll( + ":::VAR_REF:::", + `props.alem.modulesCode['${modulePath}'].${declarationReference}`, + ); + } + }); + }); + + return declarations; +} +// function extractTopLevelFunctions(code) { +// const ast = parser.parse(code, { +// sourceType: "module", +// plugins: ["jsx", "typescript"], // Habilita suporte para TypeScript e JSX +// }); + +// const functions = {}; + +// traverse(ast, { +// enter(path) { +// if ( +// path.node.type === "FunctionDeclaration" || +// (path.node.type === "VariableDeclaration" && +// path.node.declarations[0].init && +// path.node.declarations[0].init.type === "ArrowFunctionExpression") +// ) { +// const key = path.node.id +// ? path.node.id.name +// : path.node.declarations[0].id.name; +// const { code } = generate(path.node); +// functions[key] = code; +// } +// }, +// }); + +// return functions; +// } + +module.exports = extractTopLevelDeclarations; diff --git a/lib/parsers/transformVariableInCode.js b/lib/parsers/transformVariableInCode.js new file mode 100644 index 0000000..661b1f4 --- /dev/null +++ b/lib/parsers/transformVariableInCode.js @@ -0,0 +1,41 @@ +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const generate = require("@babel/generator").default; + +/** + * Transforma qualquer variável em outro valor "replacement" + * Exemplo: + * + * entrada: "const Bar = () => { console.log('oi', age); }" + * saída: const Bar = () => { console.log('oi', :::PROP_HERE:::); }; + * + * onde o :::PROP_HERE::: é o "replacement" + * + * @param {*} code + * @param {*} variableName + * @param {*} replacement + * @returns + */ +function transformVariableInCode(code, variableName, replacement) { + const ast = parser.parse(`const tempDecl = ${code}`, { + sourceType: "module", + plugins: ["jsx", "typescript"], // Suporte para TypeScript e JSX se necessário + }); + + traverse(ast, { + Identifier(path) { + if (path.node.name === variableName) { + path.node.name = replacement; + } + }, + }); + + const output = generate(ast, { concise: true, semi: false }); + // Regex para remover o último ponto e vírgula antes do fim da string ou de um fechamento de bloco + const finalOutput = output.code + .replace(/;(?=[^;]*$)/, "") + .replace("const tempDecl = ", ""); + return finalOutput; +} + +module.exports = transformVariableInCode; diff --git a/package.json b/package.json index 4de05db..a12e64b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "alem", "description": "Create web3 applications for NEAR BOS with a focus on performance and friendly development.", - "version": "1.0.0-beta.31", + "version": "1.0.0-beta.32", "main": "main.js", "types": "index.d.ts", "author": "Wenderson Pires - wendersonpires.near", From 96a3a9b38d16e88c28e504c1639a0e53ccd457f2 Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Thu, 25 Apr 2024 04:29:12 -0300 Subject: [PATCH 02/12] wip --- lib/actions/transformSchemaToWidget.js | 159 ++++++++++++++++----- lib/parsers/extractTopLevelDeclarations.js | 13 +- lib/parsers/transformVariableInCode.js | 2 +- 3 files changed, 133 insertions(+), 41 deletions(-) diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index c53796c..5df6816 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -9,6 +9,7 @@ const { getFilePathBasedOnParentAndChildFilePath, convertObjectToArray, getFilePathWithType, + removeDuplicatedValuesFromArray, } = require("../helpers"); const { process_file_content, removeImports } = require("../parse"); const { @@ -31,7 +32,11 @@ const analyzeFunctionSignature = require("../parsers/analyzeFunctionSignature"); const removeFunctionParams = require("../parsers/removeFunctionParams"); const transformAsyncAwait = require("../parsers/transformAsyncAwait"); const compilerOptions = require("./compilerOptions"); -const extractTopLevelDeclarations = require("../parsers/extractTopLevelDeclarations"); +const { + extractTopLevelDeclarations, + processDeclarations, +} = require("../parsers/extractTopLevelDeclarations"); +const transformVariableInCode = require("../parsers/transformVariableInCode"); let processError = null; @@ -336,6 +341,27 @@ const swapComponentsForStatelessFiles = (fileSchemas, fileSchema) => { // Adiciona na lista de items ja processados pastedFiles.push(importItemFilePath); + // INFO: Verifica se o caminho do arquivo a ser processado não é "null" + const importItemFileSource = fileSchema.componentImportItems[importItem]; // src/path/to/file.tsx | null + + // Le o nome do Widget dependente (usando o fileSchema dele) + let importItemWidget = fileSchemas.find( + (importFileSchema) => + importFileSchema.filePath === importItemFileSource, + ); + + // MODULOS - INICIO + // NEW - Armazena os módulos para serem inseridos apenas no final, assim garantindo + // que sua referencia ficará no topo do bundle do arquivo/code final + const updatedToBeInjectedModules = fileSchema.toBeInjectedModules || []; + const itemToBeInjectedModules = + importItemWidget.toBeInjectedModules || []; + fileSchema.toBeInjectedModules = [ + ...updatedToBeInjectedModules, + ...itemToBeInjectedModules, + ]; + // MODULOS - FIM + // Is import item comming from JSX | TSX file? const isImportItemCommingFromJsxFile = importItemFilePath.endsWith("tsx") || @@ -347,19 +373,32 @@ const swapComponentsForStatelessFiles = (fileSchemas, fileSchema) => { // Files without source are the ones that not live in the src directory - // INFO: Verifica se o caminho do arquivo a ser processado não é "null" - const importItemFileSource = - fileSchema.componentImportItems[importItem]; // src/path/to/file.tsx | null + // // INFO: Verifica se o caminho do arquivo a ser processado não é "null" + // const importItemFileSource = + // fileSchema.componentImportItems[importItem]; // src/path/to/file.tsx | null // Se existir o conteúdo localmente... segue... if (importItemFileSource) { // NOTE: Aqui que a magica acontece!!!! - // Le o nome do Widget dependente (usando o fileSchema dele) - let importItemWidget = fileSchemas.find( - (importFileSchema) => - importFileSchema.filePath === importItemFileSource, - ); + // // Le o nome do Widget dependente (usando o fileSchema dele) + // let importItemWidget = fileSchemas.find( + // (importFileSchema) => + // importFileSchema.filePath === importItemFileSource, + // ); + + // // MODULOS - INICIO + // // NEW - Armazena os módulos para serem inseridos apenas no final, assim garantindo + // // que sua referencia ficará no topo do bundle do arquivo/code final + // const updatedToBeInjectedModules = + // fileSchema.toBeInjectedModules || []; + // const itemToBeInjectedModules = + // importItemWidget.toBeInjectedModules || []; + // fileSchema.toBeInjectedModules = [ + // ...updatedToBeInjectedModules, + // ...itemToBeInjectedModules, + // ]; + // // MODULOS - FIM const importItemWidgetComponentName = importItemWidget.widgetName || @@ -458,6 +497,13 @@ const swapComponentsForStatelessFiles = (fileSchemas, fileSchema) => { } }); + // Remove duplicate modules ref + if (fileSchema.toBeInjectedModules) { + fileSchema.toBeInjectedModules = removeDuplicatedValuesFromArray( + fileSchema.toBeInjectedModules, + ); + } + return fileSchema; }; @@ -625,7 +671,6 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { const foo = (fileSchemas, fileSchema) => { let fileBundle = fileSchema.finalFileBundle; - let wasChanged = false; // Se nao tiver nada a ser injetado, somente retorna o file Schema sem alterações if (!fileSchema.toBeInjectedModules) { @@ -641,29 +686,22 @@ const foo = (fileSchemas, fileSchema) => { // Insere apenas as referencias das declarações do módulo const moduleValuesKeys = Object.keys(fileItemSchema.moduleProps.values); + let injections = ""; moduleValuesKeys.forEach((propKey) => { - // Deve trocar o conteúdo somente se for um conteúdo sendo importado dentro do arquivo principal - const importReference = fileSchema.componentImportItems[propKey]; - // Existe o import no arquivo principal && o diretório do import pertence ao "fileItemSchema.filePath"? - const isImported = - (importReference && importReference === fileItemSchema.filePath) || - false; - - if (isImported) { - wasChanged = true; - - fileBundle = ` + injections = ` const ${propKey} = props.alem.modulesCode["${fileItemSchema.moduleProps.name}"].${propKey}; - ${fileBundle} + ${injections} `; - } }); - if (wasChanged) { - fileSchema.finalFileBundle = fileBundle; - fileSchema.jsContent = fileBundle; - } + fileBundle = ` + ${injections} + ${fileBundle} + `; + + fileSchema.finalFileBundle = fileBundle; + fileSchema.jsContent = fileBundle; }); return fileSchema; @@ -833,16 +871,6 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { // } }); - // FOO - // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" - fileSchemas.forEach((fileSchema, fileSchemaIndex) => { - fileSchemas[fileSchemaIndex] = foo(fileSchemas, fileSchema); - // if (fileSchema.filePath.includes("src/Main.tsx")) { - // console.log("========== AAAA"); - // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); - // } - }); - fileSchemas.forEach((fileSchema, fileSchemaIndex) => { // if (fileSchema.filePath.includes("src/Main.tsx")) { // console.log("========== BBBB"); @@ -898,7 +926,64 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { fileSchemas[fileSchemaIndex] = fileSchema; }); + // FOO + // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" + fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + fileSchemas[fileSchemaIndex] = foo(fileSchemas, fileSchema); + // if (fileSchema.filePath.includes("src/Main.tsx")) { + // console.log("========== AAAA"); + // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); + // } + }); + + // Ajusta módulos: Faz o parse de caminho das dependencias dos módulos que importam outros módulos + fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + fileSchemas[fileSchemaIndex] = parseModules(fileSchemas, fileSchema); + }); + return { fileSchemas, processError }; }; +/** + * Faz a troca de referencia das declarações para apontarem para o props.alem.modulesCode dos outros módulos + * @param {*} fileSchemas + * @param {*} fileSchema + */ +const parseModules = (fileSchemas, fileSchema) => { + // Se for um módulo && esse módulo tiver importando outros módulos... + if (fileSchema.isModule && fileSchema.toBeInjectedModules) { + fileSchema.toBeInjectedModules.forEach((modulePath) => { + const moduleSchema = fileSchemas.find((m) => m.filePath === modulePath); + + const moduleSchemaItems = Object.keys(moduleSchema.moduleProps.values); + const fileSchemaModuleItems = Object.keys(fileSchema.moduleProps.values); + moduleSchemaItems.forEach((moduleItemKey) => { + // 1 - Neste ponto temos uma referencia/chave do módulo importado e do modulo atual + // 2 - Agora faz um loop para checar e tratar referencia em cada item do modulo atual (fileSchema) + fileSchemaModuleItems.forEach((currentModuleKey) => { + const currentModuleItemValue = + fileSchema.moduleProps.values[currentModuleKey]; + // Se o item atual do modulo atual possuir qualquer referencia de um item do modulo sendo importando + // aplica a mudanca de referencia + if (currentModuleItemValue.includes(moduleItemKey)) { + const replaced = transformVariableInCode( + currentModuleItemValue, + moduleItemKey, + ":::VAR_REF:::", + ); + // Atualiza as referencias no fileSchema (modulo) atual + fileSchema.moduleProps.values[currentModuleKey] = + replaced.replaceAll( + ":::VAR_REF:::", + `props.alem.modulesCode['${moduleSchema.moduleProps.name}'].${moduleItemKey}`, + ); + } + }); + }); + }); + } + + return fileSchema; +}; + module.exports = transformSchemaToWidget; diff --git a/lib/parsers/extractTopLevelDeclarations.js b/lib/parsers/extractTopLevelDeclarations.js index 5afbfd6..9798018 100644 --- a/lib/parsers/extractTopLevelDeclarations.js +++ b/lib/parsers/extractTopLevelDeclarations.js @@ -72,8 +72,14 @@ function transformFunctionDeclarations(code) { */ function extractTopLevelDeclarations(code, modulePath) { const transformedCode = transformFunctionDeclarations(code); - const declarations = transformedCode; + const declarations = processDeclarations(transformedCode, modulePath); + return declarations; +} + +// Verifica se o arquivo do módulo está usando recursos presentes em sí dentro de outras declarações, +// se tiver, muda elas para que apontem para o endereço usando o props.alem.modulesCode de si proprio +const processDeclarations = (declarations, modulePath) => { // console.log("\n"); // console.log("FILE:", modulePath); const referenceKeys = Object.keys(declarations); @@ -98,7 +104,8 @@ function extractTopLevelDeclarations(code, modulePath) { }); return declarations; -} +}; + // function extractTopLevelFunctions(code) { // const ast = parser.parse(code, { // sourceType: "module", @@ -127,4 +134,4 @@ function extractTopLevelDeclarations(code, modulePath) { // return functions; // } -module.exports = extractTopLevelDeclarations; +module.exports = { extractTopLevelDeclarations, processDeclarations }; diff --git a/lib/parsers/transformVariableInCode.js b/lib/parsers/transformVariableInCode.js index 661b1f4..457baa0 100644 --- a/lib/parsers/transformVariableInCode.js +++ b/lib/parsers/transformVariableInCode.js @@ -30,7 +30,7 @@ function transformVariableInCode(code, variableName, replacement) { }, }); - const output = generate(ast, { concise: true, semi: false }); + const output = generate(ast, { concise: false }); // Regex para remover o último ponto e vírgula antes do fim da string ou de um fechamento de bloco const finalOutput = output.code .replace(/;(?=[^;]*$)/, "") From 583708b552c9f67e6c6458cde0aec2a2d53d864a Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Thu, 25 Apr 2024 20:22:55 -0300 Subject: [PATCH 03/12] still testing and improving --- lib/actions/loadFilesContent.js | 22 ++- lib/actions/transformSchemaToWidget.js | 184 ++++++++++-------- lib/parsers/extractTopLevelDeclarations.js | 44 ++++- .../getFunctionExportDeclarationKeys.js | 17 ++ ...ansformAndGetFunctionExportDeclarations.js | 92 +++++++++ lib/parsers/wrapCodeInGetFunction.js | 152 +++++++++++++++ 6 files changed, 416 insertions(+), 95 deletions(-) create mode 100644 lib/parsers/getFunctionExportDeclarationKeys.js create mode 100644 lib/parsers/transformAndGetFunctionExportDeclarations.js create mode 100644 lib/parsers/wrapCodeInGetFunction.js diff --git a/lib/actions/loadFilesContent.js b/lib/actions/loadFilesContent.js index 4473624..799ee01 100644 --- a/lib/actions/loadFilesContent.js +++ b/lib/actions/loadFilesContent.js @@ -41,17 +41,21 @@ const loadComponentCodesObjectByFileSchemas = ( // Prepare modules if (fileSchema.isModule) { - let modulesValues = "{"; - const valuesEntries = Object.entries(fileSchema.moduleProps.values); - valuesEntries.forEach((entrie) => { - modulesValues += ` - ${entrie[0]}: ${scapeBacktick(entrie[1])}, - `; - }); - modulesValues += "}"; + // let modulesValues = "{"; + // const valuesEntries = Object.entries(fileSchema.moduleProps.values); + // valuesEntries.forEach((entrie) => { + // modulesValues += ` + // ${entrie[0]}: ${scapeBacktick(entrie[1])}, + // `; + // }); + // modulesValues += "}"; + + // modulesCodes += ` + // "${fileSchema.moduleProps.name}": ${parseAlemFeatures(modulesValues)}, + // `; modulesCodes += ` - "${fileSchema.moduleProps.name}": ${parseAlemFeatures(modulesValues)}, + "${fileSchema.moduleProps.name}": ${parseAlemFeatures(scapeBacktick(fileSchema.moduleProps.module))}, `; } diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index 5df6816..cee2e28 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -37,6 +37,8 @@ const { processDeclarations, } = require("../parsers/extractTopLevelDeclarations"); const transformVariableInCode = require("../parsers/transformVariableInCode"); +const wrapCodeInGetFunction = require("../parsers/wrapCodeInGetFunction"); +const getFunctionExportDeclarationKeys = require("../parsers/getFunctionExportDeclarationKeys"); let processError = null; @@ -131,10 +133,13 @@ const processSchema = (fileSchema) => { if (isModule) { fileSchema.moduleProps = { name: fileSchema.filePath, - values: extractTopLevelDeclarations( - fileSchema.pureJsContent, - fileSchema.filePath, - ), + // INFO: isso será feito no final para usar o final bundle + module: wrapCodeInGetFunction(fileSchema.content, fileSchema.filePath), + values: getFunctionExportDeclarationKeys(fileSchema.content), + // values: extractTopLevelDeclarations( + // fileSchema.pureJsContent, + // fileSchema.filePath, + // ), }; } @@ -685,12 +690,15 @@ const foo = (fileSchemas, fileSchema) => { ); // Insere apenas as referencias das declarações do módulo - const moduleValuesKeys = Object.keys(fileItemSchema.moduleProps.values); + const moduleValuesKeys = fileItemSchema.moduleProps.values; + // console.log("VALUES:", moduleValuesKeys, fileSchema.filePath); + // if (!moduleValuesKeys) return; + let injections = ""; moduleValuesKeys.forEach((propKey) => { injections = ` - const ${propKey} = props.alem.modulesCode["${fileItemSchema.moduleProps.name}"].${propKey}; + const ${propKey} = props.alem.modulesCode["${fileItemSchema.moduleProps.name}"]().${propKey}; ${injections} `; }); @@ -704,18 +712,24 @@ const foo = (fileSchemas, fileSchema) => { fileSchema.jsContent = fileBundle; }); + // Se o arquivo for um módulo... + if (fileSchema.isModule) { + console.log("FOOO", fileSchema.filePath); + // Atualiza o modulo deste arquivo para usar o finalBundle com as importacoes injetadas + fileSchema.moduleProps = { + ...fileSchema.moduleProps, + module: wrapCodeInGetFunction( + fileSchema.finalFileBundle, + fileSchema.filePath, + ), + // module: "function Get() {}", + }; + } + return fileSchema; }; -/** - * Coloca o conteúdo dos arquivos nao .ts e .jsx de dependencia dentro do bundle de cada arquivo do schema global - * Esse é um processo que ocorre para todos os arquivos, mas somente copia e cola o conteudo para arquivos nao JSX. - * - * Arquivos reconhecidos como JSX (Widgets) serão tratados de outra forma. Ver "swapComponentsForWidgets" - * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}[]} fileSchemas - * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema - */ -const injectFilesDependencies = (fileSchemas, fileSchema) => { +const injectDependencies = (fileSchemas, fileSchema, skipModules = true) => { let fileBundle = fileSchema.finalFileBundle; // Se nao tiver nada a ser injetado, somente retorna o file Schema sem alterações @@ -737,35 +751,6 @@ const injectFilesDependencies = (fileSchemas, fileSchema) => { (importFileSchema) => importFileSchema.filePath === fileItemPath, ); - // if (fileItemSchema.isModule) { - // console.log(fileSchema.filePath, "--->", fileItemSchema.filePath); - - // const moduleValuesKeys = Object.keys(fileItemSchema.moduleProps.values); - // moduleValuesKeys.forEach((propKey) => { - // // Deve trocar o conteúdo somente se for um conteúdo sendo importado dentro do arquivo principal - // const importReference = fileSchema.componentImportItems[propKey]; - // // Existe o import no arquivo principal && o diretório do import pertence ao "fileItemSchema.filePath"? - // const isImported = - // (importReference && importReference === fileItemSchema.filePath) || - // false; - - // // console.log("IS IMPOTED:", isImported, propKey); - // if (isImported) { - // // Insere apenas a referencia da dependencia - // fileBundle = ` - // const ${propKey} = props.alem.modulesCode.["${fileItemSchema.moduleProps.name}"].${propKey}; - // ${fileBundle} - // `; - // } - // }); - // } else { - // // Injeta o conteúdo literalmente - // fileBundle = ` - // ${fileItemSchema.jsContent} - // ${fileBundle} - // `; - // } - if (!fileItemSchema.isModule) { // Injeta o conteúdo literalmente fileBundle = ` @@ -783,6 +768,28 @@ const injectFilesDependencies = (fileSchemas, fileSchema) => { return fileSchema; }; +/** + * Coloca o conteúdo dos arquivos nao .ts e .jsx de dependencia dentro do bundle de cada arquivo do schema global + * Esse é um processo que ocorre para todos os arquivos, mas somente copia e cola o conteudo para arquivos nao JSX. + * + * Arquivos reconhecidos como JSX (Widgets) serão tratados de outra forma. Ver "swapComponentsForWidgets" + * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}[]} fileSchemas + * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema + */ +const injectFilesDependencies = (fileSchemas, fileSchema) => { + fileSchema = injectDependencies(fileSchemas, fileSchema, true); + + return fileSchema; +}; + +const injectModulesDependencies = (fileSchemas, fileSchema) => { + // if (fileSchema.isModule) { + // fileSchema = injectDependencies(fileSchemas, fileSchema, false); + // } + + return fileSchema; +}; + /** * Caso tenha dependencias do Alem (inportable items), prepara eles para serem injetados. * @@ -909,6 +916,9 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { // fileSchemas[fileSchemaIndex] = foo(fileSchemas, fileSchema); // }); + // Injeta as dependencias de arquivos (excluindo módulos). Tem que ser separado mesmo para evitar + // conflitos na carga de modulos, se liberar módulos, o corpo deles vao ser injetados como arquivos + // stateless causando assim duplicidade já que eles sao inseridos como módulos também. // Copia e cola o conteúdo de arquivos não .tsx | .jsx para dentro dos arquivos que dependem deles fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = injectFilesDependencies( @@ -926,6 +936,14 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { fileSchemas[fileSchemaIndex] = fileSchema; }); + // Injeta as dependencias dos módulos + fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + fileSchemas[fileSchemaIndex] = injectModulesDependencies( + fileSchemas, + fileSchema, + ); + }); + // FOO // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" fileSchemas.forEach((fileSchema, fileSchemaIndex) => { @@ -937,9 +955,9 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { }); // Ajusta módulos: Faz o parse de caminho das dependencias dos módulos que importam outros módulos - fileSchemas.forEach((fileSchema, fileSchemaIndex) => { - fileSchemas[fileSchemaIndex] = parseModules(fileSchemas, fileSchema); - }); + // fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + // fileSchemas[fileSchemaIndex] = parseModules(fileSchemas, fileSchema); + // }); return { fileSchemas, processError }; }; @@ -949,41 +967,41 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { * @param {*} fileSchemas * @param {*} fileSchema */ -const parseModules = (fileSchemas, fileSchema) => { - // Se for um módulo && esse módulo tiver importando outros módulos... - if (fileSchema.isModule && fileSchema.toBeInjectedModules) { - fileSchema.toBeInjectedModules.forEach((modulePath) => { - const moduleSchema = fileSchemas.find((m) => m.filePath === modulePath); - - const moduleSchemaItems = Object.keys(moduleSchema.moduleProps.values); - const fileSchemaModuleItems = Object.keys(fileSchema.moduleProps.values); - moduleSchemaItems.forEach((moduleItemKey) => { - // 1 - Neste ponto temos uma referencia/chave do módulo importado e do modulo atual - // 2 - Agora faz um loop para checar e tratar referencia em cada item do modulo atual (fileSchema) - fileSchemaModuleItems.forEach((currentModuleKey) => { - const currentModuleItemValue = - fileSchema.moduleProps.values[currentModuleKey]; - // Se o item atual do modulo atual possuir qualquer referencia de um item do modulo sendo importando - // aplica a mudanca de referencia - if (currentModuleItemValue.includes(moduleItemKey)) { - const replaced = transformVariableInCode( - currentModuleItemValue, - moduleItemKey, - ":::VAR_REF:::", - ); - // Atualiza as referencias no fileSchema (modulo) atual - fileSchema.moduleProps.values[currentModuleKey] = - replaced.replaceAll( - ":::VAR_REF:::", - `props.alem.modulesCode['${moduleSchema.moduleProps.name}'].${moduleItemKey}`, - ); - } - }); - }); - }); - } - - return fileSchema; -}; +// const parseModules = (fileSchemas, fileSchema) => { +// // Se for um módulo && esse módulo tiver importando outros módulos... +// if (fileSchema.isModule && fileSchema.toBeInjectedModules) { +// fileSchema.toBeInjectedModules.forEach((modulePath) => { +// const moduleSchema = fileSchemas.find((m) => m.filePath === modulePath); + +// const moduleSchemaItems = Object.keys(moduleSchema.moduleProps.values); +// const fileSchemaModuleItems = Object.keys(fileSchema.moduleProps.values); +// moduleSchemaItems.forEach((moduleItemKey) => { +// // 1 - Neste ponto temos uma referencia/chave do módulo importado e do modulo atual +// // 2 - Agora faz um loop para checar e tratar referencia em cada item do modulo atual (fileSchema) +// fileSchemaModuleItems.forEach((currentModuleKey) => { +// const currentModuleItemValue = +// fileSchema.moduleProps.values[currentModuleKey]; +// // Se o item atual do modulo atual possuir qualquer referencia de um item do modulo sendo importando +// // aplica a mudanca de referencia +// if (currentModuleItemValue.includes(moduleItemKey)) { +// const replaced = transformVariableInCode( +// currentModuleItemValue, +// moduleItemKey, +// ":::VAR_REF:::", +// ); +// // Atualiza as referencias no fileSchema (modulo) atual +// fileSchema.moduleProps.values[currentModuleKey] = +// replaced.replaceAll( +// ":::VAR_REF:::", +// `props.alem.modulesCode['${moduleSchema.moduleProps.name}'].${moduleItemKey}`, +// ); +// } +// }); +// }); +// }); +// } + +// return fileSchema; +// }; module.exports = transformSchemaToWidget; diff --git a/lib/parsers/extractTopLevelDeclarations.js b/lib/parsers/extractTopLevelDeclarations.js index 9798018..50d78e3 100644 --- a/lib/parsers/extractTopLevelDeclarations.js +++ b/lib/parsers/extractTopLevelDeclarations.js @@ -4,6 +4,41 @@ const generate = require("@babel/generator").default; const t = require("@babel/types"); const transformVariableInCode = require("./transformVariableInCode"); +function getFunctionDeclarationsKeys(code) { + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"], + }); + + const declarationsKeys = []; + + traverse(ast, { + FunctionDeclaration(path) { + const { id, params, body, async } = path.node; + if (id) { + const arrowFunction = t.arrowFunctionExpression(params, body, async); + path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator(t.identifier(id.name), arrowFunction), + ]), + ); + } + }, + VariableDeclaration(path) { + if (path.parent.type === "Program") { + path.node.declarations.forEach((declaration) => { + if (declaration.id.type === "Identifier") { + const key = declaration.id.name; + declarationsKeys.push(key); + } + }); + } + }, + }); + + return declarationsKeys; +} + function transformFunctionDeclarations(code) { const ast = parser.parse(code, { sourceType: "module", @@ -71,10 +106,13 @@ function transformFunctionDeclarations(code) { * @returns */ function extractTopLevelDeclarations(code, modulePath) { - const transformedCode = transformFunctionDeclarations(code); - const declarations = processDeclarations(transformedCode, modulePath); + // const transformedCode = transformFunctionDeclarations(code); + // const declarations = processDeclarations(transformedCode, modulePath); + const declarationKeys = getFunctionDeclarationsKeys(code); - return declarations; + // Return keys only + // return Object.keys(declarations); + return declarationKeys; } // Verifica se o arquivo do módulo está usando recursos presentes em sí dentro de outras declarações, diff --git a/lib/parsers/getFunctionExportDeclarationKeys.js b/lib/parsers/getFunctionExportDeclarationKeys.js new file mode 100644 index 0000000..44b0f9b --- /dev/null +++ b/lib/parsers/getFunctionExportDeclarationKeys.js @@ -0,0 +1,17 @@ +const transformAndGetFunctionExportDeclarations = require("./transformAndGetFunctionExportDeclarations"); + +/** + * Retorna a chave/nome de declaração de todas as declarações sendo exportadas no arquivo. + * @param {*} code + * @returns + */ +const getFunctionExportDeclarationKeys = (code) => { + const declarations = transformAndGetFunctionExportDeclarations(code); + // console.log("RESULTADO:"); + // console.log(declarations); + // console.log("\n"); + + return Object.keys(declarations); +}; + +module.exports = getFunctionExportDeclarationKeys; diff --git a/lib/parsers/transformAndGetFunctionExportDeclarations.js b/lib/parsers/transformAndGetFunctionExportDeclarations.js new file mode 100644 index 0000000..e9b54c3 --- /dev/null +++ b/lib/parsers/transformAndGetFunctionExportDeclarations.js @@ -0,0 +1,92 @@ +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const t = require("@babel/types"); +const generate = require("@babel/generator").default; + +function convertExportDefault(code) { + // Regex para capturar `export default` seguido de qualquer palavra (identificador) + // e não captura casos já com chaves. + const regex = /export\s+default\s+([a-zA-Z_$][0-9a-zA-Z_$]*)(?!\s*\{)/g; + + // Substituir o código utilizando a regex + return code.replace(regex, "export { $1 }"); +} + +/** + * Usado para extrair as chaves das declaracoes sendo exportadas de dentro de um arquivo + * + * Exemplo, dado um arquivo com esse conteúdo: + * ` + * const Foo = () => {console.log('foo');}; + * function Bar(age) {console.log('bar', age);}; + * var My = () => { + * console.log('ola'); + * } + * + * export const age = 2; + * const contract = "foobar"; + * export const ba = "boo"; + * ` + * Tem Esse retorno: + * [age, ba] + * + * @param {*} code + * @returns + */ +function transformAndGetFunctionExportDeclarations(code) { + // code = code.replaceAll(/\bexport default { \b/g, "export"); + code = convertExportDefault(code); + // console.log(code); + + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"], + }); + + const declarations = {}; + + traverse(ast, { + ExportNamedDeclaration(path) { + if (path.node.declaration) { + // Para declarações diretamente na exportação + if (path.node.declaration.type === "VariableDeclaration") { + path.node.declaration.declarations.forEach((decl) => { + if (decl.id.type === "Identifier") { + const key = decl.id.name; + const value = decl.init + ? generate(decl.init, { concise: false }).code + : "undefined"; + declarations[key] = value; + } + }); + } else if (path.node.declaration.type === "FunctionDeclaration") { + const { id, params, body, async } = path.node.declaration; + if (id) { + const arrowFunction = t.arrowFunctionExpression( + params, + body, + async, + ); + path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator(t.identifier(id.name), arrowFunction), + ]), + ); + declarations[id.name] = generate(arrowFunction, { + concise: false, + }).code; + } + } + } else if (path.node.specifiers) { + // Para exportações de variáveis ou funções já declaradas + path.node.specifiers.forEach((specifier) => { + declarations[specifier.exported.name] = specifier.local.name; + }); + } + }, + }); + + return declarations; +} + +module.exports = transformAndGetFunctionExportDeclarations; diff --git a/lib/parsers/wrapCodeInGetFunction.js b/lib/parsers/wrapCodeInGetFunction.js new file mode 100644 index 0000000..887ea12 --- /dev/null +++ b/lib/parsers/wrapCodeInGetFunction.js @@ -0,0 +1,152 @@ +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const t = require("@babel/types"); +const generate = require("@babel/generator").default; +const babel = require("@babel/core"); +// const babelPreset = require("@babel/preset-env"); +const reactPreset = require("@babel/preset-react"); +const typescriptPreset = require("@babel/preset-typescript"); + +/** + * Converte todas as "function" em arrow functions + * @param {*} code + * @returns + */ +function convertFunctionsToArrow(code) { + // Parsear o código para criar a AST + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"], // Adiciona suporte para JSX e TypeScript, se necessário + }); + + // Transformar todas as funções declaradas e funções anônimas em expressões de arrow + traverse(ast, { + FunctionDeclaration(path) { + const { id, params, body, async } = path.node; + const arrowFunction = t.arrowFunctionExpression(params, body, async); + arrowFunction.returnType = path.node.returnType; // Preserva o tipo de retorno, se for TypeScript + path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator(id, arrowFunction), + ]), + ); + }, + FunctionExpression(path) { + const { params, body, async } = path.node; + const arrowFunction = t.arrowFunctionExpression(params, body, async); + path.replaceWith(arrowFunction); + }, + }); + + // Gerar o código JavaScript modificado a partir da AST atualizada + return generate(ast, { + /* Opções aqui, se necessário */ + }).code; +} + +/** + * Esta função vai cobrir o arquivo/code com uma função Get e todas as declarações sendo exportadas + * serão retornadas dentro dela. + * + * Exemplo, dado de entrada: + * + * const code = ` + * const abrolhos = "oi"; + * export const num = 42; + * export function compute() { return num * 2; } + * export default compute; + * `; + * + * Saída: + * + * function Get() { + * const abrolhos = "oi"; + * const num = 42; + * function compute() { + * return num * 2; + * } + * const default = compute; + * return { + * num: 42, + * default: compute + * }; + * } + * + * @param {*} code + * @returns + */ +function wrapCodeInGetFunction(code, filePath) { + // Remover importações e exports + code = code.replace(/import.*;|export\s+default|export\s+/g, ""); + + // Parsear o código para AST removendo TypeScript + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"], + }); + + const identifiers = new Set(); + + // Capturar identificadores de todas as declarações top-level + traverse(ast, { + VariableDeclaration(path) { + if (path.parent.type === "Program") { + path.node.declarations.forEach((decl) => { + if (decl.id.name) { + identifiers.add(decl.id.name); + } + }); + } + }, + FunctionDeclaration(path) { + if (path.parent.type === "Program" && path.node.id) { + identifiers.add(path.node.id.name); + } + }, + ClassDeclaration(path) { + if (path.parent.type === "Program" && path.node.id) { + identifiers.add(path.node.id.name); + } + }, + }); + + // Criar a função Get que encapsula o código e retorna um objeto com todas as declarações + const returnObject = t.objectExpression( + Array.from(identifiers).map((id) => + t.objectProperty(t.identifier(id), t.identifier(id)), + ), + ); + + const getFunction = t.arrowFunctionExpression( + [], + t.blockStatement([...ast.program.body, t.returnStatement(returnObject)]), + ); + + const newAst = t.program([ + t.variableDeclaration("const", [ + t.variableDeclarator(t.identifier("Get"), getFunction), + ]), + ]); + + // Transformar o novo AST em JavaScript puro, removendo TypeScript + const { code: transformedCode } = babel.transformFromAstSync(newAst, null, { + presets: [ + // INFO: Isso estava gerando os helpers no topo do arquivo + // babelPreset, + reactPreset, + typescriptPreset, + ], + code: true, + configFile: false, // Ignora qualquer configuração do Babel externa + filename: filePath, + }); + + // Troca o "const Get = " por "", isso porque o módulo é a função diretamente + // e remove o último ";" encontrado porque a função vai ser colocar em uma lista de objetos, ou seja, + // vai ter um "," separando cada objeto. + return convertFunctionsToArrow(transformedCode) + .replace("const Get = ", "") + .replace(/;(?=[^;]*$)/, ""); +} + +module.exports = wrapCodeInGetFunction; From d14c1c13397165da910a0986ea492fa7343c5c04 Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Fri, 26 Apr 2024 02:17:03 -0300 Subject: [PATCH 04/12] wip, almost finishing --- lib/actions/transformSchemaToWidget.js | 116 ++++-------------- lib/alem-vm/components/AppIndexer.jsx | 3 +- lib/parsers/convertFunctionsToArrow.js | 43 +++++++ lib/parsers/extractTopLevelDeclarations.js | 3 +- .../getFunctionExportDeclarationKeys.js | 7 +- ...ansformAndGetFunctionExportDeclarations.js | 107 +++++++++------- lib/parsers/wrapCodeInGetFunction.js | 44 +------ 7 files changed, 144 insertions(+), 179 deletions(-) create mode 100644 lib/parsers/convertFunctionsToArrow.js diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index cee2e28..ee7939c 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -133,9 +133,12 @@ const processSchema = (fileSchema) => { if (isModule) { fileSchema.moduleProps = { name: fileSchema.filePath, - // INFO: isso será feito no final para usar o final bundle + // INFO: isso será trocado no final para usar o final bundle module: wrapCodeInGetFunction(fileSchema.content, fileSchema.filePath), - values: getFunctionExportDeclarationKeys(fileSchema.content), + values: getFunctionExportDeclarationKeys( + fileSchema.content, + fileSchema.filePath, + ), // values: extractTopLevelDeclarations( // fileSchema.pureJsContent, // fileSchema.filePath, @@ -674,7 +677,14 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { return fileSchema; }; -const foo = (fileSchemas, fileSchema) => { +/** + * Injeta a referencia dos módulos no topo de cada arquivo final que depende dos módulos + * ou que tem dependentes que dependem dos módulos + * @param {*} fileSchemas + * @param {*} fileSchema + * @returns + */ +const injectModules = (fileSchemas, fileSchema) => { let fileBundle = fileSchema.finalFileBundle; // Se nao tiver nada a ser injetado, somente retorna o file Schema sem alterações @@ -696,9 +706,10 @@ const foo = (fileSchemas, fileSchema) => { let injections = ""; + // m = modulesCode moduleValuesKeys.forEach((propKey) => { injections = ` - const ${propKey} = props.alem.modulesCode["${fileItemSchema.moduleProps.name}"]().${propKey}; + const ${propKey} = props.alem.m["${fileItemSchema.moduleProps.name}"]().${propKey}; ${injections} `; }); @@ -714,7 +725,7 @@ const foo = (fileSchemas, fileSchema) => { // Se o arquivo for um módulo... if (fileSchema.isModule) { - console.log("FOOO", fileSchema.filePath); + // console.log("FOOO", fileSchema.filePath); // Atualiza o modulo deste arquivo para usar o finalBundle com as importacoes injetadas fileSchema.moduleProps = { ...fileSchema.moduleProps, @@ -729,7 +740,15 @@ const foo = (fileSchemas, fileSchema) => { return fileSchema; }; -const injectDependencies = (fileSchemas, fileSchema, skipModules = true) => { +/** + * Coloca o conteúdo dos arquivos nao .ts e .jsx de dependencia dentro do bundle de cada arquivo do schema global + * Esse é um processo que ocorre para todos os arquivos, mas somente copia e cola o conteudo para arquivos nao JSX. + * + * Arquivos reconhecidos como JSX (Widgets) serão tratados de outra forma. Ver "swapComponentsForWidgets" + * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}[]} fileSchemas + * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema + */ +const injectFilesDependencies = (fileSchemas, fileSchema) => { let fileBundle = fileSchema.finalFileBundle; // Se nao tiver nada a ser injetado, somente retorna o file Schema sem alterações @@ -768,28 +787,6 @@ const injectDependencies = (fileSchemas, fileSchema, skipModules = true) => { return fileSchema; }; -/** - * Coloca o conteúdo dos arquivos nao .ts e .jsx de dependencia dentro do bundle de cada arquivo do schema global - * Esse é um processo que ocorre para todos os arquivos, mas somente copia e cola o conteudo para arquivos nao JSX. - * - * Arquivos reconhecidos como JSX (Widgets) serão tratados de outra forma. Ver "swapComponentsForWidgets" - * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}[]} fileSchemas - * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema - */ -const injectFilesDependencies = (fileSchemas, fileSchema) => { - fileSchema = injectDependencies(fileSchemas, fileSchema, true); - - return fileSchema; -}; - -const injectModulesDependencies = (fileSchemas, fileSchema) => { - // if (fileSchema.isModule) { - // fileSchema = injectDependencies(fileSchemas, fileSchema, false); - // } - - return fileSchema; -}; - /** * Caso tenha dependencias do Alem (inportable items), prepara eles para serem injetados. * @@ -936,72 +933,13 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { fileSchemas[fileSchemaIndex] = fileSchema; }); - // Injeta as dependencias dos módulos - fileSchemas.forEach((fileSchema, fileSchemaIndex) => { - fileSchemas[fileSchemaIndex] = injectModulesDependencies( - fileSchemas, - fileSchema, - ); - }); - - // FOO // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" + // Injeta os módulos caso sejam requeridos fileSchemas.forEach((fileSchema, fileSchemaIndex) => { - fileSchemas[fileSchemaIndex] = foo(fileSchemas, fileSchema); - // if (fileSchema.filePath.includes("src/Main.tsx")) { - // console.log("========== AAAA"); - // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); - // } + fileSchemas[fileSchemaIndex] = injectModules(fileSchemas, fileSchema); }); - // Ajusta módulos: Faz o parse de caminho das dependencias dos módulos que importam outros módulos - // fileSchemas.forEach((fileSchema, fileSchemaIndex) => { - // fileSchemas[fileSchemaIndex] = parseModules(fileSchemas, fileSchema); - // }); - return { fileSchemas, processError }; }; -/** - * Faz a troca de referencia das declarações para apontarem para o props.alem.modulesCode dos outros módulos - * @param {*} fileSchemas - * @param {*} fileSchema - */ -// const parseModules = (fileSchemas, fileSchema) => { -// // Se for um módulo && esse módulo tiver importando outros módulos... -// if (fileSchema.isModule && fileSchema.toBeInjectedModules) { -// fileSchema.toBeInjectedModules.forEach((modulePath) => { -// const moduleSchema = fileSchemas.find((m) => m.filePath === modulePath); - -// const moduleSchemaItems = Object.keys(moduleSchema.moduleProps.values); -// const fileSchemaModuleItems = Object.keys(fileSchema.moduleProps.values); -// moduleSchemaItems.forEach((moduleItemKey) => { -// // 1 - Neste ponto temos uma referencia/chave do módulo importado e do modulo atual -// // 2 - Agora faz um loop para checar e tratar referencia em cada item do modulo atual (fileSchema) -// fileSchemaModuleItems.forEach((currentModuleKey) => { -// const currentModuleItemValue = -// fileSchema.moduleProps.values[currentModuleKey]; -// // Se o item atual do modulo atual possuir qualquer referencia de um item do modulo sendo importando -// // aplica a mudanca de referencia -// if (currentModuleItemValue.includes(moduleItemKey)) { -// const replaced = transformVariableInCode( -// currentModuleItemValue, -// moduleItemKey, -// ":::VAR_REF:::", -// ); -// // Atualiza as referencias no fileSchema (modulo) atual -// fileSchema.moduleProps.values[currentModuleKey] = -// replaced.replaceAll( -// ":::VAR_REF:::", -// `props.alem.modulesCode['${moduleSchema.moduleProps.name}'].${moduleItemKey}`, -// ); -// } -// }); -// }); -// }); -// } - -// return fileSchema; -// }; - module.exports = transformSchemaToWidget; diff --git a/lib/alem-vm/components/AppIndexer.jsx b/lib/alem-vm/components/AppIndexer.jsx index 426e4a7..347ed23 100644 --- a/lib/alem-vm/components/AppIndexer.jsx +++ b/lib/alem-vm/components/AppIndexer.jsx @@ -15,7 +15,8 @@ const AlemApp = useMemo(() => { // ==================================== Modules Code ==================================== alem: { ...props.alem, - modulesCode: { + // m = modulesCode, está sendo usado "m" para reduzir o bundle final + m: { MODULES_CODE: {}, }, } diff --git a/lib/parsers/convertFunctionsToArrow.js b/lib/parsers/convertFunctionsToArrow.js new file mode 100644 index 0000000..e0b1d81 --- /dev/null +++ b/lib/parsers/convertFunctionsToArrow.js @@ -0,0 +1,43 @@ +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const t = require("@babel/types"); +const generate = require("@babel/generator").default; + +/** + * Converte todas as "function" em arrow functions + * @param {*} code + * @returns + */ +function convertFunctionsToArrow(code) { + // Parsear o código para criar a AST + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"], // Adiciona suporte para JSX e TypeScript, se necessário + }); + + // Transformar todas as funções declaradas e funções anônimas em expressões de arrow + traverse(ast, { + FunctionDeclaration(path) { + const { id, params, body, async } = path.node; + const arrowFunction = t.arrowFunctionExpression(params, body, async); + arrowFunction.returnType = path.node.returnType; // Preserva o tipo de retorno, se for TypeScript + path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator(id, arrowFunction), + ]), + ); + }, + FunctionExpression(path) { + const { params, body, async } = path.node; + const arrowFunction = t.arrowFunctionExpression(params, body, async); + path.replaceWith(arrowFunction); + }, + }); + + // Gerar o código JavaScript modificado a partir da AST atualizada + return generate(ast, { + /* Opções aqui, se necessário */ + }).code; +} + +module.exports = convertFunctionsToArrow; diff --git a/lib/parsers/extractTopLevelDeclarations.js b/lib/parsers/extractTopLevelDeclarations.js index 50d78e3..7a6f3a7 100644 --- a/lib/parsers/extractTopLevelDeclarations.js +++ b/lib/parsers/extractTopLevelDeclarations.js @@ -133,9 +133,10 @@ const processDeclarations = (declarations, modulePath) => { ":::VAR_REF:::", ); + // m = modulesCode declarations[refKey] = replaced.replaceAll( ":::VAR_REF:::", - `props.alem.modulesCode['${modulePath}'].${declarationReference}`, + `props.alem.m['${modulePath}'].${declarationReference}`, ); } }); diff --git a/lib/parsers/getFunctionExportDeclarationKeys.js b/lib/parsers/getFunctionExportDeclarationKeys.js index 44b0f9b..59bdf0e 100644 --- a/lib/parsers/getFunctionExportDeclarationKeys.js +++ b/lib/parsers/getFunctionExportDeclarationKeys.js @@ -5,8 +5,11 @@ const transformAndGetFunctionExportDeclarations = require("./transformAndGetFunc * @param {*} code * @returns */ -const getFunctionExportDeclarationKeys = (code) => { - const declarations = transformAndGetFunctionExportDeclarations(code); +const getFunctionExportDeclarationKeys = (code, filePath) => { + const declarations = transformAndGetFunctionExportDeclarations( + code, + filePath, + ); // console.log("RESULTADO:"); // console.log(declarations); // console.log("\n"); diff --git a/lib/parsers/transformAndGetFunctionExportDeclarations.js b/lib/parsers/transformAndGetFunctionExportDeclarations.js index e9b54c3..581d863 100644 --- a/lib/parsers/transformAndGetFunctionExportDeclarations.js +++ b/lib/parsers/transformAndGetFunctionExportDeclarations.js @@ -1,6 +1,7 @@ const parser = require("@babel/parser"); const traverse = require("@babel/traverse").default; const t = require("@babel/types"); +const convertFunctionsToArrow = require("./convertFunctionsToArrow"); const generate = require("@babel/generator").default; function convertExportDefault(code) { @@ -33,60 +34,74 @@ function convertExportDefault(code) { * @param {*} code * @returns */ -function transformAndGetFunctionExportDeclarations(code) { - // code = code.replaceAll(/\bexport default { \b/g, "export"); +function transformAndGetFunctionExportDeclarations(code, filePath) { + // Transforma "export default function" para "function" + code = code.replaceAll(/\bexport default function\b/g, "function"); + + // Converte functions para arrow functions + try { + code = convertFunctionsToArrow(code); + } catch (error) { + console.log(code); + throw new Error(`File: ${filePath}: ${error}`); + } + code = convertExportDefault(code); - // console.log(code); - const ast = parser.parse(code, { - sourceType: "module", - plugins: ["jsx", "typescript"], - }); + try { + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"], + }); - const declarations = {}; + const declarations = {}; - traverse(ast, { - ExportNamedDeclaration(path) { - if (path.node.declaration) { - // Para declarações diretamente na exportação - if (path.node.declaration.type === "VariableDeclaration") { - path.node.declaration.declarations.forEach((decl) => { - if (decl.id.type === "Identifier") { - const key = decl.id.name; - const value = decl.init - ? generate(decl.init, { concise: false }).code - : "undefined"; - declarations[key] = value; + traverse(ast, { + ExportNamedDeclaration(path) { + if (path.node.declaration) { + // Para declarações diretamente na exportação + if (path.node.declaration.type === "VariableDeclaration") { + path.node.declaration.declarations.forEach((decl) => { + if (decl.id.type === "Identifier") { + const key = decl.id.name; + const value = decl.init + ? generate(decl.init, { concise: false }).code + : "undefined"; + declarations[key] = value; + } + }); + } else if (path.node.declaration.type === "FunctionDeclaration") { + const { id, params, body, async } = path.node.declaration; + if (id) { + const arrowFunction = t.arrowFunctionExpression( + params, + body, + async, + ); + path.replaceWith( + t.variableDeclaration("const", [ + t.variableDeclarator(t.identifier(id.name), arrowFunction), + ]), + ); + declarations[id.name] = generate(arrowFunction, { + concise: false, + }).code; } - }); - } else if (path.node.declaration.type === "FunctionDeclaration") { - const { id, params, body, async } = path.node.declaration; - if (id) { - const arrowFunction = t.arrowFunctionExpression( - params, - body, - async, - ); - path.replaceWith( - t.variableDeclaration("const", [ - t.variableDeclarator(t.identifier(id.name), arrowFunction), - ]), - ); - declarations[id.name] = generate(arrowFunction, { - concise: false, - }).code; } + } else if (path.node.specifiers) { + // Para exportações de variáveis ou funções já declaradas + path.node.specifiers.forEach((specifier) => { + declarations[specifier.exported.name] = specifier.local.name; + }); } - } else if (path.node.specifiers) { - // Para exportações de variáveis ou funções já declaradas - path.node.specifiers.forEach((specifier) => { - declarations[specifier.exported.name] = specifier.local.name; - }); - } - }, - }); + }, + }); - return declarations; + return declarations; + } catch (error) { + console.log(code); + throw new Error(`File: ${filePath}: ${error}`); + } } module.exports = transformAndGetFunctionExportDeclarations; diff --git a/lib/parsers/wrapCodeInGetFunction.js b/lib/parsers/wrapCodeInGetFunction.js index 887ea12..44f9302 100644 --- a/lib/parsers/wrapCodeInGetFunction.js +++ b/lib/parsers/wrapCodeInGetFunction.js @@ -1,48 +1,11 @@ const parser = require("@babel/parser"); const traverse = require("@babel/traverse").default; const t = require("@babel/types"); -const generate = require("@babel/generator").default; const babel = require("@babel/core"); // const babelPreset = require("@babel/preset-env"); -const reactPreset = require("@babel/preset-react"); +// const reactPreset = require("@babel/preset-react"); const typescriptPreset = require("@babel/preset-typescript"); - -/** - * Converte todas as "function" em arrow functions - * @param {*} code - * @returns - */ -function convertFunctionsToArrow(code) { - // Parsear o código para criar a AST - const ast = parser.parse(code, { - sourceType: "module", - plugins: ["jsx", "typescript"], // Adiciona suporte para JSX e TypeScript, se necessário - }); - - // Transformar todas as funções declaradas e funções anônimas em expressões de arrow - traverse(ast, { - FunctionDeclaration(path) { - const { id, params, body, async } = path.node; - const arrowFunction = t.arrowFunctionExpression(params, body, async); - arrowFunction.returnType = path.node.returnType; // Preserva o tipo de retorno, se for TypeScript - path.replaceWith( - t.variableDeclaration("const", [ - t.variableDeclarator(id, arrowFunction), - ]), - ); - }, - FunctionExpression(path) { - const { params, body, async } = path.node; - const arrowFunction = t.arrowFunctionExpression(params, body, async); - path.replaceWith(arrowFunction); - }, - }); - - // Gerar o código JavaScript modificado a partir da AST atualizada - return generate(ast, { - /* Opções aqui, se necessário */ - }).code; -} +const convertFunctionsToArrow = require("./convertFunctionsToArrow"); /** * Esta função vai cobrir o arquivo/code com uma função Get e todas as declarações sendo exportadas @@ -133,7 +96,8 @@ function wrapCodeInGetFunction(code, filePath) { presets: [ // INFO: Isso estava gerando os helpers no topo do arquivo // babelPreset, - reactPreset, + // INFO: Esse preset estava transformando os arquivos + // reactPreset, typescriptPreset, ], code: true, From 160bc8cbc365db0b8d29f878be9deec0111a9a19 Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Fri, 26 Apr 2024 02:31:44 -0300 Subject: [PATCH 05/12] fixed layer2 widget, modules name are set using create_new_name to reduce the final bundle size --- lib/actions/transformSchemaToWidget.js | 5 +++-- lib/alem-vm/components/AppIndexer.jsx | 2 +- lib/utils.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index ee7939c..0bce2fd 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -17,7 +17,7 @@ const { MORE_THAN_ONE_SPACE, LINE_BREAKS, } = require("../regexp"); -const { log } = require("../utils"); +const { log, create_new_name } = require("../utils"); const PROHIBITED_METHODS = require("../config/prohibitedMethods"); const hasWidgetPropsCheck = require("./hasWidgetPropsCheck"); const importableFiles = require("../config/importableFiles"); @@ -132,7 +132,8 @@ const processSchema = (fileSchema) => { fileSchema.isModule = isModule; if (isModule) { fileSchema.moduleProps = { - name: fileSchema.filePath, + // name: fileSchema.filePath, + name: create_new_name(true), // Usando formato a_ para reduzir o bundle // INFO: isso será trocado no final para usar o final bundle module: wrapCodeInGetFunction(fileSchema.content, fileSchema.filePath), values: getFunctionExportDeclarationKeys( diff --git a/lib/alem-vm/components/AppIndexer.jsx b/lib/alem-vm/components/AppIndexer.jsx index 347ed23..ef5d407 100644 --- a/lib/alem-vm/components/AppIndexer.jsx +++ b/lib/alem-vm/components/AppIndexer.jsx @@ -20,7 +20,7 @@ const AlemApp = useMemo(() => { MODULES_CODE: {}, }, } - } + }; return ( diff --git a/lib/utils.js b/lib/utils.js index fc72c70..58ee1fc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -171,7 +171,7 @@ const capitalize_first_letter = (word) => { let nameChangeCounter = 0; /** * Generate new name - * @param {string} _toLowerCase + * @param {boolean} _toLowerCase */ const create_new_name = (_toLowerCase = false) => { // let randomHexName = get_randon_hexadecimal(6); From cf2d1ef39e72d5cabc17d827458dbe4108accf8c Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Fri, 26 Apr 2024 14:08:30 -0300 Subject: [PATCH 06/12] wip --- lib/actions/transformSchemaToWidget.js | 27 +++++++++++++++++++++++++- lib/data.js | 3 ++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index 0bce2fd..19e3158 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -39,6 +39,9 @@ const { const transformVariableInCode = require("../parsers/transformVariableInCode"); const wrapCodeInGetFunction = require("../parsers/wrapCodeInGetFunction"); const getFunctionExportDeclarationKeys = require("../parsers/getFunctionExportDeclarationKeys"); +const { read_alem_config } = require("../config"); + +const config = read_alem_config(); let processError = null; @@ -128,7 +131,29 @@ const processSchema = (fileSchema) => { // isModule = Arquivos que estão na pasta "src/modules". Estes são inseridos no state global para serem acessados por todos // os componentes, salvando assim bastante espaço do bundle final. - const isModule = fileSchema.filePath.includes("src/modules/"); + let isModule = fileSchema.filePath.includes("src/modules/"); // Melhor assim, usuário tem escolha. + + // Filtros = arquivos que são ignorados pela configuração, não serão processados como módulos [modules -> ignore []] + if (isModule) { + const ignoreFiles = config.modules?.ignoreFileIfIncludes || []; + + let foundIgnoredFile = false; + ignoreFiles.forEach((value) => { + if (!foundIgnoredFile) { + foundIgnoredFile = fileSchema.filePath.includes(value); + } + }); + isModule = !foundIgnoredFile; + + console.log("FINAL:", isModule); + } + + // INFO: Ficou mais pesado porque tem arquivos com conteúdo pequeno, e o codigo adicionado para pegar a referencia deixa maior + // Está em modulos OU é stateless && nao tem "styles" (filtro) no nome + // const isModule = + // (fileSchema.filePath.includes("src/modules/") || isStateless) && + // !fileSchema.filePath.includes("styles"); + fileSchema.isModule = isModule; if (isModule) { fileSchema.moduleProps = { diff --git a/lib/data.js b/lib/data.js index 3760b4e..0f729a2 100644 --- a/lib/data.js +++ b/lib/data.js @@ -31,7 +31,8 @@ function generate_data_json() { key === "testnetAccount" || key === "compilerOptions" || key === "options" || - key === "tags" + key === "tags" || + key === "modules" ) { return; } From 8f580a2fab06204b1431a7f92dd7d81a2b3d4a18 Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Fri, 26 Apr 2024 18:46:36 -0300 Subject: [PATCH 07/12] wip --- lib/actions/transformSchemaToWidget.js | 45 +++++++++----- lib/parsers/filterReturn.js | 81 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 lib/parsers/filterReturn.js diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index 19e3158..c6d7acd 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -40,6 +40,7 @@ const transformVariableInCode = require("../parsers/transformVariableInCode"); const wrapCodeInGetFunction = require("../parsers/wrapCodeInGetFunction"); const getFunctionExportDeclarationKeys = require("../parsers/getFunctionExportDeclarationKeys"); const { read_alem_config } = require("../config"); +const filterReturn = require("../parsers/filterReturn"); const config = read_alem_config(); @@ -638,7 +639,10 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { // TODO: Porque tem uma funcao para injecao de dependencias apenas de arquivos stateless? // NAO WIDGETS | MODULOS (arquivos que contenham "module" no nome): tem o conteúdo de seu arquivo copiado e colado no corpo do arquivo sendo processado atual // Também não pode ser um módulo - if (importItemFileContent.isStateless) { + if ( + importItemFileContent.isStateless && + !importItemFileContent.isModule + ) { // Funcao recursiva aqui para fazer com que arquivos ainda não processados pelo injection, sejam primeiro if (!importItemFileContent.injectFilesDependencies_Done) { // Faz o processo primeiro no arquivo dependente @@ -751,13 +755,22 @@ const injectModules = (fileSchemas, fileSchema) => { // Se o arquivo for um módulo... if (fileSchema.isModule) { + // TODO: Parei aqui, parece que ta tudo certo // console.log("FOOO", fileSchema.filePath); // Atualiza o modulo deste arquivo para usar o finalBundle com as importacoes injetadas + console.log("Exported Keys:", fileSchema.moduleProps.values); + const sofrer = wrapCodeInGetFunction( + fileSchema.finalFileBundle, + fileSchema.filePath, + ); + console.log("Module:", sofrer); + console.log("Final:", filterReturn(sofrer, fileSchema.moduleProps.values)); + fileSchema.moduleProps = { ...fileSchema.moduleProps, - module: wrapCodeInGetFunction( - fileSchema.finalFileBundle, - fileSchema.filePath, + module: filterReturn( + wrapCodeInGetFunction(fileSchema.finalFileBundle, fileSchema.filePath), + fileSchema.moduleProps.values, ), // module: "function Get() {}", }; @@ -766,15 +779,7 @@ const injectModules = (fileSchemas, fileSchema) => { return fileSchema; }; -/** - * Coloca o conteúdo dos arquivos nao .ts e .jsx de dependencia dentro do bundle de cada arquivo do schema global - * Esse é um processo que ocorre para todos os arquivos, mas somente copia e cola o conteudo para arquivos nao JSX. - * - * Arquivos reconhecidos como JSX (Widgets) serão tratados de outra forma. Ver "swapComponentsForWidgets" - * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}[]} fileSchemas - * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema - */ -const injectFilesDependencies = (fileSchemas, fileSchema) => { +const injectDependencies = (fileSchemas, fileSchema) => { let fileBundle = fileSchema.finalFileBundle; // Se nao tiver nada a ser injetado, somente retorna o file Schema sem alterações @@ -813,6 +818,20 @@ const injectFilesDependencies = (fileSchemas, fileSchema) => { return fileSchema; }; +/** + * Coloca o conteúdo dos arquivos nao .ts e .jsx de dependencia dentro do bundle de cada arquivo do schema global + * Esse é um processo que ocorre para todos os arquivos, mas somente copia e cola o conteudo para arquivos nao JSX. + * + * Arquivos reconhecidos como JSX (Widgets) serão tratados de outra forma. Ver "swapComponentsForWidgets" + * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}[]} fileSchemas + * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], toBeInjectedFiles:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema + */ +const injectFilesDependencies = (fileSchemas, fileSchema) => { + fileSchema = injectDependencies(fileSchemas, fileSchema); + + return fileSchema; +}; + /** * Caso tenha dependencias do Alem (inportable items), prepara eles para serem injetados. * diff --git a/lib/parsers/filterReturn.js b/lib/parsers/filterReturn.js new file mode 100644 index 0000000..3a8ba64 --- /dev/null +++ b/lib/parsers/filterReturn.js @@ -0,0 +1,81 @@ +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const t = require("@babel/types"); +const generate = require("@babel/generator").default; + +/** + * Dado um código, filtra quais as chaves devem permanecer no return dessa função dentro do código. + * + * Exemplo de entrada: + * + * const code = `() => { + * const myFirstModule = props.alem.m["a_1"]().myFirstModule; + * const DependeciaNormal = () => { + * return

DependeciaNormal: {myFirstModule.getIt}

; + * }; + * const Modulo = () => { + * return <> + *

Modulo: {myFirstModule.getIt}

+ * + * ; + * }; + * return { + * myFirstModule: myFirstModule, + * DependeciaNormal: DependeciaNormal, + * Modulo: Modulo + * }; + * }` + * + * console.log(code, ["Modulo", "myFirstModule"]); + * + * Saída: + * + * () => { + * const myFirstModule = props.alem.m["a_1"]().myFirstModule; + * const DependeciaNormal = () => { + * return

DependeciaNormal: {myFirstModule.getIt}

; + * }; + * const Modulo = () => { + * return <> + *

Modulo: {myFirstModule.getIt}

+ * + * ; + * }; + * return { + * myFirstModule: myFirstModule, + * Modulo: Modulo + * }; + * }; + * + * + * @param {*} code + * @param {*} keys + * @returns + */ +function filterReturn(code, keys) { + const ast = parser.parse(code, { + sourceType: "module", + plugins: ["jsx"], // Habilita o suporte para sintaxe JSX + }); + + traverse(ast, { + ReturnStatement(path) { + // Checa se o retorno é um objeto + if (t.isObjectExpression(path.node.argument)) { + const properties = path.node.argument.properties; + // Filtra as propriedades para manter apenas as chaves especificadas + const filteredProperties = properties.filter((prop) => + keys.includes(prop.key.name), + ); + // Substitui as propriedades do objeto de retorno pelas filtradas + path.node.argument.properties = filteredProperties; + } + }, + }); + + // Gera o novo código a partir do AST modificado + const { code: newCode } = generate(ast); + return newCode; +} + +module.exports = filterReturn; From 31b1e2aa5795c8a67ad91fdba0c6b56e35477112 Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Sat, 27 Apr 2024 13:28:43 -0300 Subject: [PATCH 08/12] adjust returned keys for each module --- lib/parsers/extractTopLevelDeclarations.js | 2 ++ lib/parsers/filterReturn.js | 3 ++- lib/parsers/removeGetNameAndLastComma.js | 8 ++++++++ lib/parsers/wrapCodeInGetFunction.js | 7 ++++--- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 lib/parsers/removeGetNameAndLastComma.js diff --git a/lib/parsers/extractTopLevelDeclarations.js b/lib/parsers/extractTopLevelDeclarations.js index 7a6f3a7..3ce1d13 100644 --- a/lib/parsers/extractTopLevelDeclarations.js +++ b/lib/parsers/extractTopLevelDeclarations.js @@ -1,3 +1,5 @@ +// NOTE: Este arquivo nao esta sendo usado, ver se tem algo útil, se nao, deletar! + const parser = require("@babel/parser"); const traverse = require("@babel/traverse").default; const generate = require("@babel/generator").default; diff --git a/lib/parsers/filterReturn.js b/lib/parsers/filterReturn.js index 3a8ba64..cd4a6e8 100644 --- a/lib/parsers/filterReturn.js +++ b/lib/parsers/filterReturn.js @@ -1,6 +1,7 @@ const parser = require("@babel/parser"); const traverse = require("@babel/traverse").default; const t = require("@babel/types"); +const removeGetNameAndLastComma = require("./removeGetNameAndLastComma"); const generate = require("@babel/generator").default; /** @@ -75,7 +76,7 @@ function filterReturn(code, keys) { // Gera o novo código a partir do AST modificado const { code: newCode } = generate(ast); - return newCode; + return removeGetNameAndLastComma(newCode); } module.exports = filterReturn; diff --git a/lib/parsers/removeGetNameAndLastComma.js b/lib/parsers/removeGetNameAndLastComma.js new file mode 100644 index 0000000..c4511c4 --- /dev/null +++ b/lib/parsers/removeGetNameAndLastComma.js @@ -0,0 +1,8 @@ +// Troca o "const Get = " por "", isso porque o módulo é a função diretamente +// e remove o último ";" encontrado porque a função vai ser colocar em uma lista de objetos, ou seja, +// vai ter um "," separando cada objeto. +const removeGetNameAndLastComma = (code) => { + return code.replace("const Get = ", "").replace(/;(?=[^;]*$)/, ""); +}; + +module.exports = removeGetNameAndLastComma; diff --git a/lib/parsers/wrapCodeInGetFunction.js b/lib/parsers/wrapCodeInGetFunction.js index 44f9302..ce8cb79 100644 --- a/lib/parsers/wrapCodeInGetFunction.js +++ b/lib/parsers/wrapCodeInGetFunction.js @@ -6,6 +6,7 @@ const babel = require("@babel/core"); // const reactPreset = require("@babel/preset-react"); const typescriptPreset = require("@babel/preset-typescript"); const convertFunctionsToArrow = require("./convertFunctionsToArrow"); +const removeGetNameAndLastComma = require("./removeGetNameAndLastComma"); /** * Esta função vai cobrir o arquivo/code com uma função Get e todas as declarações sendo exportadas @@ -108,9 +109,9 @@ function wrapCodeInGetFunction(code, filePath) { // Troca o "const Get = " por "", isso porque o módulo é a função diretamente // e remove o último ";" encontrado porque a função vai ser colocar em uma lista de objetos, ou seja, // vai ter um "," separando cada objeto. - return convertFunctionsToArrow(transformedCode) - .replace("const Get = ", "") - .replace(/;(?=[^;]*$)/, ""); + return removeGetNameAndLastComma(convertFunctionsToArrow(transformedCode)); + // .replace("const Get = ", "") + // .replace(/;(?=[^;]*$)/, ""); } module.exports = wrapCodeInGetFunction; From 0d5e81ac2326b6990fe46369d2f128c9f5ce847d Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Sat, 27 Apr 2024 13:39:01 -0300 Subject: [PATCH 09/12] fix filterReturn --- lib/actions/transformSchemaToWidget.js | 1 + lib/parsers/filterReturn.js | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index c6d7acd..ebc69e7 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -132,6 +132,7 @@ const processSchema = (fileSchema) => { // isModule = Arquivos que estão na pasta "src/modules". Estes são inseridos no state global para serem acessados por todos // os componentes, salvando assim bastante espaço do bundle final. + // let isModule = fileSchema.filePath.includes("src/modules/") || isStateless; let isModule = fileSchema.filePath.includes("src/modules/"); // Melhor assim, usuário tem escolha. // Filtros = arquivos que são ignorados pela configuração, não serão processados como módulos [modules -> ignore []] diff --git a/lib/parsers/filterReturn.js b/lib/parsers/filterReturn.js index cd4a6e8..db529f4 100644 --- a/lib/parsers/filterReturn.js +++ b/lib/parsers/filterReturn.js @@ -65,9 +65,11 @@ function filterReturn(code, keys) { if (t.isObjectExpression(path.node.argument)) { const properties = path.node.argument.properties; // Filtra as propriedades para manter apenas as chaves especificadas - const filteredProperties = properties.filter((prop) => - keys.includes(prop.key.name), - ); + const filteredProperties = properties.filter((prop) => { + // console.log("Prop =====>", prop); + // console.log("Prop Name =====>", prop.key?.name); + return keys.includes(prop.key?.name); + }); // Substitui as propriedades do objeto de retorno pelas filtradas path.node.argument.properties = filteredProperties; } From 9e57ca5e131865768ea5028b40c2998e371d1f98 Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Sat, 27 Apr 2024 17:33:48 -0300 Subject: [PATCH 10/12] fix function that removes/change import, export and export default statement --- lib/actions/handleNames.js | 2 +- lib/actions/transformSchemaToWidget.js | 49 ++++++++----- lib/alem-vm/importable/modules/readme.md | 4 ++ lib/helpers.js | 5 ++ lib/parse.js | 8 +++ lib/parsers/removeImportsAndExports.js | 68 +++++++++++++++++++ ...ansformAndGetFunctionExportDeclarations.js | 3 +- lib/parsers/wrapCodeInGetFunction.js | 6 +- 8 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 lib/alem-vm/importable/modules/readme.md create mode 100644 lib/parsers/removeImportsAndExports.js diff --git a/lib/actions/handleNames.js b/lib/actions/handleNames.js index fb630a7..9892cad 100644 --- a/lib/actions/handleNames.js +++ b/lib/actions/handleNames.js @@ -313,7 +313,7 @@ const checkIfItemExistInContent = (content, itemName) => { * @param {{filePath: string, toImport: string[], content: string}[]} fileSchemas */ const handleNames = (fileSchemas) => { - reset_name_counter(); + // reset_name_counter(); let tempBundle = ""; /** diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index ebc69e7..1ad7884 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -146,8 +146,6 @@ const processSchema = (fileSchema) => { } }); isModule = !foundIgnoredFile; - - console.log("FINAL:", isModule); } // INFO: Ficou mais pesado porque tem arquivos com conteúdo pequeno, e o codigo adicionado para pegar a referencia deixa maior @@ -759,13 +757,13 @@ const injectModules = (fileSchemas, fileSchema) => { // TODO: Parei aqui, parece que ta tudo certo // console.log("FOOO", fileSchema.filePath); // Atualiza o modulo deste arquivo para usar o finalBundle com as importacoes injetadas - console.log("Exported Keys:", fileSchema.moduleProps.values); - const sofrer = wrapCodeInGetFunction( - fileSchema.finalFileBundle, - fileSchema.filePath, - ); - console.log("Module:", sofrer); - console.log("Final:", filterReturn(sofrer, fileSchema.moduleProps.values)); + // console.log("Exported Keys:", fileSchema.moduleProps.values); + // const sofrer = wrapCodeInGetFunction( + // fileSchema.finalFileBundle, + // fileSchema.filePath, + // ); + // console.log("Module:", sofrer); + // console.log("Final:", filterReturn(sofrer, fileSchema.moduleProps.values)); fileSchema.moduleProps = { ...fileSchema.moduleProps, @@ -805,9 +803,9 @@ const injectDependencies = (fileSchemas, fileSchema) => { if (!fileItemSchema.isModule) { // Injeta o conteúdo literalmente fileBundle = ` - ${fileItemSchema.jsContent} - ${fileBundle} - `; + ${fileItemSchema.jsContent} + ${fileBundle} + `; injectedFiles.push(fileItemPath); } @@ -833,6 +831,14 @@ const injectFilesDependencies = (fileSchemas, fileSchema) => { return fileSchema; }; +const injectModulesDependencies = (fileSchemas, fileSchema) => { + // Faz a injeção das dependencias dos módulos, dependencias de stateless files, nao de outros módulos. + // INFO: A injeção de referencia de módulos é feito pelo "injectModules" + // fileSchema = injectDependencies(fileSchemas, fileSchema, true); + + return fileSchema; +}; + /** * Caso tenha dependencias do Alem (inportable items), prepara eles para serem injetados. * @@ -970,15 +976,24 @@ const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { ); }); - // Faz transformação de async/await para o formato promisify + // Injeta as dependencias dos arquivos módulos dentro deles fileSchemas.forEach((fileSchema, fileSchemaIndex) => { - // Transform async/await (experimental) - fileSchema.finalFileBundle = transformAsyncAwait( - fileSchema.finalFileBundle, + fileSchemas[fileSchemaIndex] = injectModulesDependencies( + fileSchemas, + fileSchema, ); - fileSchemas[fileSchemaIndex] = fileSchema; }); + // Faz transformação de async/await para o formato promisify + // fileSchemas.forEach((fileSchema, fileSchemaIndex) => { + // // Transform async/await (experimental) + // fileSchema.finalFileBundle = transformAsyncAwait( + // fileSchema.finalFileBundle, + // ); + // fileSchemas[fileSchemaIndex] = fileSchema; + // }); + // TODO: remover transformAsyncAwait.js + // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" // Injeta os módulos caso sejam requeridos fileSchemas.forEach((fileSchema, fileSchemaIndex) => { diff --git a/lib/alem-vm/importable/modules/readme.md b/lib/alem-vm/importable/modules/readme.md new file mode 100644 index 0000000..9d30e1f --- /dev/null +++ b/lib/alem-vm/importable/modules/readme.md @@ -0,0 +1,4 @@ +## Modules + +Thes files inside this folder are going to be injected to the global state and components using them will get only their references, so, saving +size for the final bundle file. diff --git a/lib/helpers.js b/lib/helpers.js index 8402db4..4dcf1bb 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -618,6 +618,10 @@ function escapeHtmlEntities(str) { .replace(/'/g, "'"); } +// Transforma "export default function" para "function" +const exportDefaultFunctionToFunction = (code) => + code.replaceAll(/\bexport default function\b/g, "function"); + module.exports = { getFileImportsElements, getImportStatements, @@ -645,4 +649,5 @@ module.exports = { hasHtmlElements, cleanErrorMessage, escapeHtmlEntities, + exportDefaultFunctionToFunction, }; diff --git a/lib/parse.js b/lib/parse.js index 75a1bd2..665da1a 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -72,6 +72,14 @@ const removeExports = (code) => { return newCode; }; +/** + * Remove Imports + * + * ATENÇÃO: Prefira usar "removeImportsAndExports" que é feito com Babel, é 100% seguro e mais robusto! + * + * @param {*} c + * @returns + */ const removeImports = (c) => { // Remove line breaks const importItems = c.match(/(import)(.*?)(from)/gs); diff --git a/lib/parsers/removeImportsAndExports.js b/lib/parsers/removeImportsAndExports.js new file mode 100644 index 0000000..c22e9c0 --- /dev/null +++ b/lib/parsers/removeImportsAndExports.js @@ -0,0 +1,68 @@ +const parser = require("@babel/parser"); +const traverse = require("@babel/traverse").default; +const generate = require("@babel/generator").default; +const t = require("@babel/types"); + +/** + * Remove linhas que tenham "import" do javascript + * Troca "export" e "export default" por nada + * + * INFO: Essa é a forma mais segura + * + * @param {*} code + * @returns + */ +function removeImportsAndExports(code) { + const ast = parser.parse(code, { + sourceType: "module", // necessário para suportar módulos ES6 + plugins: ["jsx", "typescript"], // adicione plugins se estiver usando JSX ou TypeScript + }); + + traverse(ast, { + ImportDeclaration(path) { + path.remove(); // Remove a declaração de importação completamente + }, + ExportDeclaration(path) { + if (path.node.declaration) { + // Se a declaração de exportação possui um nó de declaração (ou seja, não é apenas 'export { something }') + const { declaration } = path.node; + path.replaceWith(declaration); // Substitui a exportação pela declaração contida + } else { + // Para exportações que são apenas exportações de variáveis, remover a palavra 'export' + path.replaceWithMultiple( + path.node.specifiers.map((specifier) => + t.variableDeclaration("let", [ + t.variableDeclarator(specifier.local), + ]), + ), + ); + } + }, + ExportDefaultDeclaration(path) { + if (path.node.declaration) { + // Remove apenas a palavra 'default' e mantém a declaração + const { declaration } = path.node; + if ( + t.isFunctionDeclaration(declaration) || + t.isClassDeclaration(declaration) + ) { + // Para funções e classes, elas precisam ser convertidas para expressões antes da remoção do 'export default' + const expression = t.functionExpression( + null, + declaration.params, + declaration.body, + declaration.generator, + declaration.async, + ); + path.replaceWith(expression); + } else { + path.replaceWith(declaration); + } + } + }, + }); + + return generate(ast, { false: true }).code; +} + +module.exports = removeImportsAndExports; diff --git a/lib/parsers/transformAndGetFunctionExportDeclarations.js b/lib/parsers/transformAndGetFunctionExportDeclarations.js index 581d863..d36a00a 100644 --- a/lib/parsers/transformAndGetFunctionExportDeclarations.js +++ b/lib/parsers/transformAndGetFunctionExportDeclarations.js @@ -2,6 +2,7 @@ const parser = require("@babel/parser"); const traverse = require("@babel/traverse").default; const t = require("@babel/types"); const convertFunctionsToArrow = require("./convertFunctionsToArrow"); +const { exportDefaultFunctionToFunction } = require("../helpers"); const generate = require("@babel/generator").default; function convertExportDefault(code) { @@ -36,7 +37,7 @@ function convertExportDefault(code) { */ function transformAndGetFunctionExportDeclarations(code, filePath) { // Transforma "export default function" para "function" - code = code.replaceAll(/\bexport default function\b/g, "function"); + code = exportDefaultFunctionToFunction(code); // Converte functions para arrow functions try { diff --git a/lib/parsers/wrapCodeInGetFunction.js b/lib/parsers/wrapCodeInGetFunction.js index ce8cb79..fc4c837 100644 --- a/lib/parsers/wrapCodeInGetFunction.js +++ b/lib/parsers/wrapCodeInGetFunction.js @@ -2,11 +2,10 @@ const parser = require("@babel/parser"); const traverse = require("@babel/traverse").default; const t = require("@babel/types"); const babel = require("@babel/core"); -// const babelPreset = require("@babel/preset-env"); -// const reactPreset = require("@babel/preset-react"); const typescriptPreset = require("@babel/preset-typescript"); const convertFunctionsToArrow = require("./convertFunctionsToArrow"); const removeGetNameAndLastComma = require("./removeGetNameAndLastComma"); +const removeImportsAndExports = require("./removeImportsAndExports"); /** * Esta função vai cobrir o arquivo/code com uma função Get e todas as declarações sendo exportadas @@ -41,7 +40,8 @@ const removeGetNameAndLastComma = require("./removeGetNameAndLastComma"); */ function wrapCodeInGetFunction(code, filePath) { // Remover importações e exports - code = code.replace(/import.*;|export\s+default|export\s+/g, ""); + // code = code.replace(/import.*;|export\s+default|export\s+/g, ""); + code = removeImportsAndExports(code); // Parsear o código para AST removendo TypeScript const ast = parser.parse(code, { From fab48c54fdd411a8cb3d517a16d10a738b1e2342 Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Mon, 29 Apr 2024 04:03:51 -0300 Subject: [PATCH 11/12] modo de leitura de arquivos do Alem mudados, mais performatico; modelo de carga de modulos de app e alem implementado --- lib/actions/loadFilesContent.js | 11 +- lib/actions/loadFilesInfo.js | 30 +++--- lib/actions/prepareAlemDependencies.js | 53 ++++++++++ lib/actions/transformSchemaToWidget.js | 128 +++-------------------- lib/alem-vm/components/AppIndexer.jsx | 2 +- lib/alem-vm/importable/RouteLink.tsx | 2 +- lib/alem-vm/importable/modules/readme.md | 2 + lib/compiler.js | 11 +- lib/config.js | 6 +- lib/config/importableFiles.js | 114 +++++--------------- lib/helpers.js | 8 ++ lib/parsers/removeGetNameAndLastComma.js | 4 +- lib/parsers/transformAsyncAwait.js | 1 + 13 files changed, 138 insertions(+), 234 deletions(-) create mode 100644 lib/actions/prepareAlemDependencies.js diff --git a/lib/actions/loadFilesContent.js b/lib/actions/loadFilesContent.js index 799ee01..0445176 100644 --- a/lib/actions/loadFilesContent.js +++ b/lib/actions/loadFilesContent.js @@ -5,13 +5,9 @@ const transformSchemaToWidget = require("./transformSchemaToWidget"); /** * Load the "componentCodes" from all Widgets based on file schemas * @param {{filePath: string, toImport: string[], content: string}[]} fileSchemas - * @param {*} additionalFileSchemas FileSchemas to be added to the list of main fileSchemas. It's going to be added first before * the process starts. This is util to inject previous schema files like Além importable items. */ -const loadComponentCodesObjectByFileSchemas = ( - fileSchemas, - additionalFileSchemas, -) => { +const loadComponentCodesObjectByFileSchemas = (fileSchemas) => { let componentsCodes = ""; /** @@ -23,10 +19,7 @@ const loadComponentCodesObjectByFileSchemas = ( fileSchemas = fileSchemas.reverse(); // Get Normal js files & Widget files (components transformed to BOS Widgets) - const completeFileSchemas = transformSchemaToWidget( - fileSchemas, - additionalFileSchemas, - ); + const completeFileSchemas = transformSchemaToWidget(fileSchemas); // Processa também os módulos let modulesCodes = ""; diff --git a/lib/actions/loadFilesInfo.js b/lib/actions/loadFilesInfo.js index 18f881a..0223a3a 100644 --- a/lib/actions/loadFilesInfo.js +++ b/lib/actions/loadFilesInfo.js @@ -15,6 +15,7 @@ const { removeImports } = require("../parse"); const filesContentCache = require("../config/filesContentCache"); const replaceRegexWithReferences = require("../parsers/regex-parser/convertRegexToStringLiteral"); const regexObjects = require("../parsers/regex-parser/regexObjects"); +const prepareAlemDependencies = require("./prepareAlemDependencies"); // const extractTopLevelDeclarations = require("../parsers/extractTopLevelDeclarations"); /** * Transform statefull components references to JSX (this applies for stateful and stateless components) @@ -132,6 +133,12 @@ const processFileSchema = (filePath, processOnlyThisFile) => { } fileContent = removeCommentsResult.code; + + // Processa as dependencias de Além + const alemStuff = prepareAlemDependencies(fileContent); + fileContent = alemStuff.updatedFileContent; + + // INFO: Caminho dos imports const fileImportsPath = helpers.getImportsPath(fileContent); let currentFileSchema = { @@ -144,6 +151,7 @@ const processFileSchema = (filePath, processOnlyThisFile) => { isModule: false, moduleProps: {}, }; + fileImportsPath.forEach((importPath) => { // console.log("Check import Path:", importPath); @@ -152,13 +160,19 @@ const processFileSchema = (filePath, processOnlyThisFile) => { importPath = importPath.replaceAll("/", "\\"); } - let importedFileContentPath = ""; + let importedFileContentPath = importPath; // Replace path aliases // Check if its has path alias if (compilerOptions.hasPathAlias(importPath)) { importedFileContentPath = compilerOptions.replacePathAlias(importPath); - } else { + + // INFO: Se incluir um caminho relativo de um recurso Além, não precisa relacionar com o componente pai + // já que o diretório está pronto para acessar o arquivo + } else if ( + !importPath.includes("node_modules/alem") && // unix + !importPath.includes("node_modules\\alem") // win + ) { // Usa src para inicio ou o caminho do pai do arquivo sendo processado atualmente importedFileContentPath = path.join(parentFolder, importPath); } @@ -182,18 +196,6 @@ const processFileSchema = (filePath, processOnlyThisFile) => { processedFiles.push(importedFileContentPath); } - - // Lógica de módulos - // isModule = Arquivos que estão na pasta "src/modules". Estes são inseridos no state global para serem acessados por todos - // os componentes, salvando assim bastante espaço do bundle final. - // const isModule = filePath.includes("src/modules/"); - // currentFileSchema.isModule = isModule; - // if (isModule) { - // currentFileSchema.moduleProps = { - // name: filePath, - // values: extractTopLevelDeclarations(fileContent), - // }; - // } }); // Transform statefull components references to JSX diff --git a/lib/actions/prepareAlemDependencies.js b/lib/actions/prepareAlemDependencies.js new file mode 100644 index 0000000..290fbd4 --- /dev/null +++ b/lib/actions/prepareAlemDependencies.js @@ -0,0 +1,53 @@ +const { + getFileImportsElements, + getImportedElementFileSource, +} = require("../helpers"); +const transformImports = require("../parsers/transformImports"); +const importableFiles = require("../config/importableFiles"); + +/** + * Caso tenha dependencias do Alem (inportable items), prepara eles para serem injetados. + * + * Remove os elementos da chave em que está e coloca em uma nova linha contendo seu caminho + * até a lib alem-vm/importable/item... + * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema + */ +const prepareAlemDependencies = (originalFileContent) => { + const importItems = getFileImportsElements(originalFileContent); + + let updatedFileContent = originalFileContent; + const alemDependencies = []; // Lista de dependencias Além (diretorio dos arquivos) + + importItems.forEach((item) => { + // TODO: [Alem items: Routes, Link, etc] Checar se esta dando conflito com items do projeto + + const importStatementFileSource = getImportedElementFileSource( + updatedFileContent, + item, + ); + + // Se o item estiver vindo de um destino que contenha "alem-vm" ou "alem" + // logo é um item do Além. + if ( + /\balem-vm\b/.test(importStatementFileSource) || + /\balem\b/.test(importStatementFileSource) + ) { + const alemImportElement = importableFiles[item]; + + // Se for um elemento importavel do Além e estiver presente no importableFiles do Além, então + // insere a nova linha no arquivo pedindo para importar o elemento. + if (alemImportElement) { + alemDependencies.push(alemImportElement); + updatedFileContent = transformImports( + updatedFileContent, + item, + alemImportElement, + ); + } + } + }); + + return { updatedFileContent, alemDependencies }; +}; + +module.exports = prepareAlemDependencies; diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index 1ad7884..2239673 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -20,23 +20,15 @@ const { const { log, create_new_name } = require("../utils"); const PROHIBITED_METHODS = require("../config/prohibitedMethods"); const hasWidgetPropsCheck = require("./hasWidgetPropsCheck"); -const importableFiles = require("../config/importableFiles"); const extractPropsFromJSX = require("../parsers/extractPropsFromJSX"); const extractJSXElements = require("../parsers/extractJSXElements"); const extractJSX = require("../parsers/extractJSX"); const replaceJSXElement = require("../parsers/replaceJSXElement"); const extractJSXChildren = require("../parsers/extractJSXChildren"); const processChildrenWidget = require("./processChildrenWidget"); -const transformImports = require("../parsers/transformImports"); const analyzeFunctionSignature = require("../parsers/analyzeFunctionSignature"); const removeFunctionParams = require("../parsers/removeFunctionParams"); -const transformAsyncAwait = require("../parsers/transformAsyncAwait"); const compilerOptions = require("./compilerOptions"); -const { - extractTopLevelDeclarations, - processDeclarations, -} = require("../parsers/extractTopLevelDeclarations"); -const transformVariableInCode = require("../parsers/transformVariableInCode"); const wrapCodeInGetFunction = require("../parsers/wrapCodeInGetFunction"); const getFunctionExportDeclarationKeys = require("../parsers/getFunctionExportDeclarationKeys"); const { read_alem_config } = require("../config"); @@ -133,7 +125,11 @@ const processSchema = (fileSchema) => { // isModule = Arquivos que estão na pasta "src/modules". Estes são inseridos no state global para serem acessados por todos // os componentes, salvando assim bastante espaço do bundle final. // let isModule = fileSchema.filePath.includes("src/modules/") || isStateless; - let isModule = fileSchema.filePath.includes("src/modules/"); // Melhor assim, usuário tem escolha. + + // Melhor assim, usuário tem escolha. + let isModule = + fileSchema.filePath.includes("src/modules/") || // App Modules Folder + fileSchema.filePath.includes("alem-vm/importable/modules/"); // Além Modules Folder // Filtros = arquivos que são ignorados pela configuração, não serão processados como módulos [modules -> ignore []] if (isModule) { @@ -415,25 +411,6 @@ const swapComponentsForStatelessFiles = (fileSchemas, fileSchema) => { if (importItemFileSource) { // NOTE: Aqui que a magica acontece!!!! - // // Le o nome do Widget dependente (usando o fileSchema dele) - // let importItemWidget = fileSchemas.find( - // (importFileSchema) => - // importFileSchema.filePath === importItemFileSource, - // ); - - // // MODULOS - INICIO - // // NEW - Armazena os módulos para serem inseridos apenas no final, assim garantindo - // // que sua referencia ficará no topo do bundle do arquivo/code final - // const updatedToBeInjectedModules = - // fileSchema.toBeInjectedModules || []; - // const itemToBeInjectedModules = - // importItemWidget.toBeInjectedModules || []; - // fileSchema.toBeInjectedModules = [ - // ...updatedToBeInjectedModules, - // ...itemToBeInjectedModules, - // ]; - // // MODULOS - FIM - const importItemWidgetComponentName = importItemWidget.widgetName || getComponentName(importItemWidget.finalFileBundle); @@ -653,31 +630,6 @@ const prepareListOfInjections = (fileSchemas, fileSchema) => { ); } - // if (importItemFileContent.isModule) { - // // Modulos - - // // Insere os modulos dependentes - // if (!fileSchema.toBeInjectedModules) { - // fileSchema.toBeInjectedModules = []; - // } - - // // Adiciona somente se ainda nao tiver o item na lista de modulos - // if ( - // !fileSchema.toBeInjectedModules.includes( - // importItemFileContent.filePath, - // ) - // ) { - // fileSchema.toBeInjectedModules.push( - // importItemFileContent.filePath, - // ...(importItemFileContent.toBeInjectedModules - // ? importItemFileContent.toBeInjectedModules - // : []), - // ); - // } - // } - // else { - // // Nao modulos - // Insere os arquivos dependentes que já foram inseridos no bundle. Isso evita duplicatas de conteúdo if (!fileSchema.toBeInjectedFiles) { fileSchema.toBeInjectedFiles = []; @@ -737,10 +689,19 @@ const injectModules = (fileSchemas, fileSchema) => { // m = modulesCode moduleValuesKeys.forEach((propKey) => { + // INFO: as propriedades do escopo/widget local deve ser passado para os módulos para que esses tenham acesso a ela + // esse processo deixa sub-componentes de módulos mais lento já que a propriedade é compartilhada e pode ser + // acessada pelos componentes filhos deste módulo. Essa info de lentidão serve apenas para JSX que carregam + // outros modulos JSX aninhados injections = ` const ${propKey} = props.alem.m["${fileItemSchema.moduleProps.name}"]().${propKey}; ${injections} `; + // WARNING: enviar as props do escopo local como referencia para os módulos causa um perda significativa de desempenho + // injections = ` + // const ${propKey} = props.alem.m["${fileItemSchema.moduleProps.name}"](props).${propKey}; + // ${injections} + // `; }); fileBundle = ` @@ -839,77 +800,18 @@ const injectModulesDependencies = (fileSchemas, fileSchema) => { return fileSchema; }; -/** - * Caso tenha dependencias do Alem (inportable items), prepara eles para serem injetados. - * - * Remove os elementos da chave em que está e coloca em uma nova linha contendo seu caminho - * até a lib alem-vm/importable/item... - * @param {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}} fileSchema - */ -const prepareAlemDependencies = (fileSchema) => { - const importItems = getFileImportsElements(fileSchema.content); - - let fileContent = fileSchema.content; - let contentChanged = false; - - importItems.forEach((item) => { - // TODO: [Alem items: Routes, Link, etc] Checar se esta dando conflito com items do projeto - - const importStatementFileSource = getImportedElementFileSource( - fileContent, - item, - ); - - // Se o item estiver vindo de um destino que contenha "alem-vm" ou "alem" - // logo é um item do Além. - if ( - /\balem-vm\b/.test(importStatementFileSource) || - /\balem\b/.test(importStatementFileSource) - ) { - const alemImportElement = importableFiles[item]; - - // Se for um elemento importavel do Além e estiver presente no importableFiles do Além, então - // insere a nova linha no arquivo pedindo para importar o elemento. - if (alemImportElement) { - contentChanged = true; - - fileContent = transformImports(fileContent, item, alemImportElement); - } - } - }); - - if (contentChanged) { - fileSchema.content = fileContent; - } - - return fileSchema; -}; - // * @returns {{filePath: string, toImport: string[], content: string, finalFileBundle: string, componentImportItems:[], componentParamsItems:[], componentComponentItems: [], widgetName?: string, htmlElementsProps: {}}[]} // INFO: isso pertencia a estrutura abaixo e foi removido porque esta desatualizado /** * @param {{filePath: string, toImport: string[], content: string}[]} fileSchemas - * @param {*} additionalFileSchemas FileSchemas to be added to the list of main fileSchemas. It's going to be added first before * the process starts. This is util to inject previous schema files like Além importable items. */ -const transformSchemaToWidget = (fileSchemas, additionalFileSchemas) => { +const transformSchemaToWidget = (fileSchemas) => { // TODO: trocar esse nome, transformSchemaToWidget -> transformSchemaToWidgetSchema // Reset error state processError = null; - // Caso tenha dependencias do Alem (inportable items), prepara eles para serem injetados - // Remove os elementos da chave em que está e coloca em uma nova linha contendo seu caminho - // até a lib alem-vm/importable/item... - fileSchemas.forEach((fileSchema, fileSchemaIndex) => { - fileSchemas[fileSchemaIndex] = prepareAlemDependencies(fileSchema); - }); - - // Adiciona schemas já processados dos items importáveis do Além (hahaha) - if (additionalFileSchemas) { - fileSchemas = [...additionalFileSchemas, ...fileSchemas]; - } - // Gera o primeiro finalFileBundle e widgetName(para Widgets somente), parametros e imports fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = processSchema(fileSchema); diff --git a/lib/alem-vm/components/AppIndexer.jsx b/lib/alem-vm/components/AppIndexer.jsx index ef5d407..4e19db8 100644 --- a/lib/alem-vm/components/AppIndexer.jsx +++ b/lib/alem-vm/components/AppIndexer.jsx @@ -18,7 +18,7 @@ const AlemApp = useMemo(() => { // m = modulesCode, está sendo usado "m" para reduzir o bundle final m: { MODULES_CODE: {}, - }, + }, } }; diff --git a/lib/alem-vm/importable/RouteLink.tsx b/lib/alem-vm/importable/RouteLink.tsx index 0e723dd..b2a989e 100644 --- a/lib/alem-vm/importable/RouteLink.tsx +++ b/lib/alem-vm/importable/RouteLink.tsx @@ -1,4 +1,4 @@ -import { LinkProps, navigate, useContext } from "../alem-vm"; +import { LinkProps, useContext, navigate } from "../alem-vm"; /** * Link to access routes. diff --git a/lib/alem-vm/importable/modules/readme.md b/lib/alem-vm/importable/modules/readme.md index 9d30e1f..f8cb50c 100644 --- a/lib/alem-vm/importable/modules/readme.md +++ b/lib/alem-vm/importable/modules/readme.md @@ -2,3 +2,5 @@ Thes files inside this folder are going to be injected to the global state and components using them will get only their references, so, saving size for the final bundle file. + +WARNING: If you want to use `props` inside the modules, you must pass it as a parameter. Modules live at the very top layer of Além and can't automatically access the props where it's being used. diff --git a/lib/compiler.js b/lib/compiler.js index 83dec49..59e9ebf 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -32,10 +32,6 @@ function run_final_process(filesInfo, opts) { return; } - // Load Além files content schema - const alemImportableFilesSchema = - alemFiles.importableAlemFileSchemas().completeFileSchemas; - /** * Recebe um texto no formato: * NomeComponente: `codigo do componente`, @@ -44,7 +40,6 @@ function run_final_process(filesInfo, opts) { const finishedSchemaProcessForWidgets = loadFilesContent.loadComponentCodesObjectByFileSchemas( filesInfo.fileSchemas, - alemImportableFilesSchema, ); if (finishedSchemaProcessForWidgets.error) { @@ -144,6 +139,12 @@ function compile_files(opts) { // Load project files const filesInfo = loadFilesInfo(entryFile); + // Salva o esquema inicial. Bom para log + // fs.writeFileSync( + // path.join(`./build/src/initial.json`), + // JSON.stringify(filesInfo.fileSchemas, null, 2), + // ); + // Executa processo final para gerar bundle e esquemas de arquivos run_final_process(filesInfo, opts); } diff --git a/lib/config.js b/lib/config.js index 249af42..aecaec9 100644 --- a/lib/config.js +++ b/lib/config.js @@ -7,8 +7,10 @@ const path = require("path"); function read_alem_config() { const configPath = path.join(".", "alem.config.json"); if (!fs.existsSync(configPath)) { - console.warn(`INFO: File 'alem.config.json' not found! If you're using the CLI within a project, you must create this file to setup your project. Take a look at the Além docs: https://alem.dev/?path=config-file.`); - console.log("\n") + console.warn( + `INFO: File 'alem.config.json' not found! If you're using the CLI within a project, you must create this file to setup your project. Take a look at the Além docs: https://alem.dev/?path=config-file.`, + ); + console.log("\n"); return {}; } const configRaw = fs.readFileSync(configPath); diff --git a/lib/config/importableFiles.js b/lib/config/importableFiles.js index aae4456..b1ef63c 100644 --- a/lib/config/importableFiles.js +++ b/lib/config/importableFiles.js @@ -11,112 +11,50 @@ const path = require("path"); const { ALEM_VM_FOLDER } = require("../constants"); -const RouterContext = path.join( - __dirname, - "../", +// const importablePath = path.join( +// __dirname, +// "../", +// ALEM_VM_FOLDER, +// "importable", +// ); + +const importablePath = path.join( + ".", + "node_modules/alem/lib", ALEM_VM_FOLDER, "importable", - "RouterContext.ts", ); -const RouterProvider = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "RouterProvider.tsx", -); +const RouterContext = path.join(importablePath, "RouterContext.ts"); -const createContext = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "createContext.ts", -); -const useContext = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "useContext.ts", -); +const RouterProvider = path.join(importablePath, "RouterProvider.tsx"); -const Router = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "Router.tsx", -); +const createContext = path.join(importablePath, "createContext.ts"); -const RouteLink = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "RouteLink.tsx", -); +const useContext = path.join(importablePath, "useContext.ts"); -const useRoutes = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "useRoutes.ts", -); +const Router = path.join(importablePath, "Router.tsx"); -const getLocation = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "getLocation.ts", -); +const RouteLink = path.join(importablePath, "RouteLink.tsx"); -const createDebounce = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "createDebounce.ts", -); +const useRoutes = path.join(importablePath, "useRoutes.ts"); -const navigate = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "navigate.ts", -); +const getLocation = path.join(importablePath, "getLocation.ts"); -const ModulesContext = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "ModulesContext.ts", -); +const createDebounce = path.join(importablePath, "createDebounce.ts"); -const ModulesProvider = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "ModulesProvider.tsx", -); +const navigate = path.join(importablePath, "navigate.ts"); -const useModule = path.join( - __dirname, - "../", - ALEM_VM_FOLDER, - "importable", - "useModule.ts", -); +const ModulesContext = path.join(importablePath, "ModulesContext.ts"); + +const ModulesProvider = path.join(importablePath, "ModulesProvider.tsx"); + +const useModule = path.join(importablePath, "useModule.ts"); // TODO: Trazer outros recursos que não alteram o estado global pra ca, exemplo: promisify, etc module.exports = { + importablePath, RouterContext, RouterProvider, createContext, diff --git a/lib/helpers.js b/lib/helpers.js index 4dcf1bb..a8e33a7 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -121,6 +121,7 @@ const getImportsPath = (fileContent) => { const getFilePathWithType = (filePath) => { const pathSeparator = isWindows ? "\\" : "/"; + // Caso não exista no caminho original passado, procura pelo seu indexador if (fs.existsSync(`${filePath}.ts`)) { return `${filePath}.ts`; } else if (fs.existsSync(`${filePath}.tsx`)) { @@ -138,6 +139,13 @@ const getFilePathWithType = (filePath) => { } else if (fs.existsSync(`${filePath}${pathSeparator}index.jsx`)) { return `${filePath}${pathSeparator}index.jsx`; } + + // Por fim, se passar um caminho na qual o arquivo ja existe, simplesmente + // retorna seu caminho. TEM QUE FICAR AQUI! + if (fs.existsSync(filePath)) { + return filePath; + } + return null; }; diff --git a/lib/parsers/removeGetNameAndLastComma.js b/lib/parsers/removeGetNameAndLastComma.js index c4511c4..43e8aad 100644 --- a/lib/parsers/removeGetNameAndLastComma.js +++ b/lib/parsers/removeGetNameAndLastComma.js @@ -1,7 +1,9 @@ -// Troca o "const Get = " por "", isso porque o módulo é a função diretamente +// Troca o "const Get = " por "()", isso porque o módulo é a função diretamente // e remove o último ";" encontrado porque a função vai ser colocar em uma lista de objetos, ou seja, // vai ter um "," separando cada objeto. const removeGetNameAndLastComma = (code) => { + // WARNING: enviar as props do escopo local como referencia para os módulos causa um perda significativa de desempenho + // return code.replace("const Get = ()", "(props)").replace(/;(?=[^;]*$)/, ""); return code.replace("const Get = ", "").replace(/;(?=[^;]*$)/, ""); }; diff --git a/lib/parsers/transformAsyncAwait.js b/lib/parsers/transformAsyncAwait.js index 87b40c8..6e0e1c2 100644 --- a/lib/parsers/transformAsyncAwait.js +++ b/lib/parsers/transformAsyncAwait.js @@ -1,3 +1,4 @@ +// TODO: INFO: Não está sendo usado. Se nao for usado por muito tempo, deletar arquivo /** * Experimental: Gera uma estrutura entendível pelo Near VM a partir de um await * @param {string} code From 2b7e9dadeb5f15be0eed3594ba76941efc9e879b Mon Sep 17 00:00:00 2001 From: Wenderson Pires Date: Mon, 29 Apr 2024 04:31:13 -0300 Subject: [PATCH 12/12] added execution time logs --- lib/actions/transformSchemaToWidget.js | 52 +++++++++++++++++++++++++- lib/compiler.js | 10 +++++ lib/helpers.js | 7 ++++ lib/parsers/extractJSXElements.js | 1 - 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/lib/actions/transformSchemaToWidget.js b/lib/actions/transformSchemaToWidget.js index 2239673..7c0b166 100644 --- a/lib/actions/transformSchemaToWidget.js +++ b/lib/actions/transformSchemaToWidget.js @@ -10,6 +10,7 @@ const { convertObjectToArray, getFilePathWithType, removeDuplicatedValuesFromArray, + millisToMinutesAndSeconds, } = require("../helpers"); const { process_file_content, removeImports } = require("../parse"); const { @@ -808,15 +809,24 @@ const injectModulesDependencies = (fileSchemas, fileSchema) => { * the process starts. This is util to inject previous schema files like Além importable items. */ const transformSchemaToWidget = (fileSchemas) => { + const showLogs = process.env.SHOW_EXECUTION_TIME === "true"; // TODO: trocar esse nome, transformSchemaToWidget -> transformSchemaToWidgetSchema // Reset error state processError = null; // Gera o primeiro finalFileBundle e widgetName(para Widgets somente), parametros e imports + let start = Date.now(); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = processSchema(fileSchema); }); + let end = Date.now(); + if (showLogs) { + console.log( + `processSchema -> Execution time: ${millisToMinutesAndSeconds(end - start)} minutes`, + ); + } + start = Date.now(); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = prepareListOfModulesToInject( fileSchemas, @@ -828,7 +838,14 @@ const transformSchemaToWidget = (fileSchemas) => { // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); // } }); + end = Date.now(); + if (showLogs) { + console.log( + `prepareListOfModulesToInject -> Execution time: ${millisToMinutesAndSeconds(end - start)} minutes`, + ); + } + start = Date.now(); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { // if (fileSchema.filePath.includes("src/Main.tsx")) { // console.log("========== BBBB"); @@ -842,14 +859,20 @@ const transformSchemaToWidget = (fileSchemas) => { fileSchemas, fileSchema, ); - // if (fileSchema.filePath.includes("src/Main.tsx")) { // console.log("========== AAAA"); // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); // } }); + end = Date.now(); + if (showLogs) { + console.log( + `swapComponentsForStatelessFiles -> Execution time: ${millisToMinutesAndSeconds(end - start)} minutes`, + ); + } // Prepara lista de arquivos a serem injetados dentro de cada arquivo + start = Date.now(); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = prepareListOfInjections( fileSchemas, @@ -861,6 +884,12 @@ const transformSchemaToWidget = (fileSchemas) => { // console.log("fileSchemas[fileSchemaIndex]", fileSchemas[fileSchemaIndex]); // } }); + end = Date.now(); + if (showLogs) { + console.log( + `prepareListOfInjections -> Execution time: ${millisToMinutesAndSeconds(end - start)} minutes`, + ); + } // FOO // fileSchemas.forEach((fileSchema, fileSchemaIndex) => { @@ -871,20 +900,34 @@ const transformSchemaToWidget = (fileSchemas) => { // conflitos na carga de modulos, se liberar módulos, o corpo deles vao ser injetados como arquivos // stateless causando assim duplicidade já que eles sao inseridos como módulos também. // Copia e cola o conteúdo de arquivos não .tsx | .jsx para dentro dos arquivos que dependem deles + start = Date.now(); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = injectFilesDependencies( fileSchemas, fileSchema, ); }); + end = Date.now(); + if (showLogs) { + console.log( + `injectFilesDependencies -> Execution time: ${millisToMinutesAndSeconds(end - start)} minutes`, + ); + } // Injeta as dependencias dos arquivos módulos dentro deles + start = Date.now(); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = injectModulesDependencies( fileSchemas, fileSchema, ); }); + end = Date.now(); + if (showLogs) { + console.log( + `injectModulesDependencies -> Execution time: ${millisToMinutesAndSeconds(end - start)} minutes`, + ); + } // Faz transformação de async/await para o formato promisify // fileSchemas.forEach((fileSchema, fileSchemaIndex) => { @@ -898,9 +941,16 @@ const transformSchemaToWidget = (fileSchemas) => { // ATENÇÃO: muda tanto o "finalFileBundle" quanto o "jsContent" // Injeta os módulos caso sejam requeridos + start = Date.now(); fileSchemas.forEach((fileSchema, fileSchemaIndex) => { fileSchemas[fileSchemaIndex] = injectModules(fileSchemas, fileSchema); }); + end = Date.now(); + if (showLogs) { + console.log( + `injectModules -> Execution time: ${millisToMinutesAndSeconds(end - start)} minutes`, + ); + } return { fileSchemas, processError }; }; diff --git a/lib/compiler.js b/lib/compiler.js index 59e9ebf..c120583 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -14,6 +14,7 @@ const injectModules = require("./actions/injectModules"); const applyOptions = require("./actions/applyOptions"); const injectFoundRegExps = require("./actions/injectFoundRegExps"); const createSuspenseWidget = require("./actions/createSuspenseWidget"); +const { millisToMinutesAndSeconds } = require("./helpers"); const distFolder = process.env.DIST_FOLDER || "build"; @@ -136,8 +137,17 @@ function compile_files(opts) { process.exit(1); } + const showLogs = process.env.SHOW_EXECUTION_TIME === "true"; + if (showLogs) console.log("Starting compiler process..."); // Load project files + let start = Date.now(); const filesInfo = loadFilesInfo(entryFile); + let end = Date.now(); + if (showLogs) { + console.log( + `loadFilesInfo -> Execution time: ${millisToMinutesAndSeconds(end - start)} sec`, + ); + } // Salva o esquema inicial. Bom para log // fs.writeFileSync( diff --git a/lib/helpers.js b/lib/helpers.js index a8e33a7..ab00e0f 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -630,6 +630,12 @@ function escapeHtmlEntities(str) { const exportDefaultFunctionToFunction = (code) => code.replaceAll(/\bexport default function\b/g, "function"); +function millisToMinutesAndSeconds(millis) { + var minutes = Math.floor(millis / 60000); + var seconds = ((millis % 60000) / 1000).toFixed(0); + return minutes + ":" + (seconds < 10 ? "0" : "") + seconds; +} + module.exports = { getFileImportsElements, getImportStatements, @@ -658,4 +664,5 @@ module.exports = { cleanErrorMessage, escapeHtmlEntities, exportDefaultFunctionToFunction, + millisToMinutesAndSeconds, }; diff --git a/lib/parsers/extractJSXElements.js b/lib/parsers/extractJSXElements.js index 12e82c4..b43dccb 100644 --- a/lib/parsers/extractJSXElements.js +++ b/lib/parsers/extractJSXElements.js @@ -56,7 +56,6 @@ function extractJSXElements(code, filterByElementType) { }, }); } catch (error_) { - console.log("CODE:", code); error = `The compiler was unable to process this line. Try another way. \n${error_}`; }