vatts 1.0.2-alpha.2 → 1.0.2-alpha.4

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
@@ -58,14 +58,14 @@ const markdownPlugin = () => {
58
58
  if (id.endsWith('.md')) {
59
59
  return {
60
60
  code: `export default ${JSON.stringify(code)};`,
61
- map: null
61
+ map: null // Null map economiza memória se não precisa debugar markdown
62
62
  };
63
63
  }
64
64
  }
65
65
  };
66
66
  };
67
67
  /**
68
- * Plugin para CSS/PostCSS Manual (Smart Extraction + Tailwind Fix)
68
+ * Plugin para CSS/PostCSS Manual (Otimizado para RAM)
69
69
  */
70
70
  const customPostCssPlugin = (isProduction) => {
71
71
  let cachedProcessor = null;
@@ -95,7 +95,9 @@ const customPostCssPlugin = (isProduction) => {
95
95
  }
96
96
  }
97
97
  if (postcss) {
98
- delete require.cache[require.resolve(configPath)];
98
+ // OTIMIZAÇÃO DE RAM: Removido 'delete require.cache'.
99
+ // Limpar o cache constantemente fragmenta a memória heap do V8 em processos longos (watch).
100
+ // Se o usuário alterar o config, ele deve reiniciar o processo.
99
101
  const config = require(configPath);
100
102
  const postcssConfig = config.default || config;
101
103
  const plugins = [];
@@ -174,36 +176,24 @@ const customPostCssPlugin = (isProduction) => {
174
176
  Console.warn(`PostCSS process error:`, e.message);
175
177
  }
176
178
  }
177
- // ESTRATÉGIA DE EXTRAÇÃO INTELIGENTE
178
- if (isProduction) {
179
- // Emite arquivo físico (Melhora Cache e Tamanho do JS)
180
- const referenceId = this.emitFile({
181
- type: 'asset',
182
- name: path.basename(filePath),
183
- source: processedCss
184
- });
185
- // Retorna código JS que auto-injeta o <link>
186
- // Isso mantém compatibilidade (não quebra o app) mas usa arquivo externo
187
- return `
188
- const cssUrl = import.meta.ROLLUP_FILE_URL_${referenceId};
189
- if (typeof document !== 'undefined') {
190
- const link = document.createElement('link');
191
- link.rel = 'stylesheet';
192
- link.href = cssUrl;
193
- document.head.appendChild(link);
194
- }
195
- export default cssUrl;
196
- `;
197
- }
198
- // Modo DEV (Inline para Hot Reload mais rápido)
179
+ // OTIMIZAÇÃO: Emite arquivo físico sempre que possível.
180
+ // Strings gigantes de CSS inline consomem muita RAM no bundle JS.
181
+ const referenceId = this.emitFile({
182
+ type: 'asset',
183
+ name: path.basename(filePath),
184
+ source: processedCss
185
+ });
186
+ // Lógica unificada: Usa arquivo externo tanto em Dev quanto Prod.
187
+ // Isso libera a memória que seria usada para stringificar o CSS dentro do JS.
199
188
  return `
200
- const css = ${JSON.stringify(processedCss)};
189
+ const cssUrl = import.meta.ROLLUP_FILE_URL_${referenceId};
201
190
  if (typeof document !== 'undefined') {
202
- const style = document.createElement('style');
203
- style.textContent = css;
204
- document.head.appendChild(style);
191
+ const link = document.createElement('link');
192
+ link.rel = 'stylesheet';
193
+ link.href = cssUrl;
194
+ document.head.appendChild(link);
205
195
  }
206
- export default css;
196
+ export default cssUrl;
207
197
  `;
208
198
  }
209
199
  return null;
@@ -211,12 +201,13 @@ const customPostCssPlugin = (isProduction) => {
211
201
  };
212
202
  };
213
203
  /**
214
- * Plugin Inteligente para Assets (Substitui forceBase64Plugin)
215
- * - Dev: Base64 (Rápido)
216
- * - Prod: Arquivos > 4KB viram URL (Melhora LCP), < 4KB Base64 (Menos requests)
204
+ * Plugin Inteligente para Assets (Otimizado para RAM)
205
+ * - Agora utiliza emissão de arquivos também em DEV para arquivos grandes.
217
206
  */
