Skip to content

Commit 9c4ac3d

Browse files
committed
refactor: remove worker and restore previous code
1 parent 96e0416 commit 9c4ac3d

File tree

2 files changed

+297
-193
lines changed

2 files changed

+297
-193
lines changed

src/index.ts

Lines changed: 132 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { Component, Editor } from "grapesjs";
21
// @ts-ignore
32
import * as tailwindcss from "tailwindcss";
43
import * as assets from "./assets";
5-
import type { WorkerMessageData, WorkerResponse } from "./worker";
4+
5+
import type { Component, Editor } from "grapesjs";
66

77
export type TailwindPluginOptions = {
88
/**
@@ -64,34 +64,118 @@ export default (editor: Editor, opts: TailwindPluginOptions = {}) => {
6464

6565
const STYLE_ID = "tailwindcss-plugin";
6666

67-
// Create worker to elaborate big html structure in chunk
68-
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
69-
type: "module",
70-
});
67+
/** Tailwind CSS compiler instance */
68+
let compiler: Awaited<ReturnType<typeof tailwindcss.compile>>;
7169

7270
/** Reference to the <style> element where generated Tailwind CSS is injected */
7371
let tailwindStyle: HTMLStyleElement | undefined;
7472

73+
// Cache to store processed Tailwind classes to avoid unnecessary recompilation
74+
const classesCache = new Set<string>();
75+
76+
// Build the Tailwind CSS compiler using tailwindcss.compile with a custom stylesheet loader
77+
const buildCompiler = async () => {
78+
compiler = await tailwindcss.compile(
79+
`@import "tailwindcss" prefix(${options.prefix});${
80+
options.customCss ?? ""
81+
}`,
82+
{
83+
base: "/",
84+
loadStylesheet,
85+
},
86+
);
87+
};
88+
7589
// Override the editor's getCss method to append the generated Tailwind CSS
7690
const originalGetCss = editor.getCss.bind(editor);
7791
editor.getCss = () => {
7892
const originalCss = originalGetCss();
7993
return `${originalCss}\n${tailwindStyle?.textContent ?? ""}`;
8094
};
8195

82-
// Worker response handler. Here we receive the compiled tailwind css
83-
worker.onmessage = async (event: MessageEvent<WorkerResponse>) => {
84-
const { data } = event.data;
85-
if (data) {
86-
const { tailwindcss, notify } = data;
87-
// Append the compiled tailwind css to the tailwind style element
88-
if (typeof tailwindcss === "string" && tailwindStyle !== undefined) {
89-
tailwindStyle.textContent = tailwindcss;
96+
// Custom stylesheet loader function for Tailwind CSS assets
97+
async function loadStylesheet(id: string, base: string) {
98+
if (id === "tailwindcss") {
99+
return { base, content: assets.css.index };
100+
}
101+
if (id.includes("preflight")) {
102+
return { base, content: assets.css.preflight };
103+
}
104+
if (id.includes("theme")) {
105+
return { base, content: assets.css.theme };
106+
}
107+
if (id.includes("utilities")) {
108+
return { base, content: assets.css.utilities };
109+
}
110+
return { base, content: "" };
111+
}
112+
113+
// Initialize the Tailwind compiler, clear the classes cache, and set up the style element
114+
const initTailwindCompiler = async () => {
115+
await buildCompiler();
116+
classesCache.clear();
117+
};
118+
119+
// Extract all Tailwind-related classes from the editor's HTML content
120+
const getClassesFromHtml = (html: string) => {
121+
const classRegex = /class=["']([^"']+)["']/g;
122+
const currentClasses = new Set<string>();
123+
124+
// biome-ignore lint/suspicious/noImplicitAnyLet: <explanation>
125+
let match;
126+
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
127+
while ((match = classRegex.exec(html)) !== null) {
128+
const classes = match[1].split(" ");
129+
for (const cls of classes) {
130+
if (cls.startsWith(options.prefix)) {
131+
currentClasses.add(cls);
132+
}
90133
}
91-
if (notify) {
92-
options.notificationCallback();
134+
}
135+
return currentClasses;
136+
};
137+
138+
const processRemovedClasses = async (currentClasses: Set<string>) => {
139+
// Identify classes that have been removed
140+
let changed = false;
141+
const classesToRemove: string[] = [];
142+
for (const cls of classesCache) {
143+
if (!currentClasses.has(cls)) {
144+
classesToRemove.push(cls);
145+
changed = true;
93146
}
94147
}
148+
149+
// Remove classes non more used
150+
if (classesToRemove.length) {
151+
for (const c of classesToRemove) {
152+
classesCache.delete(c);
153+
}
154+
// Rebuild the compiler to purge Tailwind's internal cache
155+
await buildCompiler();
156+
}
157+
158+
return changed;
159+
};
160+
161+
const processAddedClasses = (currentClasses: Set<string>): boolean => {
162+
// Identify new classes to add by checking if they are in cache
163+
let changed = false;
164+
for (const c of currentClasses) {
165+
if (!classesCache.has(c)) {
166+
classesCache.add(c);
167+
changed = true;
168+
}
169+
}
170+
return changed;
171+
};
172+
173+
const compileTailwindCss = async () => {
174+
// Build Tailwind CSS if there are classes in the cache
175+
if (classesCache.size > 0) {
176+
return compiler.build(Array.from(classesCache)) as string;
177+
}
178+
return "";
95179
};
96180

97181
const setTailwindStyleElement = () => {
@@ -110,40 +194,60 @@ export default (editor: Editor, opts: TailwindPluginOptions = {}) => {
110194
};
111195

112196
// Build and update the Tailwind CSS based on the current classes in the editor
113-
const runWorker = (html: string, notify = false) => {
114-
const payload: WorkerMessageData = {
115-
html,
116-
prefix: options.prefix,
117-
customCss: options.customCss,
118-
notify,
119-
};
120-
worker.postMessage(payload);
197+
const buildTailwindCss = async (html: string, notify = false) => {
198+
if (!compiler) await initTailwindCompiler();
199+
200+
try {
201+
// Get all current tailwind related classes
202+
const currentClasses = getClassesFromHtml(html);
203+
204+
// Identify classes that have been removed
205+
const classesRemoved = await processRemovedClasses(currentClasses);
206+
207+
// Identify new classes to add
208+
const classesAdded = processAddedClasses(currentClasses);
209+
210+
const shouldRebuildCss = classesRemoved || classesAdded;
211+
212+
if (!shouldRebuildCss) return;
213+
214+
const tailwindcss = await compileTailwindCss();
215+
// Append the compiled tailwind css to the tailwind style element
216+
if (tailwindStyle !== undefined) {
217+
tailwindStyle.textContent = tailwindcss;
218+
}
219+
if (notify) {
220+
options.notificationCallback();
221+
}
222+
223+
// biome-ignore lint/suspicious/noExplicitAny: unknown
224+
} catch (error: any) {}
121225
};
122226

123227
// Build the Tailwind CSS on initial HTML load
124228
editor.on("load", async () => {
125229
// On load we need to set up the tailwind style element where we append the compiled tailwind css
126230
setTailwindStyleElement();
127-
runWorker(editor.getHtml());
231+
buildTailwindCss(editor.getHtml());
128232
});
129233

130234
// Fired by grapesjs-preset-webpage on import close
131235
editor.on("command:stop:gjs-open-import-webpage", () =>
132-
runWorker(editor.getHtml()),
236+
buildTailwindCss(editor.getHtml()),
133237
);
134238

135239
// If autobuild option is true, listen to the editor's update events to trigger Tailwind CSS rebuilds.
136240
if (options.autobuild) {
137241
// Listen to the editor's update events to trigger Tailwind CSS rebuilds
138242
editor.on("component:update:classes", (cmp: Component) =>
139-
runWorker(cmp.toHTML()),
243+
buildTailwindCss(cmp.toHTML()),
140244
);
141245
}
142246

143247
// Register a new command "build-tailwind" that can be triggered programmatically.
144248
editor.Commands.add("build-tailwind", {
145249
run(_, sender) {
146-
runWorker(editor.getHtml(), sender.id === "build-tailwind-button");
250+
buildTailwindCss(editor.getHtml(), sender.id === "build-tailwind-button");
147251
},
148252
});
149253

0 commit comments

Comments
 (0)