mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
407 lines
15 KiB
Zig
407 lines
15 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const bun = @import("root").bun;
|
|
const logger = bun.logger;
|
|
const Log = logger.Log;
|
|
|
|
pub const css = @import("./css_parser.zig");
|
|
pub const css_values = @import("./values/values.zig");
|
|
const DashedIdent = css_values.ident.DashedIdent;
|
|
const Ident = css_values.ident.Ident;
|
|
pub const Error = css.Error;
|
|
const PrintErr = css.PrintErr;
|
|
|
|
const ArrayList = std.ArrayListUnmanaged;
|
|
|
|
pub const CssModule = struct {
|
|
config: *const Config,
|
|
sources: *const ArrayList([]const u8),
|
|
hashes: ArrayList([]const u8),
|
|
exports_by_source_index: ArrayList(CssModuleExports),
|
|
references: *CssModuleReferences,
|
|
|
|
pub fn new(
|
|
allocator: Allocator,
|
|
config: *const Config,
|
|
sources: *const ArrayList([]const u8),
|
|
project_root: ?[]const u8,
|
|
references: *CssModuleReferences,
|
|
) CssModule {
|
|
const hashes = hashes: {
|
|
var hashes = ArrayList([]const u8).initCapacity(allocator, sources.items.len) catch bun.outOfMemory();
|
|
for (sources.items) |path| {
|
|
var alloced = false;
|
|
const source = source: {
|
|
if (project_root) |root| {
|
|
if (bun.path.Platform.auto.isAbsolute(root)) {
|
|
alloced = true;
|
|
// TODO: should we use this allocator or something else
|
|
break :source allocator.dupe(u8, bun.path.relative(root, path)) catch bun.outOfMemory();
|
|
}
|
|
}
|
|
break :source path;
|
|
};
|
|
defer if (alloced) allocator.free(source);
|
|
hashes.appendAssumeCapacity(hash(
|
|
allocator,
|
|
"{s}",
|
|
.{source},
|
|
config.pattern.segments.at(0).* == .hash,
|
|
));
|
|
}
|
|
break :hashes hashes;
|
|
};
|
|
const exports_by_source_index = exports_by_source_index: {
|
|
var exports_by_source_index = ArrayList(CssModuleExports).initCapacity(allocator, sources.items.len) catch bun.outOfMemory();
|
|
exports_by_source_index.appendNTimesAssumeCapacity(CssModuleExports{}, sources.items.len);
|
|
break :exports_by_source_index exports_by_source_index;
|
|
};
|
|
return CssModule{
|
|
.config = config,
|
|
.sources = sources,
|
|
.references = references,
|
|
.hashes = hashes,
|
|
.exports_by_source_index = exports_by_source_index,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(this: *CssModule) void {
|
|
_ = this; // autofix
|
|
// TODO: deinit
|
|
}
|
|
|
|
pub fn referenceDashed(
|
|
this: *CssModule,
|
|
name: []const u8,
|
|
from: *const ?css.css_properties.css_modules.Specifier,
|
|
source_index: u32,
|
|
) ?[]const u8 {
|
|
_ = this; // autofix
|
|
_ = name; // autofix
|
|
_ = from; // autofix
|
|
_ = source_index; // autofix
|
|
@panic(css.todo_stuff.depth);
|
|
}
|
|
|
|
pub fn handleComposes(
|
|
this: *CssModule,
|
|
allocator: Allocator,
|
|
selectors: *const css.selector.parser.SelectorList,
|
|
composes: *const css.css_properties.css_modules.Composes,
|
|
source_index: u32,
|
|
) css.Maybe(void, css.PrinterErrorKind) {
|
|
for (selectors.v.slice()) |*sel| {
|
|
if (sel.len() == 1) {
|
|
const component: *const css.selector.parser.Component = &sel.components.items[0];
|
|
switch (component.*) {
|
|
.class => |id| {
|
|
for (composes.names.slice()) |name| {
|
|
const reference: CssModuleReference = if (composes.from) |*specifier|
|
|
switch (specifier.*) {
|
|
.source_index => |dep_source_index| {
|
|
if (this.exports_by_source_index.items[dep_source_index].get(name.v)) |entry| {
|
|
const entry_name = entry.name;
|
|
const composes2 = &entry.composes;
|
|
const @"export" = this.exports_by_source_index.items[source_index].getPtr(id.v).?;
|
|
|
|
@"export".composes.append(allocator, .{ .local = .{ .name = entry_name } }) catch bun.outOfMemory();
|
|
@"export".composes.appendSlice(allocator, composes2.items) catch bun.outOfMemory();
|
|
}
|
|
continue;
|
|
},
|
|
.global => CssModuleReference{ .global = .{ .name = name.v } },
|
|
.file => |file| CssModuleReference{
|
|
.dependency = .{
|
|
.name = name.v,
|
|
.specifier = file,
|
|
},
|
|
},
|
|
}
|
|
else
|
|
CssModuleReference{
|
|
.local = .{
|
|
.name = this.config.pattern.writeToString(
|
|
allocator,
|
|
ArrayList(u8){},
|
|
this.hashes.items[source_index],
|
|
this.sources.items[source_index],
|
|
name.v,
|
|
),
|
|
},
|
|
};
|
|
|
|
const export_value = this.exports_by_source_index.items[source_index].getPtr(id.v) orelse unreachable;
|
|
export_value.composes.append(allocator, reference) catch bun.outOfMemory();
|
|
|
|
const contains_reference = brk: {
|
|
for (export_value.composes.items) |*compose_| {
|
|
const compose: *const CssModuleReference = compose_;
|
|
if (compose.eql(&reference)) {
|
|
break :brk true;
|
|
}
|
|
}
|
|
break :brk false;
|
|
};
|
|
if (!contains_reference) {
|
|
export_value.composes.append(allocator, reference) catch bun.outOfMemory();
|
|
}
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
// The composes property can only be used within a simple class selector.
|
|
return .{ .err = css.PrinterErrorKind.invalid_composes_selector };
|
|
}
|
|
|
|
return .{ .result = {} };
|
|
}
|
|
|
|
pub fn addDashed(this: *CssModule, allocator: Allocator, local: []const u8, source_index: u32) void {
|
|
const gop = this.exports_by_source_index.items[source_index].getOrPut(allocator, local) catch bun.outOfMemory();
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = CssModuleExport{
|
|
// todo_stuff.depth
|
|
.name = this.config.pattern.writeToStringWithPrefix(
|
|
allocator,
|
|
"--",
|
|
this.hashes.items[source_index],
|
|
this.sources.items[source_index],
|
|
local[2..],
|
|
),
|
|
.composes = .{},
|
|
.is_referenced = false,
|
|
};
|
|
}
|
|
}
|
|
|
|
pub fn addLocal(this: *CssModule, allocator: Allocator, exported: []const u8, local: []const u8, source_index: u32) void {
|
|
const gop = this.exports_by_source_index.items[source_index].getOrPut(allocator, exported) catch bun.outOfMemory();
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = CssModuleExport{
|
|
// todo_stuff.depth
|
|
.name = this.config.pattern.writeToString(
|
|
allocator,
|
|
.{},
|
|
this.hashes.items[source_index],
|
|
this.sources.items[source_index],
|
|
local,
|
|
),
|
|
.composes = .{},
|
|
.is_referenced = false,
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Configuration for CSS modules.
|
|
pub const Config = struct {
|
|
/// The name pattern to use when renaming class names and other identifiers.
|
|
/// Default is `[hash]_[local]`.
|
|
pattern: Pattern,
|
|
|
|
/// Whether to rename dashed identifiers, e.g. custom properties.
|
|
dashed_idents: bool,
|
|
|
|
/// Whether to scope animation names.
|
|
/// Default is `true`.
|
|
animation: bool,
|
|
|
|
/// Whether to scope grid names.
|
|
/// Default is `true`.
|
|
grid: bool,
|
|
|
|
/// Whether to scope custom identifiers
|
|
/// Default is `true`.
|
|
custom_idents: bool,
|
|
};
|
|
|
|
/// A CSS modules class name pattern.
|
|
pub const Pattern = struct {
|
|
/// The list of segments in the pattern.
|
|
segments: css.SmallList(Segment, 2),
|
|
|
|
/// Write the substituted pattern to a destination.
|
|
pub fn write(
|
|
this: *const Pattern,
|
|
hash_: []const u8,
|
|
path: []const u8,
|
|
local: []const u8,
|
|
closure: anytype,
|
|
comptime writefn: *const fn (@TypeOf(closure), []const u8, replace_dots: bool) void,
|
|
) void {
|
|
for (this.segments.slice()) |*segment| {
|
|
switch (segment.*) {
|
|
.literal => |s| {
|
|
writefn(closure, s, false);
|
|
},
|
|
.name => {
|
|
const stem = std.fs.path.stem(path);
|
|
if (std.mem.indexOf(u8, stem, ".")) |_| {
|
|
writefn(closure, stem, true);
|
|
} else {
|
|
writefn(closure, stem, false);
|
|
}
|
|
},
|
|
.local => {
|
|
writefn(closure, local, false);
|
|
},
|
|
.hash => {
|
|
writefn(closure, hash_, false);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn writeToStringWithPrefix(
|
|
this: *const Pattern,
|
|
allocator: Allocator,
|
|
comptime prefix: []const u8,
|
|
hash_: []const u8,
|
|
path: []const u8,
|
|
local: []const u8,
|
|
) []const u8 {
|
|
const Closure = struct { res: ArrayList(u8), allocator: Allocator };
|
|
var closure = Closure{ .res = .{}, .allocator = allocator };
|
|
this.write(
|
|
hash_,
|
|
path,
|
|
local,
|
|
&closure,
|
|
struct {
|
|
pub fn writefn(self: *Closure, slice: []const u8, replace_dots: bool) void {
|
|
self.res.appendSlice(self.allocator, prefix) catch bun.outOfMemory();
|
|
if (replace_dots) {
|
|
const start = self.res.items.len;
|
|
self.res.appendSlice(self.allocator, slice) catch bun.outOfMemory();
|
|
const end = self.res.items.len;
|
|
for (self.res.items[start..end]) |*c| {
|
|
if (c.* == '.') {
|
|
c.* = '-';
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
self.res.appendSlice(self.allocator, slice) catch bun.outOfMemory();
|
|
}
|
|
}.writefn,
|
|
);
|
|
return closure.res.items;
|
|
}
|
|
|
|
pub fn writeToString(
|
|
this: *const Pattern,
|
|
allocator: Allocator,
|
|
res_: ArrayList(u8),
|
|
hash_: []const u8,
|
|
path: []const u8,
|
|
local: []const u8,
|
|
) []const u8 {
|
|
var res = res_;
|
|
const Closure = struct { res: *ArrayList(u8), allocator: Allocator };
|
|
var closure = Closure{ .res = &res, .allocator = allocator };
|
|
this.write(
|
|
hash_,
|
|
path,
|
|
local,
|
|
&closure,
|
|
struct {
|
|
pub fn writefn(self: *Closure, slice: []const u8, replace_dots: bool) void {
|
|
if (replace_dots) {
|
|
const start = self.res.items.len;
|
|
self.res.appendSlice(self.allocator, slice) catch bun.outOfMemory();
|
|
const end = self.res.items.len;
|
|
for (self.res.items[start..end]) |*c| {
|
|
if (c.* == '.') {
|
|
c.* = '-';
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
self.res.appendSlice(self.allocator, slice) catch bun.outOfMemory();
|
|
return;
|
|
}
|
|
}.writefn,
|
|
);
|
|
|
|
return res.items;
|
|
}
|
|
};
|
|
|
|
/// A segment in a CSS modules class name pattern.
|
|
///
|
|
/// See [Pattern](Pattern).
|
|
pub const Segment = union(enum) {
|
|
/// A literal string segment.
|
|
literal: []const u8,
|
|
|
|
/// The base file name.
|
|
name,
|
|
|
|
/// The original class name.
|
|
local,
|
|
|
|
/// A hash of the file name.
|
|
hash,
|
|
};
|
|
|
|
/// A map of exported names to values.
|
|
pub const CssModuleExports = std.StringArrayHashMapUnmanaged(CssModuleExport);
|
|
|
|
/// A map of placeholders to references.
|
|
pub const CssModuleReferences = std.StringArrayHashMapUnmanaged(CssModuleReference);
|
|
|
|
/// An exported value from a CSS module.
|
|
pub const CssModuleExport = struct {
|
|
/// The local (compiled) name for this export.
|
|
name: []const u8,
|
|
/// Other names that are composed by this export.
|
|
composes: ArrayList(CssModuleReference),
|
|
/// Whether the export is referenced in this file.
|
|
is_referenced: bool,
|
|
};
|
|
|
|
/// A referenced name within a CSS module, e.g. via the `composes` property.
|
|
///
|
|
/// See [CssModuleExport](CssModuleExport).
|
|
pub const CssModuleReference = union(enum) {
|
|
/// A local reference.
|
|
local: struct {
|
|
/// The local (compiled) name for the reference.
|
|
name: []const u8,
|
|
},
|
|
/// A global reference.
|
|
global: struct {
|
|
/// The referenced global name.
|
|
name: []const u8,
|
|
},
|
|
/// A reference to an export in a different file.
|
|
dependency: struct {
|
|
/// The name to reference within the dependency.
|
|
name: []const u8,
|
|
/// The dependency specifier for the referenced file.
|
|
specifier: []const u8,
|
|
},
|
|
|
|
pub fn eql(this: *const @This(), other: *const @This()) bool {
|
|
if (@intFromEnum(this.*) != @intFromEnum(other.*)) return false;
|
|
|
|
return switch (this.*) {
|
|
.local => |v| bun.strings.eql(v.name, other.local.name),
|
|
.global => |v| bun.strings.eql(v.name, other.global.name),
|
|
.dependency => |v| bun.strings.eql(v.name, other.dependency.name) and bun.strings.eql(v.specifier, other.dependency.specifier),
|
|
};
|
|
}
|
|
};
|
|
|
|
// TODO: replace with bun's hash
|
|
pub fn hash(allocator: Allocator, comptime fmt: []const u8, args: anytype, at_start: bool) []const u8 {
|
|
_ = fmt; // autofix
|
|
_ = args; // autofix
|
|
_ = allocator; // autofix
|
|
_ = at_start; // autofix
|
|
// @compileError(css.todo_stuff.depth);
|
|
@panic(css.todo_stuff.depth);
|
|
}
|