@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 CHANGED
@@ -1,60 +1,60 @@
1
1
  {
2
- "name": "@gjsify/lightningcss-native",
3
- "version": "0.4.0",
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": "yarn init:meson --wipe",
30
- "build": "yarn build:gjsify && yarn build:types",
31
- "build:gjsify": "gjsify build --library 'src/ts/**/*.{ts,js}'",
32
- "build:meson": "yarn init:meson && meson compile -C build",
33
- "build:types": "tsc",
34
- "build:prebuilds": "yarn 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.0",
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
- }
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
+ }