monograph: use bun in prod

This commit is contained in:
Abdullah Atta
2024-11-24 15:18:38 +05:00
parent 84abfa3478
commit cb0bc39cd1
12 changed files with 480 additions and 789 deletions

View File

@@ -6,3 +6,4 @@ node_modules
.dev.vars
.wrangler
/output

View File

@@ -1,20 +1,14 @@
FROM --platform=$BUILDPLATFORM node:20-alpine
FROM --platform=$BUILDPLATFORM oven/bun:1.1.36-alpine
RUN mkdir -p /home/node/app && chown -R node:node /home/node/app
RUN mkdir -p /home/bun/app && chown -R bun:bun /home/bun/app
WORKDIR /home/node/app
WORKDIR /home/bun/app
USER node
USER bun
COPY --chown=node:node build ./build
COPY --chown=bun:bun output .
RUN mv build/package.json .
RUN bun install
RUN npm install
RUN npm install --include=optional sharp
RUN ls
CMD [ "npm", "run", "start" ]
CMD [ "bun", "run", "start" ]

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type { MetaFunction, LoaderFunctionArgs } from "@remix-run/node";
import { Cipher } from "@notesnook/crypto";
import { convert } from "html-to-text";
import { Flex, Text } from "@theme-ui/components";
import { useLoaderData } from "@remix-run/react";
import { MonographPage } from "../components/monographpost";
@@ -89,7 +88,7 @@ export async function loader({ params }: LoaderFunctionArgs) {
metadata
};
} catch (e) {
console.error(e);
// console.error(e);
return {
monograph: null,
metadata: {
@@ -135,15 +134,6 @@ export default function MonographPost() {
);
}
const extractParagraph = (html: string) => {
if (!html) return "";
return convert(html, {
wordwrap: false,
preserveNewlines: false,
decodeEntities: true
});
};
type Metadata = {
title: string;
fullDescription: string;
@@ -151,6 +141,21 @@ type Metadata = {
datePublished: string;
};
function extractFirstWords(html: string, numWords = 30): string {
// Strip HTML tags and normalize whitespace
const plainText = html
.replace(/<[^>]*>/g, " ")
.replace(/\s+/g, " ")
.trim();
// Split into words and take first N
const words = plainText.split(" ").slice(0, numWords);
// Add ellipsis if text was truncated
const excerpt = words.join(" ");
return words.length < plainText.split(" ").length ? excerpt + "..." : excerpt;
}
function trimDescription(
str: string,
length: number,
@@ -175,12 +180,12 @@ function addPeriod(str: string) {
return str + "...";
}
function getMonographMetadata(monograph?: Monograph): Metadata {
function getMonographMetadata(monograph: Monograph): Metadata {
const title = monograph?.title || "Not found";
const text = monograph?.encryptedContent
? "This monograph is encrypted. Enter password to view contents."
: monograph?.content
? extractParagraph(monograph?.content.data)
? extractFirstWords(monograph?.content.data, 100)
: "";
const shortDescription = trimDescription(text, 150, true);
const fullDescription = trimDescription(text, 300, true);

View File

@@ -74,7 +74,7 @@ export async function isSpam(monograph: Monograph) {
}
return isSpam;
} catch (e) {
console.error(e);
// console.error(e);
return false;
}
}

View File

@@ -23,7 +23,7 @@ export async function read<T>(key: string, fallback: T): Promise<T> {
try {
return (JSON.parse(await readFile(key, "utf-8")) as T) || fallback;
} catch (e) {
console.error(e);
// console.error(e);
return fallback;
}
}

View File

@@ -28,11 +28,11 @@ const cache: Record<
export async function read<T>(key: string, fallback: T) {
const cached = cache[key];
if (cached && cached.ttl > Date.now() - cached.cachedAt) {
return cached.value;
return cached.value as T;
}
const value = (await provider).read<T>(key, fallback);
cache[key] = {
ttl: 5 * 60 * 1000,
ttl: 60 * 60 * 1000,
value,
cachedAt: Date.now()
};

View File

@@ -40,7 +40,7 @@ export async function read<T>(key: string, fallback: T): Promise<T> {
key
});
if (typeof response === "object" && !response.success) {
console.error("failed:", response.errors);
// console.error("failed:", response.errors);
return fallback;
}
return (
@@ -48,7 +48,7 @@ export async function read<T>(key: string, fallback: T): Promise<T> {
fallback
);
} catch (e) {
console.error(e);
// console.error(e);
return fallback;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
"build": "remix vite:build",
"dev": "remix vite:dev",
"typecheck": "tsc",
"start": "remix-serve ./build/server/index.js"
"start": "bun server.ts"
},
"dependencies": {
"@emotion/cache": "11.11.0",
@@ -49,7 +49,7 @@
"devDependencies": {
"@remix-run/dev": "^2.12.1",
"@remix-run/serve": "^2.12.1",
"@types/bun": "^1.1.10",
"@types/bun": "^1.1.13",
"@types/html-to-text": "^9.0.4",
"@types/react": "^18.3.9",
"@types/react-dom": "^18.3.0",
@@ -58,7 +58,7 @@
"postcss": "^8.4.47",
"vite": "^5.4.8",
"vite-plugin-arraybuffer": "^0.0.8",
"vite-plugin-wasm": "^3.3.0",
"vite-plugin-static-copy": "^2.1.0",
"vite-tsconfig-paths": "^5.0.1",
"wrangler": "3.78.11"
},

48
apps/monograph/server.ts Normal file
View File

@@ -0,0 +1,48 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This server file is used to serve the Remix app in production using Bun.
// run it like so: npm run build; cd output; bun install; bun run start
// Running it directly will give an error.
import type { ServerBuild } from "@remix-run/server-runtime";
import { createRequestHandler } from "@remix-run/server-runtime";
import { resolve } from "node:path";
// @ts-expect-error server is not built yet
import * as build from "./build/server/index";
import { type Serve } from "bun";
const remix = createRequestHandler(
build as unknown as ServerBuild,
Bun.env.NODE_ENV
);
process.env.PORT = process.env.PORT || "3000";
export default {
port: process.env.PORT,
async fetch(request) {
// First we need to send handle static files
const { pathname } = new URL(request.url);
const file = Bun.file(
resolve(__dirname, "./build/client/", `.${pathname}`)
);
if (await file.exists()) return new Response(file);
// Only if a file doesn't exists we send the request to the Remix request handler
return remix(request);
}
} satisfies Serve;

View File

@@ -9,7 +9,7 @@
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["vite/client", "vite-plugin-arraybuffer/types"],
"types": ["vite/client", "vite-plugin-arraybuffer/types", "bun"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",

View File

@@ -20,11 +20,11 @@ import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import arraybuffer from "vite-plugin-arraybuffer";
import wasm from "vite-plugin-wasm";
import ThemeDark from "@notesnook/theme/theme-engine/themes/default-dark.json" with { type: "json" };
import ThemeDark from "@notesnook/theme/theme-engine/themes/default-dark.json" with {type:"json"};
import type { Plugin, ResolvedConfig } from "vite";
import { writeFile } from "fs/promises";
import path from "path";
import { viteStaticCopy } from "vite-plugin-static-copy";
import * as pkg from "./package.json";
const DEDUPE = [
@@ -44,18 +44,18 @@ const DEFAULT_THEME_KEY =
export default defineConfig(({ isSsrBuild }) => ({
plugins: [
writePlugin({
"package.json": JSON.stringify({
"../package.json": JSON.stringify({
name: pkg.name,
version: pkg.version,
type: "module",
scripts: { start: pkg.scripts.start },
dependencies: {
"@remix-run/serve": pkg.devDependencies["@remix-run/serve"],
"sharp": pkg.dependencies.sharp
}
"@napi-rs/canvas": pkg.dependencies["@napi-rs/canvas"]
},
})
}),
remix({
buildDirectory: "output/build",
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true,
@@ -64,7 +64,12 @@ export default defineConfig(({ isSsrBuild }) => ({
}),
tsconfigPaths(),
arraybuffer(),
wasm()
isSsrBuild ? viteStaticCopy({
targets: [
{ src: "./server.ts", dest: "../../" },
{ src: "./app/assets", dest: "../" }
]
}) : undefined
],
worker: {
format: "es",
@@ -75,15 +80,15 @@ export default defineConfig(({ isSsrBuild }) => ({
}
},
ssr: {
...(process.env.NODE_ENV === "development" ? {} : { noExternal: true, external: ["sharp"] }),
...(process.env.NODE_ENV === "development"
? {}
: { noExternal: true, external: ["@napi-rs/canvas"] }),
target: "node"
},
build: {
target: isSsrBuild ? "node20" : undefined,
rollupOptions: {
external: [
"sharp"
]
external: ["@napi-rs/canvas"]
}
},
define: {