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 +190 -33
- package/dist/index.js +50 -6
- package/dist/react/react.build.d.ts +2 -0
- package/dist/react/react.build.js +20 -11
- package/dist/react/renderer-react.js +2 -25
- package/dist/vue/renderer.vue.js +6 -1
- package/dist/vue/vue.build.d.ts +2 -0
- package/dist/vue/vue.build.js +23 -12
- package/package.json +2 -1
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
|
|
152
|
-
link
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
342
|
+
config = await createVueConfig(entryPoint, outdir, isProduction, pluginConfig);
|
|
269
343
|
}
|
|
270
344
|
else {
|
|
271
|
-
|
|
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
|
|
284
|
-
chunkFileNames: 'chunks/[name]
|
|
285
|
-
assetFileNames: 'assets/[name]
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
608
|
+
else {
|
|
573
609
|
const fileContent = fs_1.default.readFileSync(filePath);
|
|
574
610
|
genericRes.send(fileContent);
|
|
575
611
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
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 ? '
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
83
|
+
// [OPTIMIZATION] Mantemos false aqui pois o Terser (no bundler principal) fará o trabalho pesado
|
|
84
|
+
minify: false,
|
|
76
85
|
legalComments: 'none',
|
|
77
|
-
treeShaking:
|
|
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
|
}
|
package/dist/vue/renderer.vue.js
CHANGED
|
@@ -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
|
-
|
|
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]);
|
package/dist/vue/vue.build.d.ts
CHANGED
|
@@ -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;
|
package/dist/vue/vue.build.js
CHANGED
|
@@ -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 ? '
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
150
|
+
// [OPTIMIZATION] Mantemos false aqui pois o Terser (no bundler principal) fará o trabalho pesado
|
|
151
|
+
minify: false,
|
|
141
152
|
legalComments: 'none',
|
|
142
|
-
treeShaking:
|
|
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.
|
|
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",
|