Skip to content

Commit

Permalink
feat: allow changing the delimiter and filename prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
SegaraRai committed Jul 24, 2024
1 parent 8271700 commit 6c2d028
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 43 deletions.
7 changes: 7 additions & 0 deletions .changeset/wise-grapes-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"vite-plugin-modular-tailwindcss": patch
---

Allow changing the delimiter and filename prefix.
Now the virtual modules are prefixed with "mtw." by default.

42 changes: 29 additions & 13 deletions src/frameworks/vite/id.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import { normalizePath, type ResolvedConfig } from "vite";
import { normalizePath } from "vite";
import type { IdInfo } from "../../codegen";
import type { CodegenFunctionsForId } from "../../id";

export interface ViteIdOptions {
readonly delimiter?: string | undefined;
readonly filenamePrefix?: string | undefined;
}

export interface ResolvedViteIdOptions extends Required<ViteIdOptions> {
readonly root: string;
}

const PREFIX = "\0tailwindcss/";
const RELATIVE_PREFIX = "@@/";

export const DEFAULT_VITE_ID_OPTIONS = {
delimiter: "/",
filenamePrefix: "mtw.",
} as const satisfies Required<ViteIdOptions>;

function stringifySourceId(id: string, root: string): string {
const normalizedId = normalizePath(id);
if (normalizedId === root) {
Expand All @@ -30,8 +44,7 @@ export function toImportPath(stringifiedId: string, idInfo: IdInfo): string {
}

export function parseId(
{ root }: ResolvedConfig,
delimiter: string,
{ root, delimiter, filenamePrefix }: ResolvedViteIdOptions,
id: string
): readonly [sourceId: string | null, name: string] | null {
if (!id.startsWith(PREFIX)) {
Expand All @@ -44,35 +57,38 @@ export function parseId(
sliced !== name
? restoreSourceId(sliced.slice(0, -name.length - delimiter.length), root)
: null;
return [sourceId, name.replace(/\?.*$/, "")];
if (!name.startsWith(filenamePrefix)) {
throw new Error(
`LogicError: Invalid ID: "${id}". Expected name to start with "${filenamePrefix}"`
);
}
return [sourceId, name.slice(filenamePrefix.length).replace(/\?.*$/, "")];
}

export function stringifyId(
{ root }: ResolvedConfig,
delimiter: string,
{ root, delimiter, filenamePrefix }: ResolvedViteIdOptions,
sourceId: string | null,
name: string
): string {
if (!sourceId) {
return `${PREFIX}${name}`;
return `${PREFIX}${filenamePrefix}${name}`;
}

return `${PREFIX}${stringifySourceId(sourceId, root)}${delimiter}${name}`;
return `${PREFIX}${stringifySourceId(sourceId, root)}${delimiter}${filenamePrefix}${name}`;
}

export function createIdFunctions(
config: ResolvedConfig,
delimiter = "/"
options: ResolvedViteIdOptions
): CodegenFunctionsForId {
const { delimiter } = options;
if (/^[a-z.]*$/i.test(delimiter) || /[\\?&#]/.test(delimiter)) {
throw new Error(
`Invalid delimiter: "${delimiter}". Parsing would be ambiguous.`
);
}

return {
parseId: (id) => parseId(config, delimiter, id),
stringifyId: (sourceId, name) =>
stringifyId(config, delimiter, sourceId, name),
parseId: (id) => parseId(options, id),
stringifyId: (sourceId, name) => stringifyId(options, sourceId, name),
};
}
35 changes: 22 additions & 13 deletions src/plugins/build.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { normalizePath, type Plugin, type ResolvedConfig } from "vite";
import { normalizePath, type Plugin } from "vite";
import {
generateCode,
hasCircularDependencies,
type CodegenContext,
type CodegenFunctions,
} from "../codegen";
import { fwVite } from "../frameworks";
import {
DEFAULT_VITE_ID_OPTIONS,
type ResolvedViteIdOptions,
} from "../frameworks/vite";
import { parseId, resolveId } from "../id";
import { resolveOptions, type Options, type ResolvedOptions } from "../options";
import { resolveOptions, type ResolvedOptions } from "../options";
import { createTailwindCSSGenerator } from "../tailwindcss";
import {
getModuleCode,
getModuleImports,
shouldExclude,
type PluginContext,
} from "../utils";
import type { ViteOptions } from "./types";

// for documentation links
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -23,7 +28,7 @@ import type { modularTailwindCSSPluginServeStrict } from "./serveStrict";
function createCodegenFunctions(
resolvedOptions: ResolvedOptions,
pluginContext: PluginContext,
resolvedConfig: ResolvedConfig
idOptions: ResolvedViteIdOptions
): CodegenFunctions {
return {
shouldIncludeImport: (
Expand All @@ -39,7 +44,7 @@ function createCodegenFunctions(
) => getModuleImports(pluginContext, resolvedId, importerId ?? undefined),
toImportPath: fwVite.toImportPath,
warn: pluginContext.warn.bind(pluginContext),
...fwVite.createIdFunctions(resolvedConfig),
...fwVite.createIdFunctions(idOptions),
};
}

Expand All @@ -51,7 +56,7 @@ function createCodegenFunctions(
* We require `importedIds` and `code` to generate TailwindCSS code, but they are not available in serve mode.
* Use {@link modularTailwindCSSPluginServeStrict} for serve mode.
*/
export function modularTailwindCSSPluginBuild(options: Options): Plugin {
export function modularTailwindCSSPluginBuild(options: ViteOptions): Plugin {
const htmlMap = new Map<string, string>();
let seenCircularDependencyWarning = false;

Expand All @@ -68,7 +73,7 @@ export function modularTailwindCSSPluginBuild(options: Options): Plugin {
const codegenContextWeakMap = new WeakMap<PluginContext, CodegenContext>();
const getCodegenContext = (
pluginContext: PluginContext,
resolvedConfig: ResolvedConfig
resolvedIdOptions: ResolvedViteIdOptions
) => {
let codegenContext = codegenContextWeakMap.get(pluginContext);
if (!codegenContext) {
Expand All @@ -77,7 +82,7 @@ export function modularTailwindCSSPluginBuild(options: Options): Plugin {
functions: createCodegenFunctions(
resolvedOptions,
pluginContext,
resolvedConfig
resolvedIdOptions
),
};
codegenContextWeakMap.set(pluginContext, codegenContext);
Expand All @@ -86,14 +91,18 @@ export function modularTailwindCSSPluginBuild(options: Options): Plugin {
return codegenContext;
};

let resolvedConfig: ResolvedConfig | undefined;
let resolvedIdOptions: ResolvedViteIdOptions | undefined;

return {
name: "vite-plugin-modular-tailwindcss-build",
apply: "build",
enforce: "pre",
configResolved(config): void {
resolvedConfig = config;
resolvedIdOptions = {
...DEFAULT_VITE_ID_OPTIONS,
...options.idOptions,
root: config.root,
};
},
buildStart(): void {
htmlMap.clear();
Expand All @@ -107,22 +116,22 @@ export function modularTailwindCSSPluginBuild(options: Options): Plugin {
resolveId: {
order: "pre",
handler(source, importer): string | undefined {
if (!resolvedConfig) {
if (!resolvedIdOptions) {
throw new Error("LogicError: No resolved config.");
}

const { functions } = getCodegenContext(this, resolvedConfig);
const { functions } = getCodegenContext(this, resolvedIdOptions);
return resolveId(source, importer, functions);
},
},
load: {
order: "pre",
async handler(resolvedId) {
if (!resolvedConfig) {
if (!resolvedIdOptions) {
throw new Error("LogicError: No resolved config.");
}

const codegenContext = getCodegenContext(this, resolvedConfig);
const codegenContext = getCodegenContext(this, resolvedIdOptions);
const { functions } = codegenContext;

const parsedId = parseId(resolvedId, functions);
Expand Down
31 changes: 22 additions & 9 deletions src/plugins/serveLite.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { Plugin, ResolvedConfig } from "vite";
import { fwVite } from "../frameworks";
import {
DEFAULT_VITE_ID_OPTIONS,
type ResolvedViteIdOptions,
} from "../frameworks/vite";
import { parseId, resolveId, resolveIdFromURL } from "../id";
import { resolveOptions, type Options } from "../options";
import { resolveOptions } from "../options";
import {
getIndexHTMLModuleId,
getModuleIdFromURLPath,
getURLPathFromModuleId,
} from "../utils";
import type { ViteOptions } from "./types";

const DEV_GLOBAL_TAILWIND_CSS_ID = "tailwindcss.dev.global.css";

Expand All @@ -21,7 +26,9 @@ const DEV_GLOBAL_TAILWIND_CSS_ID = "tailwindcss.dev.global.css";
* This plugin utilizes the native PostCSS / TailwindCSS support in Vite.
* To make this plugin work, you need to configure the `postcss.config.js` and `tailwind.config.js` files.
*/
export function modularTailwindCSSPluginServeLite(options: Options): Plugin {
export function modularTailwindCSSPluginServeLite(
options: ViteOptions
): Plugin {
const resolvedOptions = resolveOptions(options);
const globalCSSCode = resolvedOptions.layers
.filter((layer) => layer.apply !== "build")
Expand All @@ -30,18 +37,24 @@ export function modularTailwindCSSPluginServeLite(options: Options): Plugin {

let seenFirstMessage = false;
let resolvedConfig: ResolvedConfig | undefined;
let resolvedIdOptions: ResolvedViteIdOptions | undefined;

return {
name: "vite-plugin-modular-tailwindcss-serve",
apply: "serve",
configResolved(config): void {
resolvedConfig = config;
resolvedIdOptions = {
...DEFAULT_VITE_ID_OPTIONS,
...options.idOptions,
root: config.root,
};
},
configureServer(server): void {
// Redirect to enable importing `?tailwindcss/inject` and `?tailwindcss/inject-shallow` from HTML.
server.middlewares.use((req, res, next): void => {
(async (): Promise<void> => {
if (!resolvedConfig) {
if (!resolvedConfig || !resolvedIdOptions) {
next();
return;
}
Expand All @@ -53,7 +66,7 @@ export function modularTailwindCSSPluginServeLite(options: Options): Plugin {
(await (resolvedConfig
? getIndexHTMLModuleId(resolvedConfig)
: Promise.reject(new Error("No resolved config.")))),
fwVite.createIdFunctions(resolvedConfig)
fwVite.createIdFunctions(resolvedIdOptions)
);
if (!resolvedId) {
next();
Expand Down Expand Up @@ -86,14 +99,14 @@ export function modularTailwindCSSPluginServeLite(options: Options): Plugin {
return source;
}

if (!resolvedConfig) {
if (!resolvedIdOptions) {
throw new Error("LogicError: No resolved config.");
}

return resolveId(
source,
importer,
fwVite.createIdFunctions(resolvedConfig)
fwVite.createIdFunctions(resolvedIdOptions)
);
},
},
Expand All @@ -107,21 +120,21 @@ export function modularTailwindCSSPluginServeLite(options: Options): Plugin {
};
}

if (!resolvedConfig) {
if (!resolvedIdOptions) {
throw new Error("LogicError: No resolved config.");
}

const parsedId = parseId(
resolvedId,
fwVite.createIdFunctions(resolvedConfig)
fwVite.createIdFunctions(resolvedIdOptions)
);
if (!parsedId) {
return;
}

if (parsedId.mode !== "entry") {
throw new Error(
`LogicError: ${parsedId.mode} code is not available in dev mode.`
`LogicError: ${parsedId.mode} code is not available in the lite plugin.`
);
}

Expand Down
Loading

0 comments on commit 6c2d028

Please sign in to comment.