diff --git a/bun.lock b/bun.lock index 6d92b9b8c..081594bb9 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "brisa-monorepo", @@ -810,7 +811,7 @@ "@vue/compiler-sfc/postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], - "brisa/brisa": ["brisa@0.2.14", "", { "dependencies": { "astring": "1.9.0", "csstype": "3.1.3", "diff-dom-streaming": "0.6.6", "meriyah": "6.0.6" }, "bin": { "brisa": "index.js" } }, "sha512-wWqNQoP/Z79lSyefAXmR/X/qMr9fz0XiAmbiuOJsdTn8Y8wN9pr2beezBWRGzF66JX95RBC6p955Pi5XkuHFAw=="], + "brisa/brisa": ["brisa@0.2.15", "", { "dependencies": { "astring": "1.9.0", "csstype": "3.1.3", "diff-dom-streaming": "0.6.6", "meriyah": "6.1.4" }, "bin": { "brisa": "index.js" } }, "sha512-rzcQHGvFwNanyQ2aYI3gt+1nT0pbcpU2c3CewhJ9q00j3E7rwqDnQOM9TYEt5pQjcePglX9efgRYWvqEp+kgAQ=="], "create-brisa/brisa": ["brisa@0.2.12", "", { "dependencies": { "astring": "1.9.0", "csstype": "3.1.3", "diff-dom-streaming": "0.6.6", "meriyah": "6.0.6" }, "bin": { "brisa": "index.js" } }, "sha512-lLceBJ6tm6LZPhvrhNZUCFDtQn04JadxYVVorGrZ69i0bvhmgXtT+4Dw+CmS9gtnK5+98FVrQMYilB7YlWqKJA=="], @@ -862,8 +863,6 @@ "@pandacss/core/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9KZZkmmy9oGDSrnyHuxP6iMhbsgChUiu/NSgOx+U1I/wTngBStDf2i2aGRCHvFqj19HqqBEI4WuGVQBa2V6e0A=="], - "brisa/brisa/meriyah": ["meriyah@6.0.6", "", {}, "sha512-jXJ2Hn1sLcge9QypNdZwbl/GqF5Qdc46TJA20g9KFa4ZnxQx74bDlJ3RfdaFhUIU3yN0lAcP99EH/XPPAcYcHA=="], - "create-brisa/brisa/meriyah": ["meriyah@6.0.6", "", {}, "sha512-jXJ2Hn1sLcge9QypNdZwbl/GqF5Qdc46TJA20g9KFa4ZnxQx74bDlJ3RfdaFhUIU3yN0lAcP99EH/XPPAcYcHA=="], "search-engine-wc/brisa/diff-dom-streaming": ["diff-dom-streaming@0.6.1", "", {}, "sha512-zGhLBNF2MdNvndGc5HzEeC6/aMuIBBm7cv6GjFwBqwXFCGmgVOGBV139GsCBFpUHjc7lqaC1L9agiVbVMSLDdQ=="], diff --git a/packages/brisa/src/cli/build-standalone/index.test.ts b/packages/brisa/src/cli/build-standalone/index.test.ts index e5400a76d..ff7e62a09 100644 --- a/packages/brisa/src/cli/build-standalone/index.test.ts +++ b/packages/brisa/src/cli/build-standalone/index.test.ts @@ -27,7 +27,7 @@ describe('cli/buildStandalone', () => { afterEach(() => { mockLog.mockRestore(); mockProcessExit.mockRestore(); - fs.rmSync(BUILD_DIR, { recursive: true }); + fs.rmSync(BUILD_DIR, { recursive: true, force: true }); }); it('should log "No standalone components provided" when no components are provided', async () => { diff --git a/packages/brisa/src/core/test/api/index.test.tsx b/packages/brisa/src/core/test/api/index.test.tsx index 6e9250985..cc281c4f3 100644 --- a/packages/brisa/src/core/test/api/index.test.tsx +++ b/packages/brisa/src/core/test/api/index.test.tsx @@ -484,6 +484,7 @@ describe('test api', () => { BUILD_DIR, SRC_DIR: BUILD_DIR, ASSETS_DIR, + WORKSPACE: BUILD_DIR, }; }); diff --git a/packages/brisa/src/core/test/matchers/index.test.ts b/packages/brisa/src/core/test/matchers/index.test.ts index ad665f602..e3fdad2dd 100644 --- a/packages/brisa/src/core/test/matchers/index.test.ts +++ b/packages/brisa/src/core/test/matchers/index.test.ts @@ -1,6 +1,17 @@ import { expect, describe, it, beforeEach, afterEach } from 'bun:test'; import { GlobalRegistrator } from '@happy-dom/global-registrator'; -import { greenLog, redLog } from '@/utils/log/log-color'; + +// Strip ANSI escape codes for color-agnostic comparison in error messages, +// since color support varies between isolated and full suite runs +const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, ''); +function expectThrowContaining(fn: () => void, expected: string) { + try { + fn(); + throw new Error('Expected function to throw'); + } catch (e: any) { + expect(stripAnsi(e.message)).toContain(expected); + } +} describe('test matchers', () => { beforeEach(() => { @@ -47,20 +58,18 @@ describe('test matchers', () => { const div = document.createElement('div'); div.setAttribute('data-test', 'test-2'); - expect(() => - expect(div).toHaveAttribute('data-test', 'test'), - ).toThrowError( - `Expected: ${greenLog('"test"')}\nReceived: ${redLog('"test-2"')}`, + expectThrowContaining( + () => expect(div).toHaveAttribute('data-test', 'test'), + 'Expected: "test"\nReceived: "test-2"', ); }); it('should not pass if the element has not the attribute specifing the attribute', () => { const div = document.createElement('div'); - expect(() => - expect(div).toHaveAttribute('data-test', 'test'), - ).toThrowError( - `Expected: ${greenLog('"test"')}\nReceived: ${redLog('null')}`, + expectThrowContaining( + () => expect(div).toHaveAttribute('data-test', 'test'), + 'Expected: "test"\nReceived: null', ); }); @@ -89,8 +98,9 @@ describe('test matchers', () => { it('should fail if the element does not have the tag name', () => { const div = document.createElement('div'); - expect(() => expect(div).toHaveTagName('span')).toThrowError( - `Expected: ${greenLog('span')}\nReceived: ${redLog('div')}`, + expectThrowContaining( + () => expect(div).toHaveTagName('span'), + 'Expected: span\nReceived: div', ); }); }); @@ -135,16 +145,18 @@ describe('test matchers', () => { const div = document.createElement('div'); div.textContent = 'test-2'; - expect(() => expect(div).toHaveTextContent('test')).toThrowError( - `Expected: ${greenLog('"test"')}\nReceived: ${redLog('"test-2"')}`, + expectThrowContaining( + () => expect(div).toHaveTextContent('test'), + 'Expected: "test"\nReceived: "test-2"', ); }); it('should fail if the element does not have the rendered text', () => { const div = document.createElement('div'); - expect(() => expect(div).toHaveTextContent('test')).toThrowError( - `Expected: ${greenLog('"test"')}\nReceived: ${redLog('""')}`, + expectThrowContaining( + () => expect(div).toHaveTextContent('test'), + 'Expected: "test"\nReceived: ""', ); }); }); @@ -205,16 +217,18 @@ describe('test matchers', () => { const div = document.createElement('div'); div.textContent = 'foo'; - expect(() => expect(div).toContainTextContent('test')).toThrowError( - `Expected to contain: ${greenLog('"test"')}\nReceived: ${redLog('"foo"')}`, + expectThrowContaining( + () => expect(div).toContainTextContent('test'), + 'Expected to contain: "test"\nReceived: "foo"', ); }); it('should fail if the element does not contain the rendered text', () => { const div = document.createElement('div'); - expect(() => expect(div).toContainTextContent('test')).toThrowError( - `Expected to contain: ${greenLog('"test"')}\nReceived: ${redLog('""')}`, + expectThrowContaining( + () => expect(div).toContainTextContent('test'), + 'Expected to contain: "test"\nReceived: ""', ); }); }); @@ -231,16 +245,18 @@ describe('test matchers', () => { const div = document.createElement('div'); div.style.color = 'red'; - expect(() => expect(div).toHaveStyle('color', 'blue')).toThrowError( - `Expected: ${greenLog('"blue"')}\nReceived: ${redLog('"red"')}`, + expectThrowContaining( + () => expect(div).toHaveStyle('color', 'blue'), + 'Expected: "blue"\nReceived: "red"', ); }); it('should fail if the element does not have any style', () => { const div = document.createElement('div'); - expect(() => expect(div).toHaveStyle('color', 'blue')).toThrowError( - `Expected: ${greenLog('"blue"')}\nReceived: ${redLog('""')}`, + expectThrowContaining( + () => expect(div).toHaveStyle('color', 'blue'), + 'Expected: "blue"\nReceived: ""', ); }); }); @@ -257,8 +273,9 @@ describe('test matchers', () => { it('should fail if the element does not have the class', () => { const div = document.createElement('div'); - expect(() => expect(div).toHaveClass('test')).toThrowError( - `Expected: ${greenLog('"test"')}\nReceived: ${redLog('""')}`, + expectThrowContaining( + () => expect(div).toHaveClass('test'), + 'Expected: "test"\nReceived: ""', ); }); }); @@ -274,8 +291,9 @@ describe('test matchers', () => { it('should fail if the input does not have the value', () => { const input = document.createElement('input'); - expect(() => expect(input).toHaveValue('test')).toThrowError( - `Expected: ${greenLog('"test"')}\nReceived: ${redLog('""')}`, + expectThrowContaining( + () => expect(input).toHaveValue('test'), + 'Expected: "test"\nReceived: ""', ); }); @@ -520,8 +538,9 @@ describe('test matchers', () => { const input = document.createElement('input'); input.type = 'text'; - expect(() => expect(input).toBeInputTypeOf('number')).toThrowError( - `Expected: ${greenLog('"number"')}\nReceived: ${redLog('"text"')}`, + expectThrowContaining( + () => expect(input).toBeInputTypeOf('number'), + 'Expected: "number"\nReceived: "text"', ); }); diff --git a/packages/brisa/src/utils/brisa-error-dialog/build-inject-code.ts b/packages/brisa/src/utils/brisa-error-dialog/build-inject-code.ts new file mode 100644 index 000000000..c2aece95c --- /dev/null +++ b/packages/brisa/src/utils/brisa-error-dialog/build-inject-code.ts @@ -0,0 +1,39 @@ +// Standalone build script spawned from inject-code.ts macro +// This avoids calling Bun.build inside a macro (deadlock in Bun >= 1.2) +import path from 'node:path'; +import clientBuildPlugin from '@/utils/client-build-plugin'; + +const pathname = path.join( + import.meta.dir, + 'web-components', + 'brisa-error-dialog.tsx', +); +const internalComponentId = '__BRISA_CLIENT__brisaErrorDialog'; + +const { success, outputs } = await Bun.build({ + throw: false, + entrypoints: [pathname], + target: 'browser', + external: ['brisa'], + define: { + __FILTER_DEV_RUNTIME_ERRORS__: '__FILTER_DEV_RUNTIME_ERRORS__', + }, + plugins: [ + { + name: 'dev-error-dialog-plugin', + setup(build) { + build.onLoad({ filter: /.*/ }, async ({ path, loader }) => ({ + contents: clientBuildPlugin( + await Bun.readableStreamToText(Bun.file(path).stream()), + internalComponentId, + ), + loader, + })); + }, + }, + ], +}); + +if (success && outputs?.[0]) { + process.stdout.write(await outputs[0].text()); +} diff --git a/packages/brisa/src/utils/brisa-error-dialog/inject-code.ts b/packages/brisa/src/utils/brisa-error-dialog/inject-code.ts index 852f97666..279d60bac 100644 --- a/packages/brisa/src/utils/brisa-error-dialog/inject-code.ts +++ b/packages/brisa/src/utils/brisa-error-dialog/inject-code.ts @@ -1,46 +1,14 @@ import path from 'node:path'; -import clientBuildPlugin from '@/utils/client-build-plugin'; import { logBuildError } from '@/utils/log/log-build'; // Should be used via macro export async function injectBrisaDialogErrorCode() { - const pathname = path.join( - import.meta.dir, - 'web-components', - 'brisa-error-dialog.tsx', - ); - const internalComponentId = '__BRISA_CLIENT__brisaErrorDialog'; + const scriptPath = path.join(import.meta.dir, 'build-inject-code.ts'); + const result = Bun.spawnSync(['bun', 'run', scriptPath]); - const { success, logs, outputs } = await Bun.build({ - // TODO: adapt to Bun > 1.2 (for now this is to force the old behavior) - throw: false, - entrypoints: [pathname], - target: 'browser', - external: ['brisa'], - define: { - __FILTER_DEV_RUNTIME_ERRORS__: '__FILTER_DEV_RUNTIME_ERRORS__', - }, - plugins: [ - { - name: 'dev-error-dialog-plugin', - setup(build) { - build.onLoad({ filter: /.*/ }, async ({ path, loader }) => ({ - contents: clientBuildPlugin( - // TODO: use (path).text() when Bun fix this issue: - // https://github.com/oven-sh/bun/issues/7611 - await Bun.readableStreamToText(Bun.file(path).stream()), - internalComponentId, - ), - loader, - })); - }, - }, - ], - }); - - if (!success) { - logBuildError('Failed to use brisa dialog error in development', logs); + if (result.exitCode !== 0) { + logBuildError('Failed to use brisa dialog error in development', []); } - return (await outputs?.[0]?.text?.()) ?? ''; + return result.stdout.toString(); } diff --git a/packages/brisa/src/utils/client-build/get-temp-page-name/index.test.ts b/packages/brisa/src/utils/client-build/get-temp-page-name/index.test.ts index 1445f9e56..0ba6fa32e 100644 --- a/packages/brisa/src/utils/client-build/get-temp-page-name/index.test.ts +++ b/packages/brisa/src/utils/client-build/get-temp-page-name/index.test.ts @@ -8,7 +8,7 @@ describe('build utils -> client build', () => { it('should return the correct temp file name', () => { const { BUILD_DIR } = getConstants(); const pagePath = '/path/to/page.tsx'; - const expected = join(BUILD_DIR, '_brisa', 'temp-path-to-page.ts'); + const expected = join(BUILD_DIR, '_brisa', 'temppage_tsx.ts'); const result = getTempPageName(pagePath); expect(result).toBe(expected); }); @@ -21,12 +21,12 @@ describe('build utils -> client build', () => { const expected1 = join( BUILD_DIR, '_brisa', - 'temp-path-to-page_example.ts', + 'temppage_example_tsx.ts', ); const expected2 = join( BUILD_DIR, '_brisa', - 'temp-path-to-page-example.ts', + 'tempexample_tsx.ts', ); const result1 = getTempPageName(pagePath1); @@ -39,7 +39,7 @@ describe('build utils -> client build', () => { it('should handle page paths with multiple extensions correctly', () => { const { BUILD_DIR } = getConstants(); const pagePath = '/path/to/page.min.tsx'; - const expected = join(BUILD_DIR, '_brisa', 'temp-path-to-page.min.ts'); + const expected = join(BUILD_DIR, '_brisa', 'temppage_min_tsx.ts'); const result = getTempPageName(pagePath); expect(result).toBe(expected); }); diff --git a/packages/brisa/src/utils/client-build/layout-build/index.test.ts b/packages/brisa/src/utils/client-build/layout-build/index.test.ts index 10d09fcfc..8fbe4e830 100644 --- a/packages/brisa/src/utils/client-build/layout-build/index.test.ts +++ b/packages/brisa/src/utils/client-build/layout-build/index.test.ts @@ -18,11 +18,11 @@ const pageWebComponents = { 'native-some-example': allWebComponents['native-some-example'], }; -const i18nCode = 3653; +const i18nCode = 3733; const brisaSize = 5738; // TODO: Reduce this size :/ const webComponents = 1453; const unsuspenseSize = 213; -const rpcSize = 2499; // TODO: Reduce this size +const rpcSize = 2490; // TODO: Reduce this size const lazyRPCSize = 4332; // TODO: Reduce this size // lazyRPC is loaded after user interaction (action, link), // so it's not included in the initial size diff --git a/packages/brisa/src/utils/client-build/process-i18n/build-inject-bridge.ts b/packages/brisa/src/utils/client-build/process-i18n/build-inject-bridge.ts new file mode 100644 index 000000000..0b37dd27a --- /dev/null +++ b/packages/brisa/src/utils/client-build/process-i18n/build-inject-bridge.ts @@ -0,0 +1,70 @@ +// Standalone build script spawned from inject-bridge.ts macro +// This avoids calling Bun.build inside a macro (deadlock in Bun >= 1.2) +import { join, resolve } from 'node:path'; + +const usei18nKeysLogic = process.argv[2] === 'true'; +const useFormatter = process.argv[3] === 'true'; + +const translateCoreFile = resolve( + import.meta.dirname, + join('..', '..', 'translate-core', 'index.ts'), +); + +function i18nKeysLogic(useFormatter: boolean) { + const formatters = useFormatter + ? `interpolation: {...i18nConfig.interpolation, format:__FORMATTER__},` + : ''; + + return ` + get t() { + return translateCore(this.locale, { ...i18nConfig, messages: this.messages, ${formatters} }); + }, + get messages() { return {[this.locale]: window.i18nMessages } }, + overrideMessages(callback) { + const p = callback(window.i18nMessages); + const a = m => Object.assign(window.i18nMessages, m); + return p.then?.(a) ?? a(p); + } + `; +} + +const { success, outputs } = await Bun.build({ + throw: false, + entrypoints: [translateCoreFile], + target: 'browser', + root: import.meta.dirname, + minify: true, + format: 'iife', + plugins: [ + { + name: 'i18n-bridge', + setup(build) { + const filter = /.*/; + + build.onLoad({ filter }, async ({ path, loader }) => { + const contents = ` + ${ + usei18nKeysLogic + ? await Bun.readableStreamToText(Bun.file(path).stream()) + : '' + } + + const i18nConfig = __CONFIG__; + + window.i18n = { + ...i18nConfig, + get locale(){ return document.documentElement.lang }, + ${usei18nKeysLogic ? i18nKeysLogic(useFormatter) : ''} + } + `; + + return { contents, loader }; + }); + }, + }, + ], +}); + +if (success && outputs?.[0]) { + process.stdout.write(await outputs[0].text()); +} diff --git a/packages/brisa/src/utils/client-build/process-i18n/inject-bridge.ts b/packages/brisa/src/utils/client-build/process-i18n/inject-bridge.ts index e166dd274..436618e0e 100644 --- a/packages/brisa/src/utils/client-build/process-i18n/inject-bridge.ts +++ b/packages/brisa/src/utils/client-build/process-i18n/inject-bridge.ts @@ -1,83 +1,29 @@ import { logBuildError } from '@/utils/log/log-build'; -import { join, resolve } from 'node:path'; +import { join } from 'node:path'; type I18nBridgeConfig = { usei18nKeysLogic?: boolean; useFormatter?: boolean; }; -const translateCoreFile = resolve( - import.meta.dirname, - join('..', '..', 'translate-core', 'index.ts'), -); - export async function build( { usei18nKeysLogic = false, useFormatter = false }: I18nBridgeConfig = { usei18nKeysLogic: false, useFormatter: false, }, ) { - const { success, logs, outputs } = await Bun.build({ - // TODO: adapt to Bun > 1.2 (for now this is to force the old behavior) - throw: false, - entrypoints: [translateCoreFile], - target: 'browser', - root: import.meta.dirname, - minify: true, - format: 'iife', - plugins: [ - { - name: 'i18n-bridge', - setup(build) { - const filter = /.*/; - - build.onLoad({ filter }, async ({ path, loader }) => { - const contents = ` - ${ - usei18nKeysLogic - ? // TODO: use (path).text() when Bun fix this issue: - // https://github.com/oven-sh/bun/issues/7611 - await Bun.readableStreamToText(Bun.file(path).stream()) - : '' - } - - const i18nConfig = __CONFIG__; - - window.i18n = { - ...i18nConfig, - get locale(){ return document.documentElement.lang }, - ${usei18nKeysLogic ? i18nKeysLogic(useFormatter) : ''} - } - `; - - return { contents, loader }; - }); - }, - }, - ], - }); - - if (!success) { - logBuildError('Failed to integrate i18n core', logs); + const scriptPath = join(import.meta.dir, 'build-inject-bridge.ts'); + const result = Bun.spawnSync([ + 'bun', + 'run', + scriptPath, + usei18nKeysLogic.toString(), + useFormatter.toString(), + ]); + + if (result.exitCode !== 0) { + logBuildError('Failed to integrate i18n core', []); } - return (await outputs?.[0]?.text?.()) ?? ''; -} - -function i18nKeysLogic(useFormatter: boolean) { - const formatters = useFormatter - ? `interpolation: {...i18nConfig.interpolation, format:__FORMATTER__},` - : ''; - - return ` - get t() { - return translateCore(this.locale, { ...i18nConfig, messages: this.messages, ${formatters} }); - }, - get messages() { return {[this.locale]: window.i18nMessages } }, - overrideMessages(callback) { - const p = callback(window.i18nMessages); - const a = m => Object.assign(window.i18nMessages, m); - return p.then?.(a) ?? a(p); - } - `; + return result.stdout.toString(); } diff --git a/packages/brisa/src/utils/compile-assets/index.test.ts b/packages/brisa/src/utils/compile-assets/index.test.ts index 5a56eb46c..67ebdb5ae 100644 --- a/packages/brisa/src/utils/compile-assets/index.test.ts +++ b/packages/brisa/src/utils/compile-assets/index.test.ts @@ -74,6 +74,7 @@ describe('compileAssets', () => { it('should not compress fixtures assets in development and neither create the sitemap.xml', async () => { const log = spyOn(console, 'log'); + log.mockClear(); globalThis.mockConstants!.IS_PRODUCTION = false; await compileAssets(); expect( @@ -82,7 +83,7 @@ describe('compileAssets', () => { expect(log).toHaveBeenCalledTimes(1); expect(log.mock.calls.toString()).toContain('sitemap.xml generated in'); - log.mockClear(); + log.mockRestore(); }); it('should not compress fixtures assets if assetCompression is false', async () => { diff --git a/packages/brisa/src/utils/compile-files/index.test.ts b/packages/brisa/src/utils/compile-files/index.test.ts index 38804f9c4..a06040f3a 100644 --- a/packages/brisa/src/utils/compile-files/index.test.ts +++ b/packages/brisa/src/utils/compile-files/index.test.ts @@ -271,12 +271,12 @@ describe('utils', () => { ${info} ${info}Route | JS server | JS client (gz) ${info}---------------------------------------------------------------- - ${info}ƒ /middleware | 818 B | + ${info}λ /pages/index | 860 B | ${greenLog('4 kB')} + ${info}ƒ /middleware | 800 B | ${info}Δ /layout | 612 B | ${info}Ω /i18n | 162 B | ${info}Ψ /websocket | 207 B | ${info}Θ /web-components/_integrations | 67 B | - ${info}λ /pages/index | 860 B | ${greenLog('4 kB')} ${info}λ /pages/page-with-web-component | 769 B | ${greenLog('5 kB')} ${info}λ /pages/somepage | 1 kB | ${greenLog('0 B')} ${info}λ /pages/somepage-with-context | 1 kB | ${greenLog('0 B')} diff --git a/packages/brisa/src/utils/context-provider/build-inject-client.ts b/packages/brisa/src/utils/context-provider/build-inject-client.ts new file mode 100644 index 000000000..86529de4c --- /dev/null +++ b/packages/brisa/src/utils/context-provider/build-inject-client.ts @@ -0,0 +1,32 @@ +// Standalone build script spawned from inject-client.ts macro +// This avoids calling Bun.build inside a macro (deadlock in Bun >= 1.2) +import path from 'node:path'; +import clientBuildPlugin from '@/utils/client-build-plugin'; + +const pathname = path.join(import.meta.dir, 'client.tsx'); +const internalComponentId = '__BRISA_CLIENT__contextProvider'; + +const { success, outputs } = await Bun.build({ + throw: false, + entrypoints: [pathname], + target: 'browser', + external: ['brisa'], + plugins: [ + { + name: 'context-provider-transformer', + setup(build) { + build.onLoad({ filter: /.*/ }, async ({ path, loader }) => ({ + contents: clientBuildPlugin( + await Bun.readableStreamToText(Bun.file(path).stream()), + internalComponentId, + ), + loader, + })); + }, + }, + ], +}); + +if (success && outputs?.[0]) { + process.stdout.write(await outputs[0].text()); +} diff --git a/packages/brisa/src/utils/context-provider/inject-client.ts b/packages/brisa/src/utils/context-provider/inject-client.ts index a42a7eaf3..99693e980 100644 --- a/packages/brisa/src/utils/context-provider/inject-client.ts +++ b/packages/brisa/src/utils/context-provider/inject-client.ts @@ -1,39 +1,14 @@ import path from 'node:path'; -import clientBuildPlugin from '@/utils/client-build-plugin'; import { logBuildError } from '@/utils/log/log-build'; // Should be used via macro export async function injectClientContextProviderCode() { - const pathname = path.join(import.meta.dir, 'client.tsx'); - const internalComponentId = '__BRISA_CLIENT__contextProvider'; + const scriptPath = path.join(import.meta.dir, 'build-inject-client.ts'); + const result = Bun.spawnSync(['bun', 'run', scriptPath]); - const { success, logs, outputs } = await Bun.build({ - // TODO: adapt to Bun > 1.2 (for now this is to force the old behavior) - throw: false, - entrypoints: [pathname], - target: 'browser', - external: ['brisa'], - plugins: [ - { - name: 'context-provider-transformer', - setup(build) { - build.onLoad({ filter: /.*/ }, async ({ path, loader }) => ({ - contents: clientBuildPlugin( - // TODO: use Bun.file(path).text() when Bun fix this issue: - // https://github.com/oven-sh/bun/issues/7611 - await Bun.readableStreamToText(Bun.file(path).stream()), - internalComponentId, - ), - loader, - })); - }, - }, - ], - }); - - if (!success) { - logBuildError('Failed to compile client context provider', logs); + if (result.exitCode !== 0) { + logBuildError('Failed to compile client context provider', []); } - return (await outputs?.[0]?.text?.()) ?? ''; + return result.stdout.toString(); } diff --git a/packages/brisa/src/utils/get-page-component-with-headers/index.test.ts b/packages/brisa/src/utils/get-page-component-with-headers/index.test.ts index 88b1bafb0..144ae80ad 100644 --- a/packages/brisa/src/utils/get-page-component-with-headers/index.test.ts +++ b/packages/brisa/src/utils/get-page-component-with-headers/index.test.ts @@ -1,4 +1,4 @@ -import { getConstants } from '@/constants'; +import defaultConstants, { getConstants } from '@/constants'; import type { MatchedBrisaRoute } from '@/types'; import extendRequestContext from '@/utils/extend-request-context'; import getPageComponentWithHeaders from '@/utils/get-page-component-with-headers'; @@ -18,11 +18,12 @@ describe('utils', () => { describe('getPageComponentWithHeaders', () => { beforeEach(async () => { globalThis.mockConstants = { - ...(getConstants() ?? {}), + ...defaultConstants, PAGES_DIR, BUILD_DIR, SRC_DIR: BUILD_DIR, ASSETS_DIR, + WORKSPACE: BUILD_DIR, LOCALES_SET: new Set(['en', 'es']), I18N_CONFIG: { locales: ['en', 'es'], diff --git a/packages/brisa/src/utils/get-page-component-with-headers/index.ts b/packages/brisa/src/utils/get-page-component-with-headers/index.ts index c79ed6354..5a4ba74bd 100644 --- a/packages/brisa/src/utils/get-page-component-with-headers/index.ts +++ b/packages/brisa/src/utils/get-page-component-with-headers/index.ts @@ -12,9 +12,6 @@ type Params = { headers?: Record; }; -const { HEADERS, BUILD_DIR, MODULES } = getConstants(); -const { middleware } = MODULES; - export default async function getPageComponentWithHeaders({ req, route, @@ -22,6 +19,8 @@ export default async function getPageComponentWithHeaders({ status = 200, headers, }: Params) { + const { HEADERS, MODULES } = getConstants(); + const middleware = MODULES?.middleware; const { Page, module, layoutModule } = await processPageRoute(route, error); let pageHeaders = new Headers({ 'cache-control': HEADERS.CACHE_CONTROL, diff --git a/packages/brisa/src/utils/inject-unsuspense-code/index.ts b/packages/brisa/src/utils/inject-unsuspense-code/index.ts index 2dc06f8f5..d71b2e2a9 100644 --- a/packages/brisa/src/utils/inject-unsuspense-code/index.ts +++ b/packages/brisa/src/utils/inject-unsuspense-code/index.ts @@ -3,17 +3,19 @@ import path from 'node:path'; // Should be used via macro export async function injectUnsuspenseCode() { - const { success, logs, outputs } = await Bun.build({ - // TODO: adapt to Bun > 1.2 (for now this is to force the old behavior) - throw: false, - entrypoints: [path.join(import.meta.dir, 'unsuspense.ts')], - target: 'browser', - minify: true, - }); + const entrypoint = path.join(import.meta.dir, 'unsuspense.ts'); + const result = Bun.spawnSync([ + 'bun', + 'build', + entrypoint, + '--target', + 'browser', + '--minify', + ]); - if (!success) { - logBuildError('Failed to compile unsuspense code', logs); + if (result.exitCode !== 0) { + logBuildError('Failed to compile unsuspense code', []); } - return (await outputs?.[0]?.text?.()) ?? ''; + return result.stdout.toString(); } diff --git a/packages/brisa/src/utils/process-page-route/index.tsx b/packages/brisa/src/utils/process-page-route/index.tsx index d55b95d57..87d46ba59 100644 --- a/packages/brisa/src/utils/process-page-route/index.tsx +++ b/packages/brisa/src/utils/process-page-route/index.tsx @@ -11,7 +11,7 @@ export default async function processPageRoute( error?: Error, ) { const { MODULES } = getConstants(); - const { pages, layoutModule } = MODULES; + const { pages, layoutModule } = MODULES ?? {}; // TODO: Remove async-await after finish #628 const module = await pages[route.name]; const PageComponent = module?.default ?? Empty; diff --git a/packages/brisa/src/utils/resolve-import-sync/index.ts b/packages/brisa/src/utils/resolve-import-sync/index.ts index be1d50afa..6890f19e1 100644 --- a/packages/brisa/src/utils/resolve-import-sync/index.ts +++ b/packages/brisa/src/utils/resolve-import-sync/index.ts @@ -25,29 +25,33 @@ export default function resolveImportSync(id: string, parent?: string) { : undefined, }); } catch (e) { - // Note: this is tested on packages/brisa/src/cli/serve/node-serve/handler.node-test.js with .mjs files - if (!isBun) { - for (const extension of extensions) { - const filePath = path.join(parent ?? '', id + extension); - const fileWithIndexPath = path.join( - parent ?? '', - id, - 'index' + extension, - ); + // Bun caches module resolution failures, so if a file is deleted and + // recreated (as happens during build), createRequire().resolve() and + // Bun.resolveSync() may return stale cached failures. Use filesystem + // checks as fallback to bypass the cache. + for (const extension of extensions) { + const filePath = path.join(parent ?? '', id + extension); + const fileWithIndexPath = path.join( + parent ?? '', + id, + 'index' + extension, + ); - if (fs.existsSync(filePath)) return filePath; - if (fs.existsSync(fileWithIndexPath)) return fileWithIndexPath; - } + if (fs.existsSync(filePath)) return filePath; + if (fs.existsSync(fileWithIndexPath)) return fileWithIndexPath; + } - throw e; + if (isBun) { + // This resolves "exports" inside the package.json of dependencies in Bun runtime + // Issue: https://github.com/brisa-build/brisa/issues/434 + // This error only happens in Build-time, so Bun.js: + // Related Bun issue: https://github.com/oven-sh/bun/issues/4668 + return Bun.resolveSync( + id, + parent ? path.dirname(parent) : import.meta.dirname, + ); } - // This resolves "exports" inside the package.json of dependencies in Bun runtime - // Issue: https://github.com/brisa-build/brisa/issues/434 - // This error only happens in Build-time, so Bun.js: - // Related Bun issue: https://github.com/oven-sh/bun/issues/4668 - return Bun.resolveSync( - id, - parent ? path.dirname(parent) : import.meta.dirname, - ); + + throw e; } } diff --git a/packages/brisa/src/utils/rpc/index.ts b/packages/brisa/src/utils/rpc/index.ts index 0202d56dd..bade68fde 100644 --- a/packages/brisa/src/utils/rpc/index.ts +++ b/packages/brisa/src/utils/rpc/index.ts @@ -18,23 +18,25 @@ export async function injectRPCLazyCode() { } async function buildRPC(file: string, isStatic = false) { - const { success, logs, outputs } = await Bun.build({ - // TODO: adapt to Bun > 1.2 (for now this is to force the old behavior) - throw: false, - entrypoints: [path.join(import.meta.dir, file)], - target: 'browser', - minify: true, - define: { - __RPC_LAZY_FILE__: `'/_brisa/pages/_rpc-lazy-${constants.VERSION}.js'`, - __IS_STATIC__: isStatic.toString(), - }, - }); + const entrypoint = path.join(import.meta.dir, file); + const result = Bun.spawnSync([ + 'bun', + 'build', + entrypoint, + '--target', + 'browser', + '--minify', + '--define', + `__RPC_LAZY_FILE__='/_brisa/pages/_rpc-lazy-${constants.VERSION}.js'`, + '--define', + `__IS_STATIC__=${isStatic.toString()}`, + ]); - if (!success) { - logBuildError('Failed to compile RPC code', logs); + if (result.exitCode !== 0) { + logBuildError('Failed to compile RPC code', []); } - const code = (await outputs?.[0]?.text?.()) ?? ''; + const code = result.stdout.toString(); return `(()=>{${code}})()`; } diff --git a/packages/create-brisa/create-brisa.test.ts b/packages/create-brisa/create-brisa.test.ts index 668f94210..7da1d489c 100644 --- a/packages/create-brisa/create-brisa.test.ts +++ b/packages/create-brisa/create-brisa.test.ts @@ -38,7 +38,7 @@ describe('create-brisa', () => { it('should exit and display an error if the folder exists', async () => { fs.mkdirSync('out'); const projectName = 'out/foo'; - const res = await $`echo "${projectName}" | bun run ${CREATE_BRISA_PATH}`; + const res = await $`echo "${projectName}" | bun run ${CREATE_BRISA_PATH}`.nothrow(); expect(res.stderr.toString()).toBe('Error: out folder already exists\n'); });