@gjsify/lightningcss-native 0.4.0 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +59 -59
- package/prebuilds/linux-x86_64/libgjsify_lightningcss.so +0 -0
- package/prebuilds/linux-x86_64/libgjsifylightningcss.so +0 -0
- package/src/rust/Cargo.toml +18 -0
- package/src/rust/src/lib.rs +330 -0
- package/src/vala/gjsify-lightningcss-glue.c +117 -0
- package/src/vala/gjsify-lightningcss-glue.h +73 -0
- package/src/vala/gjsify-lightningcss.h +60 -0
- package/src/vala/lightningcss.vala +111 -0
package/package.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
2
|
+
"name": "@gjsify/lightningcss-native",
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "Vala/GObject + Rust cdylib bridge to lightningcss for GJS. Replaces the Node-only npm `lightningcss` package: parse, transform, minify, and browserslist-target lowering of CSS without leaving a Node-free GJS process. Backs `@gjsify/rolldown-plugin-gjsify`'s cssAsStringPlugin under `--app gjs` so GTK4-CSS lowering (firefox60) works without a Node runtime in the build pipeline.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/esm/index.js",
|
|
7
|
+
"module": "lib/esm/index.js",
|
|
8
|
+
"types": "lib/types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/types/index.d.ts",
|
|
12
|
+
"default": "./lib/esm/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"prebuilds",
|
|
18
|
+
"meson.build",
|
|
19
|
+
"src/rust",
|
|
20
|
+
"src/vala"
|
|
21
|
+
],
|
|
22
|
+
"gjsify": {
|
|
23
|
+
"prebuilds": "prebuilds"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clear": "rm -rf lib build tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo || exit 0",
|
|
27
|
+
"check": "tsc --noEmit",
|
|
28
|
+
"init:meson": "meson setup build .",
|
|
29
|
+
"init:meson:wipe": "gjsify run init:meson --wipe",
|
|
30
|
+
"build": "gjsify run build:gjsify && gjsify run build:types",
|
|
31
|
+
"build:gjsify": "gjsify build --library 'src/ts/**/*.{ts,js}'",
|
|
32
|
+
"build:meson": "gjsify run init:meson && meson compile -C build",
|
|
33
|
+
"build:types": "tsc",
|
|
34
|
+
"build:prebuilds": "gjsify run build:meson && mkdir -p prebuilds/linux-x86_64 && cp build/libgjsifylightningcss.so build/libgjsify_lightningcss.so build/GjsifyLightningcss-1.0.gir build/GjsifyLightningcss-1.0.typelib prebuilds/linux-x86_64/",
|
|
35
|
+
"build:gir-types": "ts-for-gir generate --externalDeps --allowMissingDeps --girDirectories=./prebuilds/linux-x86_64 --girDirectories=/usr/share/gir-1.0 --modules=GjsifyLightningcss-1.0 --outdir=src/ts --npmScope=@girs --package=false --ignoreVersionConflicts=true"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"gjs",
|
|
39
|
+
"lightningcss",
|
|
40
|
+
"css",
|
|
41
|
+
"minify",
|
|
42
|
+
"browserslist",
|
|
43
|
+
"vala",
|
|
44
|
+
"rust",
|
|
45
|
+
"native"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@girs/gjs": "4.0.0-rc.15",
|
|
49
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
|
|
50
|
+
"@girs/gobject-2.0": "2.88.0-4.0.0-rc.15"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@gjsify/cli": "^0.4.4",
|
|
54
|
+
"@ts-for-gir/cli": "^4.0.0-rc.15",
|
|
55
|
+
"@types/node": "^25.6.2",
|
|
56
|
+
"typescript": "^6.0.3"
|
|
57
|
+
},
|
|
58
|
+
"author": "Pascal Garber <pascal@artandcode.studio>",
|
|
59
|
+
"license": "MIT"
|
|
60
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "gjsify_lightningcss"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
publish = false
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
crate-type = ["cdylib"]
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
lightningcss = { version = "1.0.0-alpha.71", features = ["browserslist"] }
|
|
12
|
+
parcel_sourcemap = { version = "2.1.1", features = ["json"] }
|
|
13
|
+
browserslist-rs = { version = "0.19.0" }
|
|
14
|
+
|
|
15
|
+
[profile.release]
|
|
16
|
+
lto = true
|
|
17
|
+
codegen-units = 1
|
|
18
|
+
strip = true
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
//! gjsify_lightningcss — single-call Rust→C FFI for lightningcss.
|
|
2
|
+
//!
|
|
3
|
+
//! Adapted from refs/lightningcss/c/src/lib.rs (lightningcss_c_bindings,
|
|
4
|
+
//! Devon Govett, MIT). The upstream C bindings expose a 3-step pipeline
|
|
5
|
+
//! (parse → transform → to_css) plus separate Targets/PseudoClasses/etc
|
|
6
|
+
//! structs, which is more surface area than the Vala bridge needs.
|
|
7
|
+
//!
|
|
8
|
+
//! This crate collapses the warm-up POC's surface to a single call:
|
|
9
|
+
//! gjsify_lightningcss_transform(opts) -> GjsifyResult
|
|
10
|
+
//! with browserslist resolved internally and the result owning all buffer
|
|
11
|
+
//! allocations until gjsify_lightningcss_result_free() is called.
|
|
12
|
+
//!
|
|
13
|
+
//! Modifications: combined pipeline, browserslist-as-string in the same
|
|
14
|
+
//! call, simplified result struct, no css-modules / no analyze-dependencies
|
|
15
|
+
//! / no pseudo-class rewrites. Those can be added back if Phase B needs
|
|
16
|
+
//! them.
|
|
17
|
+
|
|
18
|
+
#![allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
19
|
+
|
|
20
|
+
use std::ffi::{CStr, CString};
|
|
21
|
+
use std::os::raw::c_char;
|
|
22
|
+
use std::ptr;
|
|
23
|
+
|
|
24
|
+
use lightningcss::bundler::{Bundler, FileProvider};
|
|
25
|
+
use lightningcss::stylesheet::{
|
|
26
|
+
MinifyOptions, ParserFlags, ParserOptions, PrinterOptions, StyleSheet,
|
|
27
|
+
};
|
|
28
|
+
use lightningcss::targets::{Browsers, Targets};
|
|
29
|
+
use parcel_sourcemap::SourceMap;
|
|
30
|
+
|
|
31
|
+
#[repr(C)]
|
|
32
|
+
pub struct GjsifyTransformOpts {
|
|
33
|
+
pub filename: *const c_char, // may be NULL → ""
|
|
34
|
+
pub code: *const u8,
|
|
35
|
+
pub code_len: usize,
|
|
36
|
+
pub browserslist: *const c_char, // may be NULL → no targets
|
|
37
|
+
pub minify: bool,
|
|
38
|
+
pub source_map: bool,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Result of a transform. On success `error` is NULL; otherwise `code`/`map`
|
|
42
|
+
/// are NULL and `error` points at a NUL-terminated UTF-8 message.
|
|
43
|
+
///
|
|
44
|
+
/// All non-NULL pointers were allocated on the Rust side and MUST be freed
|
|
45
|
+
/// via `gjsify_lightningcss_result_free` — never with libc free().
|
|
46
|
+
#[repr(C)]
|
|
47
|
+
pub struct GjsifyResult {
|
|
48
|
+
pub code: *mut u8,
|
|
49
|
+
pub code_len: usize,
|
|
50
|
+
pub code_cap: usize,
|
|
51
|
+
pub map: *mut u8,
|
|
52
|
+
pub map_len: usize,
|
|
53
|
+
pub map_cap: usize,
|
|
54
|
+
pub error: *mut c_char,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
impl GjsifyResult {
|
|
58
|
+
fn empty() -> Self {
|
|
59
|
+
GjsifyResult {
|
|
60
|
+
code: ptr::null_mut(),
|
|
61
|
+
code_len: 0,
|
|
62
|
+
code_cap: 0,
|
|
63
|
+
map: ptr::null_mut(),
|
|
64
|
+
map_len: 0,
|
|
65
|
+
map_cap: 0,
|
|
66
|
+
error: ptr::null_mut(),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn err(msg: impl Into<String>) -> Self {
|
|
71
|
+
let mut r = Self::empty();
|
|
72
|
+
let cstr = CString::new(msg.into()).unwrap_or_else(|_| {
|
|
73
|
+
CString::new("lightningcss: error message contained interior NUL byte").unwrap()
|
|
74
|
+
});
|
|
75
|
+
r.error = cstr.into_raw();
|
|
76
|
+
r
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn cstr_to_str<'a>(p: *const c_char) -> Option<&'a str> {
|
|
81
|
+
if p.is_null() {
|
|
82
|
+
None
|
|
83
|
+
} else {
|
|
84
|
+
unsafe { CStr::from_ptr(p) }.to_str().ok()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn vec_to_owned_ptr(mut v: Vec<u8>) -> (*mut u8, usize, usize) {
|
|
89
|
+
let ptr = v.as_mut_ptr();
|
|
90
|
+
let len = v.len();
|
|
91
|
+
let cap = v.capacity();
|
|
92
|
+
std::mem::forget(v);
|
|
93
|
+
(ptr, len, cap)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// One-shot CSS transform.
|
|
97
|
+
///
|
|
98
|
+
/// Steps internally: parse → resolve browserslist (if any) → minify (with
|
|
99
|
+
/// optional targets lowering) → print to CSS string + source map.
|
|
100
|
+
///
|
|
101
|
+
/// Returns a `GjsifyResult`. Caller MUST pass the result to
|
|
102
|
+
/// `gjsify_lightningcss_result_free` exactly once to release buffers.
|
|
103
|
+
#[no_mangle]
|
|
104
|
+
pub extern "C" fn gjsify_lightningcss_transform(opts: GjsifyTransformOpts) -> GjsifyResult {
|
|
105
|
+
if opts.code.is_null() || opts.code_len == 0 {
|
|
106
|
+
return GjsifyResult::err("lightningcss: empty input CSS");
|
|
107
|
+
}
|
|
108
|
+
let code = {
|
|
109
|
+
let slice = unsafe { std::slice::from_raw_parts(opts.code, opts.code_len) };
|
|
110
|
+
match std::str::from_utf8(slice) {
|
|
111
|
+
Ok(s) => s,
|
|
112
|
+
Err(_) => return GjsifyResult::err("lightningcss: invalid UTF-8 in input CSS"),
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let filename = cstr_to_str(opts.filename).unwrap_or("").to_owned();
|
|
117
|
+
|
|
118
|
+
// Resolve browserslist → Targets (if requested).
|
|
119
|
+
let targets: Targets = match cstr_to_str(opts.browserslist) {
|
|
120
|
+
None => Targets::default(),
|
|
121
|
+
Some(query) => match Browsers::from_browserslist([query]) {
|
|
122
|
+
Ok(Some(browsers)) => Targets::from(browsers),
|
|
123
|
+
Ok(None) => Targets::default(),
|
|
124
|
+
Err(e) => {
|
|
125
|
+
return GjsifyResult::err(format!("lightningcss: browserslist: {e}"))
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
let parser_opts = ParserOptions {
|
|
131
|
+
filename: filename.clone(),
|
|
132
|
+
flags: ParserFlags::empty(),
|
|
133
|
+
css_modules: None,
|
|
134
|
+
error_recovery: false,
|
|
135
|
+
source_index: 0,
|
|
136
|
+
warnings: None,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
let mut stylesheet = match StyleSheet::parse(code, parser_opts) {
|
|
140
|
+
Ok(ss) => ss,
|
|
141
|
+
Err(e) => return GjsifyResult::err(format!("lightningcss: parse: {e}")),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// minify() must run regardless of options.minify — it's where the
|
|
145
|
+
// targets-driven lowering (nesting flatten, vendor prefixes, …) happens.
|
|
146
|
+
let minify_opts = MinifyOptions {
|
|
147
|
+
targets,
|
|
148
|
+
..Default::default()
|
|
149
|
+
};
|
|
150
|
+
if let Err(e) = stylesheet.minify(minify_opts) {
|
|
151
|
+
return GjsifyResult::err(format!("lightningcss: transform: {e}"));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Optional source map.
|
|
155
|
+
let mut source_map = if opts.source_map {
|
|
156
|
+
let mut sm = SourceMap::new("/");
|
|
157
|
+
let src_name = if filename.is_empty() { "input.css".to_owned() } else { filename.clone() };
|
|
158
|
+
sm.add_source(&src_name);
|
|
159
|
+
if let Err(e) = sm.set_source_content(0, code) {
|
|
160
|
+
return GjsifyResult::err(format!("lightningcss: source-map: {e}"));
|
|
161
|
+
}
|
|
162
|
+
Some(sm)
|
|
163
|
+
} else {
|
|
164
|
+
None
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
let printer_opts = PrinterOptions {
|
|
168
|
+
minify: opts.minify,
|
|
169
|
+
project_root: None,
|
|
170
|
+
source_map: source_map.as_mut(),
|
|
171
|
+
targets,
|
|
172
|
+
analyze_dependencies: None,
|
|
173
|
+
pseudo_classes: None,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
let printed = match stylesheet.to_css(printer_opts) {
|
|
177
|
+
Ok(p) => p,
|
|
178
|
+
Err(e) => return GjsifyResult::err(format!("lightningcss: print: {e}")),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
let (code_ptr, code_len, code_cap) = vec_to_owned_ptr(printed.code.into_bytes());
|
|
182
|
+
|
|
183
|
+
let (map_ptr, map_len, map_cap) = if let Some(mut sm) = source_map {
|
|
184
|
+
match sm.to_json(None) {
|
|
185
|
+
Ok(json) => vec_to_owned_ptr(json.into_bytes()),
|
|
186
|
+
Err(e) => {
|
|
187
|
+
// free code we just allocated
|
|
188
|
+
unsafe { Vec::from_raw_parts(code_ptr, code_len, code_cap) };
|
|
189
|
+
return GjsifyResult::err(format!("lightningcss: source-map serialize: {e}"));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
(ptr::null_mut(), 0, 0)
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
GjsifyResult {
|
|
197
|
+
code: code_ptr,
|
|
198
|
+
code_len,
|
|
199
|
+
code_cap,
|
|
200
|
+
map: map_ptr,
|
|
201
|
+
map_len,
|
|
202
|
+
map_cap,
|
|
203
|
+
error: ptr::null_mut(),
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Free the buffers owned by a `GjsifyResult`. Safe to call on a result
|
|
208
|
+
/// where `error` is set (only the error CString is freed in that case).
|
|
209
|
+
/// Safe to call on a zeroed/empty result.
|
|
210
|
+
#[no_mangle]
|
|
211
|
+
pub extern "C" fn gjsify_lightningcss_result_free(result: GjsifyResult) {
|
|
212
|
+
if !result.code.is_null() {
|
|
213
|
+
unsafe { drop(Vec::from_raw_parts(result.code, result.code_len, result.code_cap)) };
|
|
214
|
+
}
|
|
215
|
+
if !result.map.is_null() {
|
|
216
|
+
unsafe { drop(Vec::from_raw_parts(result.map, result.map_len, result.map_cap)) };
|
|
217
|
+
}
|
|
218
|
+
if !result.error.is_null() {
|
|
219
|
+
unsafe { drop(CString::from_raw(result.error)) };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[repr(C)]
|
|
224
|
+
pub struct GjsifyBundleOpts {
|
|
225
|
+
pub filename: *const c_char, // entry CSS path; must NOT be NULL
|
|
226
|
+
pub browserslist: *const c_char, // may be NULL → no targets
|
|
227
|
+
pub minify: bool,
|
|
228
|
+
pub source_map: bool,
|
|
229
|
+
pub error_recovery: bool,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Bundle a CSS entry file: resolve all `@import` chains via the
|
|
233
|
+
/// lightningcss `Bundler` + `FileProvider` (filesystem-backed), then
|
|
234
|
+
/// run the same minify/transform/print pipeline as `transform()`.
|
|
235
|
+
///
|
|
236
|
+
/// Same `GjsifyResult` ownership rules as `gjsify_lightningcss_transform`.
|
|
237
|
+
#[no_mangle]
|
|
238
|
+
pub extern "C" fn gjsify_lightningcss_bundle(opts: GjsifyBundleOpts) -> GjsifyResult {
|
|
239
|
+
let filename = match cstr_to_str(opts.filename) {
|
|
240
|
+
Some(f) if !f.is_empty() => f.to_owned(),
|
|
241
|
+
_ => return GjsifyResult::err("lightningcss: bundle requires a non-empty filename"),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
let targets: Targets = match cstr_to_str(opts.browserslist) {
|
|
245
|
+
None => Targets::default(),
|
|
246
|
+
Some(query) => match Browsers::from_browserslist([query]) {
|
|
247
|
+
Ok(Some(browsers)) => Targets::from(browsers),
|
|
248
|
+
Ok(None) => Targets::default(),
|
|
249
|
+
Err(e) => {
|
|
250
|
+
return GjsifyResult::err(format!("lightningcss: browserslist: {e}"))
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
let parser_opts = ParserOptions {
|
|
256
|
+
filename: filename.clone(),
|
|
257
|
+
flags: ParserFlags::empty(),
|
|
258
|
+
css_modules: None,
|
|
259
|
+
error_recovery: opts.error_recovery,
|
|
260
|
+
source_index: 0,
|
|
261
|
+
warnings: None,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
let fs = FileProvider::new();
|
|
265
|
+
let mut bundler = Bundler::new(&fs, None, parser_opts);
|
|
266
|
+
let mut stylesheet = match bundler.bundle(std::path::Path::new(&filename)) {
|
|
267
|
+
Ok(ss) => ss,
|
|
268
|
+
Err(e) => return GjsifyResult::err(format!("lightningcss: bundle: {e}")),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
let minify_opts = MinifyOptions {
|
|
272
|
+
targets,
|
|
273
|
+
..Default::default()
|
|
274
|
+
};
|
|
275
|
+
if let Err(e) = stylesheet.minify(minify_opts) {
|
|
276
|
+
return GjsifyResult::err(format!("lightningcss: transform: {e}"));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let mut source_map = if opts.source_map {
|
|
280
|
+
let mut sm = SourceMap::new("/");
|
|
281
|
+
sm.add_source(&filename);
|
|
282
|
+
// Best effort: read the entry file content so the map has something
|
|
283
|
+
// sensible to point at. Bundled imports won't have their content
|
|
284
|
+
// captured; that's acceptable for the GJS lower path.
|
|
285
|
+
if let Ok(src) = std::fs::read_to_string(&filename) {
|
|
286
|
+
let _ = sm.set_source_content(0, &src);
|
|
287
|
+
}
|
|
288
|
+
Some(sm)
|
|
289
|
+
} else {
|
|
290
|
+
None
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
let printer_opts = PrinterOptions {
|
|
294
|
+
minify: opts.minify,
|
|
295
|
+
project_root: None,
|
|
296
|
+
source_map: source_map.as_mut(),
|
|
297
|
+
targets,
|
|
298
|
+
analyze_dependencies: None,
|
|
299
|
+
pseudo_classes: None,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
let printed = match stylesheet.to_css(printer_opts) {
|
|
303
|
+
Ok(p) => p,
|
|
304
|
+
Err(e) => return GjsifyResult::err(format!("lightningcss: print: {e}")),
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
let (code_ptr, code_len, code_cap) = vec_to_owned_ptr(printed.code.into_bytes());
|
|
308
|
+
|
|
309
|
+
let (map_ptr, map_len, map_cap) = if let Some(mut sm) = source_map {
|
|
310
|
+
match sm.to_json(None) {
|
|
311
|
+
Ok(json) => vec_to_owned_ptr(json.into_bytes()),
|
|
312
|
+
Err(e) => {
|
|
313
|
+
unsafe { Vec::from_raw_parts(code_ptr, code_len, code_cap) };
|
|
314
|
+
return GjsifyResult::err(format!("lightningcss: source-map serialize: {e}"));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
(ptr::null_mut(), 0, 0)
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
GjsifyResult {
|
|
322
|
+
code: code_ptr,
|
|
323
|
+
code_len,
|
|
324
|
+
code_cap,
|
|
325
|
+
map: map_ptr,
|
|
326
|
+
map_len,
|
|
327
|
+
map_cap,
|
|
328
|
+
error: ptr::null_mut(),
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GLib-friendly glue for gjsify_lightningcss. See gjsify-lightningcss-glue.h.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "gjsify-lightningcss-glue.h"
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
GQuark
|
|
9
|
+
gjsify_lightningcss_error_quark (void)
|
|
10
|
+
{
|
|
11
|
+
return g_quark_from_static_string ("gjsify-lightningcss-error-quark");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
GBytes *
|
|
15
|
+
gjsify_lightningcss_glue_transform (const char *filename,
|
|
16
|
+
GBytes *code,
|
|
17
|
+
const char *browserslist,
|
|
18
|
+
gboolean minify,
|
|
19
|
+
gboolean source_map,
|
|
20
|
+
GBytes **out_map,
|
|
21
|
+
GError **error)
|
|
22
|
+
{
|
|
23
|
+
if (out_map)
|
|
24
|
+
*out_map = NULL;
|
|
25
|
+
|
|
26
|
+
if (code == NULL)
|
|
27
|
+
{
|
|
28
|
+
g_set_error_literal (error,
|
|
29
|
+
GJSIFY_LIGHTNINGCSS_ERROR,
|
|
30
|
+
GJSIFY_LIGHTNINGCSS_ERROR_FAILED,
|
|
31
|
+
"lightningcss: NULL input bytes");
|
|
32
|
+
return NULL;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
gsize code_len = 0;
|
|
36
|
+
const guint8 *code_data = g_bytes_get_data (code, &code_len);
|
|
37
|
+
|
|
38
|
+
GjsifyTransformOpts opts;
|
|
39
|
+
opts.filename = filename;
|
|
40
|
+
opts.code = code_data;
|
|
41
|
+
opts.code_len = code_len;
|
|
42
|
+
opts.browserslist = browserslist;
|
|
43
|
+
opts.minify = minify ? true : false;
|
|
44
|
+
opts.source_map = source_map ? true : false;
|
|
45
|
+
|
|
46
|
+
GjsifyResult res = gjsify_lightningcss_transform (opts);
|
|
47
|
+
|
|
48
|
+
if (res.error != NULL)
|
|
49
|
+
{
|
|
50
|
+
g_set_error_literal (error,
|
|
51
|
+
GJSIFY_LIGHTNINGCSS_ERROR,
|
|
52
|
+
GJSIFY_LIGHTNINGCSS_ERROR_FAILED,
|
|
53
|
+
res.error);
|
|
54
|
+
gjsify_lightningcss_result_free (res);
|
|
55
|
+
return NULL;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Copy Rust-allocated buffers into GBytes (GLib heap), then free the
|
|
59
|
+
* Rust result. Could be optimized later by giving GBytes a destroy
|
|
60
|
+
* notify that calls gjsify_lightningcss_result_free, but copying keeps
|
|
61
|
+
* the boundary clean and the buffers are typically small. */
|
|
62
|
+
GBytes *out_code = g_bytes_new (res.code, res.code_len);
|
|
63
|
+
|
|
64
|
+
if (source_map && res.map != NULL && out_map != NULL)
|
|
65
|
+
*out_map = g_bytes_new (res.map, res.map_len);
|
|
66
|
+
|
|
67
|
+
gjsify_lightningcss_result_free (res);
|
|
68
|
+
return out_code;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
GBytes *
|
|
72
|
+
gjsify_lightningcss_glue_bundle (const char *filename,
|
|
73
|
+
const char *browserslist,
|
|
74
|
+
gboolean minify,
|
|
75
|
+
gboolean source_map,
|
|
76
|
+
gboolean error_recovery,
|
|
77
|
+
GBytes **out_map,
|
|
78
|
+
GError **error)
|
|
79
|
+
{
|
|
80
|
+
if (out_map)
|
|
81
|
+
*out_map = NULL;
|
|
82
|
+
|
|
83
|
+
if (filename == NULL || filename[0] == '\0')
|
|
84
|
+
{
|
|
85
|
+
g_set_error_literal (error,
|
|
86
|
+
GJSIFY_LIGHTNINGCSS_ERROR,
|
|
87
|
+
GJSIFY_LIGHTNINGCSS_ERROR_FAILED,
|
|
88
|
+
"lightningcss: bundle requires a filename");
|
|
89
|
+
return NULL;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
GjsifyBundleOpts opts;
|
|
93
|
+
opts.filename = filename;
|
|
94
|
+
opts.browserslist = browserslist;
|
|
95
|
+
opts.minify = minify ? true : false;
|
|
96
|
+
opts.source_map = source_map ? true : false;
|
|
97
|
+
opts.error_recovery = error_recovery ? true : false;
|
|
98
|
+
|
|
99
|
+
GjsifyResult res = gjsify_lightningcss_bundle (opts);
|
|
100
|
+
|
|
101
|
+
if (res.error != NULL)
|
|
102
|
+
{
|
|
103
|
+
g_set_error_literal (error,
|
|
104
|
+
GJSIFY_LIGHTNINGCSS_ERROR,
|
|
105
|
+
GJSIFY_LIGHTNINGCSS_ERROR_FAILED,
|
|
106
|
+
res.error);
|
|
107
|
+
gjsify_lightningcss_result_free (res);
|
|
108
|
+
return NULL;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
GBytes *out_code = g_bytes_new (res.code, res.code_len);
|
|
112
|
+
if (source_map && res.map != NULL && out_map != NULL)
|
|
113
|
+
*out_map = g_bytes_new (res.map, res.map_len);
|
|
114
|
+
|
|
115
|
+
gjsify_lightningcss_result_free (res);
|
|
116
|
+
return out_code;
|
|
117
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GLib-friendly wrapper around the Rust transform shim.
|
|
3
|
+
*
|
|
4
|
+
* The Rust side returns its own struct with malloc'd buffers. This glue
|
|
5
|
+
* layer copies those buffers into GBytes (so GLib's refcount controls
|
|
6
|
+
* lifetime — friendly to SpiderMonkey GC) and frees the Rust result
|
|
7
|
+
* immediately. Errors become GError on the GJSIFY_LIGHTNINGCSS quark.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#ifndef GJSIFY_LIGHTNINGCSS_GLUE_H
|
|
11
|
+
#define GJSIFY_LIGHTNINGCSS_GLUE_H
|
|
12
|
+
|
|
13
|
+
#include <glib.h>
|
|
14
|
+
#include <stdbool.h>
|
|
15
|
+
#include "gjsify-lightningcss.h"
|
|
16
|
+
|
|
17
|
+
G_BEGIN_DECLS
|
|
18
|
+
|
|
19
|
+
#define GJSIFY_LIGHTNINGCSS_ERROR (gjsify_lightningcss_error_quark ())
|
|
20
|
+
|
|
21
|
+
typedef enum {
|
|
22
|
+
GJSIFY_LIGHTNINGCSS_ERROR_FAILED = 0,
|
|
23
|
+
} GjsifyLightningcssError;
|
|
24
|
+
|
|
25
|
+
GQuark gjsify_lightningcss_error_quark (void);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* gjsify_lightningcss_glue_transform:
|
|
29
|
+
* @filename: logical filename for diagnostics (NULL OK)
|
|
30
|
+
* @code: input CSS bytes
|
|
31
|
+
* @browserslist: browserslist query (NULL = no targets lowering)
|
|
32
|
+
* @minify: TRUE to minify the output
|
|
33
|
+
* @source_map: TRUE to also compute a source map
|
|
34
|
+
* @out_map: (out)(transfer full)(nullable): JSON source map, set
|
|
35
|
+
* only when @source_map is TRUE
|
|
36
|
+
* @error: (out)(optional): GError on failure
|
|
37
|
+
*
|
|
38
|
+
* Returns: (transfer full): output CSS as GBytes, or NULL on error.
|
|
39
|
+
*/
|
|
40
|
+
GBytes *gjsify_lightningcss_glue_transform (const char *filename,
|
|
41
|
+
GBytes *code,
|
|
42
|
+
const char *browserslist,
|
|
43
|
+
gboolean minify,
|
|
44
|
+
gboolean source_map,
|
|
45
|
+
GBytes **out_map,
|
|
46
|
+
GError **error);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* gjsify_lightningcss_glue_bundle:
|
|
50
|
+
* @filename: entry CSS path (must not be NULL)
|
|
51
|
+
* @browserslist: targets query (NULL = no lowering)
|
|
52
|
+
* @minify: minify the output
|
|
53
|
+
* @source_map: compute a source map for the entry file
|
|
54
|
+
* @error_recovery: continue on parse errors (lightningcss
|
|
55
|
+
* `errorRecovery: true` semantics)
|
|
56
|
+
* @out_map: (out)(transfer full)(nullable): JSON source map
|
|
57
|
+
* @error: (out)(optional)
|
|
58
|
+
*
|
|
59
|
+
* Returns: (transfer full): bundled output CSS as GBytes. The bundler
|
|
60
|
+
* resolves @import chains via lightningcss's built-in `FileProvider`
|
|
61
|
+
* (filesystem-backed).
|
|
62
|
+
*/
|
|
63
|
+
GBytes *gjsify_lightningcss_glue_bundle (const char *filename,
|
|
64
|
+
const char *browserslist,
|
|
65
|
+
gboolean minify,
|
|
66
|
+
gboolean source_map,
|
|
67
|
+
gboolean error_recovery,
|
|
68
|
+
GBytes **out_map,
|
|
69
|
+
GError **error);
|
|
70
|
+
|
|
71
|
+
G_END_DECLS
|
|
72
|
+
|
|
73
|
+
#endif /* GJSIFY_LIGHTNINGCSS_GLUE_H */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hand-written C header matching the Rust extern "C" surface of
|
|
3
|
+
* the gjsify_lightningcss cdylib (src/rust/src/lib.rs).
|
|
4
|
+
*
|
|
5
|
+
* Vala includes this via [CCode (cheader_filename = "gjsify-lightningcss.h")]
|
|
6
|
+
* to call into the Rust shim. We do NOT use cbindgen at build time —
|
|
7
|
+
* the surface is small enough that a hand-maintained header is simpler
|
|
8
|
+
* and avoids pulling cbindgen into the meson build.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#ifndef GJSIFY_LIGHTNINGCSS_H
|
|
12
|
+
#define GJSIFY_LIGHTNINGCSS_H
|
|
13
|
+
|
|
14
|
+
#include <glib.h>
|
|
15
|
+
#include <stdbool.h>
|
|
16
|
+
#include <stddef.h>
|
|
17
|
+
#include <stdint.h>
|
|
18
|
+
|
|
19
|
+
G_BEGIN_DECLS
|
|
20
|
+
|
|
21
|
+
typedef struct {
|
|
22
|
+
const char *filename; /* may be NULL → "" */
|
|
23
|
+
const uint8_t *code;
|
|
24
|
+
size_t code_len;
|
|
25
|
+
const char *browserslist; /* may be NULL → no targets lowering */
|
|
26
|
+
bool minify;
|
|
27
|
+
bool source_map;
|
|
28
|
+
} GjsifyTransformOpts;
|
|
29
|
+
|
|
30
|
+
typedef struct {
|
|
31
|
+
uint8_t *code;
|
|
32
|
+
size_t code_len;
|
|
33
|
+
size_t code_cap;
|
|
34
|
+
uint8_t *map; /* NULL if source_map was false */
|
|
35
|
+
size_t map_len;
|
|
36
|
+
size_t map_cap;
|
|
37
|
+
char *error; /* NULL on success */
|
|
38
|
+
} GjsifyResult;
|
|
39
|
+
|
|
40
|
+
/* One-shot CSS transform. The returned struct owns its buffers — pass it
|
|
41
|
+
* to gjsify_lightningcss_result_free() exactly once when done. */
|
|
42
|
+
GjsifyResult gjsify_lightningcss_transform (GjsifyTransformOpts opts);
|
|
43
|
+
|
|
44
|
+
typedef struct {
|
|
45
|
+
const char *filename; /* entry CSS path; MUST NOT be NULL */
|
|
46
|
+
const char *browserslist; /* may be NULL → no targets lowering */
|
|
47
|
+
bool minify;
|
|
48
|
+
bool source_map;
|
|
49
|
+
bool error_recovery;
|
|
50
|
+
} GjsifyBundleOpts;
|
|
51
|
+
|
|
52
|
+
/* Bundle a CSS entry file (resolves @import chains via lightningcss's
|
|
53
|
+
* built-in FileProvider). Same ownership rules as transform(). */
|
|
54
|
+
GjsifyResult gjsify_lightningcss_bundle (GjsifyBundleOpts opts);
|
|
55
|
+
|
|
56
|
+
void gjsify_lightningcss_result_free (GjsifyResult result);
|
|
57
|
+
|
|
58
|
+
G_END_DECLS
|
|
59
|
+
|
|
60
|
+
#endif /* GJSIFY_LIGHTNINGCSS_H */
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GjsifyLightningcss — Vala wrapper around the Rust lightningcss cdylib.
|
|
3
|
+
*
|
|
4
|
+
* The real CSS pipeline lives in src/rust/ (compiled by meson via cargo
|
|
5
|
+
* to libgjsify_lightningcss.so) and a tiny C glue file
|
|
6
|
+
* (gjsify-lightningcss-glue.{h,c}) translates the malloc'd Rust struct
|
|
7
|
+
* into GBytes + GError, which GJS handles natively.
|
|
8
|
+
*
|
|
9
|
+
* This Vala layer just exposes the GLib-friendly glue function as a
|
|
10
|
+
* GObject method that emits to GIR/typelib, so JS can do:
|
|
11
|
+
*
|
|
12
|
+
* import GjsifyLightningcss from "gi://GjsifyLightningcss?version=1.0";
|
|
13
|
+
* const engine = new GjsifyLightningcss.Engine();
|
|
14
|
+
* const code = engine.transform("app.css", input, null, true, false, out null);
|
|
15
|
+
*
|
|
16
|
+
* Pattern: same as @gjsify/http2-native — Rust/C ownership stays in
|
|
17
|
+
* native land via GLib.Bytes; SpiderMonkey only sees refcounted handles.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
namespace GjsifyLightningcss {
|
|
21
|
+
|
|
22
|
+
[CCode (cname = "gjsify_lightningcss_glue_transform",
|
|
23
|
+
cheader_filename = "gjsify-lightningcss-glue.h")]
|
|
24
|
+
private extern GLib.Bytes? _glue_transform (string? filename,
|
|
25
|
+
GLib.Bytes code,
|
|
26
|
+
string? browserslist,
|
|
27
|
+
bool minify,
|
|
28
|
+
bool source_map,
|
|
29
|
+
out GLib.Bytes? out_map) throws GLib.Error;
|
|
30
|
+
|
|
31
|
+
[CCode (cname = "gjsify_lightningcss_glue_bundle",
|
|
32
|
+
cheader_filename = "gjsify-lightningcss-glue.h")]
|
|
33
|
+
private extern GLib.Bytes? _glue_bundle (string filename,
|
|
34
|
+
string? browserslist,
|
|
35
|
+
bool minify,
|
|
36
|
+
bool source_map,
|
|
37
|
+
bool error_recovery,
|
|
38
|
+
out GLib.Bytes? out_map) throws GLib.Error;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Engine — stateless one-shot CSS transform pipeline.
|
|
42
|
+
*
|
|
43
|
+
* Each call constructs a fresh lightningcss StyleSheet on the Rust
|
|
44
|
+
* side, optionally lowers per browserslist targets, and prints back
|
|
45
|
+
* to CSS (with optional minify + source map). Designed for the
|
|
46
|
+
* @gjsify/rolldown-plugin-gjsify cssAsStringPlugin call site —
|
|
47
|
+
* single-shot transforms, no plugin/composes/css-modules surface.
|
|
48
|
+
*/
|
|
49
|
+
public class Engine : GLib.Object {
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* transform:
|
|
53
|
+
* @filename: logical filename for diagnostics (may be null)
|
|
54
|
+
* @code: input CSS bytes (UTF-8)
|
|
55
|
+
* @browserslist: browserslist query, e.g. "firefox >= 60"; null
|
|
56
|
+
* disables targets-driven lowering
|
|
57
|
+
* @minify: whitespace-strip + property/selector merging
|
|
58
|
+
* @source_map: also produce a JSON source map
|
|
59
|
+
* @out_source_map: (out)(transfer full)(nullable): JSON source
|
|
60
|
+
* map, set only when @source_map was true
|
|
61
|
+
*
|
|
62
|
+
* Returns: (transfer full): output CSS as GLib.Bytes. Throws
|
|
63
|
+
* GjsifyLightningcssError.FAILED on any pipeline error.
|
|
64
|
+
*/
|
|
65
|
+
public GLib.Bytes transform (string? filename,
|
|
66
|
+
GLib.Bytes code,
|
|
67
|
+
string? browserslist,
|
|
68
|
+
bool minify,
|
|
69
|
+
bool source_map,
|
|
70
|
+
out GLib.Bytes? out_source_map) throws GLib.Error {
|
|
71
|
+
GLib.Bytes? map = null;
|
|
72
|
+
var bytes = _glue_transform (filename, code, browserslist,
|
|
73
|
+
minify, source_map, out map);
|
|
74
|
+
out_source_map = map;
|
|
75
|
+
if (bytes == null)
|
|
76
|
+
throw new GLib.Error (GLib.Quark.from_string ("gjsify-lightningcss-error-quark"),
|
|
77
|
+
0, "lightningcss: unknown error (NULL result without GError)");
|
|
78
|
+
return bytes;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* bundle:
|
|
83
|
+
* @filename: entry CSS file path
|
|
84
|
+
* @browserslist: browserslist query (null disables targets lowering)
|
|
85
|
+
* @minify: minify the output
|
|
86
|
+
* @source_map: also produce a JSON source map
|
|
87
|
+
* @error_recovery: keep parsing after recoverable errors
|
|
88
|
+
* (matches lightningcss `errorRecovery: true`)
|
|
89
|
+
* @out_source_map: (out)(transfer full)(nullable)
|
|
90
|
+
*
|
|
91
|
+
* Returns: (transfer full): bundled output CSS as GLib.Bytes.
|
|
92
|
+
* Resolves @import chains via lightningcss's built-in
|
|
93
|
+
* filesystem-backed FileProvider.
|
|
94
|
+
*/
|
|
95
|
+
public GLib.Bytes bundle (string filename,
|
|
96
|
+
string? browserslist,
|
|
97
|
+
bool minify,
|
|
98
|
+
bool source_map,
|
|
99
|
+
bool error_recovery,
|
|
100
|
+
out GLib.Bytes? out_source_map) throws GLib.Error {
|
|
101
|
+
GLib.Bytes? map = null;
|
|
102
|
+
var bytes = _glue_bundle (filename, browserslist, minify,
|
|
103
|
+
source_map, error_recovery, out map);
|
|
104
|
+
out_source_map = map;
|
|
105
|
+
if (bytes == null)
|
|
106
|
+
throw new GLib.Error (GLib.Quark.from_string ("gjsify-lightningcss-error-quark"),
|
|
107
|
+
0, "lightningcss: unknown bundle error");
|
|
108
|
+
return bytes;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|