Skip to content

Commit bd74faa

Browse files
refactor CSS Modules plugin for better concurrency safety
1 parent dfaa6f8 commit bd74faa

1 file changed

Lines changed: 108 additions & 144 deletions

File tree

Lines changed: 108 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
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..075a350 100644
2+
index e2f5928..56ff4f2 100644
33
--- a/node_modules/@greenwood/plugin-css-modules/src/index.js
44
+++ b/node_modules/@greenwood/plugin-css-modules/src/index.js
55
@@ -4,6 +4,7 @@
@@ -10,42 +10,33 @@ index e2f5928..075a350 100644
1010
import { parse as hparse } from "node-html-parser";
1111
import { parse, walk } from "css-tree";
1212
import * as acornWalk from "acorn-walk";
13-
@@ -11,7 +12,8 @@ import * as acorn from "acorn";
13+
@@ -11,24 +12,7 @@ import * as acorn from "acorn";
1414
import { hashString } from "@greenwood/cli/src/lib/hashing-utils.js";
1515
import { ACORN_OPTIONS } from "@greenwood/cli/src/lib/parsing-utils.js";
1616

1717
-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-
*/
18+
-/*
19+
- * we have to write the modules map to a file to preserve the state between static and SSR / prerendering
20+
- * since if we try and do something like `globalThis.cssModulesMap = globalThis.cssModulesMap ?? {}`
21+
- * it won't persist across Worker threads. Maybe if we find a solution to this, we would handle this all in memory.
22+
- *
23+
- * https://github.com/ProjectEvergreen/greenwood/discussions/1117
24+
- */
2725
-function getCssModulesMap(compilation) {
2826
- const locationUrl = new URL(`./${MODULES_MAP_FILENAME}`, compilation.context.scratchDir);
2927
- let cssModulesMap = {};
30-
+// function getCssModulesMap(compilation) {
31-
+// const locationUrl = new URL(`./${MODULES_MAP_FILENAME}`, compilation.context.scratchDir);
32-
+// let cssModulesMap = {};
33-
28+
-
3429
- if (fs.existsSync(locationUrl)) {
3530
- cssModulesMap = JSON.parse(fs.readFileSync(locationUrl, "utf-8"));
3631
- }
37-
+// if (fs.existsSync(locationUrl)) {
38-
+// cssModulesMap = JSON.parse(fs.readFileSync(locationUrl, "utf-8"));
39-
+// }
40-
32+
-
4133
- return cssModulesMap;
4234
-}
43-
+// return cssModulesMap;
44-
+// }
35+
+const MODULES_MAP_DIR_NAME = "__css-modules-map";
4536

4637
async function getTransformedScriptContents(scriptUrl, compilation) {
4738
const resourcePlugins = compilation.config.plugins
48-
@@ -74,7 +76,7 @@ async function getTransformedScriptContents(scriptUrl, compilation) {
39+
@@ -74,7 +58,7 @@ async function getTransformedScriptContents(scriptUrl, compilation) {
4940
return await response.text();
5041
}
5142

@@ -54,80 +45,53 @@ index e2f5928..075a350 100644
5445
const scriptContents = await getTransformedScriptContents(scriptUrl, compilation);
5546
const additionalScripts = [];
5647

57-
@@ -140,20 +142,63 @@ async function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
48+
@@ -140,19 +124,34 @@ async function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
5849
},
5950
});
6051

6152
- const cssModulesMap = getCssModulesMap(compilation);
62-
+ // TODO better truncate name
63-
+ const ouputPathUrl = new URL(`./${MODULES_MAP_DIR_NAME}${scriptUrl.pathname}.json`, compilation.context.scratchDir);
53+
+ const outputPathUrl = new URL(
54+
+ `./${MODULES_MAP_DIR_NAME}/${hashString(scriptUrl.pathname)}.map.json`,
55+
+ compilation.context.scratchDir,
56+
+ );
6457
+
65-
+ // console.log({ ouputPathUrl })
66-
+ if (!fs.existsSync(ouputPathUrl)) {
67-
+ fs.mkdirSync(path.dirname(ouputPathUrl.pathname), { recursive: true });
58+
+ if (!fs.existsSync(outputPathUrl)) {
59+
+ fs.mkdirSync(path.dirname(scriptUrl.pathname), { recursive: true });
6860
+ }
6961
+
70-
+ cssModulesMap[ouputPathUrl] = {
62+
+ const moduleContents = {
7163
+ module: classNameMap,
7264
+ contents: scopedCssContents,
7365
+ importer: scriptUrl,
7466
+ identifier,
75-
+ }
67+
+ };
68+
+
69+
+ cssModulesMap[outputPathUrl] = moduleContents;
70+
+
71+
+ // output one file for transforming imports from CSS -> ESM
72+
+ fs.writeFileSync(outputPathUrl, JSON.stringify(moduleContents));
7673

74+
+ // output one file for SSR / prerendering handling in loaders as ESM
7775
fs.writeFileSync(
7876
- new URL(`./${MODULES_MAP_FILENAME}`, compilation.context.scratchDir),
79-
+ new URL(`./${MODULES_MAP_DIR_NAME}${cssModuleUrl.pathname}.json`, compilation.context.scratchDir),
80-
JSON.stringify({
77+
- JSON.stringify({
8178
- ...cssModulesMap,
8279
- [`${cssModuleUrl.href}`]: {
8380
- module: classNameMap,
8481
- contents: scopedCssContents,
8582
- importer: scriptUrl,
8683
- identifier,
8784
- },
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-
}),
85+
- }),
86+
+ new URL(
87+
+ `./${MODULES_MAP_DIR_NAME}/${hashString(cssModuleUrl.pathname)}.module.json`,
88+
+ compilation.context.scratchDir,
89+
+ ),
90+
+ JSON.stringify(moduleContents),
10391
);
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-
+ // );
12792
} else {
12893
const recursiveScriptUrl = new URL(value, scriptUrl);
129-
130-
@@ -165,8 +210,10 @@ async function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
94+
@@ -165,8 +164,10 @@ async function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
13195
});
13296

