Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/__tests__/compiler/compiler.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { debug } from "debug";
import { compile } from "react-native-css/compiler";

test("hello world", () => {
Expand Down Expand Up @@ -26,6 +27,39 @@ test("hello world", () => {
});
});

test("compiles bytes exactly as it compiles their text", () => {
const css = `:root { font-size: 16px; } .my-class { padding: 1rem; }`;
const fromText = compile(css).stylesheet();

expect(compile(new TextEncoder().encode(css)).stylesheet()).toStrictEqual(
fromText,
);
expect(compile(Buffer.from(css)).stylesheet()).toStrictEqual(fromText);
});

test("the debug log is handed the decoded text, not the byte values", () => {
const css = `:root { font-size: 16px; }`;
const written: string[] = [];
const emit = debug.log;

// `enable` flips the instance the compiler module already built, so no reload is needed.
debug.enable("react-native-css:compiler");
debug.log = (...args: unknown[]): void => {
written.push(args.map(String).join(" "));
};

try {
compile(new TextEncoder().encode(css));
} finally {
debug.log = emit;
debug.disable();
}

const joined = written.join("\n");
expect(joined).toContain("font-size: 16px");
expect(joined).not.toContain("58,114");
});

test("reads global CSS variables", () => {
const compiled = compile(
`@layer theme {
Expand Down
48 changes: 48 additions & 0 deletions src/__tests__/compiler/public-surface-node-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import path from "node:path";

import ts from "typescript";

// `native/conditions/index.d.ts` imports `StyleRule` from `react-native-css/compiler`, so a Node
// global in that surface is a TS2591 inside `node_modules` for any consumer without `@types/node`.
const COMPILER_ENTRY = path.join(__dirname, "../../compiler/index.ts");

const OPTIONS: ts.CompilerOptions = {
lib: ["lib.es2024.d.ts"],
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
noEmit: true,
skipLibCheck: true,
strict: true,
target: ts.ScriptTarget.ES2022,
types: [],
};

function checkCompilerSurface(
options: ts.CompilerOptions,
): readonly ts.Diagnostic[] {
return ts.getPreEmitDiagnostics(ts.createProgram([COMPILER_ENTRY], options));
}

function namesMissing(
diagnostics: readonly ts.Diagnostic[],
name: string,
): readonly string[] {
return diagnostics
.map((diagnostic) =>
ts.flattenDiagnosticMessageText(diagnostic.messageText, " "),
)
.filter((message) => message.includes(`Cannot find name '${name}'`));
}

test("no Node global reaches the compiler's public surface", () => {
expect(namesMissing(checkCompilerSurface(OPTIONS), "Buffer")).toStrictEqual(
[],
);
});

test("the probe reports a missing global when one is genuinely absent", () => {
// Without this, the assertion above is satisfied by a program that resolved nothing.
const onES5 = checkCompilerSurface({ ...OPTIONS, lib: ["lib.es5.d.ts"] });

expect(onES5.length).toBeGreaterThan(0);
});
14 changes: 10 additions & 4 deletions src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ const defaultLogger = debug("react-native-css:compiler");
* @param options - Compiler options
* @returns A `ReactNativeCssStyleSheet` that can be passed to `StyleSheet.register` or used with a custom runtime
*/
export function compile(code: Buffer | string, options: CompilerOptions = {}) {
export function compile(
code: Uint8Array | string,
options: CompilerOptions = {},
) {
const { logger = defaultLogger } = options;

const isLoggerEnabled =
Expand All @@ -60,9 +63,13 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) {

logger(`Features ${JSON.stringify(features)}`);

// Decoded once: a `Uint8Array` that is not a `Buffer` stringifies to its bytes, not its text.
const source =
typeof code === "string" ? code : new TextDecoder().decode(code);

if (process.env.NODE_ENV !== "production") {
if (defaultLogger.enabled) {
defaultLogger(code.toString());
defaultLogger(source);
}
}

Expand All @@ -89,8 +96,7 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) {
// :root { font-size: Npx } in the CSS to allow CSS-based configuration.
let effectiveRem: number | false = options.inlineRem ?? undefined!;
if (effectiveRem === undefined) {
const css = typeof code === "string" ? code : code.toString();
const match = css.match(/:root\s*\{[^}]*font-size:\s*([\d.]+)px/);
const match = source.match(/:root\s*\{[^}]*font-size:\s*([\d.]+)px/);
effectiveRem = match?.[1] ? parseFloat(match[1]) : 14;
options.inlineRem = effectiveRem;
}
Expand Down