Skip to content

Commit 2647b34

Browse files
committed
feat(build) :: bundle the browser scripts with rolldown
Before SQLPage prepended 3rd party dependencies to frontend scripts and shared state throuh globals. Now we use Rolldown to bundle and minify frontend assets into `frontend/dist`. This also caused files to be typechecked for the first time so some smaller changes, like `ChartPoint.y`, had to be made. Rolldown is also available on crate.io, so we could also migrate to a unified build.rs in the future or use nix.
1 parent 8a7544c commit 2647b34

19 files changed

Lines changed: 508 additions & 151 deletions

‎.dockerignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/target
22
node_modules/
3+
frontend/dist/
34
.git/
45
sqlpage.db
56
docs/

‎.github/workflows/ci.yml‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ jobs:
4040
node-version: 26
4141
cache: 'npm'
4242
- run: npm ci
43+
- run: npm run build
4344
- run: npm test
4445
- name: Set up cargo cache
4546
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
@@ -86,7 +87,8 @@ jobs:
8687
with:
8788
node-version: 26
8889
cache: 'npm'
89-
- run: npm ci --omit=dev --ignore-scripts
90+
- run: npm ci --ignore-scripts
91+
- run: npm run build
9092
- name: Set up cargo cache
9193
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
9294
env:
@@ -184,7 +186,8 @@ jobs:
184186
with:
185187
node-version: 26
186188
cache: 'npm'
187-
- run: npm ci --omit=dev --ignore-scripts
189+
- run: npm ci --ignore-scripts
190+
- run: npm run build
188191
- name: Set up cargo cache
189192
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
190193
env:
@@ -277,7 +280,7 @@ jobs:
277280
ref_scope="pr-${{ github.event.pull_request.number }}"
278281
fi
279282
280-
recipe_hash="${{ hashFiles('Dockerfile', '.cargo/**', 'Cargo.toml', 'Cargo.lock', 'build.rs', 'scripts/**', 'sqlpage/**') }}"
283+
recipe_hash="${{ hashFiles('Dockerfile', '.cargo/**', 'Cargo.toml', 'Cargo.lock', 'build.rs', 'scripts/**', 'sqlpage/**', 'package.json', 'package-lock.json') }}"
281284
{
282285
echo "current=sqlpage-${ref_scope}${{ matrix.tag_suffix }}-${recipe_hash}"
283286
echo "main=sqlpage-main${{ matrix.tag_suffix }}-${recipe_hash}"
@@ -357,7 +360,7 @@ jobs:
357360
ref_scope="pr-${{ github.event.pull_request.number }}"
358361
fi
359362
360-
recipe_hash="${{ hashFiles('Dockerfile', '.cargo/**', 'Cargo.toml', 'Cargo.lock', 'build.rs', 'scripts/**', 'sqlpage/**') }}"
363+
recipe_hash="${{ hashFiles('Dockerfile', '.cargo/**', 'Cargo.toml', 'Cargo.lock', 'build.rs', 'scripts/**', 'sqlpage/**', 'package.json', 'package-lock.json') }}"
361364
{
362365
echo "artifact=sqlpage-${ref_scope}-linux-amd64-hurl-${recipe_hash}"
363366
echo "current=sqlpage-${ref_scope}-linux-amd64-${recipe_hash}"

‎.github/workflows/release.yml‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ jobs:
4242
with:
4343
node-version: 26
4444
cache: 'npm'
45-
- run: npm ci --omit=dev --ignore-scripts
45+
- run: npm ci --ignore-scripts
46+
- run: npm run build
4647
- name: Build
4748
run: cargo build --profile superoptimized --locked --target ${{ matrix.target }} --features "${{ matrix.features }}"
4849
- name: Upload unsigned Windows artifact
@@ -104,7 +105,8 @@ jobs:
104105
with:
105106
node-version: 26
106107
cache: 'npm'
107-
- run: npm ci --omit=dev --ignore-scripts
108+
- run: npm ci --ignore-scripts
109+
- run: npm run build
108110
- name: Build
109111
run: cargo build --profile superoptimized --locked --target x86_64-unknown-linux-gnu --features "odbc-static"
110112
- uses: actions/upload-artifact@v7
@@ -174,7 +176,8 @@ jobs:
174176
with:
175177
node-version: 26
176178
cache: 'npm'
177-
- run: npm ci --omit=dev --ignore-scripts
179+
- run: npm ci --ignore-scripts
180+
- run: npm run build
178181
- name: Publish to crates.io
179182
run: |
180183
set +e

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ x.sql
1010
xbed.sql
1111
**/sqlpage.bin
1212
node_modules/
13+
frontend/dist/
1314
sqlpage/sqlpage.db
1415
tests_uploads/
1516
/test-results/

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## v0.47.0 (unreleased)
44
- `cargo install sqlpage`, and any build from the crates.io tarball, no longer needs internet access. The browser libraries now come from npm and ship inside the published crate. Building from a git checkout needs `npm ci` first. Pre-built binaries and the Docker image are unaffected.
5+
- SQLPage's browser scripts are now bundled and minified along with their dependencies.
56

67
## v0.46.3
78

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ include = [
1313
"/README.md",
1414
"/build.rs",
1515
"/sqlpage",
16+
"/frontend/dist",
1617
"/node_modules/@tabler/core/dist/css/tabler.min.css",
1718
"/node_modules/@tabler/core/dist/css/tabler-vendors.min.css",
1819
"/node_modules/@tabler/core/dist/js/tabler.min.js",

‎Dockerfile‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
FROM --platform=$BUILDPLATFORM node:26-slim AS frontend
22
WORKDIR /usr/src/sqlpage
33
COPY package.json package-lock.json ./
4-
RUN npm ci --omit=dev --ignore-scripts
4+
RUN npm ci --ignore-scripts
5+
COPY sqlpage/ sqlpage/
6+
COPY scripts/build-frontend.mjs scripts/
7+
RUN node scripts/build-frontend.mjs
58

69
FROM --platform=$BUILDPLATFORM rust:1.95-slim AS builder
710

@@ -20,6 +23,7 @@ COPY .cargo/ .cargo/
2023
COPY Cargo.toml Cargo.lock build.rs ./
2124
COPY sqlpage/ sqlpage/
2225
COPY --from=frontend /usr/src/sqlpage/node_modules node_modules
26+
COPY --from=frontend /usr/src/sqlpage/frontend/dist frontend/dist
2327
RUN /usr/local/bin/build-dependencies.sh
2428

2529
COPY . .

‎biome.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"includes": [
55
"**",
66
"!**/*.svg",
7+
"!frontend/dist",
78
"!examples/official-site/pgconf",
89
"!tests/end-to-end/test-results",
910
"!.zed/*.json",

‎build.rs‎

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,35 @@ use std::io::Write;
77
use std::path::{Path, PathBuf};
88

99
const SERVED_ASSETS: &[(&str, &[&str])] = &[
10-
("sqlpage.js", &["@tabler/core/dist/js/tabler.min.js"]),
1110
(
12-
"sqlpage.css",
11+
"frontend/dist/sqlpage.js",
12+
&["node_modules/@tabler/core/dist/js/tabler.min.js"],
13+
),
14+
(
15+
"sqlpage/sqlpage.css",
1316
&[
14-
"@tabler/core/dist/css/tabler.min.css",
15-
"tom-select/dist/css/tom-select.bootstrap5.css",
16-
"@tabler/core/dist/css/tabler-vendors.min.css",
17+
"node_modules/@tabler/core/dist/css/tabler.min.css",
18+
"node_modules/tom-select/dist/css/tom-select.bootstrap5.css",
19+
"node_modules/@tabler/core/dist/css/tabler-vendors.min.css",
1720
],
1821
),
19-
("apexcharts.js", &["apexcharts/dist/apexcharts.min.js"]),
2022
(
21-
"tomselect.js",
22-
&["tom-select/dist/js/tom-select.popular.min.js"],
23+
"frontend/dist/apexcharts.js",
24+
&["node_modules/apexcharts/dist/apexcharts.min.js"],
2325
),
24-
("favicon.svg", &[]),
26+
(
27+
"frontend/dist/tomselect.js",
28+
&["node_modules/tom-select/dist/js/tom-select.popular.min.js"],
29+
),
30+
("sqlpage/favicon.svg", &[]),
2531
];
2632

27-
const ICON_SPRITE: &str = "@tabler/icons-sprite/dist/tabler-sprite.svg";
33+
const ICON_SPRITE: &str = "node_modules/@tabler/icons-sprite/dist/tabler-sprite.svg";
2834

2935
fn main() {
3036
println!("cargo:rerun-if-changed=build.rs");
31-
for &(name, libraries) in SERVED_ASSETS {
32-
build_served_asset(name, libraries);
37+
for &(source, libraries) in SERVED_ASSETS {
38+
build_served_asset(source, libraries);
3339
}
3440
build_icon_map();
3541
set_odbc_rpath();
@@ -39,29 +45,29 @@ fn out_dir() -> PathBuf {
3945
PathBuf::from(std::env::var("OUT_DIR").unwrap())
4046
}
4147

42-
fn open_library(library: &str) -> File {
43-
let path = Path::new("node_modules").join(library);
44-
println!("cargo:rerun-if-changed={}", path.display());
45-
File::open(&path).unwrap_or_else(|err| {
48+
fn open_input(path: &str) -> File {
49+
println!("cargo:rerun-if-changed={path}");
50+
File::open(path).unwrap_or_else(|err| {
4651
panic!(
47-
"Unable to read {}: {err}\n\
48-
The browser libraries come from npm: run `npm ci` before `cargo build`.",
49-
path.display()
52+
"Unable to read {path}: {err}\n\
53+
The browser assets are built by npm: \
54+
run `npm ci && npm run build` before `cargo build`."
5055
)
5156
})
5257
}
5358

54-
fn build_served_asset(name: &str, libraries: &[&str]) {
55-
let source = Path::new("sqlpage").join(name);
56-
println!("cargo:rerun-if-changed={}", source.display());
57-
58-
let built = out_dir().join(name);
59+
fn build_served_asset(source: &str, libraries: &[&str]) {
60+
let built = out_dir().join(Path::new(source).file_name().unwrap());
61+
// A minified library can end without a semicolon, and the bundle that
62+
// follows opens with `(`, which JavaScript would read as a call to it.
63+
let is_script = Path::new(source).extension().is_some_and(|ext| ext == "js");
64+
let separator: &[u8] = if is_script { b";\n" } else { b"\n" };
5965
let mut gzipped = gzip::Encoder::new(File::create(&built).unwrap()).unwrap();
6066
for library in libraries {
61-
std::io::copy(&mut open_library(library), &mut gzipped).unwrap();
62-
gzipped.write_all(b"\n").unwrap();
67+
std::io::copy(&mut open_input(library), &mut gzipped).unwrap();
68+
gzipped.write_all(separator).unwrap();
6369
}
64-
std::io::copy(&mut File::open(&source).unwrap(), &mut gzipped).unwrap();
70+
std::io::copy(&mut open_input(source), &mut gzipped).unwrap();
6571
gzipped
6672
.finish()
6773
.as_result()
@@ -76,7 +82,7 @@ fn build_served_asset(name: &str, libraries: &[&str]) {
7682

7783
fn build_icon_map() {
7884
let mut sprite = Vec::with_capacity(3 * 1024 * 1024);
79-
open_library(ICON_SPRITE).read_to_end(&mut sprite).unwrap();
85+
open_input(ICON_SPRITE).read_to_end(&mut sprite).unwrap();
8086
let mut icon_map = File::create(out_dir().join("icons.rs")).unwrap();
8187
icon_map.write_all(b"[").unwrap();
8288
extract_icons_from_sprite(&sprite, |name, content| {

‎lambda.Dockerfile‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
FROM node:26-slim AS frontend
22
WORKDIR /usr/src/sqlpage
33
COPY package.json package-lock.json ./
4-
RUN npm ci --omit=dev --ignore-scripts
4+
RUN npm ci --ignore-scripts
5+
COPY sqlpage/ sqlpage/
6+
COPY scripts/build-frontend.mjs scripts/
7+
RUN node scripts/build-frontend.mjs
58

69
FROM rust:1.95-alpine AS builder
710
RUN rustup component add clippy rustfmt
@@ -12,6 +15,7 @@ COPY Cargo.toml Cargo.lock ./
1215
RUN cargo build --release
1316
COPY . .
1417
COPY --from=frontend /usr/src/sqlpage/node_modules node_modules
18+
COPY --from=frontend /usr/src/sqlpage/frontend/dist frontend/dist
1519
RUN cargo build --release --features lambda-web
1620
RUN mv target/release/sqlpage bootstrap && \
1721
strip --strip-all bootstrap && \

0 commit comments

Comments
 (0)