mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 22:32:06 +00:00
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
268 lines
9.7 KiB
Zig
268 lines
9.7 KiB
Zig
const std = @import("std");
|
|
const bun = @import("bun");
|
|
pub const css = @import("../css_parser.zig");
|
|
const MediaList = css.MediaList;
|
|
const Printer = css.Printer;
|
|
const PrintErr = css.PrintErr;
|
|
const Dependency = css.Dependency;
|
|
const dependencies = css.dependencies;
|
|
const LayerName = css.css_rules.layer.LayerName;
|
|
const SupportsCondition = css.css_rules.supports.SupportsCondition;
|
|
const Location = css.css_rules.Location;
|
|
|
|
/// TODO: change this to be field on ImportRule
|
|
/// The fields of this struct need to match the fields of ImportRule
|
|
/// because we cast between them
|
|
pub const ImportConditions = struct {
|
|
/// An optional cascade layer name, or `None` for an anonymous layer.
|
|
layer: ?struct {
|
|
/// PERF: null pointer optimizaiton, nullable
|
|
v: ?LayerName,
|
|
|
|
pub fn eql(this: *const @This(), other: *const @This()) bool {
|
|
if (this.v == null and other.v == null) return true;
|
|
if (this.v == null or other.v == null) return false;
|
|
return this.v.?.eql(&other.v.?);
|
|
}
|
|
} = null,
|
|
|
|
/// An optional `supports()` condition.
|
|
supports: ?SupportsCondition = null,
|
|
|
|
/// A media query.
|
|
media: css.MediaList = .{},
|
|
|
|
pub fn hash(this: *const @This(), hasher: anytype) void {
|
|
return css.implementHash(@This(), this, hasher);
|
|
}
|
|
|
|
pub fn hasAnonymousLayer(this: *const @This()) bool {
|
|
return this.layer != null and this.layer.?.v == null;
|
|
}
|
|
|
|
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) ImportConditions {
|
|
return ImportConditions{
|
|
.layer = if (this.layer) |*l| if (l.v) |layer| .{ .v = layer.deepClone(allocator) } else .{ .v = null } else null,
|
|
.supports = if (this.supports) |*s| s.deepClone(allocator) else null,
|
|
.media = this.media.deepClone(allocator),
|
|
};
|
|
}
|
|
|
|
pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void {
|
|
if (this.layer) |*lyr| {
|
|
try dest.writeStr(" layer");
|
|
if (lyr.v) |l| {
|
|
try dest.writeChar('(');
|
|
try l.toCss(W, dest);
|
|
try dest.writeChar(')');
|
|
}
|
|
}
|
|
|
|
if (this.supports) |*sup| {
|
|
try dest.writeStr(" supports");
|
|
if (sup.* == .declaration) {
|
|
try sup.toCss(W, dest);
|
|
} else {
|
|
try dest.writeChar('(');
|
|
try sup.toCss(W, dest);
|
|
try dest.writeChar(')');
|
|
}
|
|
}
|
|
|
|
if (this.media.media_queries.items.len > 0) {
|
|
try dest.writeChar(' ');
|
|
try this.media.toCss(W, dest);
|
|
}
|
|
}
|
|
|
|
/// This code does the same thing as `deepClone` right now, but might change in the future so keeping this separate.
|
|
///
|
|
/// So this code is used when we wrap a CSS file in import conditions in the final output chunk:
|
|
/// ```css
|
|
/// @layer foo {
|
|
/// /* css file contents */
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// However, the *prelude* of the condition /could/ contain a URL token:
|
|
/// ```css
|
|
/// @supports (background-image: url('example.png')) {
|
|
/// /* css file contents */
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// In this case, the URL token's import record actually belongs to the /parent/ of the current CSS file (the one who imported it).
|
|
/// Therefore, we need to copy this import record from the parent into the import record list of this current CSS file.
|
|
///
|
|
/// In actuality, the css parser doesn't create an import record for URL tokens in `@supports` because that's pointless in the context of hte
|
|
/// @supports rule.
|
|
///
|
|
/// Furthermore, a URL token is not valid in `@media` or `@layer` rules.
|
|
///
|
|
/// But this could change in the future, so still keeping this function.
|
|
///
|
|
pub fn cloneWithImportRecords(this: *const @This(), allocator: std.mem.Allocator, import_records: *bun.BabyList(bun.ImportRecord)) ImportConditions {
|
|
return ImportConditions{
|
|
.layer = if (this.layer) |layer| if (layer.v) |l| .{ .v = l.cloneWithImportRecords(allocator, import_records) } else .{ .v = null } else null,
|
|
.supports = if (this.supports) |*supp| supp.cloneWithImportRecords(allocator, import_records) else null,
|
|
.media = this.media.cloneWithImportRecords(allocator, import_records),
|
|
};
|
|
}
|
|
|
|
pub fn layersEql(lhs: *const @This(), rhs: *const @This()) bool {
|
|
if (lhs.layer == null and rhs.layer == null) return true;
|
|
if (lhs.layer == null or rhs.layer == null) return false;
|
|
return lhs.layer.?.eql(&rhs.layer.?);
|
|
}
|
|
|
|
pub fn supportsEql(lhs: *const @This(), rhs: *const @This()) bool {
|
|
if (lhs.supports == null and rhs.supports == null) return true;
|
|
if (lhs.supports == null or rhs.supports == null) return false;
|
|
|
|
return lhs.supports.?.eql(&rhs.supports.?);
|
|
}
|
|
};
|
|
|
|
/// A [@import](https://drafts.csswg.org/css-cascade/#at-import) rule.
|
|
pub const ImportRule = struct {
|
|
/// The url to import.
|
|
url: []const u8,
|
|
|
|
/// An optional cascade layer name, or `None` for an anonymous layer.
|
|
layer: ?struct {
|
|
/// PERF: null pointer optimizaiton, nullable
|
|
v: ?LayerName,
|
|
|
|
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
|
|
return css.implementDeepClone(@This(), this, allocator);
|
|
}
|
|
} = null,
|
|
|
|
/// An optional `supports()` condition.
|
|
supports: ?SupportsCondition = null,
|
|
|
|
/// A media query.
|
|
media: css.MediaList = .{},
|
|
|
|
/// This is default initialized to 2^32 - 1 when parsing.
|
|
/// If we are bundling, this will be set to the index of the corresponding ImportRecord
|
|
/// created for this import rule.
|
|
import_record_idx: u32 = std.math.maxInt(u32),
|
|
|
|
/// The location of the rule in the source file.
|
|
loc: Location,
|
|
|
|
const This = @This();
|
|
|
|
pub fn fromUrl(url: []const u8) This {
|
|
return .{
|
|
.url = url,
|
|
.layer = null,
|
|
.supports = null,
|
|
.media = MediaList{ .media_queries = .{} },
|
|
.import_record_idx = std.math.maxInt(u32),
|
|
.loc = Location.dummy(),
|
|
};
|
|
}
|
|
|
|
pub fn fromUrlAndImportRecordIdx(url: []const u8, import_record_idx: u32) This {
|
|
return .{
|
|
.url = url,
|
|
.layer = null,
|
|
.supports = null,
|
|
.media = MediaList{ .media_queries = .{} },
|
|
.import_record_idx = import_record_idx,
|
|
.loc = Location.dummy(),
|
|
};
|
|
}
|
|
|
|
pub fn fromConditionsAndUrl(url: []const u8, conds: ImportConditions) This {
|
|
return ImportRule{
|
|
.url = url,
|
|
.layer = if (conds.layer) |layer| if (layer.v) |ly| .{ .v = ly } else .{ .v = null } else null,
|
|
.supports = conds.supports,
|
|
.media = conds.media,
|
|
.import_record_idx = std.math.maxInt(u32),
|
|
.loc = Location.dummy(),
|
|
};
|
|
}
|
|
|
|
pub fn conditions(this: *const @This()) *const ImportConditions {
|
|
return @ptrCast(&this.layer);
|
|
}
|
|
|
|
pub fn conditionsMut(this: *@This()) *ImportConditions {
|
|
return @ptrCast(&this.layer);
|
|
}
|
|
|
|
/// The `import_records` here is preserved from esbuild in the case that we do need it, it doesn't seem necessary now
|
|
pub fn conditionsWithImportRecords(this: *const This, allocator: std.mem.Allocator, import_records: *bun.BabyList(bun.ImportRecord)) ImportConditions {
|
|
return ImportConditions{
|
|
.layer = if (this.layer) |layer| if (layer.v) |l| .{ .v = l.cloneWithImportRecords(allocator, import_records) } else .{ .v = null } else null,
|
|
.supports = if (this.supports) |*supp| supp.cloneWithImportRecords(allocator, import_records) else null,
|
|
.media = this.media.cloneWithImportRecords(allocator, import_records),
|
|
};
|
|
}
|
|
|
|
pub fn hasConditions(this: *const This) bool {
|
|
return this.layer != null or this.supports != null or this.media.media_queries.items.len > 0;
|
|
}
|
|
|
|
pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void {
|
|
const dep = if (dest.dependencies != null) dependencies.ImportDependency.new(
|
|
dest.allocator,
|
|
this,
|
|
dest.filename(),
|
|
dest.local_names,
|
|
dest.symbols,
|
|
) else null;
|
|
|
|
// #[cfg(feature = "sourcemap")]
|
|
// dest.add_mapping(self.loc);
|
|
|
|
try dest.writeStr("@import ");
|
|
if (dep) |d| {
|
|
css.serializer.serializeString(d.placeholder, dest) catch return dest.addFmtError();
|
|
|
|
if (dest.dependencies) |*deps| {
|
|
deps.append(
|
|
dest.allocator,
|
|
Dependency{ .import = d },
|
|
) catch unreachable;
|
|
}
|
|
} else {
|
|
css.serializer.serializeString(this.url, dest) catch return dest.addFmtError();
|
|
}
|
|
|
|
if (this.layer) |*lyr| {
|
|
try dest.writeStr(" layer");
|
|
if (lyr.v) |l| {
|
|
try dest.writeChar('(');
|
|
try l.toCss(W, dest);
|
|
try dest.writeChar(')');
|
|
}
|
|
}
|
|
|
|
if (this.supports) |*sup| {
|
|
try dest.writeStr(" supports");
|
|
if (sup.* == .declaration) {
|
|
try sup.toCss(W, dest);
|
|
} else {
|
|
try dest.writeChar('(');
|
|
try sup.toCss(W, dest);
|
|
try dest.writeChar(')');
|
|
}
|
|
}
|
|
|
|
if (this.media.media_queries.items.len > 0) {
|
|
try dest.writeChar(' ');
|
|
try this.media.toCss(W, dest);
|
|
}
|
|
try dest.writeStr(";");
|
|
}
|
|
|
|
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) This {
|
|
return css.implementDeepClone(@This(), this, allocator);
|
|
}
|
|
};
|