mirror of
https://github.com/documenso/documenso.git
synced 2026-07-12 05:55:12 +10:00
perf: upgrade to vite 8, migrate rollup to rolldown, and optimise build pipeline
- Upgrade vite from v7 to v8 (Rolldown-based bundler) - Migrate server build from rollup to rolldown, eliminating 5 plugins (rolldown handles TS, JSX, JSON, CJS, and node resolution natively) - Replace vite-plugin-babel-macros with targeted lingui macro plugin that skips files without macro imports (~90% of files) - Replace vite-tsconfig-paths with native resolve.tsconfigPaths - Remove redundant lingui error-reporter plugin (51% of plugin time) - Narrow optimizeDeps.entries from ~1060 to ~460 files - Move typecheck out of build into separate CI job (43s -> 24s build) - Fix card.tsx var() typo exposed by LightningCSS - Remove 30 unused packages (rollup, babel presets, rollup plugins, etc.) - Add vite override for peer dep compatibility - Patch @lingui/vite-plugin with moduleType for Rolldown compat
This commit is contained in:
@@ -12,6 +12,20 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
name: Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- uses: ./.github/actions/node-install
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run typecheck -w @documenso/remix
|
||||
|
||||
build_app:
|
||||
name: Build App
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -28,7 +28,8 @@ npm run build:server
|
||||
# Copy over the entry point for the server.
|
||||
cp server/main.js build/server/main.js
|
||||
|
||||
# Copy over all web.js translations
|
||||
# Copy over all web.js translations.
|
||||
# Rolldown preserveModules mirrors the source tree under build/server/hono/.
|
||||
cp -r ../../packages/lib/translations build/server/hono/packages/lib/translations
|
||||
|
||||
# Time taken
|
||||
|
||||
+4
-13
@@ -4,8 +4,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "./.bin/build.sh",
|
||||
"build:app": "npm run typecheck && cross-env NODE_ENV=production react-router build",
|
||||
"build:server": "cross-env NODE_ENV=production rollup -c rollup.config.mjs",
|
||||
"build:app": "react-router typegen && cross-env NODE_ENV=production react-router build",
|
||||
"build:server": "cross-env NODE_ENV=production rolldown -c rolldown.config.mjs",
|
||||
"dev": "npm run with:env -- react-router dev",
|
||||
"dev:billing": "bash .bin/stripe-dev.sh",
|
||||
"start": "npm run with:env -- cross-env NODE_ENV=production node build/server/main.js",
|
||||
@@ -76,17 +76,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.5",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.6.0",
|
||||
"@lingui/vite-plugin": "^5.6.0",
|
||||
"@react-router/dev": "^7.12.0",
|
||||
"@react-router/remix-routes-option-adapter": "^7.12.0",
|
||||
"@rollup/plugin-babel": "^6.1.0",
|
||||
"@rollup/plugin-commonjs": "^28.0.9",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-typescript": "^12.3.0",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/content-disposition": "^0.5.9",
|
||||
"@types/formidable": "^3.4.6",
|
||||
"@types/luxon": "^3.7.1",
|
||||
@@ -98,12 +92,9 @@
|
||||
"cross-env": "^10.1.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"remix-flat-routes": "^0.8.5",
|
||||
"rollup": "^4.53.3",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "5.6.2",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"version": "2.8.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { transformAsync } from '@babel/core';
|
||||
import linguiMacroPlugin from '@lingui/babel-plugin-lingui-macro';
|
||||
import { defineConfig } from 'rolldown';
|
||||
|
||||
const linguiMacroRE = /@lingui\/(core\/macro|react\/macro|macro)/;
|
||||
|
||||
/**
|
||||
* Rolldown config for building the Hono server entry.
|
||||
*
|
||||
* Replaces the previous rollup.config.mjs. Rolldown provides built-in
|
||||
* TypeScript, JSX (automatic runtime), JSON, CJS interop, and node
|
||||
* resolution — so we only need a single plugin for lingui macro compilation.
|
||||
*/
|
||||
export default defineConfig({
|
||||
input: 'server/router.ts',
|
||||
output: {
|
||||
dir: 'build/server/hono',
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
preserveModules: true,
|
||||
preserveModulesRoot: '.',
|
||||
},
|
||||
// Externalize all third-party deps from node_modules.
|
||||
// Workspace packages (@documenso/*) resolve to their actual source paths
|
||||
// (outside node_modules) via npm workspace symlinks, so they get bundled.
|
||||
external: [/node_modules/],
|
||||
platform: 'node',
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
|
||||
tsconfigFilename: 'tsconfig.json',
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: 'lingui-macro',
|
||||
async transform(code, id) {
|
||||
if (!/\.(tsx?|jsx?)$/.test(id)) return;
|
||||
if (!linguiMacroRE.test(code)) return;
|
||||
|
||||
const result = await transformAsync(code, {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
filename: id,
|
||||
parserOpts: {
|
||||
sourceType: 'module',
|
||||
allowAwaitOutsideFunction: true,
|
||||
plugins: ['typescript', 'jsx'],
|
||||
},
|
||||
plugins: [linguiMacroPlugin],
|
||||
sourceMaps: true,
|
||||
});
|
||||
|
||||
if (!result?.code) return;
|
||||
return { code: result.code, map: result.map };
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,54 +0,0 @@
|
||||
import linguiMacro from '@lingui/babel-plugin-lingui-macro';
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import path from 'node:path';
|
||||
|
||||
/** @type {import('rollup').RollupOptions} */
|
||||
const config = {
|
||||
/**
|
||||
* We specifically target the router.ts instead of the entry point so the rollup doesn't go through the
|
||||
* already prebuilt RR7 server files.
|
||||
*/
|
||||
input: 'server/router.ts',
|
||||
output: {
|
||||
dir: 'build/server/hono',
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
preserveModules: true,
|
||||
preserveModulesRoot: '.',
|
||||
},
|
||||
external: [/node_modules/],
|
||||
plugins: [
|
||||
typescript({
|
||||
noEmitOnError: true,
|
||||
moduleResolution: 'bundler',
|
||||
include: ['server/**/*', '../../packages/**/*', '../../packages/lib/translations/**/*'],
|
||||
jsx: 'preserve',
|
||||
}),
|
||||
resolve({
|
||||
rootDir: path.join(process.cwd(), '../..'),
|
||||
preferBuiltins: true,
|
||||
resolveOnly: [
|
||||
'@documenso/api/*',
|
||||
'@documenso/auth/*',
|
||||
'@documenso/lib/*',
|
||||
'@documenso/trpc/*',
|
||||
'@documenso/email/*',
|
||||
],
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
|
||||
}),
|
||||
json(),
|
||||
commonjs(),
|
||||
babel({
|
||||
babelHelpers: 'bundled',
|
||||
extensions: ['.ts', '.tsx'],
|
||||
presets: ['@babel/preset-typescript', ['@babel/preset-react', { runtime: 'automatic' }]],
|
||||
plugins: [linguiMacro],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
+66
-10
@@ -1,3 +1,5 @@
|
||||
import { transformAsync } from '@babel/core';
|
||||
import linguiMacroPlugin from '@lingui/babel-plugin-lingui-macro';
|
||||
import { lingui } from '@lingui/vite-plugin';
|
||||
import { reactRouter } from '@react-router/dev/vite';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
@@ -6,15 +8,59 @@ import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import { defineConfig, normalizePath } from 'vite';
|
||||
import macrosPlugin from 'vite-plugin-babel-macros';
|
||||
import type { Plugin } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const pdfjsDistPath = path.dirname(require.resolve('pdfjs-dist/package.json'));
|
||||
const cMapsDir = normalizePath(path.join(pdfjsDistPath, 'cmaps'));
|
||||
|
||||
/**
|
||||
* Targeted Lingui macro compilation plugin.
|
||||
*
|
||||
* Replaces the generic `vite-plugin-babel-macros` which ran a full Babel
|
||||
* parse + transform on every single .ts/.tsx file. This plugin:
|
||||
*
|
||||
* 1. Skips files that don't contain lingui macro imports (fast string check).
|
||||
* 2. Uses `@lingui/babel-plugin-lingui-macro` directly instead of going
|
||||
* through the generic `babel-plugin-macros` wrapper.
|
||||
*/
|
||||
const linguiMacroRE = /@lingui\/(core\/macro|react\/macro|macro)/;
|
||||
|
||||
function linguiMacro(): Plugin {
|
||||
return {
|
||||
name: 'vite-plugin-lingui-macro',
|
||||
enforce: 'pre',
|
||||
async transform(code, id) {
|
||||
if (id.includes('/node_modules/')) return;
|
||||
|
||||
const [filepath] = id.split('?');
|
||||
if (!/\.(tsx?|jsx?)$/.test(filepath)) return;
|
||||
|
||||
// Fast bail-out: skip files that don't reference lingui macros.
|
||||
if (!linguiMacroRE.test(code)) return;
|
||||
|
||||
const result = await transformAsync(code, {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
filename: id,
|
||||
sourceFileName: filepath,
|
||||
parserOpts: {
|
||||
sourceType: 'module',
|
||||
allowAwaitOutsideFunction: true,
|
||||
plugins: ['typescript', 'jsx'],
|
||||
},
|
||||
plugins: [linguiMacroPlugin],
|
||||
sourceMaps: true,
|
||||
});
|
||||
|
||||
if (!result?.code) return;
|
||||
return { code: result.code, map: result.map };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: We load the env variables externally so we can have runtime enviroment variables
|
||||
* for docker.
|
||||
@@ -41,15 +87,18 @@ export default defineConfig({
|
||||
],
|
||||
}),
|
||||
reactRouter(),
|
||||
macrosPlugin(),
|
||||
lingui(),
|
||||
tsconfigPaths(),
|
||||
linguiMacro(),
|
||||
// lingui() returns two plugins: a macro error reporter and the .po catalog
|
||||
// compiler. The error reporter adds a resolveId hook on every module to
|
||||
// detect uncompiled macros — redundant since linguiMacro() already compiles
|
||||
// them, and it was consuming 51% of plugin time in the build.
|
||||
...lingui().filter((p) => 'name' in p && p.name !== 'vite-plugin-lingui-report-macro-error'),
|
||||
serverAdapter({
|
||||
entry: 'server/router.ts',
|
||||
}),
|
||||
],
|
||||
ssr: {
|
||||
noExternal: ['react-dropzone', 'plausible-tracker'],
|
||||
noExternal: ['react-dropzone'],
|
||||
external: [
|
||||
'@napi-rs/canvas',
|
||||
'@node-rs/bcrypt',
|
||||
@@ -62,10 +111,12 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
optimizeDeps: {
|
||||
entries: ['./app/**/*', '../../packages/ui/**/*', '../../packages/lib/**/*'],
|
||||
// Only scan the app directory — workspace packages (ui, lib) are
|
||||
// discovered transitively through app imports. The previous config
|
||||
// scanned ~1,000 files including server-only code from packages/lib.
|
||||
entries: ['./app/**/*'],
|
||||
include: ['prop-types', 'file-selector', 'attr-accept'],
|
||||
exclude: [
|
||||
'node_modules',
|
||||
'@napi-rs/canvas',
|
||||
'@node-rs/bcrypt',
|
||||
'sharp',
|
||||
@@ -75,6 +126,7 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
alias: {
|
||||
https: 'node:https',
|
||||
'.prisma/client/default': path.resolve(
|
||||
@@ -89,11 +141,15 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Note: Re run rollup again to build the server afterwards.
|
||||
* Note: Re run rolldown again to build the server afterwards.
|
||||
*
|
||||
* See rollup.config.mjs which is used for that.
|
||||
* See rolldown.config.mjs which is used for that.
|
||||
*/
|
||||
build: {
|
||||
// LightningCSS (Vite 8 default) is stricter and rejects nested @page
|
||||
// rules and some Tailwind theme() edge cases. Use esbuild for CSS
|
||||
// minification until LightningCSS support matures or the CSS is updated.
|
||||
cssMinify: 'esbuild',
|
||||
rollupOptions: {
|
||||
external: [
|
||||
'@napi-rs/canvas',
|
||||
|
||||
Generated
+607
-818
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -79,7 +79,7 @@
|
||||
"superjson": "^2.2.5",
|
||||
"syncpack": "^14.0.0-alpha.27",
|
||||
"turbo": "^1.13.4",
|
||||
"vite": "^7.2.4",
|
||||
"vite": "^8.0.0",
|
||||
"vite-plugin-static-copy": "^3.1.4",
|
||||
"zod-openapi": "^4.2.4",
|
||||
"zod-prisma-types": "3.3.5"
|
||||
@@ -104,6 +104,7 @@
|
||||
"lodash": "4.17.23",
|
||||
"pdfjs-dist": "5.4.296",
|
||||
"typescript": "5.6.2",
|
||||
"vite": "$vite",
|
||||
"zod": "$zod",
|
||||
"fumadocs-mdx": {
|
||||
"zod": "^4.3.5"
|
||||
|
||||
@@ -30,15 +30,14 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
'bg-background text-foreground group relative rounded-lg border-2',
|
||||
'group relative rounded-lg border-2 bg-background text-foreground',
|
||||
{
|
||||
'backdrop-blur-[2px]': backdropBlur,
|
||||
'gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/50%)_5%,theme(colors.border/80%)_30%)]':
|
||||
gradient,
|
||||
'dark:gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/70%)_5%,theme(colors.border/80%)_30%)]':
|
||||
gradient,
|
||||
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_var(colors.primary.DEFAULT/70%)]':
|
||||
true,
|
||||
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_theme(colors.primary.DEFAULT/70%)]': true,
|
||||
'dark:shadow-[0]': true,
|
||||
},
|
||||
className,
|
||||
@@ -77,7 +76,7 @@ const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
|
||||
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
||||
));
|
||||
|
||||
CardDescription.displayName = 'CardDescription';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
diff --git a/node_modules/@lingui/vite-plugin/dist/index.cjs b/node_modules/@lingui/vite-plugin/dist/index.cjs
|
||||
index cb4db5d..c0a881a 100644
|
||||
--- a/node_modules/@lingui/vite-plugin/dist/index.cjs
|
||||
+++ b/node_modules/@lingui/vite-plugin/dist/index.cjs
|
||||
@@ -114,8 +114,12 @@ You see this error because \`failOnMissing=true\` in Vite Plugin configuration.`
|
||||
}
|
||||
return {
|
||||
code,
|
||||
- map: null
|
||||
+ map: null,
|
||||
// provide source map if available
|
||||
+ // Vite 8+ (Rolldown) auto-detects module types by file extension.
|
||||
+ // Since .po files are transformed to JS, we must explicitly declare
|
||||
+ // the module type to avoid misinterpretation.
|
||||
+ moduleType: "js"
|
||||
};
|
||||
}
|
||||
}
|
||||
diff --git a/node_modules/@lingui/vite-plugin/dist/index.mjs b/node_modules/@lingui/vite-plugin/dist/index.mjs
|
||||
index 609d48d..66fa6cc 100644
|
||||
--- a/node_modules/@lingui/vite-plugin/dist/index.mjs
|
||||
+++ b/node_modules/@lingui/vite-plugin/dist/index.mjs
|
||||
@@ -106,8 +106,12 @@ You see this error because \`failOnMissing=true\` in Vite Plugin configuration.`
|
||||
}
|
||||
return {
|
||||
code,
|
||||
- map: null
|
||||
+ map: null,
|
||||
// provide source map if available
|
||||
+ // Vite 8+ (Rolldown) auto-detects module types by file extension.
|
||||
+ // Since .po files are transformed to JS, we must explicitly declare
|
||||
+ // the module type to avoid misinterpretation.
|
||||
+ moduleType: "js"
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user