diff --git a/src/bundler.zig b/src/bundler.zig index 5a11d98b28..ed9c903a87 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -13,7 +13,7 @@ const C = bun.C; const std = @import("std"); const lex = bun.js_lexer; const logger = bun.logger; -const options = @import("options.zig"); +pub const options = @import("options.zig"); const js_parser = bun.js_parser; const JSON = bun.JSON; const js_printer = bun.js_printer; @@ -958,6 +958,7 @@ pub const Bundler = struct { return null; } const result = sheet.toCss(alloc, bun.css.PrinterOptions{ + .targets = bun.css.Targets.forBundlerTarget(bundler.options.target), .minify = bundler.options.minify_whitespace, }, null) catch |e| { bun.handleErrorReturnTrace(e, @errorReturnTrace()); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 66c4018634..c03935eecb 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -3744,7 +3744,7 @@ pub const ParseTask = struct { }, }; if (css_ast.minify(allocator, bun.css.MinifyOptions{ - .targets = .{}, + .targets = bun.css.Targets.forBundlerTarget(bundler.options.target), .unused_symbols = .{}, }).asErr()) |e| { try e.addToLogger(log, &source); @@ -9106,13 +9106,15 @@ pub const LinkerContext = struct { }; var import_records = BabyList(ImportRecord).init(&import_records_); const css: *const bun.css.BundlerStyleSheet = &chunk.content.css.asts[imports_in_chunk_index]; + const printer_options = bun.css.PrinterOptions{ + // TODO: make this more configurable + .minify = c.options.minify_whitespace, + .targets = bun.css.Targets.forBundlerTarget(c.options.target), + }; _ = css.toCssWithWriter( worker.allocator, &buffer_writer, - bun.css.PrinterOptions{ - // TODO: make this more configurable - .minify = c.options.minify_whitespace, - }, + printer_options, &import_records, ) catch { @panic("TODO: HANDLE THIS ERROR!"); @@ -9126,13 +9128,15 @@ pub const LinkerContext = struct { }, .source_index => |idx| { const css: *const bun.css.BundlerStyleSheet = &chunk.content.css.asts[imports_in_chunk_index]; + const printer_options = bun.css.PrinterOptions{ + .targets = bun.css.Targets.forBundlerTarget(c.options.target), + // TODO: make this more configurable + .minify = c.options.minify_whitespace or c.options.minify_syntax or c.options.minify_identifiers, + }; _ = css.toCssWithWriter( worker.allocator, &buffer_writer, - bun.css.PrinterOptions{ - // TODO: make this more configurable - .minify = c.options.minify_whitespace or c.options.minify_syntax or c.options.minify_identifiers, - }, + printer_options, &c.graph.ast.items(.import_records)[idx.get()], ) catch { @panic("TODO: HANDLE THIS ERROR!"); diff --git a/src/css/css_internals.zig b/src/css/css_internals.zig index 46b7f133f0..09a86a80a0 100644 --- a/src/css/css_internals.zig +++ b/src/css/css_internals.zig @@ -60,18 +60,19 @@ pub fn testingImpl(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, c const expected = expected_bunstr.toUTF8(bun.default_allocator); defer expected.deinit(); - const options_arg = arguments.nextEat(); + const browser_options_arg = arguments.nextEat(); var log = bun.logger.Log.init(alloc); defer log.deinit(); + var browsers: ?bun.css.targets.Browsers = null; const parser_options = parser_options: { const opts = bun.css.ParserOptions.default(alloc, &log); // if (test_kind == .prefix) break :parser_options opts; - if (options_arg) |optargs| { + if (browser_options_arg) |optargs| { if (optargs.isObject()) { - // minify_options.targets.browsers = targetsFromJS(globalThis, optarg); + browsers = try targetsFromJS(globalThis, optargs); } } @@ -88,11 +89,7 @@ pub fn testingImpl(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, c .result => |stylesheet_| { var stylesheet = stylesheet_; var minify_options: bun.css.MinifyOptions = bun.css.MinifyOptions.default(); - if (options_arg) |optarg| { - if (optarg.isObject()) { - minify_options.targets.browsers = try targetsFromJS(globalThis, optarg); - } - } + minify_options.targets.browsers = browsers; _ = stylesheet.minify(alloc, minify_options).assert(); const result = stylesheet.toCss(alloc, bun.css.PrinterOptions{ diff --git a/src/css/css_parser.zig b/src/css/css_parser.zig index 40ceaa2966..306b4d4dcd 100644 --- a/src/css/css_parser.zig +++ b/src/css/css_parser.zig @@ -186,8 +186,14 @@ pub const VendorPrefix = packed struct(u8) { } /// Returns VendorPrefix::None if empty. - pub fn orNone(this: VendorPrefix) VendorPrefix { - return this.bitwiseOr(VendorPrefix{ .none = true }); + pub inline fn orNone(this: VendorPrefix) VendorPrefix { + return this._or(VendorPrefix{ .none = true }); + } + + /// **WARNING**: NOT THE SAME as .bitwiseOr!! + pub inline fn _or(this: VendorPrefix, other: VendorPrefix) VendorPrefix { + if (this.isEmpty()) return other; + return this; } }; diff --git a/src/css/declaration.zig b/src/css/declaration.zig index f84c4f9c11..a7ec7e1182 100644 --- a/src/css/declaration.zig +++ b/src/css/declaration.zig @@ -47,7 +47,7 @@ pub const DeclarationBlock = struct { var arraylist = ArrayList(u8){}; const w = arraylist.writer(bun.default_allocator); defer arraylist.deinit(bun.default_allocator); - var printer = css.Printer(@TypeOf(w)).new(bun.default_allocator, std.ArrayList(u8).init(bun.default_allocator), w, .{}, null); + var printer = css.Printer(@TypeOf(w)).new(bun.default_allocator, std.ArrayList(u8).init(bun.default_allocator), w, css.PrinterOptions.default(), null); defer printer.deinit(); this.self.toCss(@TypeOf(w), &printer) catch |e| return try writer.print("\n", .{@errorName(e)}); try writer.writeAll(arraylist.items); diff --git a/src/css/dependencies.zig b/src/css/dependencies.zig index 7d922244dd..453f9cefb3 100644 --- a/src/css/dependencies.zig +++ b/src/css/dependencies.zig @@ -70,7 +70,7 @@ pub const ImportDependency = struct { allocator, css.css_rules.supports.SupportsCondition, supports, - css.PrinterOptions{}, + css.PrinterOptions.default(), null, ) catch bun.Output.panic( "Unreachable code: failed to stringify SupportsCondition.\n\nThis is a bug in Bun's CSS printer. Please file a bug report at https://github.com/oven-sh/bun/issues/new/choose", @@ -80,7 +80,7 @@ pub const ImportDependency = struct { } else null; const media = if (rule.media.media_queries.items.len > 0) media: { - const s = css.to_css.string(allocator, css.MediaList, &rule.media, css.PrinterOptions{}, null) catch bun.Output.panic( + const s = css.to_css.string(allocator, css.MediaList, &rule.media, css.PrinterOptions.default(), null) catch bun.Output.panic( "Unreachable code: failed to stringify MediaList.\n\nThis is a bug in Bun's CSS printer. Please file a bug report at https://github.com/oven-sh/bun/issues/new/choose", .{}, ); diff --git a/src/css/printer.zig b/src/css/printer.zig index 9d7b029e2a..c50adb3bcd 100644 --- a/src/css/printer.zig +++ b/src/css/printer.zig @@ -26,7 +26,7 @@ pub const PrinterOptions = struct { /// An optional project root path, used to generate relative paths for sources used in CSS module hashes. project_root: ?[]const u8 = null, /// Targets to output the CSS for. - targets: Targets = .{}, + targets: Targets, /// Whether to analyze dependencies (i.e. `@import` and `url()`). /// If true, the dependencies are returned as part of the /// [ToCssResult](super::stylesheet::ToCssResult). @@ -39,6 +39,23 @@ pub const PrinterOptions = struct { /// from JavaScript. Useful for polyfills, for example. pseudo_classes: ?PseudoClasses = null, public_path: []const u8 = "", + + pub fn default() PrinterOptions { + return .{ + .targets = Targets{ + .browsers = null, + }, + }; + } + + pub fn defaultWithMinify(minify: bool) PrinterOptions { + return .{ + .targets = Targets{ + .browsers = null, + }, + .minify = minify, + }; + } }; /// A mapping of user action pseudo classes to replace with class names. diff --git a/src/css/properties/custom.zig b/src/css/properties/custom.zig index eaa7ad2f89..cfde458ae0 100644 --- a/src/css/properties/custom.zig +++ b/src/css/properties/custom.zig @@ -1433,6 +1433,10 @@ pub const UnparsedProperty = struct { pub fn deepClone(this: *const @This(), allocator: Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } }; /// A CSS custom property, representing any unknown property. diff --git a/src/css/properties/generate_properties.ts b/src/css/properties/generate_properties.ts index e7f79c1e2e..f55f04d022 100644 --- a/src/css/properties/generate_properties.ts +++ b/src/css/properties/generate_properties.ts @@ -287,7 +287,8 @@ function generatePropertyImpl(property_defs: Record): strin return `.${escapeIdent(name)} => |*v| css.generic.eql(${meta.ty}, v, &rhs.${escapeIdent(name)}),`; }) .join("\n")} - .all, .unparsed => true, + .unparsed => |*u| u.eql(&rhs.unparsed), + .all => true, .custom => |*c| c.eql(&rhs.custom), }; } @@ -1077,6 +1078,7 @@ generateCode({ "margin-right": { ty: "LengthPercentageOrAuto", logical_group: { ty: "margin", category: "physical" }, + eval_branch_quota: 5000, }, "margin-block-start": { ty: "LengthPercentageOrAuto", diff --git a/src/css/properties/properties_generated.zig b/src/css/properties/properties_generated.zig index be76ee77a7..46615f071a 100644 --- a/src/css/properties/properties_generated.zig +++ b/src/css/properties/properties_generated.zig @@ -6951,7 +6951,8 @@ pub const Property = union(PropertyIdTag) { .@"mask-box-image-width" => |*v| css.generic.eql(Rect(BorderImageSideWidth), &v[0], &v[0]) and v[1].eq(rhs.@"mask-box-image-width"[1]), .@"mask-box-image-outset" => |*v| css.generic.eql(Rect(LengthOrNumber), &v[0], &v[0]) and v[1].eq(rhs.@"mask-box-image-outset"[1]), .@"mask-box-image-repeat" => |*v| css.generic.eql(BorderImageRepeat, &v[0], &v[0]) and v[1].eq(rhs.@"mask-box-image-repeat"[1]), - .all, .unparsed => true, + .unparsed => |*u| u.eql(&rhs.unparsed), + .all => true, .custom => |*c| c.eql(&rhs.custom), }; } diff --git a/src/css/properties/properties_impl.zig b/src/css/properties/properties_impl.zig index 6a56e20d0e..871a61cdd3 100644 --- a/src/css/properties/properties_impl.zig +++ b/src/css/properties/properties_impl.zig @@ -20,6 +20,7 @@ pub fn PropertyIdImpl() type { var first = true; const name = this.name(this); const prefix_value = this.prefix().orNone(); + inline for (VendorPrefix.FIELDS) |field| { if (@field(prefix_value, field)) { var prefix: VendorPrefix = .{}; diff --git a/src/css/properties/transform.zig b/src/css/properties/transform.zig index bcc9ebce99..8a1c879798 100644 --- a/src/css/properties/transform.zig +++ b/src/css/properties/transform.zig @@ -77,9 +77,7 @@ pub const TransformList = struct { dest.allocator, scratchbuf, base_writer, - css.PrinterOptions{ - .minify = true, - }, + css.PrinterOptions.defaultWithMinify(true), dest.import_records, ); defer p.deinit(); diff --git a/src/css/rules/rules.zig b/src/css/rules/rules.zig index a66ebd6cd2..951a302c92 100644 --- a/src/css/rules/rules.zig +++ b/src/css/rules/rules.zig @@ -290,8 +290,6 @@ pub fn CssRuleList(comptime AtRule: type) type { // Attempt to merge the new rule with the last rule we added. var merged = false; - const ZACK_REMOVE_THIS = false; - _ = ZACK_REMOVE_THIS; // autofix if (rules.items.len > 0 and rules.items[rules.items.len - 1] == .style) { const last_style_rule = &rules.items[rules.items.len - 1].style; if (mergeStyleRules(AtRule, sty, last_style_rule, context)) { diff --git a/src/css/selectors/parser.zig b/src/css/selectors/parser.zig index c545c826b9..bcd01753cc 100644 --- a/src/css/selectors/parser.zig +++ b/src/css/selectors/parser.zig @@ -927,7 +927,7 @@ pub const PseudoClass = union(enum) { const writer = s.writer(dest.allocator); const W2 = @TypeOf(writer); const scratchbuf = std.ArrayList(u8).init(dest.allocator); - var printer = Printer(W2).new(dest.allocator, scratchbuf, writer, css.PrinterOptions{}, dest.import_records); + var printer = Printer(W2).new(dest.allocator, scratchbuf, writer, css.PrinterOptions.default(), dest.import_records); try serialize.serializePseudoClass(this, W2, &printer, null); return dest.writeStr(s.items); } @@ -1059,10 +1059,13 @@ pub const SelectorParser = struct { } /// Whether the given function name is an alias for the `:is()` function. - fn parseAnyPrefix(this: *const SelectorParser, name: []const u8) ?css.VendorPrefix { - _ = this; // autofix - _ = name; // autofix - return null; + fn parseAnyPrefix(_: *const SelectorParser, name: []const u8) ?css.VendorPrefix { + const Map = comptime bun.ComptimeStringMap(css.VendorPrefix, .{ + .{ "-webkit-any", css.VendorPrefix{ .webkit = true } }, + .{ "-moz-any", css.VendorPrefix{ .moz = true } }, + }); + + return Map.getAnyCase(name); } pub fn parseNonTsPseudoClass( @@ -1287,6 +1290,10 @@ pub const SelectorParser = struct { return .{ .result = pseudo_class }; } + pub fn parseHost(_: *SelectorParser) bool { + return true; + } + pub fn parseNonTsFunctionalPseudoClass( this: *SelectorParser, name: []const u8, @@ -1689,7 +1696,7 @@ pub fn GenericSelector(comptime Impl: type) type { var arraylist = ArrayList(u8){}; const w = arraylist.writer(bun.default_allocator); defer arraylist.deinit(bun.default_allocator); - var printer = css.Printer(@TypeOf(w)).new(bun.default_allocator, std.ArrayList(u8).init(bun.default_allocator), w, .{}, null); + var printer = css.Printer(@TypeOf(w)).new(bun.default_allocator, std.ArrayList(u8).init(bun.default_allocator), w, css.PrinterOptions.default(), null); defer printer.deinit(); css.selector.tocss_servo.toCss_Selector(this.this, @TypeOf(w), &printer) catch |e| return try writer.print("\n", .{@errorName(e)}); try writer.writeAll(arraylist.items); @@ -2544,7 +2551,7 @@ pub const PseudoElement = union(enum) { const writer = s.writer(dest.allocator); const W2 = @TypeOf(writer); const scratchbuf = std.ArrayList(u8).init(dest.allocator); - var printer = Printer(W2).new(dest.allocator, scratchbuf, writer, css.PrinterOptions{}, dest.import_records); + var printer = Printer(W2).new(dest.allocator, scratchbuf, writer, css.PrinterOptions.default(), dest.import_records); try serialize.serializePseudoElement(this, W2, &printer, null); return dest.writeStr(s.items); } @@ -2689,6 +2696,7 @@ pub fn parse_one_simple_selector( const S = SimpleSelectorParseResult(Impl); const start = input.state(); + const token_location = input.currentSourceLocation(); const token = switch (input.nextIncludingWhitespace()) { .result => |v| v.*, .err => { @@ -2700,7 +2708,7 @@ pub fn parse_one_simple_selector( switch (token) { .idhash => |id| { if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) { - return .{ .err = input.newCustomError(SelectorParseErrorKind.intoDefaultParserError(.invalid_state)) }; + return .{ .err = token_location.newCustomError(SelectorParseErrorKind.intoDefaultParserError(.{ .unexpected_selector_after_pseudo_element = .{ .idhash = id } })) }; } const component: GenericComponent(Impl) = .{ .id = .{ .v = id } }; return .{ .result = S{ @@ -2709,18 +2717,20 @@ pub fn parse_one_simple_selector( }, .open_square => { if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) { - return .{ .err = input.newCustomError(SelectorParseErrorKind.intoDefaultParserError(.invalid_state)) }; + return .{ .err = token_location.newCustomError(SelectorParseErrorKind.intoDefaultParserError(.{ .unexpected_selector_after_pseudo_element = .open_square })) }; } const Closure = struct { parser: *SelectorParser, - pub fn parsefn(this: *@This(), input2: *css.Parser) Result(GenericComponent(Impl)) { - return parse_attribute_selector(Impl, this.parser, input2); - } }; var closure = Closure{ .parser = parser, }; - const attr = switch (input.parseNestedBlock(GenericComponent(Impl), &closure, Closure.parsefn)) { + const attr = switch (input.parseNestedBlock(GenericComponent(Impl), &closure, struct { + pub fn parsefn(this: *Closure, input2: *css.Parser) Result(GenericComponent(Impl)) { + return parse_attribute_selector(Impl, this.parser, input2); + } + } + .parsefn)) { .err => |e| return .{ .err = e }, .result => |v| v, }; @@ -2878,7 +2888,7 @@ pub fn parse_one_simple_selector( switch (d) { '.' => { if (state.intersects(SelectorParsingState.AFTER_PSEUDO)) { - return .{ .err = input.newCustomError(SelectorParseErrorKind.intoDefaultParserError(.invalid_state)) }; + return .{ .err = token_location.newCustomError(SelectorParseErrorKind.intoDefaultParserError(.{ .unexpected_selector_after_pseudo_element = .{ .delim = '.' } })) }; } const location = input.currentSourceLocation(); const class = switch ((switch (input.nextIncludingWhitespace()) { @@ -3167,6 +3177,9 @@ pub fn parse_functional_pseudo_class( return .{ .result = .{ .non_ts_pseudo_class = result } }; } +const TreeStructuralPseudoClass = enum { @"first-child", @"last-child", @"only-child", root, empty, scope, host, @"first-of-type", @"last-of-type", @"only-of-type" }; +const TreeStructuralPseudoClassMap = bun.ComptimeEnumMap(TreeStructuralPseudoClass); + pub fn parse_simple_pseudo_class( comptime Impl: type, parser: *SelectorParser, @@ -3179,28 +3192,20 @@ pub fn parse_simple_pseudo_class( } if (state.allowsTreeStructuralPseudoClasses()) { - // css.todo_stuff.match_ignore_ascii_case - if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "first-child")) { - return .{ .result = .{ .nth = NthSelectorData.first(false) } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "last-child")) { - return .{ .result = .{ .nth = NthSelectorData.last(false) } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "only-child")) { - return .{ .result = .{ .nth = NthSelectorData.only(false) } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "root")) { - return .{ .result = .root }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "empty")) { - return .{ .result = .empty }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "scope")) { - return .{ .result = .scope }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "host")) { - return .{ .result = .{ .host = null } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "first-of-type")) { - return .{ .result = .{ .nth = NthSelectorData.first(true) } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "last-of-type")) { - return .{ .result = .{ .nth = NthSelectorData.last(true) } }; - } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(name, "only-of-type")) { - return .{ .result = .{ .nth = NthSelectorData.only(true) } }; - } else {} + if (TreeStructuralPseudoClassMap.getAnyCase(name)) |pseudo_class| { + switch (pseudo_class) { + .@"first-child" => return .{ .result = .{ .nth = NthSelectorData.first(false) } }, + .@"last-child" => return .{ .result = .{ .nth = NthSelectorData.last(false) } }, + .@"only-child" => return .{ .result = .{ .nth = NthSelectorData.only(false) } }, + .root => return .{ .result = .root }, + .empty => return .{ .result = .empty }, + .scope => return .{ .result = .scope }, + .host => if (parser.parseHost()) return .{ .result = .{ .host = null } }, + .@"first-of-type" => return .{ .result = .{ .nth = NthSelectorData.first(true) } }, + .@"last-of-type" => return .{ .result = .{ .nth = NthSelectorData.last(true) } }, + .@"only-of-type" => return .{ .result = .{ .nth = NthSelectorData.only(true) } }, + } + } } // The view-transition pseudo elements accept the :only-child pseudo class. diff --git a/src/css/selectors/selector.zig b/src/css/selectors/selector.zig index 84364080ae..6388630930 100644 --- a/src/css/selectors/selector.zig +++ b/src/css/selectors/selector.zig @@ -708,7 +708,7 @@ pub const serialize = struct { const writer = id.writer(); css.serializer.serializeIdentifier(v.value, writer) catch return dest.addFmtError(); - const s = try css.to_css.string(dest.allocator, CSSString, &v.value, css.PrinterOptions{}, dest.import_records); + const s = try css.to_css.string(dest.allocator, CSSString, &v.value, css.PrinterOptions.default(), dest.import_records); if (id.items.len > 0 and id.items.len < s.len) { try dest.writeStr(id.items); @@ -748,7 +748,7 @@ pub const serialize = struct { try dest.writeStr(":not("); }, .any => |v| { - const vp = dest.vendor_prefix.bitwiseOr(v.vendor_prefix); + const vp = dest.vendor_prefix._or(v.vendor_prefix); if (vp.intersects(css.VendorPrefix{ .webkit = true, .moz = true })) { try dest.writeChar(':'); try vp.toCss(W, dest); @@ -1039,6 +1039,7 @@ pub const serialize = struct { // If the printer has a vendor prefix override, use that. const vp = if (!d.vendor_prefix.isEmpty()) d.vendor_prefix.bitwiseAnd(prefix).orNone() else prefix; try vp.toCss(W, d); + debug("VENDOR PREFIX {d} OVERRIDE {d}", .{ vp.asBits(), d.vendor_prefix.asBits() }); return vp; } diff --git a/src/css/targets.zig b/src/css/targets.zig index ab720f8304..29b63d02ed 100644 --- a/src/css/targets.zig +++ b/src/css/targets.zig @@ -17,6 +17,27 @@ pub const Targets = struct { /// 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, + }; + } + + pub fn forBundlerTarget(target: bun.bundler.options.Target) Targets { + 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.contains(css.VendorPrefix{ .none = true }) and !this.exclude.contains(css.targets.Features{ .vendor_prefixes = true })) { if (this.include.contains(css.targets.Features{ .vendor_prefixes = true })) { @@ -125,8 +146,155 @@ pub const Features = packed struct(u32) { }; pub fn BrowsersImpl(comptime T: type) type { - _ = T; // autofix - return struct {}; + return struct { + pub const browserDefault = convertFromString(&.{ + "es2020", // support import.meta.url + "edge88", + "firefox78", + "chrome87", + "safari14", + }) catch |e| std.debug.panic("WOOPSIE: {s}\n", .{@errorName(e)}); + + // pub const bundlerDefault = T{ + // .chrome = 80 << 16, + // .edge = 80 << 16, + // .firefox = 78 << 16, + // .safari = 14 << 16, + // .opera = 67 << 16, + // }; + + pub fn convertFromString(esbuild_target: []const []const u8) anyerror!T { + var browsers: T = .{}; + + 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; + } + }; } pub fn FeaturesImpl(comptime T: type) type { diff --git a/src/css/values/color_js.zig b/src/css/values/color_js.zig index 052d92ed70..d1dd0f6abc 100644 --- a/src/css/values/color_js.zig +++ b/src/css/values/color_js.zig @@ -426,7 +426,7 @@ pub fn jsFunctionColor(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFram allocator, std.ArrayList(u8).init(allocator), writer, - .{}, + css.PrinterOptions.default(), null, ); diff --git a/test/bundler/esbuild/css.test.ts b/test/bundler/esbuild/css.test.ts index 2666a81c42..db04947753 100644 --- a/test/bundler/esbuild/css.test.ts +++ b/test/bundler/esbuild/css.test.ts @@ -41,6 +41,7 @@ body { itBundled("css/CSSNesting", { experimentalCss: true, + target: "bun", files: { "/entry.css": /* css */ ` body { diff --git a/test/js/bun/css/css.test.ts b/test/js/bun/css/css.test.ts index da634ba716..2e4405893a 100644 --- a/test/js/bun/css/css.test.ts +++ b/test/js/bun/css/css.test.ts @@ -8,7 +8,37 @@ import path from "path"; import { attrTest, cssTest, indoc, minify_test, minifyTest, prefix_test } from "./util"; describe("css tests", () => { - test("edge case", () => { + describe("pseudo-class edge case", () => { + cssTest( + indoc`[type="file"]::file-selector-button:-moz-any() { + --pico-background-color: var(--pico-primary-hover-background); + --pico-border-color: var(--pico-primary-hover-border); + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 #0000); + --pico-color: var(--pico-primary-inverse); + }`, + indoc`[type="file"]::-webkit-file-upload-button:-webkit-any() { + --pico-background-color: var(--pico-primary-hover-background); + --pico-border-color: var(--pico-primary-hover-border); + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 #0000); + --pico-color: var(--pico-primary-inverse); + } + [type="file"]::file-selector-button:is() { + --pico-background-color: var(--pico-primary-hover-background); + --pico-border-color: var(--pico-primary-hover-border); + --pico-box-shadow: var(--pico-button-hover-box-shadow, 0 0 0 #0000); + --pico-color: var(--pico-primary-inverse); + }`, + { + chrome: 80 << 16, + edge: 80 << 16, + firefox: 78 << 16, + safari: 14 << 16, + opera: 67 << 16, + }, + ); + }); + + test("calc edge case", () => { minifyTest( // Problem: the value is being printed as Infinity in our restrict_prec thing but the internal thing actually wants it as 3.40282e38px `.rounded-full { diff --git a/test/js/bun/css/doesnt_crash.test.ts b/test/js/bun/css/doesnt_crash.test.ts index 8cca195bb6..c7cdad5947 100644 --- a/test/js/bun/css/doesnt_crash.test.ts +++ b/test/js/bun/css/doesnt_crash.test.ts @@ -19,7 +19,14 @@ describe("doesnt_crash", async () => { absolute = absolute.replaceAll("\\", "/"); const file = path.basename(absolute); - for (let minify of [false, true]) { + const configs: { target: string; minify: boolean }[] = [ + { target: "bun", minify: false }, + { target: "bun", minify: true }, + { target: "browser", minify: false }, + { target: "browser", minify: true }, + ]; + + for (const { target, minify } of configs) { test(`${file} - ${minify ? "minify" : "not minify"}`, async () => { const timeLog = `Transpiled ${file} - ${minify ? "minify" : "not minify"}`; console.time(timeLog); @@ -27,6 +34,7 @@ describe("doesnt_crash", async () => { entrypoints: [absolute], experimentalCss: true, minify: minify, + target, }); console.timeEnd(timeLog); @@ -42,9 +50,11 @@ describe("doesnt_crash", async () => { { const timeLog = `Re-transpiled ${file} - ${minify ? "minify" : "not minify"}`; console.time(timeLog); + console.log(" Transpiled file path:", outfile1); const { logs, outputs } = await Bun.build({ entrypoints: [outfile1], experimentalCss: true, + target, minify: minify, }); diff --git a/test/js/bun/css/util.ts b/test/js/bun/css/util.ts index 09b4701739..0ec5da219c 100644 --- a/test/js/bun/css/util.ts +++ b/test/js/bun/css/util.ts @@ -30,12 +30,14 @@ export function prefix_test(source: string, expected: string, targets: Browsers) }); } -export function css_test(source: string, expected: string) { - return cssTest(source, expected); +export function css_test(source: string, expected: string, browsers?: Browsers) { + return cssTest(source, expected, browsers); } -export function cssTest(source: string, expected: string) { +export function cssTest(source: string, expected: string, browsers?: Browsers) { test(source, () => { - expect(testWithOptions(source, expected)).toEqualIgnoringWhitespace(expected); + const output = testWithOptions(source, expected, browsers); + console.log("Output", output); + expect(output).toEqualIgnoringWhitespace(expected); }); }