13397
for (const script of additionalScripts) {
@@ -139,46 +103,36 @@ index e2f5928..075a350 100644
139103
}
140104

141105
// this happens 'first' as the HTML is returned, to find viable references to CSS Modules
142-
@@ -179,34 +226,40 @@ class ScanForCssModulesResource {
106+
@@ -178,35 +179,28 @@ class ScanForCssModulesResource {
107+
this.contentType = "text/javascript";
143108
const { scratchDir } = this.compilation.context;
144109

145-
if (
110+
- if (
146111
- fs.existsSync(scratchDir) &&
147112
- !fs.existsSync(new URL(`./${MODULES_MAP_FILENAME}`, scratchDir))
148-
+ !fs.existsSync(new URL(`./${MODULES_MAP_DIR_NAME}/`, scratchDir))
149-
) {
113+
- ) {
150114
- fs.writeFileSync(new URL(`./${MODULES_MAP_FILENAME}`, scratchDir), JSON.stringify({}));
115+
+ if (!fs.existsSync(new URL(`./${MODULES_MAP_DIR_NAME}/`, scratchDir))) {
151116
+ fs.mkdirSync(new URL(`./${MODULES_MAP_DIR_NAME}/`, scratchDir), { recursive: true });
152117
}
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-
+ // }
159118
}
160119

161120
async shouldIntercept(url) {
162121
const { pathname, protocol } = url;
163122
- const mapKey = `${protocol}//${pathname}`;
164123
- const cssModulesMap = getCssModulesMap(this.compilation);
165-
+ // const mapKey = `${protocol}//${pathname}`;
166-
+ // const cssModulesMap = getCssModulesMap(this.compilation);
167124

168125
return (
169126
- url.pathname.endsWith("/") ||
170127
- (protocol === "file:" && pathname.endsWith(this.extensions[0]) && cssModulesMap[mapKey])
171-
+ url.pathname.endsWith("/")
172-
+ || (protocol === "file:" && pathname.endsWith(this.extensions[0]))
128+
+ url.pathname.endsWith("/") || (protocol === "file:" && pathname.endsWith(this.extensions[0]))
173129
);
174130
}
175131

176132
async intercept(url, request, response) {
177133
const { pathname, protocol } = url;
178134
- const mapKey = `${protocol}//${pathname}`;
179135
- const cssModulesMap = getCssModulesMap(this.compilation);
180-
+ // const mapKey = `${protocol}//${pathname}`;
181-
+ // const cssModulesMap = getCssModulesMap(this.compilation);
182136

183137
if (url.pathname.endsWith("/")) {
184138
const body = await response.text();
@@ -189,100 +143,110 @@ index e2f5928..075a350 100644
189143

190144
for (const script of scripts) {
191145
const type = script.getAttribute("type") ?? "";
192-
@@ -219,12 +272,13 @@ class ScanForCssModulesResource {
146+
@@ -219,12 +213,15 @@ class ScanForCssModulesResource {
193147
this.compilation.context.userWorkspace,
194148
);
195149

196150
- await walkAllImportsForCssModules(scriptUrl, sheets, this.compilation);
197-
+ cssModulesMap = await walkAllImportsForCssModules(cssModulesMap, scriptUrl, sheets, this.compilation);
151+
+ cssModulesMap = await walkAllImportsForCssModules(
152+
+ cssModulesMap,
153+
+ scriptUrl,
154+
+ sheets,
155+
+ this.compilation,
156+
+ );
198157
}
199158
}
200159

201160
- const cssModulesMap = getCssModulesMap(this.compilation);
202-
+ // const cssModulesMap = getCssModulesMap(this.compilation);
203-
204-
+ // console.log('&&&&&', { cssModulesMap });
161+
-
205162
Object.keys(cssModulesMap).forEach((key) => {
206163
sheets.push(cssModulesMap[key].contents);
207164
});
208-
@@ -242,11 +296,14 @@ class ScanForCssModulesResource {
165+
@@ -240,13 +237,18 @@ class ScanForCssModulesResource {
166+
);
167+
209168
return new Response(newBody);
210-
} else if (
211-
protocol === "file:" &&
169+
- } else if (
170+
- protocol === "file:" &&
212171
- pathname.endsWith(this.extensions[0]) &&
213172
- 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-
+
173+
- ) {
174+
+ } else if (protocol === "file:" && pathname.endsWith(this.extensions[0])) {
220175
// handle this primarily for SSR / prerendering use case
221176
- const cssModule = `export default ${JSON.stringify(cssModulesMap[mapKey].module)}`;
177+
+ const cssModulesMap = JSON.parse(
178+
+ fs.readFileSync(
179+
+ new URL(
180+
+ `./${MODULES_MAP_DIR_NAME}/${hashString(url.pathname)}.module.json`,
181+
+ this.compilation.context.scratchDir,
182+
+ ),
183+
+ ),
184+
+ );
185+
+ const { module } = cssModulesMap;
222186
+ const cssModule = `export default ${JSON.stringify(module)}`;
223187

224188
return new Response(cssModule, {
225189
headers: {
226-
@@ -267,16 +324,21 @@ class StripCssModulesResource {
190+
@@ -267,13 +269,9 @@ class StripCssModulesResource {
227191
}
228192

229193
async shouldIntercept(url) {
230194
- 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-
195+
-
234196
- for (const [, value] of Object.entries(cssModulesMap)) {
235197
- if (url.href === value.importer) {
236198
- return true;
237199
- }
238200
- }
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-
+ // }
201+
+ return fs.existsSync(
202+
+ new URL(`./${MODULES_MAP_DIR_NAME}/${hashString(url.pathname)}.map.json`, this.compilation.context.scratchDir),
203+
+ );
247204
}
248205

249206
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 {
207+
@@ -287,26 +285,25 @@ class StripCssModulesResource {
255208

256209
if (value.endsWith(".module.css") && specifiers.length === 1) {
257210
contents = `${contents.slice(0, start)} \n ${contents.slice(end)}`;
258211
- const cssModulesMap = getCssModulesMap({ context });
259212
-
260213
- Object.values(cssModulesMap).forEach((value) => {
261214
- 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-
215+
-
268216
- 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-
+ // });
217+
- Object.keys(module).forEach((key) => {
218+
- const literalUsageRegex = new RegExp(String.raw`\$\{${identifier}.${key}\}`, "g");
219+
- // https://stackoverflow.com/a/20851557/417806
220+
- const expressionUsageRegex = new RegExp(
221+
- String.raw`(((?<![-\w\d\W])|(?<=[> \n\r\b]))${identifier}\.${key}((?![-\w\d\W])|(?=[ <.,:;!?\n\r\b])))`,
222+
- "g",
223+
- );
224+
-
225+
- if (literalUsageRegex.test(contents)) {
226+
- contents = contents.replace(literalUsageRegex, module[key]);
227+
- } else if (expressionUsageRegex.test(contents)) {
228+
- contents = contents.replace(expressionUsageRegex, `'${module[key]}'`);
229+
- }
230+
- });
231+
+ const cssModulesMap = JSON.parse(
232+
+ fs.readFileSync(
233+
+ new URL(`./${MODULES_MAP_DIR_NAME}/${hashString(url.pathname)}.map.json`, context.scratchDir),
234+
+ ),
235+
+ );
236+
+ const { identifier, module } = cssModulesMap;
237+
+
238+
+ Object.keys(module).forEach((key) => {
239+
+ const literalUsageRegex = new RegExp(String.raw`\$\{${identifier}.${key}\}`, "g");
240+
+ // https://stackoverflow.com/a/20851557/417806
241+
+ const expressionUsageRegex = new RegExp(
242+
+ String.raw`(((?<![-\w\d\W])|(?<=[> \n\r\b]))${identifier}\.${key}((?![-\w\d\W])|(?=[ <.,:;!?\n\r\b])))`,
243+
+ "g",
244+
+ );
245+
+
246+
+ if (literalUsageRegex.test(contents)) {
247+
+ contents = contents.replace(literalUsageRegex, module[key]);
248+
+ } else if (expressionUsageRegex.test(contents)) {
249+
+ contents = contents.replace(expressionUsageRegex, `'${module[key]}'`);
250+
}
251+
});
281252
}
282-
},
283-
});
284-
285-
+ // console.log('CONTENTS!!!!', { contents });
286-
return new Response(contents);
287-
}
288-
}

0 commit comments

Comments
 (0)