vatts 1.2.5 → 1.3.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.
package/dist/builder.js CHANGED
@@ -29,6 +29,15 @@ const Console = require("./api/console").default;
29
29
  const fs = require('fs');
30
30
  const { readdir, stat, rm } = require("node:fs/promises");
31
31
  const { loadTsConfigPaths, resolveTsConfigAlias } = require('./tsconfigPaths');
32
+ // --- Optimization Plugins ---
33
+ let terser, replace;
34
+ try {
35
+ terser = require('@rollup/plugin-terser');
36
+ replace = require('@rollup/plugin-replace');
37
+ }
38
+ catch (e) {
39
+ Console.warn("Optimization plugins (@rollup/plugin-terser, @rollup/plugin-replace) not found. Build will be larger.");
40
+ }
32
41
  // Import Framework specific builders
33
42
  const { createReactConfig } = require('./react/react.build');
34
43
  const { createVueConfig } = require('./vue/vue.build');
@@ -61,7 +70,7 @@ const markdownPlugin = () => {
61
70
  }
62
71
  };
63
72
  };
64
- const customPostCssPlugin = (isProduction) => {
73
+ const customPostCssPlugin = (isProduction, isWatch = false) => {
65
74
  let cachedProcessor = null;
66
75
  let configLoaded = false;
67
76
  const initPostCss = async (projectDir) => {
@@ -128,9 +137,15 @@ const customPostCssPlugin = (isProduction) => {
128
137
  };
129
138
  return {
130
139
  name: 'custom-postcss-plugin',
140
+ // O load hook anterior estava causando conflitos com o watch interno do Rollup.
141
+ // Removemos ele e usamos addWatchFile no transform.
131
142
  async transform(code, id) {
132
143
  if (!id.endsWith('.css'))
133
144
  return null;
145
+ // Garante que o Rollup vigie este arquivo explicitamente
146
+ if (isWatch) {
147
+ this.addWatchFile(id);
148
+ }
134
149
  const processor = await initPostCss(process.cwd());
135
150
  let processedCss = code;
136
151
  if (processor) {
@@ -143,15 +158,29 @@ const customPostCssPlugin = (isProduction) => {
143
158
  }
144
159
  }
145
160
  const cleanName = path.basename(id).split('?')[0];
161
+ // Sanitiza o nome para usar como ID no DOM
162
+ const safeId = cleanName.replace(/[^a-zA-Z0-9-_]/g, '_');
146
163
  const referenceId = this.emitFile({ type: 'asset', name: cleanName, source: processedCss });
164
+ // Lógica melhorada para injetar o CSS:
165
+ // 1. Usa um ID único para evitar tags <link> duplicadas.
166
+ // 2. Adiciona um timestamp (?t=...) no href se estiver em dev para quebrar o cache do navegador.
147
167
  return {
148
168
  code: `
149
169
  const cssUrl = String(import.meta.ROLLUP_FILE_URL_${referenceId});
150
170
  if (typeof document !== 'undefined') {
151
- const link = document.createElement('link');
152
- link.rel = 'stylesheet';
153
- link.href = cssUrl;
154
- document.head.appendChild(link);
171
+ const linkId = 'vatts-css-' + "${safeId}";
172
+ let link = document.getElementById(linkId);
173
+
174
+ if (!link) {
175
+ link = document.createElement('link');
176
+ link.id = linkId;
177
+ link.rel = 'stylesheet';
178
+ document.head.appendChild(link);
179
+ }
180
+
181
+ // Em dev, força o reload do CSS adicionando timestamp
182
+ const timestamp = ${isWatch ? 'Date.now()' : 'null'};
183
+ link.href = timestamp ? (cssUrl + '?t=' + timestamp) : cssUrl;
155
184
  }
156
185
  export default cssUrl;
157
186
  `,
@@ -224,6 +253,54 @@ const nodeBuiltIns = [
224
253
  'querystring', 'readline', 'stream', 'string_decoder', 'tls', 'tty', 'url',
225
254
  'util', 'v8', 'vm', 'zlib', 'module', 'worker_threads', 'perf_hooks'
226
255
  ];
256
+ // --- Optimization Logic ---
257
+ function getOptimizationPlugins(isProduction) {
258
+ const plugins = [];
259
+ const env = isProduction ? 'production' : 'development';
260
+ if (replace) {
261
+ plugins.push(replace({
262
+ 'process.env.NODE_ENV': JSON.stringify(env),
263
+ 'process.env': JSON.stringify({ NODE_ENV: env }),
264
+ 'process.browser': 'true',
265
+ '__REACT_DEVTOOLS_GLOBAL_HOOK__': '({ isDisabled: true })',
266
+ preventAssignment: true,
267
+ objectGuards: true
268
+ }));
269
+ }
270
+ if (isProduction && terser) {
271
+ plugins.push(terser({
272
+ ecma: 2020,
273
+ module: true,
274
+ toplevel: true,
275
+ compress: {
276
+ passes: 3,
277
+ pure_getters: true,
278
+ unsafe: true,
279
+ unsafe_arrows: true,
280
+ unsafe_methods: true,
281
+ unsafe_proto: true,
282
+ booleans_as_integers: true,
283
+ drop_console: true,
284
+ drop_debugger: true,
285
+ keep_fargs: false,
286
+ hoist_funs: true,
287
+ hoist_vars: true,
288
+ reduce_funcs: true,
289
+ reduce_vars: true,
290
+ pure_funcs: ['console.info', 'console.debug', 'console.warn', 'console.log', 'Object.freeze']
291
+ },
292
+ mangle: {
293
+ properties: false,
294
+ toplevel: true,
295
+ },
296
+ format: {
297
+ comments: false,
298
+ wrap_func_args: false,
299
+ }
300
+ }));
301
+ }
302
+ return plugins;
303
+ }
227
304
  // --- Core Logic ---
228
305
  function detectFramework(projectDir = process.cwd()) {
229
306
  try {
@@ -240,15 +317,13 @@ function detectFramework(projectDir = process.cwd()) {
240
317
  catch (e) { }
241
318
  return 'react';
242
319
  }
243
- async function getFrameworkConfig(entryPoint, outdir, isProduction) {
320
+ async function getFrameworkConfig(entryPoint, outdir, isProduction, isWatch = false) {
244
321
  const framework = detectFramework();
245
- // Plugins que rodam ANTES da resolução de módulos (Configuração, Bloqueios de Framework incorreto)
246
322
  const prePlugins = [
247
323
  tsconfigPathsPlugin(process.cwd()),
248
324
  {
249
325
  name: 'block-vue-artifacts-generic',
250
326
  load(id) {
251
- // Se não for vue, bloqueia artifacts. O build.vue lida com o inverso.
252
327
  if (framework !== 'vue' && (id.endsWith('.vue') || id.endsWith('.vue.js'))) {
253
328
  return 'export default {};';
254
329
  }
@@ -256,47 +331,102 @@ async function getFrameworkConfig(entryPoint, outdir, isProduction) {
256
331
  }
257
332
  }
258
333
  ];
259
- // Plugins que rodam DEPOIS da transformação do Framework (Markdown, CSS, Assets)
260
- // Isso é crucial para o Vue, pois o CSS é gerado dinamicamente e precisa ser pego aqui.
261
334
  const postPlugins = [
262
335
  markdownPlugin(),
263
- customPostCssPlugin(isProduction),
336
+ customPostCssPlugin(isProduction, isWatch),
264
337
  smartAssetPlugin(isProduction)
265
338
  ];
266
339
  const pluginConfig = { prePlugins, postPlugins };
340
+ let config;
267
341
  if (framework === 'vue') {
268
- return await createVueConfig(entryPoint, outdir, isProduction, pluginConfig);
342
+ config = await createVueConfig(entryPoint, outdir, isProduction, pluginConfig);
269
343
  }
270
344
  else {
271
- return await createReactConfig(entryPoint, outdir, isProduction, pluginConfig);
345
+ config = await createReactConfig(entryPoint, outdir, isProduction, pluginConfig);
272
346
  }
347
+ return config;
273
348
  }
274
349
  // --- Build Functions ---
275
350
  async function buildWithChunks(entryPoint, outdir, isProduction = false) {
276
351
  await cleanDirectoryExcept(outdir, 'temp');
277
352
  try {
278
- const inputOptions = await getFrameworkConfig(entryPoint, outdir, isProduction);
353
+ const inputOptions = await getFrameworkConfig(entryPoint, outdir, isProduction, false);
279
354
  inputOptions.external = nodeBuiltIns;
355
+ if (isProduction) {
356
+ inputOptions.treeshake = {
357
+ preset: 'smallest',
358
+ moduleSideEffects: (id) => !id.includes('node_modules') || id.endsWith('.css') || id.includes('entry.client'),
359
+ propertyReadSideEffects: false,
360
+ tryCatchDeoptimization: false
361
+ };
362
+ }
363
+ const optimizationPlugins = getOptimizationPlugins(isProduction);
364
+ const replacePlugin = optimizationPlugins.find(p => p.name === 'replace');
365
+ const otherPlugins = optimizationPlugins.filter(p => p.name !== 'replace');
366
+ if (replacePlugin)
367
+ inputOptions.plugins.unshift(replacePlugin);
368
+ inputOptions.plugins.push(...otherPlugins);
369
+ const processPolyfill = `var process = { env: { NODE_ENV: "${isProduction ? 'production' : 'development'}" } };`;
280
370
  const outputOptions = {
281
371
  dir: outdir,
282
372
  format: 'es',
283
- entryFileNames: isProduction ? 'main-[hash].js' : 'main.js',
284
- chunkFileNames: 'chunks/[name]-[hash].js',
285
- assetFileNames: 'assets/[name]-[hash][extname]',
373
+ entryFileNames: isProduction ? 'main.[hash].js' : 'main.js',
374
+ chunkFileNames: 'chunks/[name].[hash].js',
375
+ assetFileNames: 'assets/[name].[hash][extname]',
286
376
  sourcemap: !isProduction,
287
377
  compact: isProduction,
378
+ intro: processPolyfill,
288
379
  manualChunks(id) {
289
380
  if (id.includes('node_modules')) {
290
381
  const normalizedId = id.replace(/\\/g, '/');
291
- if (/\/node_modules\/(react|react-dom|scheduler|prop-types|loose-envify|object-assign)\//.test(normalizedId))
292
- return 'vendor-react';
293
- if (/\/node_modules\/(vue|@vue)\//.test(normalizedId))
294
- return 'vendor-vue';
295
- if (id.includes('framer-motion') || id.includes('@radix-ui') || id.includes('@headlessui'))
296
- return 'vendor-ui';
297
- if (id.includes('lodash') || id.includes('date-fns') || id.includes('axios'))
298
- return 'vendor-utils';
299
- return 'vendor';
382
+ // --- VUE SPLITTING AVANÇADO ---
383
+ if (/\/node_modules\/vue-router\//.test(normalizedId))
384
+ return 'vendor-vue-router';
385
+ if (/\/node_modules\/(pinia|vuex)\//.test(normalizedId))
386
+ return 'vendor-vue-store';
387
+ // Separa os modulos internos do Vue para evitar um chunk gigante
388
+ if (/\/node_modules\/(vue|@vue)\//.test(normalizedId)) {
389
+ if (normalizedId.includes('/runtime-core'))
390
+ return 'vendor-vue-runtime-core';
391
+ if (normalizedId.includes('/runtime-dom'))
392
+ return 'vendor-vue-runtime-dom';
393
+ if (normalizedId.includes('/reactivity'))
394
+ return 'vendor-vue-reactivity';
395
+ if (normalizedId.includes('/shared'))
396
+ return 'vendor-vue-shared';
397
+ if (normalizedId.includes('/compiler-'))
398
+ return 'vendor-vue-compiler';
399
+ return 'vendor-vue-core';
400
+ }
401
+ // --- REACT SPLITTING AVANÇADO ---
402
+ // Separa DOM de Core e Scheduler
403
+ if (/\/node_modules\/react-dom\//.test(normalizedId))
404
+ return 'vendor-react-dom';
405
+ if (/\/node_modules\/scheduler\//.test(normalizedId))
406
+ return 'vendor-react-scheduler';
407
+ if (/\/node_modules\/react-router/.test(normalizedId))
408
+ return 'vendor-react-router';
409
+ if (/\/node_modules\/react\//.test(normalizedId))
410
+ return 'vendor-react-core';
411
+ // --- UI LIBS (Granular) ---
412
+ if (id.includes('framer-motion'))
413
+ return 'vendor-framer';
414
+ if (id.includes('@radix-ui'))
415
+ return 'vendor-radix';
416
+ if (id.includes('@headlessui'))
417
+ return 'vendor-headless';
418
+ if (id.includes('@heroicons'))
419
+ return 'vendor-icons';
420
+ // --- UTILS ---
421
+ if (id.includes('lodash'))
422
+ return 'vendor-lodash';
423
+ if (id.includes('date-fns') || id.includes('moment'))
424
+ return 'vendor-date';
425
+ if (id.includes('axios'))
426
+ return 'vendor-axios';
427
+ // Resto cai em vendor-libs genérico para não criar 1 arquivo por pacote,
428
+ // mas já tiramos o peso pesado acima.
429
+ return 'vendor-libs';
300
430
  }
301
431
  }
302
432
  };
@@ -313,15 +443,31 @@ async function build(entryPoint, outfile, isProduction = false) {
313
443
  const outdir = path.dirname(outfile);
314
444
  await cleanDirectoryExcept(outdir, 'temp');
315
445
  try {
316
- const inputOptions = await getFrameworkConfig(entryPoint, outdir, isProduction);
446
+ const inputOptions = await getFrameworkConfig(entryPoint, outdir, isProduction, false);
317
447
  inputOptions.external = nodeBuiltIns;
448
+ if (isProduction) {
449
+ inputOptions.treeshake = {
450
+ preset: 'smallest',
451
+ moduleSideEffects: (id) => !id.includes('node_modules') || id.endsWith('.css') || id.includes('entry.client'),
452
+ propertyReadSideEffects: false
453
+ };
454
+ }
455
+ const optimizationPlugins = getOptimizationPlugins(isProduction);
456
+ const replacePlugin = optimizationPlugins.find(p => p.name === 'replace');
457
+ const otherPlugins = optimizationPlugins.filter(p => p.name !== 'replace');
458
+ if (replacePlugin)
459
+ inputOptions.plugins.unshift(replacePlugin);
460
+ inputOptions.plugins.push(...otherPlugins);
461
+ const processPolyfill = `var process = { env: { NODE_ENV: "${isProduction ? 'production' : 'development'}" } };`;
318
462
  const outputOptions = {
319
463
  file: outfile,
320
464
  format: 'iife',
321
465
  name: 'Vattsjs',
322
466
  sourcemap: !isProduction,
323
467
  inlineDynamicImports: true,
324
- compact: true
468
+ compact: true,
469
+ annotations: true,
470
+ intro: processPolyfill
325
471
  };
326
472
  const bundle = await rollup(inputOptions);
327
473
  await bundle.write(outputOptions);
@@ -332,7 +478,6 @@ async function build(entryPoint, outfile, isProduction = false) {
332
478
  process.exit(1);
333
479
  }
334
480
  }
335
- // --- Watch Functions ---
336
481
  function handleWatcherEvents(watcher, hotReloadManager, resolveFirstBuild) {
337
482
  let currentBuildId = 0;
338
483
  let lastStartedBuildId = 0;
@@ -387,13 +532,19 @@ function handleWatcherEvents(watcher, hotReloadManager, resolveFirstBuild) {
387
532
  async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
388
533
  await cleanDirectoryExcept(outdir, 'temp');
389
534
  try {
390
- const inputOptions = await getFrameworkConfig(entryPoint, outdir, false);
535
+ const inputOptions = await getFrameworkConfig(entryPoint, outdir, false, true);
391
536
  inputOptions.external = nodeBuiltIns;
537
+ const optimizationPlugins = getOptimizationPlugins(false);
538
+ inputOptions.plugins = [...inputOptions.plugins, ...optimizationPlugins];
539
+ const processPolyfill = `var process = { env: { NODE_ENV: "development" } };`;
392
540
  const outputOptions = {
393
541
  dir: outdir,
394
542
  format: 'es',
395
543
  entryFileNames: 'main.js',
396
- sourcemap: true
544
+ // CHANGE: Remove hash in watch mode to prevent file accumulation in assets folder
545
+ assetFileNames: 'assets/[name][extname]',
546
+ sourcemap: true,
547
+ intro: processPolyfill
397
548
  };
398
549
  const watchOptions = {
399
550
  ...inputOptions,
@@ -414,12 +565,18 @@ async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
414
565
  async function watch(entryPoint, outfile, hotReloadManager = null) {
415
566
  const outdir = path.dirname(outfile);
416
567
  try {
417
- const inputOptions = await getFrameworkConfig(entryPoint, outdir, false);
568
+ const inputOptions = await getFrameworkConfig(entryPoint, outdir, false, true);
418
569
  inputOptions.external = nodeBuiltIns;
570
+ const optimizationPlugins = getOptimizationPlugins(false);
571
+ inputOptions.plugins = [...inputOptions.plugins, ...optimizationPlugins];
572
+ const processPolyfill = `var process = { env: { NODE_ENV: "development" } };`;
419
573
  const outputOptions = {
420
574
  file: outfile,
421
575
  format: 'es',
422
- sourcemap: true
576
+ // CHANGE: Remove hash in watch mode to prevent file accumulation
577
+ assetFileNames: 'assets/[name][extname]',
578
+ sourcemap: true,
579
+ intro: processPolyfill
423
580
  };
424
581
  const watchOptions = {
425
582
  ...inputOptions,
package/dist/index.js CHANGED
@@ -557,27 +557,71 @@ function vatts(options) {
557
557
  const requestPath = pathname.replace('/_vatts/', '');
558
558
  if (!isSuspiciousPathname(requestPath)) {
559
559
  const filePath = resolveWithin(staticPath, requestPath);
560
+ // Verifica se existe E se é arquivo
560
561
  if (filePath && fs_1.default.existsSync(filePath) && fs_1.default.statSync(filePath).isFile()) {
562
+ const stats = fs_1.default.statSync(filePath);
561
563
  const ext = path_1.default.extname(filePath).toLowerCase();
562
564
  const contentTypes = {
563
565
  '.js': 'application/javascript',
564
566
  '.css': 'text/css',
565
567
  '.map': 'application/json',
566
- '.vue': 'text/css'
568
+ '.vue': 'text/css',
569
+ '.png': 'image/png',
570
+ '.jpg': 'image/jpeg',
571
+ '.svg': 'image/svg+xml'
567
572
  };
573
+ // CORREÇÃO 1: Cache diferenciado para Dev vs Produção
574
+ if (options.dev) {
575
+ // Em dev, proibimos o cache para garantir que main.js atualize sempre
576
+ genericRes.header('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
577
+ genericRes.header('Pragma', 'no-cache');
578
+ genericRes.header('Expires', '0');
579
+ }
580
+ else {
581
+ // Em produção, mantemos o cache agressivo (assumindo hash nos arquivos exceto entry points)
582
+ // Dica: Se o main.js de prod também não tiver hash, use 'no-cache' nele também
583
+ genericRes.header('Cache-Control', 'public, max-age=31536000, immutable');
584
+ }
585
+ const lastModified = stats.mtime.toUTCString();
586
+ genericRes.header('Last-Modified', lastModified);
568
587
  genericRes.header('Content-Type', contentTypes[ext] || 'text/plain');
588
+ // Lógica 304 (Mantida igual, pois ajuda na performance mesmo em dev se o arquivo n mudou nada)
589
+ const ifModifiedSince = req.headers['if-modified-since'];
590
+ if (ifModifiedSince) {
591
+ const requestDate = new Date(ifModifiedSince).getTime();
592
+ const fileDate = new Date(lastModified).getTime();
593
+ if (requestDate >= fileDate) {
594
+ if (adapter.type === 'express') {
595
+ res.status(304).end();
596
+ }
597
+ else {
598
+ genericRes.status(304);
599
+ genericRes.send(null);
600
+ }
601
+ return;
602
+ }
603
+ }
604
+ // Envia o arquivo
569
605
  if (adapter.type === 'express') {
570
606
  res.sendFile(filePath);
571
607
  }
572
- else if (adapter.type === 'fastify') {
608
+ else {
573
609
  const fileContent = fs_1.default.readFileSync(filePath);
574
610
  genericRes.send(fileContent);
575
611
  }
576
- else if (adapter.type === 'native') {
577
- const fileContent = fs_1.default.readFileSync(filePath);
578
- genericRes.send(fileContent);
612
+ return; // Encerra aqui com sucesso
613
+ }
614
+ else {
615
+ // CORREÇÃO 2: Se o arquivo não existe, retornamos 404 AQUI.
616
+ // Isso impede que o código continue e caia na rota de renderização do HTML (SPA Fallback)
617
+ if (adapter.type === 'express') {
618
+ res.status(404).send('Vatts Asset Not Found');
579
619
  }
580
- return;
620
+ else {
621
+ genericRes.status(404);
622
+ genericRes.send('Vatts Asset Not Found');
623
+ }
624
+ return; // Mata a requisição
581
625
  }
582
626
  }
583
627
  }
@@ -15,6 +15,8 @@ export function createReactConfig(entryPoint: string, outdir: string, isProducti
15
15
  treeshake: {
16
16
  moduleSideEffects: string;
17
17
  preset: string;
18
+ propertyReadSideEffects: boolean;
19
+ tryCatchDeoptimization: boolean;
18
20
  };
19
21
  cache: boolean;
20
22
  perf: boolean;
@@ -32,7 +32,8 @@ const jsonPlugin = require("@rollup/plugin-json").default;
32
32
  async function createReactConfig(entryPoint, outdir, isProduction, { prePlugins = [], postPlugins = [] } = {}) {
33
33
  const replaceValues = {
34
34
  'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
35
- 'process.env.PORT': JSON.stringify(process.vatts?.port || 3000)
35
+ 'process.env.PORT': JSON.stringify(process.vatts?.port || 3000),
36
+ preventAssignment: true
36
37
  };
37
38
  const extensions = ['.mjs', '.js', '.json', '.node', '.jsx', '.tsx', '.ts'];
38
39
  const esbuildLoaders = {
@@ -42,39 +43,47 @@ async function createReactConfig(entryPoint, outdir, isProduction, { prePlugins
42
43
  };
43
44
  return {
44
45
  input: entryPoint,
46
+ // [OPTIMIZATION] Preset 'smallest' é o mais agressivo do Rollup
45
47
  treeshake: {
46
48
  moduleSideEffects: 'no-external',
47
- preset: isProduction ? 'recommended' : 'smallest'
49
+ preset: isProduction ? 'smallest' : 'recommended',
50
+ propertyReadSideEffects: false,
51
+ tryCatchDeoptimization: false
48
52
  },
49
53
  cache: isProduction ? true : false,
50
54
  perf: false,
51
55
  maxParallelFileOps: 20,
52
56
  plugins: [
53
- replace({
54
- preventAssignment: true,
55
- values: replaceValues
56
- }),
57
+ replace(replaceValues),
57
58
  ...prePlugins,
58
59
  nodeResolve({
59
60
  extensions,
60
61
  preferBuiltins: true,
61
62
  browser: true,
63
+ // [FIX] Apenas dedupe React principal para evitar conflitos, mas deixe libs internas resolverem
62
64
  dedupe: ['react', 'react-dom']
63
65
  }),
64
66
  commonjs({
65
67
  sourceMap: !isProduction,
66
- requireReturnsDefault: 'auto',
67
- ignoreTryCatch: true
68
+ // [FIX] 'preferred' ajuda o Rollup a escolher o export default correto para React
69
+ // Isso muitas vezes resolve o erro de "undefined reading createElement"
70
+ requireReturnsDefault: 'preferred',
71
+ ignoreTryCatch: true,
72
+ transformMixedEsModules: true,
73
+ esmExternals: false
68
74
  }),
69
75
  ...postPlugins,
70
- jsonPlugin(),
76
+ jsonPlugin({
77
+ compact: true
78
+ }),
71
79
  esbuild({
72
80
  include: /\.[jt]sx?$/,
73
81
  exclude: /node_modules/,
74
82
  sourceMap: !isProduction,
75
- minify: isProduction,
83
+ // [OPTIMIZATION] Mantemos false aqui pois o Terser (no bundler principal) fará o trabalho pesado
84
+ minify: false,
76
85
  legalComments: 'none',
77
- treeShaking: isProduction,
86
+ treeShaking: true,
78
87
  target: 'esnext',
79
88
  jsx: 'automatic',
80
89
  define: { __VERSION__: '"1.0.0"' },
@@ -57,22 +57,6 @@ function requireWithoutStyles(modulePath) {
57
57
  });
58
58
  }
59
59
  }
60
- // --- Gerenciamento de Console (Silenciador) ---
61
- const originalConsole = {
62
- log: console.log,
63
- info: console.info,
64
- debug: console.debug
65
- };
66
- function silenceConsole() {
67
- console.log = () => { };
68
- console.info = () => { };
69
- console.debug = () => { };
70
- }
71
- function restoreConsole() {
72
- console.log = originalConsole.log;
73
- console.info = originalConsole.info;
74
- console.debug = originalConsole.debug;
75
- }
76
60
  // --- Funções de Metadata e Scripts ---
77
61
  function generateMetaTags(metadata) {
78
62
  const tags = [];
@@ -173,6 +157,7 @@ function getBuildAssets(req) {
173
157
  const projectDir = process.cwd();
174
158
  const distDir = path_1.default.join(projectDir, '.vatts');
175
159
  const assetsDir = path_1.default.join(distDir, 'assets');
160
+ const chunksDir = path_1.default.join(distDir, 'chunks');
176
161
  if (!fs_1.default.existsSync(distDir))
177
162
  return null;
178
163
  let scripts = [];
@@ -216,6 +201,7 @@ function getBuildAssets(req) {
216
201
  processDirectory(distDir, '/_vatts');
217
202
  // Scan em .vatts/assets/ (Assets estáticos, chunks, CSS extraído)
218
203
  processDirectory(assetsDir, '/_vatts/assets');
204
+ processDirectory(chunksDir, '/_vatts/chunks');
219
205
  // Ordenação básica para garantir que o main carregue
220
206
  scripts.sort((a, b) => {
221
207
  if (a.includes('main'))
@@ -255,13 +241,11 @@ async function render({ req, res, route, params, allRoutes }) {
255
241
  const isProduction = !req.hwebDev;
256
242
  const hotReloadManager = req.hotReloadManager;
257
243
  // SILENCIAR CONSOLE: Inicia o silêncio para evitar logs de renderização
258
- silenceConsole();
259
244
  try {
260
245
  // 1. Verificar Build - Se não tiver scripts, retorna tela de Loading
261
246
  const assets = getBuildAssets(req);
262
247
  if (!assets || assets.scripts.length === 0) {
263
248
  // Se falhar o build, restauramos o console para o erro aparecer se necessário
264
- restoreConsole();
265
249
  // Usando stream para a tela de loading também, agora via React Component
266
250
  const { pipe } = (0, server_1.renderToPipeableStream)(react_1.default.createElement(BuildingPage_1.default, null), {
267
251
  onShellReady() {
@@ -281,10 +265,7 @@ async function render({ req, res, route, params, allRoutes }) {
281
265
  LayoutComponent = layoutModule.default;
282
266
  }
283
267
  catch (e) {
284
- // Usamos console.error original aqui, pois erro de layout é crítico
285
- restoreConsole();
286
268
  console.error("Error loading layout component for SSR:", e);
287
- silenceConsole(); // Volta a silenciar
288
269
  }
289
270
  }
290
271
  // 3. Preparar Metadata
@@ -339,14 +320,11 @@ async function render({ req, res, route, params, allRoutes }) {
339
320
  // Usar bootstrapModules para scripts tipo módulo (ESM)
340
321
  bootstrapModules: assets.scripts,
341
322
  onShellReady() {
342
- // Restaurar console assim que o shell estiver pronto (cabeçalho enviado)
343
- restoreConsole();
344
323
  res.setHeader('Content-Type', 'text/html');
345
324
  pipe(res);
346
325
  resolve();
347
326
  },
348
327
  onShellError(error) {
349
- restoreConsole(); // Restaura para mostrar o erro real
350
328
  console.error('Streaming Shell Error:', error);
351
329
  res.statusCode = 500;
352
330
  res.setHeader('Content-Type', 'text/html');
@@ -363,7 +341,6 @@ async function render({ req, res, route, params, allRoutes }) {
363
341
  });
364
342
  }
365
343
  catch (err) {
366
- restoreConsole();
367
344
  console.error("Critical Render Error:", err);
368
345
  throw err;
369
346
  }
@@ -101,6 +101,10 @@ function extractComponentPreloads(componentPath) {
101
101
  tags.add(`<link rel="preload" as="style" href="${publicUrl}">`);
102
102
  tags.add(`<link rel="stylesheet" href="${publicUrl}">`);
103
103
  }
104
+ else if (['.js'].includes(ext)) {
105
+ // Adicionado suporte para JS
106
+ tags.add(`<link rel="preload" as="script" href="${publicUrl}">`);
107
+ }
104
108
  else if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'].includes(ext)) {
105
109
  tags.add(`<link rel="preload" as="image" href="${publicUrl}">`);
106
110
  }
@@ -110,7 +114,8 @@ function extractComponentPreloads(componentPath) {
110
114
  // - import foo from './foo.png' (Named import)
111
115
  // - import './style.css' (Side-effect import)
112
116
  // - require('./image.jpg') (CommonJS)
113
- const importRegex = /(?:import(?:\s+[^;'"]+\s+from)?\s+|require\(\s*)['"]([^'"]+\.(png|jpg|jpeg|gif|svg|webp|avif|mp4|webm|css))['"]/g;
117
+ // Adicionado |js na lista de extensões
118
+ const importRegex = /(?:import(?:\s+[^;'"]+\s+from)?\s+|require\(\s*)['"]([^'"]+\.(png|jpg|jpeg|gif|svg|webp|avif|mp4|webm|css|js))['"]/g;
114
119
  let match;
115
120
  while ((match = importRegex.exec(content)) !== null) {
116
121
  processPath(match[1]);
@@ -15,6 +15,8 @@ export function createVueConfig(entryPoint: string, outdir: string, isProduction
15
15
  treeshake: {
16
16
  moduleSideEffects: string;
17
17
  preset: string;
18
+ propertyReadSideEffects: boolean;
19
+ tryCatchDeoptimization: boolean;
18
20
  };
19
21
  cache: boolean;
20
22
  perf: boolean;
@@ -56,7 +56,8 @@ async function createVueConfig(entryPoint, outdir, isProduction, { prePlugins =
56
56
  'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
57
57
  'process.env.PORT': JSON.stringify(process.vatts?.port || 3000),
58
58
  '__VUE_OPTIONS_API__': JSON.stringify(true),
59
- '__VUE_PROD_DEVTOOLS__': JSON.stringify(!isProduction)
59
+ '__VUE_PROD_DEVTOOLS__': JSON.stringify(!isProduction),
60
+ preventAssignment: true
60
61
  };
61
62
  let vuePlugin = null;
62
63
  try {
@@ -75,8 +76,12 @@ async function createVueConfig(entryPoint, outdir, isProduction, { prePlugins =
75
76
  const vueFactory = vuePkg.default || vuePkg;
76
77
  if (typeof vueFactory === 'function') {
77
78
  vuePlugin = vueFactory({
79
+ // [OPTIMIZATION] Informa explicitamente que é produção para otimizações internas do Vue
80
+ isProduction: isProduction,
78
81
  compilerOptions: {
79
- isCustomElement: (tag) => tag.includes('-')
82
+ isCustomElement: (tag) => tag.includes('-'),
83
+ // [FIX] Remove comentários HTML (<!-- ... -->) do template compilado
84
+ comments: false
80
85
  }
81
86
  });
82
87
  }
@@ -93,18 +98,18 @@ async function createVueConfig(entryPoint, outdir, isProduction, { prePlugins =
93
98
  };
94
99
  return {
95
100
  input: entryPoint,
101
+ // [OPTIMIZATION] Preset 'smallest' é o mais agressivo do Rollup
96
102
  treeshake: {
97
103
  moduleSideEffects: 'no-external',
98
- preset: isProduction ? 'recommended' : 'smallest'
104
+ preset: isProduction ? 'smallest' : 'recommended',
105
+ propertyReadSideEffects: false,
106
+ tryCatchDeoptimization: false
99
107
  },
100
108
  cache: isProduction ? true : false,
101
109
  perf: false,
102
110
  maxParallelFileOps: 20,
103
111
  plugins: [
104
- replace({
105
- preventAssignment: true,
106
- values: replaceValues
107
- }),
112
+ replace(replaceValues),
108
113
  // Plugins de Infra (TSConfig, etc)
109
114
  ...prePlugins,
110
115
  {
@@ -127,19 +132,25 @@ async function createVueConfig(entryPoint, outdir, isProduction, { prePlugins =
127
132
  vueScriptFixPlugin(),
128
133
  commonjs({
129
134
  sourceMap: !isProduction,
130
- requireReturnsDefault: 'auto',
131
- ignoreTryCatch: true
135
+ // [FIX] 'preferred' ajuda o Rollup a escolher o export default correto para Vue/Libs
136
+ requireReturnsDefault: 'preferred',
137
+ ignoreTryCatch: true,
138
+ transformMixedEsModules: true,
139
+ esmExternals: false // Garante que deps CommonJS sejam empacotadas
132
140
  }),
133
141
  // Plugins de Assets/CSS rodam DEPOIS do Vue ter gerado os arquivos virtuais de estilo
134
142
  ...postPlugins,
135
- jsonPlugin(),
143
+ jsonPlugin({
144
+ compact: true
145
+ }),
136
146
  esbuild({
137
147
  include: /\.[jt]sx?$|\.vue\?vue.*lang\.ts/,
138
148
  exclude: /node_modules/,
139
149
  sourceMap: !isProduction,
140
- minify: isProduction,
150
+ // [OPTIMIZATION] Mantemos false aqui pois o Terser (no bundler principal) fará o trabalho pesado
151
+ minify: false,
141
152
  legalComments: 'none',
142
- treeShaking: isProduction,
153
+ treeShaking: true,
143
154
  target: 'esnext',
144
155
  jsx: 'automatic',
145
156
  define: { __VERSION__: '"1.0.0"' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vatts",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "description": "Vatts.js is a high-level framework for building web applications with ease and speed. It provides a robust set of tools and features to streamline development and enhance productivity.",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "mfraz",
@@ -105,6 +105,7 @@
105
105
  "@rollup/plugin-json": "^6.1.0",
106
106
  "@rollup/plugin-node-resolve": "^16.0.3",
107
107
  "@rollup/plugin-replace": "^6.0.3",
108
+ "@rollup/plugin-terser": "^0.4.4",
108
109
  "@vue/server-renderer": "^3.5.27",
109
110
  "chokidar": "^3.6.0",
110
111
  "commander": "^14.0.2",