mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
350 lines
13 KiB
Zig
350 lines
13 KiB
Zig
pub const css = @import("./css_parser.zig");
|
|
|
|
const VendorPrefix = css.VendorPrefix;
|
|
|
|
/// Target browsers and features to compile.
|
|
pub const Targets = struct {
|
|
/// Browser targets to compile the CSS for.
|
|
browsers: ?Browsers = null,
|
|
/// Features that should always be compiled, even when supported by targets.
|
|
include: Features = .{},
|
|
/// Features that should never be compiled, even when unsupported by targets.
|
|
exclude: Features = .{},
|
|
|
|
/// Set a sane default for bundler
|
|
pub fn browserDefault() Targets {
|
|
return .{
|
|
.browsers = Browsers.browserDefault,
|
|
};
|
|
}
|
|
|
|
/// Set a sane default for bundler
|
|
pub fn runtimeDefault() Targets {
|
|
return .{
|
|
.browsers = null,
|
|
};
|
|
}
|
|
|
|
fn parseDebugTarget(val_: []const u8) ?u32 {
|
|
const val = bun.strings.trim(val_, " \n\r\t");
|
|
if (val.len == 0) return null;
|
|
if (bun.strings.eqlCaseInsensitiveASCII(val, "null", true)) return null;
|
|
|
|
var lhs: u32 = 0;
|
|
var rhs: u32 = 0;
|
|
|
|
var i: usize = 0;
|
|
for (val, 0..) |c, j| {
|
|
if (!std.ascii.isDigit(c)) {
|
|
i = j;
|
|
lhs = std.fmt.parseInt(u32, val[0..j], 10) catch @panic("invalid bytes");
|
|
break;
|
|
}
|
|
}
|
|
if (i >= val.len) {
|
|
lhs = std.fmt.parseInt(u32, val, 10) catch @panic("invalid bytes");
|
|
return lhs;
|
|
}
|
|
if (val[i] != ' ') {
|
|
@panic("bad string");
|
|
}
|
|
i += 1;
|
|
if (val[i] != '<' or i + 1 >= val.len or val[i + 1] != '<') {
|
|
@panic("bad string");
|
|
}
|
|
i += 2;
|
|
if (val[i] != ' ') {
|
|
@panic("bad string");
|
|
}
|
|
i += 1;
|
|
rhs = std.fmt.parseInt(u32, val[i..], 10) catch @panic("invalid bytes");
|
|
return lhs << @intCast(rhs);
|
|
}
|
|
|
|
pub fn forBundlerTarget(target: bun.transpiler.options.Target) Targets {
|
|
if (comptime bun.Environment.isDebug) {
|
|
var browsers: Browsers = .{};
|
|
const browser_fields = std.meta.fields(Browsers);
|
|
var has_any = false;
|
|
inline for (browser_fields) |field| {
|
|
const env_var = "BUN_DEBUG_CSS_TARGET_" ++ field.name;
|
|
if (bun.getenvZAnyCase(env_var)) |val| {
|
|
@field(browsers, field.name) = parseDebugTarget(val);
|
|
has_any = true;
|
|
}
|
|
}
|
|
if (has_any) {
|
|
return .{ .browsers = browsers };
|
|
}
|
|
}
|
|
return switch (target) {
|
|
.node, .bun => runtimeDefault(),
|
|
.browser, .bun_macro, .bake_server_components_ssr => browserDefault(),
|
|
};
|
|
}
|
|
|
|
pub fn prefixes(this: *const Targets, prefix: css.VendorPrefix, feature: css.prefixes.Feature) css.VendorPrefix {
|
|
if (prefix.none and !this.exclude.vendor_prefixes) {
|
|
if (this.include.vendor_prefixes) {
|
|
return .all;
|
|
} else {
|
|
return if (this.browsers) |b| feature.prefixesFor(b) else prefix;
|
|
}
|
|
} else {
|
|
return prefix;
|
|
}
|
|
}
|
|
|
|
pub fn shouldCompileLogical(this: *const Targets, feature: css.compat.Feature) bool {
|
|
return this.shouldCompile(feature, css.Features{ .logical_properties = true });
|
|
}
|
|
|
|
pub fn shouldCompile(this: *const Targets, feature: css.compat.Feature, flag: Features) bool {
|
|
return bits.contains(Features, this.include, flag) or
|
|
(!bits.contains(Features, this.exclude, flag) and !this.isCompatible(feature));
|
|
}
|
|
|
|
pub fn shouldCompileSame(this: *const Targets, comptime compat_feature: css.compat.Feature) bool {
|
|
const target_feature: css.targets.Features = target_feature: {
|
|
var feature: css.targets.Features = .{};
|
|
@field(feature, @tagName(compat_feature)) = true;
|
|
break :target_feature feature;
|
|
};
|
|
|
|
return shouldCompile(this, compat_feature, target_feature);
|
|
}
|
|
|
|
pub fn shouldCompileSelectors(this: *const Targets) bool {
|
|
return bun.bits.intersects(Features, this.include, Features.selectors) or
|
|
(!bun.bits.intersects(Features, this.exclude, Features.selectors) and this.browsers != null);
|
|
}
|
|
|
|
pub fn isCompatible(this: *const Targets, feature: css.compat.Feature) bool {
|
|
if (this.browsers) |*targets| {
|
|
return feature.isCompatible(targets.*);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/// Browser versions to compile CSS for.
|
|
///
|
|
/// Versions are represented as a single 24-bit integer, with one byte
|
|
/// per `major.minor.patch` component.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// This example represents a target of Safari 13.2.0.
|
|
///
|
|
/// ```
|
|
/// const Browsers = struct {
|
|
/// safari: ?u32 = (13 << 16) | (2 << 8),
|
|
/// ..Browsers{}
|
|
/// };
|
|
/// ```
|
|
pub const Browsers = struct {
|
|
android: ?u32 = null,
|
|
chrome: ?u32 = null,
|
|
edge: ?u32 = null,
|
|
firefox: ?u32 = null,
|
|
ie: ?u32 = null,
|
|
ios_saf: ?u32 = null,
|
|
opera: ?u32 = null,
|
|
safari: ?u32 = null,
|
|
samsung: ?u32 = null,
|
|
|
|
pub const browserDefault = convertFromString(&.{
|
|
"es2020", // support import.meta.url
|
|
"edge88",
|
|
"firefox78",
|
|
"chrome87",
|
|
"safari14",
|
|
}) catch unreachable;
|
|
|
|
/// Ported from here:
|
|
/// https://github.com/vitejs/vite/blob/ac329685bba229e1ff43e3d96324f817d48abe48/packages/vite/src/node/plugins/css.ts#L3335
|
|
pub fn convertFromString(esbuild_target: []const []const u8) anyerror!Browsers {
|
|
var browsers: Browsers = .{};
|
|
|
|
for (esbuild_target) |str| {
|
|
var entries_buf: [5][]const u8 = undefined;
|
|
const entries_without_es: [][]const u8 = entries_without_es: {
|
|
if (str.len <= 2 or !(str[0] == 'e' and str[1] == 's')) {
|
|
entries_buf[0] = str;
|
|
break :entries_without_es entries_buf[0..1];
|
|
}
|
|
|
|
const number_part = str[2..];
|
|
const year = try std.fmt.parseInt(u16, number_part, 10);
|
|
switch (year) {
|
|
// https://caniuse.com/?search=es2015
|
|
2015 => {
|
|
entries_buf[0..5].* = .{ "chrome49", "edge13", "safari10", "firefox44", "opera36" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2016
|
|
2016 => {
|
|
entries_buf[0..5].* = .{ "chrome50", "edge13", "safari10", "firefox43", "opera37" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2017
|
|
2017 => {
|
|
entries_buf[0..5].* = .{ "chrome58", "edge15", "safari11", "firefox52", "opera45" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2018
|
|
2018 => {
|
|
entries_buf[0..5].* = .{ "chrome63", "edge79", "safari12", "firefox58", "opera50" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2019
|
|
2019 => {
|
|
entries_buf[0..5].* = .{ "chrome73", "edge79", "safari12.1", "firefox64", "opera60" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2020
|
|
2020 => {
|
|
entries_buf[0..5].* = .{ "chrome80", "edge80", "safari14.1", "firefox80", "opera67" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2021
|
|
2021 => {
|
|
entries_buf[0..5].* = .{ "chrome85", "edge85", "safari14.1", "firefox80", "opera71" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2022
|
|
2022 => {
|
|
entries_buf[0..5].* = .{ "chrome94", "edge94", "safari16.4", "firefox93", "opera80" };
|
|
break :entries_without_es entries_buf[0..5];
|
|
},
|
|
// https://caniuse.com/?search=es2023
|
|
2023 => {
|
|
entries_buf[0..4].* = .{ "chrome110", "edge110", "safari16.4", "opera96" };
|
|
break :entries_without_es entries_buf[0..4];
|
|
},
|
|
else => {
|
|
if (@inComptime()) {
|
|
@compileLog("Invalid target: " ++ str);
|
|
}
|
|
return error.UnsupportedCSSTarget;
|
|
},
|
|
}
|
|
};
|
|
|
|
for_loop: for (entries_without_es) |entry| {
|
|
if (bun.strings.eql(entry, "esnext")) continue;
|
|
const maybe_idx: ?usize = maybe_idx: {
|
|
for (entry, 0..) |c, i| {
|
|
if (std.ascii.isDigit(c)) break :maybe_idx i;
|
|
}
|
|
break :maybe_idx null;
|
|
};
|
|
|
|
if (maybe_idx) |idx| {
|
|
const Browser = enum {
|
|
chrome,
|
|
edge,
|
|
firefox,
|
|
ie,
|
|
ios_saf,
|
|
opera,
|
|
safari,
|
|
no_mapping,
|
|
};
|
|
const Map = bun.ComptimeStringMap(Browser, .{
|
|
.{ "chrome", Browser.chrome },
|
|
.{ "edge", Browser.edge },
|
|
.{ "firefox", Browser.firefox },
|
|
.{ "hermes", Browser.no_mapping },
|
|
.{ "ie", Browser.ie },
|
|
.{ "ios", Browser.ios_saf },
|
|
.{ "node", Browser.no_mapping },
|
|
.{ "opera", Browser.opera },
|
|
.{ "rhino", Browser.no_mapping },
|
|
.{ "safari", Browser.safari },
|
|
});
|
|
const browser = Map.get(entry[0..idx]);
|
|
if (browser == null or browser.? == .no_mapping) continue; // No mapping available
|
|
|
|
const major, const minor = major_minor: {
|
|
const version_str = entry[idx..];
|
|
const dot_index = std.mem.indexOfScalar(u8, version_str, '.') orelse version_str.len;
|
|
const major = std.fmt.parseInt(u16, version_str[0..dot_index], 10) catch continue;
|
|
const minor = if (dot_index < version_str.len)
|
|
std.fmt.parseInt(u16, version_str[dot_index + 1 ..], 10) catch 0
|
|
else
|
|
0;
|
|
break :major_minor .{ major, minor };
|
|
};
|
|
|
|
const version: u32 = (@as(u32, major) << 16) | @as(u32, minor << 8);
|
|
switch (browser.?) {
|
|
inline else => |browser_name| {
|
|
if (@field(browsers, @tagName(browser_name)) == null or
|
|
version < @field(browsers, @tagName(browser_name)).?)
|
|
{
|
|
@field(browsers, @tagName(browser_name)) = version;
|
|
}
|
|
continue :for_loop;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return browsers;
|
|
}
|
|
};
|
|
|
|
/// Autogenerated by build-prefixes.js
|
|
/// Features to explicitly enable or disable.
|
|
pub const Features = packed struct(u32) {
|
|
nesting: bool = false,
|
|
not_selector_list: bool = false,
|
|
dir_selector: bool = false,
|
|
lang_selector_list: bool = false,
|
|
is_selector: bool = false,
|
|
text_decoration_thickness_percent: bool = false,
|
|
media_interval_syntax: bool = false,
|
|
media_range_syntax: bool = false,
|
|
custom_media_queries: bool = false,
|
|
clamp_function: bool = false,
|
|
color_function: bool = false,
|
|
oklab_colors: bool = false,
|
|
lab_colors: bool = false,
|
|
p3_colors: bool = false,
|
|
hex_alpha_colors: bool = false,
|
|
space_separated_color_notation: bool = false,
|
|
font_family_system_ui: bool = false,
|
|
double_position_gradients: bool = false,
|
|
vendor_prefixes: bool = false,
|
|
logical_properties: bool = false,
|
|
__unused: u12 = 0,
|
|
|
|
pub const selectors: @This() = .{
|
|
.nesting = true,
|
|
.not_selector_list = true,
|
|
.dir_selector = true,
|
|
.lang_selector_list = true,
|
|
.is_selector = true,
|
|
};
|
|
pub const media_queries: @This() = .{
|
|
.media_interval_syntax = true,
|
|
.media_range_syntax = true,
|
|
.custom_media_queries = true,
|
|
};
|
|
pub const colors: @This() = .{
|
|
.color_function = true,
|
|
.oklab_colors = true,
|
|
.lab_colors = true,
|
|
.p3_colors = true,
|
|
.hex_alpha_colors = true,
|
|
.space_separated_color_notation = true,
|
|
};
|
|
};
|
|
|
|
const std = @import("std");
|
|
|
|
const bun = @import("bun");
|
|
const bits = bun.bits;
|