Skip to content

Commit

Permalink
feat(meta-css): add CSS scoping option, internal refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Mar 27, 2024
1 parent 1d8c65d commit d7286c5
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 45 deletions.
4 changes: 4 additions & 0 deletions packages/meta-css/src/api.ts
Expand Up @@ -148,6 +148,10 @@ export const ARG_NO_HEADER = {
noHeader: flag({ desc: "Don't emit generated header comment" }),
};

export const ARG_SCOPE = {
scope: string({ desc: "Suffix for CSS class names" }),
};

export const ARG_WATCH = {
watch: flag({
alias: "w",
Expand Down
42 changes: 20 additions & 22 deletions packages/meta-css/src/convert.ts
Expand Up @@ -11,7 +11,7 @@ import {
QUOTED_FNS,
at_media,
css,
type Format,
type CSSOpts,
} from "@thi.ng/hiccup-css";
import { type ILogger } from "@thi.ng/logger";
import { sync } from "@thi.ng/rstream";
Expand All @@ -28,6 +28,7 @@ import {
ARG_NO_HEADER,
ARG_OUTPUT,
ARG_PRETTY,
ARG_SCOPE,
ARG_SPECS,
ARG_WATCH,
type AppCtx,
Expand All @@ -44,16 +45,17 @@ import {
type State = "sel" | "class" | "nest";

export interface ConvertOpts extends CommonOpts {
out?: string;
specs: string;
include?: string[];
bundle: boolean;
eval?: string;
force?: string[];
bundle: boolean;
pretty: boolean;
include?: string[];
noDecls: boolean;
noHeader: boolean;
noWrite: boolean;
out?: string;
pretty: boolean;
scope?: string;
specs: string;
watch: boolean;
}

Expand All @@ -71,25 +73,26 @@ interface ProcessCtx {
}

export interface ProcessOpts {
css: Partial<CSSOpts>;
logger: ILogger;
format: Format;
specs: CompiledSpecs;
plainRules: IObjectOf<Set<string>>;
mediaQueryIDs: Set<string>;
mediaQueryRules: IObjectOf<IObjectOf<Set<string>>>;
plainRules: IObjectOf<Set<string>>;
specs: CompiledSpecs;
}

export const CONVERT: Command<ConvertOpts, CommonOpts, AppCtx<ConvertOpts>> = {
desc: "Transpile (and optionally bundle) meta stylesheets to CSS",
opts: {
...ARG_BUNDLE,
...ARG_EVAL,
...ARG_NO_DECLS,
...ARG_FORCE_INCLUDE,
...ARG_INCLUDE,
...ARG_NO_DECLS,
...ARG_NO_HEADER,
...ARG_OUTPUT,
...ARG_PRETTY,
...ARG_SCOPE,
...ARG_SPECS,
...ARG_WATCH,
noWrite: flag({ desc: "Don't write files, use stdout only" }),
Expand Down Expand Up @@ -261,7 +264,7 @@ const watchBundleInputs = async (
export const processInputs = (
{
logger,
opts: { include, noDecls, noHeader, pretty },
opts: { include, noDecls, noHeader, pretty, scope },
}: Pick<AppCtx<Omit<ConvertOpts, "noWrite">>, "logger" | "opts">,
specs: CompiledSpecs,
forceRules: ReturnType<typeof processForceIncludes>,
Expand All @@ -271,7 +274,7 @@ export const processInputs = (
const procOpts: ProcessOpts = {
logger,
specs,
format: pretty ? PRETTY : COMPACT,
css: { format: pretty ? PRETTY : COMPACT, fns: QUOTED_FNS, scope },
mediaQueryIDs: new Set(Object.keys(specs.media)),
mediaQueryRules: { ...forceRules.mediaQueryRules },
plainRules: { ...forceRules.plainRules },
Expand All @@ -281,9 +284,7 @@ export const processInputs = (
: [];
if (!noHeader) bundle.push(generateHeader(specs));
if (!noDecls && specs.decls.length) {
bundle.push(
css(specs.decls, { format: procOpts.format, fns: QUOTED_FNS })
);
bundle.push(css(specs.decls, procOpts.css));
}
inputs.forEach((input) => processSpec(input, procOpts));
processPlainRules(bundle, procOpts);
Expand All @@ -293,27 +294,24 @@ export const processInputs = (

export const processMediaQueries = (
result: string[],
{ logger, specs, format, mediaQueryRules }: ProcessOpts
{ css: opts, logger, mediaQueryRules, specs }: ProcessOpts
) => {
for (let queryID in mediaQueryRules) {
const rules = buildDecls(mediaQueryRules[queryID], specs);
logger.debug("mediaquery rules", queryID, rules);
result.push(
css(at_media(mergeMediaQueries(specs.media, queryID), rules), {
format,
fns: QUOTED_FNS,
})
css(at_media(mergeMediaQueries(specs.media, queryID), rules), opts)
);
}
};

export const processPlainRules = (
bundle: string[],
{ logger, specs, format, plainRules }: ProcessOpts
{ css: opts, logger, plainRules, specs }: ProcessOpts
) => {
const rules = buildDecls(plainRules, specs);
logger.debug("plain rules", rules);
bundle.push(css(rules, { format, fns: QUOTED_FNS }));
bundle.push(css(rules, opts));
};

export const processForceIncludes = (
Expand Down
3 changes: 3 additions & 0 deletions packages/meta-css/src/develop.ts
Expand Up @@ -9,6 +9,7 @@ import {
ARG_NO_HEADER,
ARG_PREC,
ARG_PRETTY,
ARG_SCOPE,
ARG_WATCH,
type AppCtx,
type CommonOpts,
Expand Down Expand Up @@ -36,6 +37,7 @@ export const DEVELOP: Command<DevelopOpts, CommonOpts, AppCtx<DevelopOpts>> = {
...ARG_NO_HEADER,
...ARG_PREC,
...ARG_PRETTY,
...ARG_SCOPE,
...ARG_WATCH,
outCss: string({
desc: "Output file for CSS bundle",
Expand Down Expand Up @@ -69,6 +71,7 @@ export const DEVELOP: Command<DevelopOpts, CommonOpts, AppCtx<DevelopOpts>> = {
...opts,
noWrite: false,
out: opts.outCss,
scope: opts.scope,
specs: opts.outSpecs,
},
};
Expand Down
11 changes: 4 additions & 7 deletions packages/meta-css/src/doc.ts
Expand Up @@ -20,8 +20,8 @@ import { maybeWriteText } from "./utils.js";
interface DocOpts extends CommonOpts {
out?: string;
level: number;
title?: string;
// noDecls: boolean;
title?: string;
}

export const DOC: Command<DocOpts, CommonOpts, AppCtx<DocOpts>> = {
Expand All @@ -32,8 +32,7 @@ export const DOC: Command<DocOpts, CommonOpts, AppCtx<DocOpts>> = {
level: int({ alias: "l", desc: "Initial heading level", default: 1 }),
title: string({
alias: "t",
desc: "Main title, set to 'none' to disable",
default: "meta",
desc: "Custom main title, set to NONE to disable",
}),
},
inputs: 1,
Expand Down Expand Up @@ -73,10 +72,8 @@ export const generateDocs = (
"",
];

if (title !== "none") {
doc.unshift(
`${__hd(level)} ${title === "meta" ? specs.info.name : title}\n`
);
if (title !== "NONE") {
doc.unshift(`${__hd(level)} ${title ?? specs.info.name}\n`);
}
return doc;
};
Expand Down
51 changes: 35 additions & 16 deletions packages/meta-css/src/export.ts
@@ -1,7 +1,14 @@
// thing:no-export
import type { Command } from "@thi.ng/args";
import { readJSON, readText } from "@thi.ng/file-io";
import { COMPACT, PRETTY, QUOTED_FNS, at_media, css } from "@thi.ng/hiccup-css";
import {
COMPACT,
PRETTY,
QUOTED_FNS,
at_media,
css,
type CSSOpts,
} from "@thi.ng/hiccup-css";
import type { ILogger } from "@thi.ng/logger";
import { resolve } from "node:path";
import {
Expand All @@ -12,20 +19,22 @@ import {
ARG_ONLY_DECLS,
ARG_OUTPUT,
ARG_PRETTY,
ARG_SCOPE,
type AppCtx,
type CommonOpts,
type CompiledSpecs,
} from "./api.js";
import { generateHeader, maybeWriteText, withoutInternals } from "./utils.js";

interface ExportOpts extends CommonOpts {
out?: string;
pretty: boolean;
noDecls: boolean;
onlyDecls: boolean;
noHeader: boolean;
include?: string[];
media?: string[];
noDecls: boolean;
noHeader: boolean;
onlyDecls: boolean;
out?: string;
pretty: boolean;
scope?: string;
}

export const EXPORT: Command<ExportOpts, CommonOpts, AppCtx<ExportOpts>> = {
Expand All @@ -38,29 +47,39 @@ export const EXPORT: Command<ExportOpts, CommonOpts, AppCtx<ExportOpts>> = {
...ARG_PRETTY,
...ARG_NO_HEADER,
...ARG_MEDIA_QUERIES,
...ARG_SCOPE,
},
inputs: 1,
fn: async (ctx) => {
const {
logger,
opts: { include, media, noDecls, noHeader, onlyDecls, pretty, out },
opts: {
include,
media,
noDecls,
noHeader,
onlyDecls,
out,
pretty,
scope,
},
inputs,
} = ctx;
const cssOpts: Partial<CSSOpts> = {
format: pretty ? PRETTY : COMPACT,
fns: QUOTED_FNS,
scope,
};
const specs = readJSON<CompiledSpecs>(resolve(inputs[0]), logger);
const bundle: string[] = include
? include.map((x) => readText(resolve(x), logger).trim())
: [];
if (!noHeader) bundle.push(generateHeader(specs));
if (!noDecls && specs.decls.length) {
bundle.push(
css(specs.decls, {
format: pretty ? PRETTY : COMPACT,
fns: QUOTED_FNS,
})
);
bundle.push(css(specs.decls, cssOpts));
}
if (!onlyDecls) {
bundle.push(serializeSpecs(specs, media, pretty, logger));
bundle.push(serializeSpecs(specs, media, cssOpts, logger));
}
maybeWriteText(out, bundle, logger);
},
Expand All @@ -69,7 +88,7 @@ export const EXPORT: Command<ExportOpts, CommonOpts, AppCtx<ExportOpts>> = {
export const serializeSpecs = (
specs: CompiledSpecs,
media: string[] | undefined,
pretty: boolean,
opts: Partial<CSSOpts>,
logger: ILogger
) => {
const rules: any[] = __suffixed("", specs);
Expand All @@ -86,7 +105,7 @@ export const serializeSpecs = (
}
}
}
return css(rules, { format: pretty ? PRETTY : COMPACT, fns: QUOTED_FNS });
return css(rules, opts);
};

/** @internal */
Expand Down

0 comments on commit d7286c5

Please sign in to comment.