versacompiler 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ import * as Parser from 'acorn';
2
+ /**
3
+ * Parses the given JavaScript code using Acorn and returns the Abstract Syntax Tree (AST).
4
+ *
5
+ * @param {string} data - The JavaScript code to be parsed.
6
+ * @returns {Promise<Object|null>} The parsed AST object if successful, or null if an error occurs.
7
+ * @throws {Error} If there is an error during parsing, it logs the error details and stack trace.
8
+ */
9
+ export const checkSintaxysAcorn = async data => {
10
+ try {
11
+ const ast = Parser.parse(data, {
12
+ ecmaVersion: 2020,
13
+ sourceType: 'module',
14
+ locations: true,
15
+ ranges: true,
16
+ });
17
+
18
+ return { ast, error: null };
19
+ } catch (error) {
20
+ console.log(
21
+ chalk.red(
22
+ `🚩 :Error durante la compilación JS:${error.loc.line}:${error.loc.column}: ${error.message}\n`,
23
+ ),
24
+ );
25
+ console.error(error.stack); // Imprime la pila de llamadas para depuración
26
+ return { ast: null, error };
27
+ }
28
+ };
@@ -0,0 +1,55 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import chalk from 'chalk';
3
+ export const linter = async filePath => {
4
+ // 1. Calcula la ruta del binario
5
+ const oxlintExe = 'npx oxlint';
6
+ const args = filePath ? [filePath] : [];
7
+ // const args = filePath ? [`${filePath}`] : [];
8
+ const processOXC = spawnSync(
9
+ oxlintExe,
10
+ args, // Ejecuta en lote (ej: ['src/**/*.js', 'lib/*.ts', 'lib/*.vue'])
11
+ {
12
+ stdio: 'pipe',
13
+ encoding: 'utf-8', // Evita .toString()
14
+ shell: true, // ¡Más rápido sin shell!
15
+ },
16
+ );
17
+ if (processOXC.error) {
18
+ console.error(
19
+ chalk.red('🚨 Error ejecutando oxlint:', processOXC.error),
20
+ );
21
+ return false;
22
+ }
23
+
24
+ const output = processOXC.stdout.trim();
25
+ if (!output) {
26
+ console.log(chalk.green('✅ No se encontraron errores de linting.'));
27
+ return true;
28
+ }
29
+
30
+ // Regex optimizado (unificado)
31
+ const LINT_REGEX =
32
+ /([×x!]|warning)\s+([^:]+):\s+([^\n]+)\n\s+[,╭][-─]\[([^\]]+)\][\s\S]+?help:\s+([^\n]+)/gi;
33
+ const matches = output.matchAll(LINT_REGEX);
34
+ let errorFiles = 0; // Reiniciar el contador de archivos con errores
35
+ const errorList = []; // Lista de errores
36
+ for (const match of matches) {
37
+ const [_, severitySymbol, ruleId, message, filePath, help] = match;
38
+ const normalizedPath = filePath.trim().replace(/\\/g, '/');
39
+
40
+ errorFiles++;
41
+ errorList.push({
42
+ file: normalizedPath,
43
+ error: `${ruleId}: ${message.trim()}`,
44
+ proceso: 'Linting',
45
+ help: help.trim(),
46
+ severity: severitySymbol === '!' ? 'warning' : 'error',
47
+ });
48
+ }
49
+
50
+ return {
51
+ error: errorFiles > 0,
52
+ errorFiles,
53
+ errorList,
54
+ };
55
+ };
@@ -0,0 +1,31 @@
1
+ import { minify } from 'oxc-minify';
2
+
3
+ /**
4
+ * Minifica el codigo JavaScript usando opciones especificas.
5
+ *
6
+ * @param {string} data - The JavaScript code to be minified.
7
+ * @param {string} filename - The name of the file containing the JavaScript code.
8
+ * @param {boolean} isProd - Indica si está en modo producción.
9
+ * @returns {Promise<Object>} The result of the minification process.
10
+ */
11
+ export const minifyJS = async (data, filename, isProd = false) => {
12
+ try {
13
+ const options = {
14
+ compress: {
15
+ target: 'esnext',
16
+ },
17
+ mangle: {
18
+ toplevel: true,
19
+ debug: isProd ? false : true,
20
+ },
21
+ codegen: {
22
+ removeWhitespace: true,
23
+ },
24
+ sourcemap: false,
25
+ };
26
+ const result = await minify(filename, data, options);
27
+ return { code: result.code, error: null };
28
+ } catch (error) {
29
+ return { error, code: '' };
30
+ }
31
+ };
@@ -0,0 +1,89 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import * as ts from 'typescript';
4
+ /**
5
+ * Precompila el código TypeScript proporcionado.
6
+ * @param {string} data - El código TypeScript a precompilar.
7
+ * @param {string} fileName - El nombre del archivo que contiene el código TypeScript.
8
+ *
9
+ * @returns {Promise<Object>} - Un objeto con el código precompilado o un error.
10
+ */
11
+ export const preCompileTS = async (data, fileName, PATH_CONFIG_FILE) => {
12
+ try {
13
+ // Leer tsconfig.json
14
+ const tsConfigContent = await readFile(PATH_CONFIG_FILE, 'utf-8');
15
+ if (!tsConfigContent) {
16
+ throw new Error(
17
+ `No se pudo leer el archivo tsconfig.json en: ${PATH_CONFIG_FILE}`,
18
+ );
19
+ }
20
+
21
+ const tsConfig = JSON.parse(tsConfigContent);
22
+
23
+ // Obtener las opciones del compilador
24
+ const { compilerOptions } = tsConfig;
25
+
26
+ if (!compilerOptions) {
27
+ throw new Error(
28
+ 'No se encontraron compilerOptions en tsconfig.json',
29
+ );
30
+ }
31
+
32
+ // Crear host de configuración de parseo
33
+ const parseConfigHost = {
34
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
35
+ readDirectory: ts.sys.readDirectory,
36
+ fileExists: ts.sys.fileExists,
37
+ readFile: ts.sys.readFile,
38
+ onUnRecoverableConfigFileDiagnostic: diagnostic => {
39
+ throw new Error(
40
+ ts.flattenDiagnosticMessageText(
41
+ diagnostic.messageText,
42
+ '\n',
43
+ ),
44
+ );
45
+ },
46
+ };
47
+
48
+ // Parsear la configuración para que TS la entienda
49
+ const parsedConfig = ts.parseJsonConfigFileContent(
50
+ tsConfig,
51
+ parseConfigHost,
52
+ path.dirname(PATH_CONFIG_FILE),
53
+ );
54
+ if (parsedConfig.errors.length) {
55
+ const errors = parsedConfig.errors.map(diagnostic =>
56
+ ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
57
+ );
58
+ throw new Error(
59
+ `Error al parsear tsconfig.json:\n${errors.join('\n')}`,
60
+ );
61
+ }
62
+
63
+ // Transpilar el código
64
+ const result = ts.transpileModule(data, {
65
+ compilerOptions: parsedConfig.options,
66
+ reportDiagnostics: true,
67
+ fileName,
68
+ });
69
+ if (result.diagnostics && result.diagnostics.length > 0) {
70
+ const errors = result.diagnostics.map(diagnostic => {
71
+ if (diagnostic.file) {
72
+ const { line, character } =
73
+ diagnostic.file.getLineAndCharacterOfPosition(
74
+ diagnostic.start,
75
+ );
76
+ return `${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')} - ${diagnostic.file.fileName} (${line + 1},${character + 1})`;
77
+ } else {
78
+ return `${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`;
79
+ }
80
+ });
81
+
82
+ throw new Error(`${errors.join('\n')}`);
83
+ }
84
+
85
+ return { error: null, data: result.outputText };
86
+ } catch (error) {
87
+ return { error, data: null };
88
+ }
89
+ };
@@ -0,0 +1,235 @@
1
+ import * as vCompiler from 'vue/compiler-sfc';
2
+ import path from 'node:path';
3
+
4
+ const getComponentsVue = async data => {
5
+ let components = [];
6
+
7
+ const importRegExp = /import\s+[\s\S]*?\s+from\s+['"].*['"];/g;
8
+
9
+ const _a = data.replace(importRegExp, match => {
10
+ const ruta = match.match(/from\s+['"](.*)['"];/)[1];
11
+
12
+ if (ruta.endsWith('.vue')) {
13
+ const resultVue = match.match(/from\s+['"](.+\/(\w+))\.vue['"];/);
14
+ if (resultVue) {
15
+ const fullPath = resultVue[1].replace('.vue', '');
16
+ const fileName = resultVue[2];
17
+ components.push(fileName);
18
+ return `import ${fileName} from '${fullPath}.js';`;
19
+ }
20
+ }
21
+ return match; // Devolver el match original si no se cumple ninguna condición
22
+ });
23
+
24
+ return components;
25
+ };
26
+
27
+ /**
28
+ * Compila un bloque personalizado.
29
+ * @param {Object} block - El bloque personalizado a compilar.
30
+ * @param {string} source - La fuente del bloque.
31
+ */
32
+ const _compileCustomBlock = async (_block, _source) => {};
33
+
34
+ /**
35
+ * Precompila un componente Vue.
36
+ * @param {string} data - El código del componente Vue.
37
+ * @param {string} source - La fuente del componente Vue.
38
+ * @returns {Promise<Object>} - Un objeto con el código precompilado o un error.
39
+ */
40
+ export const preCompileVue = async (data, source, isProd = false) => {
41
+ try {
42
+ const fileName = path.basename(source).replace('.vue', '');
43
+ const { descriptor, errors } = vCompiler.parse(data, {
44
+ filename: fileName,
45
+ sourceMap: false,
46
+ sourceRoot: path.dirname(source),
47
+ });
48
+
49
+ if (errors.length) {
50
+ throw new Error(
51
+ `Error al analizar el componente Vue ${source}:\n${errors.map(e => e.message).join('\n')}`,
52
+ );
53
+ }
54
+
55
+ const id = Math.random().toString(36).slice(2, 12);
56
+ const scopeId = descriptor.styles.some(s => s.scoped)
57
+ ? `data-v-${id}`
58
+ : null;
59
+ const templateOptions = {
60
+ sourceMap: false,
61
+ filename: `${fileName}.vue`,
62
+ id,
63
+ scoped: !!scopeId,
64
+ slotted: descriptor.slotted,
65
+ source: descriptor.template?.content,
66
+ comments: isProd ? false : 'all',
67
+ isProd,
68
+ compilerOptions: {
69
+ scopeId,
70
+ mode: 'module',
71
+ isProd,
72
+ inlineTemplate: true,
73
+ prefixIdentifiers: true,
74
+ hoistStatic: true,
75
+ cacheHandlers: true,
76
+ runtimeGlobalName: 'Vue',
77
+ runtimeModuleName: 'vue',
78
+ optimizeBindings: true,
79
+ runtimeContextBuiltins: true,
80
+ runtimeDirectives: true,
81
+ runtimeVNode: true,
82
+ runtimeProps: true,
83
+ runtimeSlots: true,
84
+ runtimeComponents: true,
85
+ runtimeCompiledRender: true,
86
+ whitespace: 'condense',
87
+ ssrCssExternal: true,
88
+ ssr: false,
89
+ nodeTransforms: [],
90
+ directiveTransforms: {},
91
+ },
92
+ };
93
+
94
+ // Compile script
95
+ let compiledScript;
96
+ if (descriptor.script || descriptor.scriptSetup) {
97
+ const scriptDescriptor =
98
+ descriptor.script || descriptor.scriptSetup;
99
+
100
+ compiledScript = {
101
+ content: descriptor.script
102
+ ? scriptDescriptor.content
103
+ : vCompiler.compileScript(descriptor, {
104
+ id,
105
+ templateOptions,
106
+ }).content,
107
+ lang:
108
+ scriptDescriptor.lang === 'ts' ||
109
+ scriptDescriptor.lang === 'typescript'
110
+ ? 'ts'
111
+ : 'js',
112
+ };
113
+ } else {
114
+ compiledScript = { content: `export default {}`, lang: 'js' };
115
+ }
116
+
117
+ // Compile template y obtener el contenido del template
118
+ const compiledTemplate = descriptor.template?.content // Usar optional chaining
119
+ ? vCompiler.compileTemplate({
120
+ ...templateOptions,
121
+ })
122
+ : { code: '' }; // Manejar caso sin template
123
+ if (!descriptor.template?.content) {
124
+ console.warn(
125
+ chalk.yellow(
126
+ `Advertencia: El componente Vue ${source} no tiene una sección de plantilla.`,
127
+ ),
128
+ );
129
+ }
130
+
131
+ let customBlocks = '';
132
+ if (descriptor.customBlocks.length > 0) {
133
+ // eliminar el ultimo caracter que es un punto y coma
134
+ customBlocks =
135
+ descriptor?.customBlocks[0].content.slice(0, -1) ?? '';
136
+ }
137
+
138
+ // Compile styles Y obtener el contenido de los estilos
139
+ const compiledStyles = descriptor.styles.map(style =>
140
+ vCompiler.compileStyle({
141
+ id,
142
+ source: style.content,
143
+ scoped: style.scoped,
144
+ preprocessLang: style.lang,
145
+ isProd,
146
+ trim: true,
147
+ filename: `${fileName}.vue`,
148
+ }),
149
+ );
150
+
151
+ const insertStyles = compiledStyles.length
152
+ ? `(function(){
153
+ let styleTag = document.createElement('style');
154
+ styleTag.setAttribute('data-v-${id}', '');
155
+ styleTag.innerHTML = \`${compiledStyles.map(s => s.code).join('\n')}\`;
156
+ document.head.appendChild(styleTag);
157
+ })();`
158
+ : '';
159
+
160
+ // Combine all parts into a single module
161
+ let output = `
162
+ ${insertStyles}
163
+ ${compiledScript.content}
164
+ ${compiledTemplate.code}
165
+ `;
166
+ //añardir instancia de app
167
+
168
+ // output = `${appImport}${output}`;
169
+
170
+ const componentName = `${fileName}_component`;
171
+ const components = await getComponentsVue(data);
172
+ const exportComponent = `
173
+ __file: '${source}',
174
+ __name: '${fileName}',
175
+ name: '${fileName}',
176
+ components: { ${components.join(', ')} },
177
+ `;
178
+
179
+ // quitamos export default y añadimos el nombre del archivo
180
+ if (output.includes('export default {')) {
181
+ output = output.replace(
182
+ 'export default {',
183
+ `const ${componentName} = {
184
+ \n${exportComponent}
185
+ `,
186
+ );
187
+ } else {
188
+ output = output.replace(
189
+ 'export default /*@__PURE__*/_defineComponent({',
190
+ `const ${componentName} = /*@__PURE__*/_defineComponent({
191
+ \n${exportComponent}
192
+ `,
193
+ );
194
+ }
195
+
196
+ // reemplazamos cuando usamos script setup
197
+ if (descriptor.scriptSetup) {
198
+ output = output.replaceAll(/_ctx\.(?!\$)/g, '$setup.');
199
+ output = output.replace(
200
+ 'export function render(_ctx, _cache) {',
201
+ `function render(_ctx, _cache, $props, $setup, $data, $options) {`,
202
+ );
203
+ } else {
204
+ output = output.replace(
205
+ 'export function render(_ctx, _cache) {',
206
+ `function render(_ctx, _cache, $props, $setup, $data, $options) {`,
207
+ );
208
+ }
209
+
210
+ const finishComponent = `
211
+ ${componentName}.render = render;
212
+ ${scopeId ? `${componentName}.__scopeId = '${scopeId}';` : ''}
213
+ ${customBlocks}
214
+
215
+ export default ${componentName};
216
+ `;
217
+
218
+ output = `${output}\n${finishComponent}`;
219
+
220
+ // await writeFile(
221
+ // `./public/dashboard/js/${fileName}-temp.js`,
222
+ // output,
223
+ // 'utf-8',
224
+ // );
225
+ // await unlink(`./public/dashboard/js/${fileName}-temp.js`);
226
+
227
+ return {
228
+ lang: compiledScript.lang,
229
+ error: null,
230
+ data: output,
231
+ };
232
+ } catch (error) {
233
+ return { lang: null, error, data: null }; // Devolver error en objeto
234
+ }
235
+ };
@@ -0,0 +1,26 @@
1
+ import path from 'node:path';
2
+ /**
3
+ * Converts a 24-hour time string to a 12-hour time string with AM/PM.
4
+ *
5
+ * @param {number} timing - The value of the timing en miliseconds.
6
+ * @returns {string} the timing in ms, seconds, minutes or hours.
7
+ */
8
+ export const showTimingForHumans = timing => {
9
+ if (timing < 1000) {
10
+ return `${timing} ms`;
11
+ } else if (timing < 60000) {
12
+ return `${timing / 1000} s`;
13
+ } else if (timing < 3600000) {
14
+ return `${timing / 60000} min`;
15
+ } else {
16
+ return `${timing / 3600000} h`;
17
+ }
18
+ };
19
+
20
+ /**
21
+ * Mapea una ruta de origen a una ruta de destino en el directorio de distribución.
22
+ * @param {string} ruta - La ruta de origen.
23
+ * @returns {Promise<string>} - La ruta mapeada en el directorio de distribución.
24
+ */
25
+ export const mapRuta = async (ruta, PATH_DIST) =>
26
+ path.join(PATH_DIST, path.relative(PATH_SOURCE, ruta));
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "versacompiler",
3
+ "version": "1.0.0",
4
+ "description": "Una herramienta para compilar y minificar archivos .vue, .js y .ts para proyectos de Vue 3 con soporte para TypeScript.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "versacompiler": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "LICENSE",
12
+ "README.md"
13
+ ],
14
+ "type": "module",
15
+ "scripts": {
16
+ "dev": "node --watch dist/index.js",
17
+ "compile": "node dist/index.js",
18
+ "compile-dev": "node dist/index.js --all",
19
+ "compile-prod": "node dist/index.js --all --prod"
20
+ },
21
+ "keywords": [
22
+ "vue",
23
+ "compiler",
24
+ "minifier",
25
+ "vue3",
26
+ "versacompiler",
27
+ "typescript"
28
+ ],
29
+ "author": "Jorge Jara H (kriollone@gmail.com)",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "chalk": "5.4.1",
33
+ "chokidar": "^4.0.3",
34
+ "oxc-minify": "^0.67.0",
35
+ "acorn": "^8.14.1",
36
+ "vue": "3.5.13",
37
+ "get-port": "^7.1.0",
38
+ "typescript": "^5.8.3",
39
+ "oxlint": "^0.16.9",
40
+ "browser-sync": "^3.0.4"
41
+ },
42
+ "devDependencies": {
43
+ "prettier": "3.5.3",
44
+ "@types/node": "^22.15.3",
45
+ "code-tag": "^1.2.0"
46
+ }
47
+ }