218
207
  const smartAssetPlugin = (isProduction) => {
219
- const INLINE_LIMIT = 4096; // 4KB
208
+ // 4KB - Arquivos maiores que isso viram referência externa.
209
+ // Manter isso baixo economiza MUITA RAM, pois evita strings Base64 gigantes no JS.
210
+ const INLINE_LIMIT = 4096;
220
211
  return {
221
212
  name: 'smart-asset-loader',
222
213
  async load(id) {
@@ -238,42 +229,23 @@ const smartAssetPlugin = (isProduction) => {
238
229
  const type = mimeTypes[ext];
239
230
  if (!type)
240
231
  return null;
241
- // Text files always strings
232
+ // Text files always strings (geralmente pequenos)
242
233
  if (type === 'txt') {
243
234
  const content = await fs.promises.readFile(cleanId, 'utf8');
244
235
  return `export default ${JSON.stringify(content)};`;
245
236
  }
246
- const buffer = await fs.promises.readFile(cleanId);
237
+ let buffer = await fs.promises.readFile(cleanId);
247
238
  const size = buffer.length;
248
- // MODO PRODUÇÃO: Otimização de Assets
249
- if (isProduction) {
250
- // SVG: Se for pequeno inlina, se grande emite arquivo
251
- if (type === 'svg') {
252
- if (size < INLINE_LIMIT) {
253
- const content = buffer.toString('utf8');
254
- const base64 = buffer.toString('base64');
255
- return `
256
- export default "data:image/svg+xml;base64,${base64}";
257
- export const svgContent = ${JSON.stringify(content)};
258
- `;
259
- }
260
- else {
261
- // Emite arquivo físico
262
- const referenceId = this.emitFile({
263
- type: 'asset',
264
- name: path.basename(cleanId),
265
- source: buffer
266
- });
267
- const content = buffer.toString('utf8');
268
- return `
269
- export default import.meta.ROLLUP_FILE_URL_${referenceId};
270
- export const svgContent = ${JSON.stringify(content)};
271
- `;
272
- }
273
- }
274
- // Outros assets
239
+ // Tratamento especial para SVG (inline SVG vs URL)
240
+ if (type === 'svg') {
275
241
  if (size < INLINE_LIMIT) {
276
- return `export default "data:${type};base64,${buffer.toString('base64')}";`;
242
+ const content = buffer.toString('utf8');
243
+ const base64 = buffer.toString('base64');
244
+ buffer = null; // GC Hint
245
+ return `
246
+ export default "data:image/svg+xml;base64,${base64}";
247
+ export const svgContent = ${JSON.stringify(content)};
248
+ `;
277
249
  }
278
250
  else {
279
251
  const referenceId = this.emitFile({
@@ -281,19 +253,32 @@ const smartAssetPlugin = (isProduction) => {
281
253
  name: path.basename(cleanId),
282
254
  source: buffer
283
255
  });
284
- return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
256
+ const content = buffer.toString('utf8');
257
+ buffer = null; // GC Hint
258
+ return `
259
+ export default import.meta.ROLLUP_FILE_URL_${referenceId};
260
+ export const svgContent = ${JSON.stringify(content)};
261
+ `;
285
262
  }
286
263
  }
287
- // MODO DESENVOLVIMENTO: Tudo Base64 para performance de build
288
- if (type === 'svg') {
289
- const content = buffer.toString('utf8');
264
+ // Para outros assets:
265
+ // Se for pequeno, Base64 (reduz requests HTTP)
266
+ // Se for grande, Arquivo (reduz uso de RAM e tamanho do bundle JS)
267
+ // Essa lógica agora aplica para DEV e PROD. Base64 em Dev para arquivos grandes era o vilão da RAM.
268
+ if (size < INLINE_LIMIT) {
290
269
  const base64 = buffer.toString('base64');
291
- return `
292
- export default "data:image/svg+xml;base64,${base64}";
293
- export const svgContent = ${JSON.stringify(content)};
294
- `;
270
+ buffer = null; // Libera memória do buffer bruto imediatamente
271
+ return `export default "data:${type};base64,${base64}";`;
272
+ }
273
+ else {
274
+ const referenceId = this.emitFile({
275
+ type: 'asset',
276
+ name: path.basename(cleanId),
277
+ source: buffer
278
+ });
279
+ buffer = null; // Libera memória
280
+ return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
295
281
  }
296
- return `export default "data:${type};base64,${buffer.toString('base64')}";`;
297
282
  }
298
283
  };
299
284
  };
@@ -303,25 +288,26 @@ const smartAssetPlugin = (isProduction) => {
303
288
  function createRollupConfig(entryPoint, outdir, isProduction) {
304
289
  return {
305
290
  input: entryPoint,
306
- // Para evitar bare imports no browser (sem import map), em DEV também bundle React/ReactDOM.
307
- // O HMR evita "Invalid hook call" removendo o script antigo e recarregando main.js.
308
291
  external: nodeBuiltIns,
309
- // Otimização: Em prod usa 'recommended' para limpar código morto
310
- treeshake: isProduction ? 'recommended' : false,
311
- // CORREÇÃO CRÍTICA: Desativa cache em desenvolvimento (watch mode)
312
- // Isso previne que o Rollup emita "sucesso" baseado em um cache obsoleto quando o arquivo ainda tem erro.
292
+ // Otimização: Treeshake limpa memória removendo nós da AST não usados
293
+ treeshake: {
294
+ moduleSideEffects: 'no-external', // Mais agressivo, economiza memória
295
+ preset: isProduction ? 'recommended' : 'smallest'
296
+ },
297
+ // Cache desativado em DEV conforme solicitado anteriormente,
298
+ // o que ajuda na RAM pois não mantém a AST antiga em memória.
313
299
  cache: isProduction ? true : false,
314
300
  perf: false,
301
+ // Limita execuções paralelas de leitura de arquivo internas do Rollup
302
+ maxParallelFileOps: 20,
315
303
  plugins: [
316
- // CRÍTICO: 'replace' deve vir PRIMEIRO para injetar NODE_ENV antes que
317
- // libs como React decidam qual bundle importar.
318
304
  replace({
319
305
  preventAssignment: true,
320
306
  values: {
321
- 'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development')
307
+ 'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
308
+ 'proccess.env.PORT': JSON.stringify(process.vatts.port || 3000)
322
309
  }
323
310
  }),
324
- // Precisa vir antes do nodeResolve
325
311
  tsconfigPathsPlugin(process.cwd()),
326
312
  nodeResolve({
327
313
  extensions: ['.mjs', '.js', '.json', '.node', '.jsx', '.tsx', '.ts'],
@@ -331,20 +317,21 @@ function createRollupConfig(entryPoint, outdir, isProduction) {
331
317
  }),
332
318
  commonjs({
333
319
  sourceMap: !isProduction,
334
- requireReturnsDefault: 'auto'
320
+ requireReturnsDefault: 'auto',
321
+ // Ignora try-catch dinâmicos para economizar análise
322
+ ignoreTryCatch: true
335
323
  }),
336
324
  markdownPlugin(),
337
- // Passamos isProduction para ativar a Extração Inteligente
325
+ // PostCSS Otimizado
338
326
  customPostCssPlugin(isProduction),
339
- // Substitui forceBase64Plugin pelo Smart
327
+ // Assets Otimizados (menos Base64)
340
328
  smartAssetPlugin(isProduction),
341
329
  esbuild({
342
330
  include: /\.[jt]sx?$/,
343
331
  exclude: /node_modules/,
344
332
  sourceMap: !isProduction,
345
333
  minify: isProduction,
346
- // Otimização: Remove comentários legais em produção
347
- legalComments: isProduction ? 'none' : 'eof',
334
+ legalComments: 'none', // Remove comentários para limpar buffer
348
335
  treeShaking: isProduction,
349
336
  target: isProduction ? 'es2020' : 'esnext',
350
337
  jsx: 'automatic',
@@ -357,6 +344,9 @@ function createRollupConfig(entryPoint, outdir, isProduction) {
357
344
  return;
358
345
  if (warning.code === 'THIS_IS_UNDEFINED')
359
346
  return;
347
+ // Ignora avisos circulares comuns que enchem o log/buffer
348
+ if (warning.code === 'CIRCULAR_DEPENDENCY' && warning.message.includes('node_modules'))
349
+ return;
360
350
  warn(warning);
361
351
  }
362
352
  };
@@ -371,39 +361,31 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
371
361
  const outputOptions = {
372
362
  dir: outdir,
373
363
  format: 'es',
374
- // Padrão de nomes mantido, mas com estrutura para vários arquivos
375
364
  entryFileNames: isProduction ? 'main-[hash].js' : 'main.js',
376
365
  chunkFileNames: 'chunks/[name]-[hash].js',
377
366
  assetFileNames: 'assets/[name]-[hash][extname]',
378
367
  sourcemap: !isProduction,
379
- // OTIMIZAÇÃO: Separação granular para melhor Cache e Load Time
368
+ // Compacta output para economizar memória de escrita
369
+ compact: isProduction,
380
370
  manualChunks(id) {
381
371
  if (id.includes('node_modules')) {
382
- // Normaliza separadores para garantir funcionamento em Windows/Linux
383
372
  const normalizedId = id.replace(/\\/g, '/');
384
- // React Core isolado
385
- // IMPORTANTE: Uso de Regex para garantir que pegamos APENAS os pacotes do core
386
- // e não pacotes que tenham 'react' no nome (ex: react-router, react-icons),
387
- // pois isso causa dependências circulares com o chunk 'vendor'.
388
373
  if (/\/node_modules\/(react|react-dom|scheduler|prop-types|loose-envify|object-assign)\//.test(normalizedId)) {
389
374
  return 'vendor-react';
390
375
  }
391
- // UI Libs comuns (opcional, pode ajustar conforme necessidade)
392
376
  if (id.includes('framer-motion') || id.includes('@radix-ui')) {
393
377
  return 'vendor-ui';
394
378
  }
395
- // Utils comuns
396
379
  if (id.includes('lodash') || id.includes('date-fns') || id.includes('axios')) {
397
380
  return 'vendor-utils';
398
381
  }
399
- // Resto das dependências
400
382
  return 'vendor';
401
383
  }
402
384
  }
403
385
  };
404
386
  const bundle = await rollup(inputOptions);
405
387
  await bundle.write(outputOptions);
406
- await bundle.close();
388
+ await bundle.close(); // Importante fechar para liberar memória
407
389
  }
408
390
  catch (error) {
409
391
  Console.error('An error occurred while building with chunks:', error);
@@ -423,7 +405,8 @@ async function build(entryPoint, outfile, isProduction = false) {
423
405
  format: 'iife',
424
406
  name: 'Vattsjs',
425
407
  sourcemap: !isProduction,
426
- inlineDynamicImports: true
408
+ inlineDynamicImports: true,
409
+ compact: true // Ajuda na RAM
427
410
  };
428
411
  const bundle = await rollup(inputOptions);
429
412
  await bundle.write(outputOptions);
@@ -438,20 +421,22 @@ async function build(entryPoint, outfile, isProduction = false) {
438
421
  * Helper para lidar com notificações do Watcher
439
422
  */
440
423
  function handleWatcherEvents(watcher, hotReloadManager, resolveFirstBuild) {
441
- // Controla o estado do build por "geração" para evitar END atrasado
442
- // (ou múltiplos ciclos) emitirem sucesso após um erro.
443
424
  let currentBuildId = 0;
444
425
  let lastStartedBuildId = 0;
445
426
  const erroredBuildIds = new Set();
446
- // DEBUG: stack trace rate-limited
447
- let lastTraceAt = 0;
448
427
  watcher.on('event', event => {
449
428
  if (event.code === 'START') {
450
429
  currentBuildId += 1;
451
430
  lastStartedBuildId = currentBuildId;
431
+ // Dica pro V8 limpar lixo antes de começar um build pesado
432
+ if (global.gc) {
433
+ try {
434
+ global.gc();
435
+ }
436
+ catch (e) { }
437
+ }
452
438
  }
453
439
  if (event.code === 'ERROR') {
454
- // Marca erro para o build atualmente em andamento.
455
440
  erroredBuildIds.add(currentBuildId);
456
441
  const errDetails = {
457
442
  message: event.error?.message || 'Unknown build error',
@@ -461,7 +446,6 @@ function handleWatcherEvents(watcher, hotReloadManager, resolveFirstBuild) {
461
446
  loc: event.error?.loc,
462
447
  buildId: currentBuildId
463
448
  };
464
- // Notifica erro imediatamente
465
449
  if (hotReloadManager) {
466
450
  hotReloadManager.onBuildComplete(false, errDetails);
467
451
  }
@@ -470,21 +454,18 @@ function handleWatcherEvents(watcher, hotReloadManager, resolveFirstBuild) {
470
454
  if (resolveFirstBuild)
471
455
  resolveFirstBuild();
472
456
  }
473
- if (event.code === 'BUNDLE_END')
457
+ if (event.code === 'BUNDLE_END') {
458
+ // CRÍTICO: Fechar o bundle libera a memória dos módulos
474
459
  event.result.close();
460
+ }
475
461
  if (event.code === 'END') {
476
462
  const endBuildId = currentBuildId;
477
463
  const hadError = erroredBuildIds.has(endBuildId);
478
- // Só emite sucesso se:
479
- // 1) esse END é do build mais recentemente iniciado (evita END atrasado)
480
- // 2) esse build não teve ERROR
481
464
  if (endBuildId === lastStartedBuildId && !hadError) {
482
465
  if (hotReloadManager) {
483
466
  hotReloadManager.onBuildComplete(true, { buildId: endBuildId });
484
467
  }
485
468
  }
486
- // Limpa estados antigos pra não crescer sem limite.
487
- // (qualquer build mais antigo que o último START não faz mais sentido manter)
488
469
  for (const id of erroredBuildIds) {
489
470
  if (id < lastStartedBuildId)
490
471
  erroredBuildIds.delete(id);
@@ -502,11 +483,9 @@ function handleWatcherEvents(watcher, hotReloadManager, resolveFirstBuild) {
502
483
  async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
503
484
  await cleanDirectoryExcept(outdir, 'temp');
504
485
  try {
505
- // DEV MODE: isProduction = false
506
486
  const inputOptions = createRollupConfig(entryPoint, outdir, false);
507
487
  const outputOptions = {
508
488
  dir: outdir,
509
- // Em DEV usamos ESM para suportar externals como react/react-dom sem output.globals
510
489
  format: 'es',
511
490
  entryFileNames: 'main.js',
512
491
  sourcemap: true
@@ -517,7 +496,9 @@ async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
517
496
  watch: {
518
497
  exclude: 'node_modules/**',
519
498
  clearScreen: false,
520
- skipWrite: false
499
+ skipWrite: false,
500
+ // Atraso curto para evitar múltiplos rebuilds rápidos que comem CPU/RAM
501
+ buildDelay: 100
521
502
  }
522
503
  };
523
504
  const watcher = rollupWatch(watchOptions);
@@ -540,7 +521,6 @@ async function watch(entryPoint, outfile, hotReloadManager = null) {
540
521
  const inputOptions = createRollupConfig(entryPoint, outdir, false);
541
522
  const outputOptions = {
542
523
  file: outfile,
543
- // Em DEV usamos ESM para suportar externals como react/react-dom sem output.globals
544
524
  format: 'es',
545
525
  sourcemap: true
546
526
  };
@@ -549,7 +529,8 @@ async function watch(entryPoint, outfile, hotReloadManager = null) {
549
529
  output: outputOptions,
550
530
  watch: {
551
531
  exclude: 'node_modules/**',
552
- clearScreen: false
532
+ clearScreen: false,
533
+ buildDelay: 100
553
534
  }
554
535
  };
555
536
  const watcher = rollupWatch(watchOptions);
@@ -569,14 +550,21 @@ async function cleanDirectoryExcept(dirPath, excludeFolder) {
569
550
  return;
570
551
  const excludes = Array.isArray(excludeFolder) ? excludeFolder : [excludeFolder];
571
552
  const items = await readdir(dirPath);
572
- // Paraleliza a limpeza
573
- await Promise.all(items.map(async (item) => {
553
+ // OTIMIZAÇÃO: Loop sequencial ao invés de Promise.all.
554
+ // Promise.all é mais rápido, mas cria dezenas/centenas de Promises simultâneas na RAM.
555
+ // O loop sequencial é mais gentil com o Garbage Collector.
556
+ for (const item of items) {
574
557
  if (excludes.includes(item))
575
- return;
558
+ continue;
576
559
  const itemPath = path.join(dirPath, item);
577
- const info = await stat(itemPath);
578
- await rm(itemPath, { recursive: info.isDirectory(), force: true });
579
- }));
560
+ try {
561
+ const info = await stat(itemPath);
562
+ await rm(itemPath, { recursive: info.isDirectory(), force: true });
563
+ }
564
+ catch (e) {
565
+ // Ignora erro se arquivo sumir durante o loop
566
+ }
567
+ }
580
568
  }
581
569
  catch (e) {
582
570
  Console.warn(`Warning cleaning directory: ${e.message}`);
@@ -1,58 +1,15 @@
1
- export interface RouterEvents {
2
- beforeNavigate?: (url: string) => boolean | Promise<boolean>;
3
- afterNavigate?: (url: string) => void;
4
- }
5
- declare class Router {
6
- private events;
1
+ type RouteListener = () => void;
2
+ export declare class Router {
7
3
  private listeners;
8
- /**
9
- * Navega para uma nova rota
10
- */
11
- push(url: string): Promise<void>;
12
- /**
13
- * Substitui a entrada atual do histórico
14
- */
15
- replace(url: string): Promise<void>;
16
- /**
17
- * Volta uma página no histórico
18
- */
19
- back(): void;
20
- /**
21
- * Avança uma página no histórico
22
- */
23
- forward(): void;
24
- /**
25
- * Recarrega a página atual (re-renderiza o componente)
26
- */
27
- refresh(): void;
28
- /**
29
- * Obtém a URL atual
30
- */
4
+ constructor();
31
5
  get pathname(): string;
32
- /**
33
- * Obtém os query parameters atuais
34
- */
35
- get query(): URLSearchParams;
36
- /**
37
- * Obtém a URL completa atual
38
- */
39
- get url(): string;
40
- /**
41
- * Adiciona event listeners para eventos de roteamento
42
- */
43
- on(events: RouterEvents): void;
44
- /**
45
- * Remove event listeners
46
- */
47
- off(): void;
48
- /**
49
- * Adiciona um listener para mudanças de rota
50
- */
51
- subscribe(listener: () => void): () => void;
52
- /**
53
- * Dispara evento de navegação para todos os listeners
54
- */
55
- private triggerNavigation;
6
+ get search(): string;
7
+ get hash(): string;
8
+ push(path: string): void;
9
+ replace(path: string): void;
10
+ back(): void;
11
+ subscribe(listener: RouteListener): () => void;
12
+ private notify;
56
13
  }
57
14
  export declare const router: Router;
58
- export default router;
15
+ export {};
@@ -16,116 +16,61 @@
16
16
  * limitations under the License.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.router = void 0;
19
+ exports.router = exports.Router = void 0;
20
20
  class Router {
21
21
  constructor() {
22
- this.events = {};
23
22
  this.listeners = new Set();
24
- }
25
- /**
26
- * Navega para uma nova rota
27
- */
28
- async push(url) {
29
- // Callback antes de navegar
30
- if (this.events.beforeNavigate) {
31
- const shouldProceed = await this.events.beforeNavigate(url);
32
- if (shouldProceed === false)
33
- return;
34
- }
35
- // Atualiza a URL na barra de endereço
36
- window.history.pushState({ path: url }, '', url);
37
- // Dispara evento para o roteador capturar de forma assíncrona
38
- setTimeout(() => this.triggerNavigation(), 0);
39
- // Callback após navegar
40
- if (this.events.afterNavigate) {
41
- this.events.afterNavigate(url);
42
- }
43
- }
44
- /**
45
- * Substitui a entrada atual do histórico
46
- */
47
- async replace(url) {
48
- // Callback antes de navegar
49
- if (this.events.beforeNavigate) {
50
- const shouldProceed = await this.events.beforeNavigate(url);
51
- if (shouldProceed === false)
52
- return;
53
- }
54
- // Substitui a URL atual no histórico
55
- window.history.replaceState({ path: url }, '', url);
56
- // Dispara evento para o roteador capturar de forma assíncrona
57
- setTimeout(() => this.triggerNavigation(), 0);
58
- // Callback após navegar
59
- if (this.events.afterNavigate) {
60
- this.events.afterNavigate(url);
23
+ // Só adiciona listener no lado do cliente
24
+ if (typeof window !== 'undefined') {
25
+ window.addEventListener('popstate', () => {
26
+ this.notify();
27
+ });
61
28
  }
62
29
  }
63
- /**
64
- * Volta uma página no histórico
65
- */
66
- back() {
67
- window.history.back();
68
- }
69
- /**
70
- * Avança uma página no histórico
71
- */
72
- forward() {
73
- window.history.forward();
74
- }
75
- /**
76
- * Recarrega a página atual (re-renderiza o componente)
77
- */
78
- refresh() {
79
- setTimeout(() => this.triggerNavigation(), 0);
80
- }
81
- /**
82
- * Obtém a URL atual
83
- */
84
30
  get pathname() {
31
+ if (typeof window === 'undefined') {
32
+ return '/'; // Retorno seguro para SSR
33
+ }
85
34
  return window.location.pathname;
86
35
  }
87
- /**
88
- * Obtém os query parameters atuais
89
- */
90
- get query() {
91
- return new URLSearchParams(window.location.search);
36
+ get search() {
37
+ if (typeof window === 'undefined') {
38
+ return '';
39
+ }
40
+ return window.location.search;
41
+ }
42
+ get hash() {
43
+ if (typeof window === 'undefined') {
44
+ return '';
45
+ }
46
+ return window.location.hash;
92
47
  }
93
- /**
94
- * Obtém a URL completa atual
95
- */
96
- get url() {
97
- return window.location.href;
48
+ push(path) {
49
+ if (typeof window !== 'undefined') {
50
+ window.history.pushState({}, '', path);
51
+ this.notify();
52
+ }
98
53
  }
99
- /**
100
- * Adiciona event listeners para eventos de roteamento
101
- */
102
- on(events) {
103
- this.events = { ...this.events, ...events };
54
+ replace(path) {
55
+ if (typeof window !== 'undefined') {
56
+ window.history.replaceState({}, '', path);
57
+ this.notify();
58
+ }
104
59
  }
105
- /**
106
- * Remove event listeners
107
- */
108
- off() {
109
- this.events = {};
60
+ back() {
61
+ if (typeof window !== 'undefined') {
62
+ window.history.back();
63
+ }
110
64
  }
111
- /**
112
- * Adiciona um listener para mudanças de rota
113
- */
114
65
  subscribe(listener) {
115
66
  this.listeners.add(listener);
116
- return () => this.listeners.delete(listener);
67
+ return () => {
68
+ this.listeners.delete(listener);
69
+ };
117
70
  }
118
- /**
119
- * Dispara evento de navegação para todos os listeners
120
- */
121
- triggerNavigation() {
122
- // Dispara o evento nativo para o roteador do hweb capturar
123
- window.dispatchEvent(new PopStateEvent('popstate'));
124
- // Notifica todos os listeners customizados
71
+ notify() {
125
72
  this.listeners.forEach(listener => listener());
126
73
  }
127
74
  }
128
- // Instância singleton do router
75
+ exports.Router = Router;
129
76
  exports.router = new Router();
130
- // Para compatibilidade, também exporta como default
131
- exports.default = exports.router;