Skip to content

Commit

Permalink
feat: build in dev + serve web components at top level + adapt hotreload
Browse files Browse the repository at this point in the history
  • Loading branch information
aralroca committed Oct 10, 2023
1 parent f8ee0ec commit b3307ba
Show file tree
Hide file tree
Showing 46 changed files with 734 additions and 257 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ node_modules
# Build output

/out/
**/inject-unsuspense-script/out
**/inject-unsuspense-code/out
**/__fixtures__/out
/jsx-runtime/
/jsx-dev-runtime/
cli-build
Expand Down
6 changes: 3 additions & 3 deletions cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ if [ "$1" = "dev" ]; then
esac
shift
done
NODE_ENV=development $BUN_EXEC --hot node_modules/brisa/out/cli/dev/index.js $PORT
NODE_ENV=development $BUN_EXEC node_modules/brisa/out/cli/build.js && NODE_ENV=development $BUN_EXEC node_modules/brisa/out/cli/serve/index.js $PORT

# brisa build
elif [ "$1" = "build" ]; then
NODE_ENV=production $BUN_EXEC node_modules/brisa/out/cli/build.js
NODE_ENV=production $BUN_EXEC node_modules/brisa/out/cli/build.js PROD

# brisa start
elif [ "$1" = "start" ]; then
Expand All @@ -54,7 +54,7 @@ elif [ "$1" = "start" ]; then
esac
shift
done
NODE_ENV=production $BUN_EXEC node_modules/brisa/out/cli/serve/index.js $PORT
NODE_ENV=production $BUN_EXEC node_modules/brisa/out/cli/serve/index.js $PORT PROD

