Skip to content

Commit bccce17

Browse files
patch concurrent safe css modules plugin parsing
1 parent 70754ad commit bccce17

1 file changed

Lines changed: 288 additions & 0 deletions

File tree

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
diff --git a/node_modules/@greenwood/plugin-css-modules/src/index.js b/node_modules/@greenwood/plugin-css-modules/src/index.js
2+
index e2f5928..952d928 100644
3+
--- a/node_modules/@greenwood/plugin-css-modules/src/index.js
4+
+++ b/node_modules/@greenwood/plugin-css-modules/src/index.js
5+
@@ -4,6 +4,7 @@
6+
*
7+
*/
8+
import fs from "node:fs";
9+
+import path from "node:path";
10+
import { parse as hparse } from "node-html-parser";
11+
import { parse, walk } from "css-tree";
12+
import * as acornWalk from "acorn-walk";
13+
@@ -11,7 +12,8 @@ import * as acorn from "acorn";
14+
import { hashString } from "@greenwood/cli/src/lib/hashing-utils.js";
15+
import { ACORN_OPTIONS } from "@greenwood/cli/src/lib/parsing-utils.js";
16+
17+
-const MODULES_MAP_FILENAME = "__css-modules-map.json";
18+
+const MODULES_MAP_DIR_NAME = "__css-modules-map";
19+
+// const MODULES_MAP_FILENAME = "__css-modules-map.json";
20+
/*
21+
* we have to write the modules map to a file to preserve the state between static and SSR / prerendering
22+
* since if we try and do something like `globalThis.cssModulesMap = globalThis.cssModulesMap ?? {}`
23+
@@ -19,16 +21,16 @@ const MODULES_MAP_FILENAME = "__css-modules-map.json";
24+
*
25+
* https://github.com/ProjectEvergreen/greenwood/discussions/1117
26+
*/
27+
-function getCssModulesMap(compilation) {
28+
- const locationUrl = new URL(`./${MODULES_MAP_FILENAME}`, compilation.context.scratchDir);
29+
- let cssModulesMap = {};
30+
+// function getCssModulesMap(compilation) {
31+
+// const locationUrl = new URL(`./${MODULES_MAP_FILENAME}`, compilation.context.scratchDir);
32+
+// let cssModulesMap = {};
33+
34+
- if (fs.existsSync(locationUrl)) {
35+
- cssModulesMap = JSON.parse(fs.readFileSync(locationUrl, "utf-8"));
36+
- }
37+
+// if (fs.existsSync(locationUrl)) {
38+
+// cssModulesMap = JSON.parse(fs.readFileSync(locationUrl, "utf-8"));
39+
+// }
40+
41+
- return cssModulesMap;
42+
-}
43+
+// return cssModulesMap;
44+
+// }
45+
46+
async function getTransformedScriptContents(scriptUrl, compilation) {
47+
const resourcePlugins = compilation.config.plugins
48+
@@ -74,7 +76,7 @@ async function getTransformedScriptContents(scriptUrl, compilation) {
49+
return await response.text();
50+
}
51+
52+
-async function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
53+
+async function walkAllImportsForCssModules(cssModulesMap = {}, scriptUrl, sheets, compilation) {
54+
const scriptContents = await getTransformedScriptContents(scriptUrl, compilation);
55+
const additionalScripts = [];
56+
57+
@@ -140,20 +142,63 @@ async function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
58+
},
59+
});
60+
61+
- const cssModulesMap = getCssModulesMap(compilation);
62+
+ // TODO better truncate name
63+
+ const ouputPathUrl = new URL(`./${MODULES_MAP_DIR_NAME}${scriptUrl.pathname}.json`, compilation.context.scratchDir);
64+
+
65+
+ // console.log({ ouputPathUrl })
66+
+ if (!fs.existsSync(ouputPathUrl)) {
67+
+ fs.mkdirSync(path.dirname(ouputPathUrl.pathname), { recursive: true });
68+
+ }
69+
+
70+
+ cssModulesMap[ouputPathUrl] = {
71+
+ module: classNameMap,
72+
+ contents: scopedCssContents,
73+
+ importer: scriptUrl,
74+
+ identifier,
75+
+ }
76+
77+
fs.writeFileSync(
78+
- new URL(`./${MODULES_MAP_FILENAME}`, compilation.context.scratchDir),
79+
+ new URL(`./${MODULES_MAP_DIR_NAME}${cssModuleUrl.pathname}.json`, compilation.context.scratchDir),
80+
JSON.stringify({
81+
- ...cssModulesMap,
82+
- [`${cssModuleUrl.href}`]: {
83+
- module: classNameMap,
84+
- contents: scopedCssContents,
85+
- importer: scriptUrl,
86+
- identifier,
87+
- },
88+
+ module: classNameMap,
89+
+ contents: scopedCssContents,
90+
+ importer: scriptUrl,
91+
+ identifier,
92+
+ }),
93+
+ )
94+
+
95+
+ fs.writeFileSync(
96+
+ ouputPathUrl,
97+
+ JSON.stringify({
98+
+ module: classNameMap,
99+
+ contents: scopedCssContents,
100+
+ importer: scriptUrl,
101+
+ identifier,
102+
}),
103+
);
104+
+
105+
+ fs.writeFileSync(
106+
+ ouputPathUrl,
107+
+ JSON.stringify({
108+
+ module: classNameMap,
109+
+ contents: scopedCssContents,
110+
+ importer: scriptUrl,
111+
+ identifier,
112+
+ }),
113+
+ );
114+
+
115+
+ // fs.writeFileSync(
116+
+ // new URL(`./${MODULES_MAP_FILENAME}`, compilation.context.scratchDir),
117+
+ // JSON.stringify({
118+
+ // ...cssModulesMap,
119+
+ // [`${cssModuleUrl.href}`]: {
120+
+ // module: classNameMap,
121+
+ // contents: scopedCssContents,
122+
+ // importer: scriptUrl,
123+
+ // identifier,
124+
+ // },
125+
+ // }),
126+
+ // );
127+
} else {
128+
const recursiveScriptUrl = new URL(value, scriptUrl);
129+
130+
@@ -165,8 +210,10 @@ async function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
131+
});
132+
133+
for (const script of additionalScripts) {
134+
- await walkAllImportsForCssModules(script, sheets, compilation);
135+
+ await walkAllImportsForCssModules(cssModulesMap, script, sheets, compilation);
136+
}
137+
+
138+
+ return cssModulesMap;
139+
}
140+
141+
// this happens 'first' as the HTML is returned, to find viable references to CSS Modules
142+
@@ -179,34 +226,40 @@ class ScanForCssModulesResource {
143+
const { scratchDir } = this.compilation.context;
144+
145+
if (
146+
- fs.existsSync(scratchDir) &&
147+
- !fs.existsSync(new URL(`./${MODULES_MAP_FILENAME}`, scratchDir))
148+
+ !fs.existsSync(new URL(`./${MODULES_MAP_DIR_NAME}/`, scratchDir))
149+
) {
150+
- fs.writeFileSync(new URL(`./${MODULES_MAP_FILENAME}`, scratchDir), JSON.stringify({}));
151+
+ fs.mkdirSync(new URL(`./${MODULES_MAP_DIR_NAME}/`, scratchDir));
152+
}
153+
+ // if (
154+
+ // fs.existsSync(scratchDir) &&
155+
+ // !fs.existsSync(new URL(`./${MODULES_MAP_FILENAME}`, scratchDir))
156+
+ // ) {
157+
+ // fs.writeFileSync(new URL(`./${MODULES_MAP_FILENAME}`, scratchDir), JSON.stringify({}));
158+
+ // }
159+
}
160+
161+
async shouldIntercept(url) {
162+
const { pathname, protocol } = url;
163+
- const mapKey = `${protocol}//${pathname}`;
164+
- const cssModulesMap = getCssModulesMap(this.compilation);
165+
+ // const mapKey = `${protocol}//${pathname}`;
166+
+ // const cssModulesMap = getCssModulesMap(this.compilation);
167+
168+
return (
169+
- url.pathname.endsWith("/") ||
170+
- (protocol === "file:" && pathname.endsWith(this.extensions[0]) && cssModulesMap[mapKey])
171+
+ url.pathname.endsWith("/")
172+
+ || (protocol === "file:" && pathname.endsWith(this.extensions[0]))
173+
);
174+
}
175+
176+
async intercept(url, request, response) {
177+
const { pathname, protocol } = url;
178+
- const mapKey = `${protocol}//${pathname}`;
179+
- const cssModulesMap = getCssModulesMap(this.compilation);
180+
+ // const mapKey = `${protocol}//${pathname}`;
181+
+ // const cssModulesMap = getCssModulesMap(this.compilation);
182+
183+
if (url.pathname.endsWith("/")) {
184+
const body = await response.text();
185+
const dom = hparse(body);
186+
const scripts = dom.querySelectorAll("head script");
187+
const sheets = [];
188+
+ let cssModulesMap = {};
189+
190+
for (const script of scripts) {
191+
const type = script.getAttribute("type") ?? "";
192+
@@ -219,12 +272,13 @@ class ScanForCssModulesResource {
193+
this.compilation.context.userWorkspace,
194+
);
195+
196+
- await walkAllImportsForCssModules(scriptUrl, sheets, this.compilation);
197+
+ cssModulesMap = await walkAllImportsForCssModules(cssModulesMap, scriptUrl, sheets, this.compilation);
198+
}
199+
}
200+
201+
- const cssModulesMap = getCssModulesMap(this.compilation);
202+
+ // const cssModulesMap = getCssModulesMap(this.compilation);
203+
204+
+ // console.log('&&&&&', { cssModulesMap });
205+
Object.keys(cssModulesMap).forEach((key) => {
206+
sheets.push(cssModulesMap[key].contents);
207+
});
208+
@@ -242,11 +296,14 @@ class ScanForCssModulesResource {
209+
return new Response(newBody);
210+
} else if (
211+
protocol === "file:" &&
212+
- pathname.endsWith(this.extensions[0]) &&
213+
- cssModulesMap[mapKey]
214+
+ pathname.endsWith(this.extensions[0])
215+
) {
216+
+ console.log('herehehre?')
217+
+ const cssModulesMap = JSON.parse(fs.readFileSync(new URL(`./${MODULES_MAP_DIR_NAME}${url.pathname}.json`, this.compilation.context.scratchDir)));
218+
+ const { identifier, module } = cssModulesMap
219+
+
220+
// handle this primarily for SSR / prerendering use case
221+
- const cssModule = `export default ${JSON.stringify(cssModulesMap[mapKey].module)}`;
222+
+ const cssModule = `export default ${JSON.stringify(module)}`;
223+
224+
return new Response(cssModule, {
225+
headers: {
226+
@@ -267,16 +324,21 @@ class StripCssModulesResource {
227+
}
228+
229+
async shouldIntercept(url) {
230+
- const cssModulesMap = getCssModulesMap(this.compilation);
231+
+ // console.log('StripCssModulesResource intercept url => ??? ', { url });
232+
+ // console.log('??', new URL(`./${MODULES_MAP_DIR_NAME}${url.pathname}.json`, this.compilation.context.scratchDir));
233+
234+
- for (const [, value] of Object.entries(cssModulesMap)) {
235+
- if (url.href === value.importer) {
236+
- return true;
237+
- }
238+
- }
239+
+ return fs.existsSync(new URL(`./${MODULES_MAP_DIR_NAME}${url.pathname}.json`, this.compilation.context.scratchDir));
240+
+ // const cssModulesMap = getCssModulesMap(this.compilation);
241+
+
242+
+ // for (const [, value] of Object.entries(cssModulesMap)) {
243+
+ // if (url.href === value.importer) {
244+
+ // return true;
245+
+ // }
246+
+ // }
247+
}
248+
249+
async intercept(url, request, response) {
250+
+ // console.log('StripCssModulesResource intercept url $$$$ => ', url.pathname);
251+
const { context } = this.compilation;
252+
let contents = await response.text();
253+
254+
@@ -287,12 +349,13 @@ class StripCssModulesResource {
255+
256+
if (value.endsWith(".module.css") && specifiers.length === 1) {
257+
contents = `${contents.slice(0, start)} \n ${contents.slice(end)}`;
258+
- const cssModulesMap = getCssModulesMap({ context });
259+
-
260+
- Object.values(cssModulesMap).forEach((value) => {
261+
- const { importer, module, identifier } = value;
262+
+ const cssModulesMap = JSON.parse(fs.readFileSync(new URL(`./${MODULES_MAP_DIR_NAME}${url.pathname}.json`, context.scratchDir)));
263+
+ const { identifier, module } = cssModulesMap
264+
+ // console.log({cssModulesMap})
265+
+ // Object.values(cssModulesMap).forEach((value) => {
266+
+ // const { importer, module, identifier } = value;
267+
268+
- if (importer === url.href) {
269+
+ // if (importer === url.href) {
270+
Object.keys(module).forEach((key) => {
271+
const literalUsageRegex = new RegExp(String.raw`\$\{${identifier}.${key}\}`, "g");
272+
// https://stackoverflow.com/a/20851557/417806
273+
@@ -307,12 +370,13 @@ class StripCssModulesResource {
274+
contents = contents.replace(expressionUsageRegex, `'${module[key]}'`);
275+
}
276+
});
277+
- }
278+
- });
279+
+ // }
280+
+ // });
281+
}
282+
},
283+
});
284+
285+
+ // console.log('CONTENTS!!!!', { contents });
286+
return new Response(contents);
287+
}
288+
}

0 commit comments

Comments
 (0)