# brisa --help
else
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"scripts": {
"build": "bun run clean && bun run build:jsx-runtime && bun run build:jsx-dev-runtime && bun run build:core",
"build:core": "bun build --minify --target=bun --outdir=out/core src/core/index.ts && bun run build:cli && cp src/types/index.d.ts out/core/index.d.ts",
"build:cli": "NODE_ENV=production bun build --minify --target=bun --outdir=out/cli src/cli/build.ts src/cli/serve/index.tsx && NODE_ENV=development bun build --minify --target=bun --outdir=out/cli/dev src/cli/serve/index.tsx",
"build:cli": "bun build --minify --target=bun --outdir=out/cli src/cli/build.ts src/cli/serve/index.tsx",
"build:jsx-runtime": "bun build --minify --target=bun --outdir=jsx-runtime src/jsx-runtime/index.ts && cp src/types/index.d.ts jsx-runtime/index.d.ts",
"build:jsx-dev-runtime": "bun build --minify --target=bun --outdir=jsx-dev-runtime src/jsx-runtime/index.ts && cp src/types/index.d.ts jsx-dev-runtime/index.d.ts",
"test": "bun test",
Expand Down
3 changes: 3 additions & 0 deletions src/__fixtures__/dev/pages/dev-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default async function SomePage() {
return <h1>Some dev page</h1>;
}
3 changes: 3 additions & 0 deletions src/__fixtures__/dev/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Home() {
return <div>Hello world</div>;
}
6 changes: 5 additions & 1 deletion src/__fixtures__/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export default function Home() {
export default async function Home() {
return <div>Hello world</div>;
}

Home.suspense = () => {
return <div>Loading...</div>;
};
4 changes: 3 additions & 1 deletion src/__fixtures__/pages/user/[username].tsx
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
// for testing purposes
export default function User() {
return <div>user</div>;
}
File renamed without changes.
97 changes: 12 additions & 85 deletions src/cli/build.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,19 @@
import path from "node:path";
import fs from "node:fs";
import getRootDir from "../utils/get-root-dir";
import logTable from "../utils/log-table";
import byteSizeToString from "../utils/byte-size-to-string";
import precompressAssets from "../utils/precompress-assets";
import getEntrypoints from "../utils/get-entrypoints";
import getImportableFilepath from "../utils/get-importable-filepath";
import compileAll from "../utils/compile-all";
import getConstants from "../constants";

const { SRC_DIR, CONFIG } = getConstants();
const pagesDir = path.join(SRC_DIR, "pages");
const apiDir = path.join(SRC_DIR, "api");
let outdir = getRootDir("production");
const outAssetsDir = path.join(outdir, "public");
const inAssetsDir = path.join(SRC_DIR, "public");
const pagesEntrypoints = getEntrypoints(pagesDir);
const apiEntrypoints = getEntrypoints(apiDir);
const middlewarePath = getImportableFilepath("middleware", SRC_DIR);
const layoutPath = getImportableFilepath("layout", SRC_DIR);
const i18nPath = getImportableFilepath("i18n", SRC_DIR);
const entrypoints = [...pagesEntrypoints, ...apiEntrypoints];
const { IS_PRODUCTION, LOG_PREFIX } = getConstants();

if (middlewarePath) entrypoints.push(middlewarePath);
if (layoutPath) entrypoints.push(layoutPath);
if (i18nPath) entrypoints.push(i18nPath);

// This fix Bun build with only one entrypoint because it doesn't create the subfolder
if (entrypoints.length === 1) {
const subfolder = entrypoints[0].includes(path.join(outdir, "api"))
? "api"
: "pages";
outdir = path.join(outdir, subfolder);
}

console.log("🚀 Building your app...\n");

const { success, logs, outputs } = await Bun.build({
entrypoints,
outdir,
root: SRC_DIR,
minify: true,
splitting: true,
plugins: [...(CONFIG?.plugins ?? [])],
});

if (!success) {
logs.forEach((log) => console.error(log));
process.exit(1);
}

let hasChunk = false;

logTable(
outputs.map((output) => {
const route = output.path.replace(outdir, "");
const isChunk = route.startsWith("/chunk-");
let symbol = "λ";

if (isChunk) {
hasChunk = true;
symbol = "Φ";
} else if (route.startsWith("/middleware")) {
symbol = "ƒ";
} else if (route.startsWith("/layout")) {
symbol = "Δ";
} else if (route.startsWith("/i18n")) {
symbol = "Ω";
}

return {
Route: `${symbol} ${route}`,
Size: byteSizeToString(output.size, 0),
};
}),
console.log(
LOG_PREFIX.WAIT,
IS_PRODUCTION
? "🚀 building your Brisa app..."
: "starting the development server...",
);

console.log("\nλ Server entry-points");
if (layoutPath) console.log("Δ Layout");
if (middlewarePath) console.log("ƒ Middleware");
if (i18nPath) console.log("Ω i18n");
console.log("Φ JS shared by all \n");

if (fs.existsSync(inAssetsDir)) {
// Copy all assets to the build directory
fs.cpSync(inAssetsDir, outAssetsDir, { recursive: true });
const success = await compileAll();
const ms = (Bun.nanoseconds() / 1000000).toFixed(2);

// Precompress all assets
await precompressAssets(outAssetsDir).catch(console.error);
}
if (!success) process.exit(1);

console.info(`✨ Done in ${(Bun.nanoseconds() / 1000000).toFixed(2)}ms.`);
if (IS_PRODUCTION) console.info(LOG_PREFIX.INFO, `✨ Done in ${ms}ms.`);
else console.info(LOG_PREFIX.INFO, `compiled successfully in ${ms}ms.`);
36 changes: 35 additions & 1 deletion src/cli/dev-live-reload.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
import { watch } from "node:fs";
import path from "node:path";
import dangerHTML from "../core/danger-html";
import getConstants from "../constants";

const { LOG_PREFIX, SRC_DIR, IS_PRODUCTION } = getConstants();
const LIVE_RELOAD_WEBSOCKET_PATH = "__brisa_live_reload__";
const LIVE_RELOAD_COMMAND = "reload";

globalThis.ws?.send?.(LIVE_RELOAD_COMMAND);
if (!IS_PRODUCTION) {
console.log(LOG_PREFIX.INFO, "hot reloading enabled");
watch(SRC_DIR, { recursive: true }, async (event, filename) => {
if (event !== "change") return;

console.log(LOG_PREFIX.WAIT, `recompiling ${filename}...`);
globalThis.Loader.registry.clear();

const nsStart = Bun.nanoseconds();
const { exitCode, stderr } = Bun.spawnSync({
cmd: [process.execPath, path.join(import.meta.dir, "..", "build.js")],
env: process.env,
stderr: "pipe",
stdout: "inherit",
});
const nsEnd = Bun.nanoseconds();
const ms = ((nsEnd - nsStart) / 1000000).toFixed(2);

if (exitCode !== 0) {
console.log(
LOG_PREFIX.ERROR,
`failed to recompile ${filename}`,
stderr.toString(),
);
return;
}

console.log(LOG_PREFIX.READY, `recompiled successfully in ${ms}ms`);
globalThis?.ws?.send(LIVE_RELOAD_COMMAND);
});
}

export function LiveReloadScript({
port,
Expand Down
7 changes: 4 additions & 3 deletions src/cli/serve/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import fs from "node:fs";
import getConstants from "../../constants";
import { serveOptions } from "./serve-options";

const { IS_PRODUCTION, ROOT_DIR, PAGES_DIR } = getConstants();
const { IS_PRODUCTION, BUILD_DIR, PAGES_DIR, LOG_PREFIX } = getConstants();

if (IS_PRODUCTION && !fs.existsSync(ROOT_DIR)) {
if (IS_PRODUCTION && !fs.existsSync(BUILD_DIR)) {
console.error('Not exist "build" yet. Please run "brisa build" first');
process.exit(1);
}
Expand All @@ -20,5 +20,6 @@ if (!fs.existsSync(PAGES_DIR)) {
const server = Bun.serve(serveOptions);

console.log(
`Listening on http://localhost:${server.port} (${process.env.NODE_ENV})...`,
LOG_PREFIX.READY,
`listening on http://${server.hostname}:${server.port}...`,
);
44 changes: 28 additions & 16 deletions src/cli/serve/serve-options.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import fs from "node:fs";
import {
describe,
it,
expect,
beforeEach,
afterEach,
spyOn,
mock,
} from "bun:test";
import getConstants from "../../constants";
import { BunFile } from "bun";

const ROOT_DIR = path.join(import.meta.dir, "..", "..", "__fixtures__");
const PAGES_DIR = path.join(ROOT_DIR, "pages");
const ASSETS_DIR = path.join(ROOT_DIR, "assets");
const BUILD_DIR = path.join(import.meta.dir, "..", "..", "__fixtures__");
const PAGES_DIR = path.join(BUILD_DIR, "pages");
const ASSETS_DIR = path.join(BUILD_DIR, "public");

async function testRequest(request: Request): Promise<Response> {
const serveOptions = (await import("./serve-options")).serveOptions;
Expand All @@ -23,16 +33,8 @@ describe("CLI: serve", () => {
globalThis.mockConstants = {
...(getConstants() ?? {}),
PAGES_DIR,
ROOT_DIR,
SRC_DIR: ROOT_DIR,
WEB_COMPONENTS: {
"native-some-example": path.join(
ROOT_DIR,
"web-components",
"@native",
"some-example.tsx",
),
},
BUILD_DIR,
SRC_DIR: BUILD_DIR,
ASSETS_DIR,
LOCALES_SET: new Set(["en", "es"]),
I18N_CONFIG: {
Expand Down Expand Up @@ -107,15 +109,25 @@ describe("CLI: serve", () => {
});

it("should return 200 page with web component", async () => {
const mockFs = spyOn(fs, "existsSync").mockImplementation(() => true);
const mockFile = spyOn(Bun, "file").mockImplementation(
() =>
({
text: () => Promise.resolve("I am a web component JS code"),
}) as BunFile,
);

const response = await testRequest(
new Request("http://localhost:1234/es/page-with-web-component"),
);
const html = await response.text();

expect(response.status).toBe(200);
mockFs.mockRestore();
mockFile.mockRestore();

expect(response.status).toBe(200);
expect(html).toContain('<title id="title">CUSTOM LAYOUT</title>');
expect(html).toContain('customElements.define("native-some-example",');
expect(html).toContain("<script>I am a web component JS code</script>");
expect(html).toContain(
'<native-some-example name="web component"></native-some-example>',
);
Expand Down
14 changes: 6 additions & 8 deletions src/cli/serve/serve-options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ const {
PAGE_404,
PAGE_500,
RESERVED_PAGES,
ROOT_DIR,
BUILD_DIR,
PORT,
PAGES_DIR,
ASSETS_DIR,
} = getConstants();

let pagesRouter = getRouteMatcher(PAGES_DIR, RESERVED_PAGES);
let rootRouter = getRouteMatcher(ROOT_DIR);
let rootRouter = getRouteMatcher(BUILD_DIR);

const WEBSOCKET_PATH = getImportableFilepath("websocket", ROOT_DIR);
const WEBSOCKET_PATH = getImportableFilepath("websocket", BUILD_DIR);
const wsModule = WEBSOCKET_PATH ? await import(WEBSOCKET_PATH) : null;
const route404 = pagesRouter.reservedRoutes[PAGE_404];
const middlewareModule = await importFileIfExists("middleware", ROOT_DIR);
const middlewareModule = await importFileIfExists("middleware", BUILD_DIR);
const customMiddleware = middlewareModule?.default;

const responseInitWithGzip = {
Expand Down Expand Up @@ -159,9 +159,7 @@ async function handleRequest(req: RequestContext, isAnAsset: boolean) {
// Assets
if (isAnAsset) {
const assetPath = path.join(ASSETS_DIR, url.pathname);
const isGzip =
IS_PRODUCTION && req.headers.get("accept-encoding")?.includes?.("gzip");

const isGzip = req.headers.get("accept-encoding")?.includes?.("gzip");
const file = Bun.file(isGzip ? `${assetPath}.gz` : assetPath);
const responseOptions = isGzip ? responseInitWithGzip : {};

Expand All @@ -187,7 +185,7 @@ async function responseRenderedPage({
}) {
const module = await import(route.filePath);
const PageComponent = module.default;
const layoutPath = getImportableFilepath("layout", ROOT_DIR);
const layoutPath = getImportableFilepath("layout", BUILD_DIR);
const layoutModule = layoutPath ? await import(layoutPath) : undefined;

const pageElement = (
Expand Down
25 changes: 16 additions & 9 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import path from "node:path";
import getWebComponentsList from "./utils/get-web-components-list";
import getRootDir from "./utils/get-root-dir";
import importFileIfExists from "./utils/import-file-if-exists";
import { Configuration, I18nConfig } from "./types";

const rootDir = getRootDir();
const srcDir = getRootDir("development");
const srcDir = path.join(rootDir, "src");
const buildDir = path.join(rootDir, "build");
const PAGE_404 = "/_404";
const PAGE_500 = "/_500";
const I18N_CONFIG = (await importFileIfExists("i18n", rootDir))
const I18N_CONFIG = (await importFileIfExists("i18n", buildDir))
?.default as I18nConfig;
const CONFIG_DIR = path.join(srcDir, "..");
const CONFIG =
(await importFileIfExists("brisa.config", CONFIG_DIR))?.default ?? {};
(await importFileIfExists("brisa.config", rootDir))?.default ?? {};

const defaultConfig = {
trailingSlash: false,
Expand All @@ -24,14 +23,22 @@ const constants = {
PAGE_404,
PAGE_500,
RESERVED_PAGES: [PAGE_404, PAGE_500],
IS_PRODUCTION: process.env.NODE_ENV === "production",
IS_PRODUCTION:
process.argv.some((t) => t == "PROD") ||
process.env.NODE_ENV === "production",
PORT: parseInt(process.argv[2]) || 0,
BUILD_DIR: buildDir,
ROOT_DIR: rootDir,
SRC_DIR: srcDir,
ASSETS_DIR: path.join(rootDir, "public"),
PAGES_DIR: path.join(rootDir, "pages"),
ASSETS_DIR: path.join(buildDir, "public"),
PAGES_DIR: path.join(buildDir, "pages"),
I18N_CONFIG,
WEB_COMPONENTS: getWebComponentsList(srcDir),
LOG_PREFIX: {
WAIT: Bun.enableANSIColors ? `[ \x1b[36mwait\x1b[0m ] ` : "[ wait ] ",
READY: Bun.enableANSIColors ? `[ \x1b[32mready\x1b[0m ] ` : "[ ready ] ",
INFO: Bun.enableANSIColors ? `[ \x1b[34minfo\x1b[0m ] ` : "[ info ] ",
ERROR: Bun.enableANSIColors ? `[ \x1b[31merror\x1b[0m ] ` : "[ error ] ",
},
LOCALES_SET: new Set(I18N_CONFIG?.locales || []) as Set<string>,
CONFIG: { ...defaultConfig, ...CONFIG } as Configuration,
REGEX: {
Expand Down
Loading

0 comments on commit b3307ba

Please sign in to comment.