Skip to content

Commit cfbdd3c

Browse files
feature/issue 30 home page (#57)
1 parent 828a615 commit cfbdd3c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2129
-118
lines changed

CONTRIBUTING.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ This would emit the following generated HTML
104104
105105
#### Interactive Components (Declarative Shadow DOM)
106106
107-
For interactive components that would require client side interactivity, like for event handlers, these component should be authored rendering into a Shadow Root with [Declarative Shadow DOM](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom) and using [Constructable Stylesheets](https://web.dev/articles/constructable-stylesheets).
107+
For interactive components that would require client side interactivity, like event handlers, these components should be authored rendering into a Shadow Root using [Declarative Shadow DOM](https://developer.chrome.com/docs/css-ui/declarative-shadow-dom) and with Greenwood's [raw plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-raw).
108+
109+
<details>
110+
Ideally we would be using <a href="https://web.dev/articles/constructable-stylesheets">Constructable Stylesheets and Import Attributes</a> but CSS Import Attributes are <a href="https://github.com/ProjectEvergreen/www.greenwoodjs.dev/pull/57#issuecomment-2295349811">not baseline yet</a>. 😞
111+
</details>
108112
109113
```css
110114
/* card.css */
@@ -115,7 +119,7 @@ For interactive components that would require client side interactivity, like fo
115119
```
116120
117121
```js
118-
import sheet from "./card.css" with { type: "css" };
122+
import styles from "./card.css?type=raw";
119123
120124
export default class Card extends HTMLElement {
121125
selectItem() {
@@ -130,6 +134,9 @@ export default class Card extends HTMLElement {
130134
131135
template.innerHTML = `
132136
<div class="card">
137+
<style>
138+
${styles}
139+
</style>
133140
<h3>${title}</h3>
134141
<img src="${thumbnail}" alt="${title}" loading="lazy" width="100%">
135142
<button>View Item Details</button>
@@ -139,7 +146,6 @@ export default class Card extends HTMLElement {
139146
this.shadowRoot.appendChild(template.content.cloneNode(true));
140147
}
141148
142-
this.shadowRoot.adoptedStylesheets = [sheet];
143149
this.shadowRoot?.querySelector("button").addEventListener("click", this.selectItem.bind(this));
144150
}
145151
}

greenwood.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { greenwoodPluginCssModules } from "./plugin-css-modules.js";
33

44
export default {
55
prerender: true,
6-
plugins: [greenwoodPluginImportRaw(), greenwoodPluginCssModules()],
6+
plugins: [greenwoodPluginCssModules(), greenwoodPluginImportRaw()],
77
markdown: {
88
plugins: ["@mapbox/rehype-prism"],
99
},

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

patches/wc-compiler+0.14.0.patch

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
diff --git a/node_modules/wc-compiler/src/dom-shim.js b/node_modules/wc-compiler/src/dom-shim.js
2-
index be289a3..dd4692a 100644
2+
index be289a3..db07eb9 100644
33
--- a/node_modules/wc-compiler/src/dom-shim.js
44
+++ b/node_modules/wc-compiler/src/dom-shim.js
5-
@@ -102,6 +102,9 @@ class ShadowRoot extends DocumentFragment {
5+
@@ -83,6 +83,9 @@ class Document extends Node {
6+
createDocumentFragment(html) {
7+
return new DocumentFragment(html);
8+
}
9+
+
10+
+ querySelector() { }
11+
+ querySelectorAll() { }
12+
}
13+
14+
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
15+
@@ -102,6 +105,10 @@ class ShadowRoot extends DocumentFragment {
616
super();
717
this.mode = options.mode || 'closed';
818
this.adoptedStyleSheets = [];
919
+ // TODO not sure if this is the right base class for these?
1020
+ this.querySelector = noop;
1121
+ this.querySelectorAll = noop;
22+
+ this.getElementById = noop;
1223
}
1324
}
1425

plugin-css-modules.js

Lines changed: 113 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
5252
let scopedCssContents = cssContents;
5353

5454
const ast = parse(cssContents, {
55+
// positions: true,
5556
onParseError(error) {
5657
console.log(error.formattedMessage);
5758
},
@@ -66,14 +67,34 @@ function walkAllImportsForCssModules(scriptUrl, sheets, compilation) {
6667
if (node.children?.head?.data?.type === "Selector") {
6768
if (node.children?.head?.data?.children?.head?.data?.type === "ClassSelector") {
6869
const { name } = node.children.head.data.children.head.data;
69-
7070
const scopedClassName = `${scope}-${hash}-${name}`;
7171
classNameMap[name] = scopedClassName;
7272

73-
scopedCssContents = scopedCssContents.replace(
74-
`\.${name}`,
75-
`\.${scopedClassName}`,
76-
);
73+
/*
74+
* bit of a hacky solution since as we are walking class names one at a time, if we have multiple uses of .heading (for example)
75+
* then by the end we could have .my-component-111-header.my-component-111-header.etc, since we want to replace all instances (e.g. the g flag in Regex)
76+
*
77+
* csstree supports loc so we _could_ target the class replacement down to start / end points, but that unfortunately slows things down a lot
78+
*/
79+
// TODO this is a pretty ugly find / replace technique...
80+
// will definitely want to refactor and test this well
81+
if (
82+
scopedCssContents.indexOf(`.${scopedClassName} `) < 0 &&
83+
scopedCssContents.indexOf(`.${scopedClassName} {`) < 0
84+
) {
85+
scopedCssContents = scopedCssContents.replace(
86+
new RegExp(String.raw`.${name} `, "g"),
87+
`.${scope}-${hash}-${name} `,
88+
);
89+
scopedCssContents = scopedCssContents.replace(
90+
new RegExp(String.raw`.${name},`, "g"),
91+
`.${scope}-${hash}-${name},`,
92+
);
93+
scopedCssContents = scopedCssContents.replace(
94+
new RegExp(String.raw`.${name}:`, "g"),
95+
`.${scope}-${hash}-${name}:`,
96+
);
97+
}
7798
}
7899
}
79100
}
@@ -137,7 +158,7 @@ class CssModulesResource extends ResourceInterface {
137158
}
138159

139160
async shouldResolve(url) {
140-
return url.pathname.endsWith("module.css");
161+
return url.protocol === "file:" && url.pathname.endsWith("module.css");
141162
}
142163

143164
async resolve(url) {
@@ -159,78 +180,104 @@ class CssModulesResource extends ResourceInterface {
159180
return new Request(matchedUrl);
160181
}
161182

162-
async shouldServe(url) {
183+
// async shouldIntercept(url) {
184+
// console.log('css modules intercept', { url });
185+
// const { pathname, protocol } = url;
186+
// const mapKey = `${protocol}//${pathname}`;
187+
// // // console.log(this.compilation.context.scratchDir)
188+
// // // console.log(new URL('./__css-modules-map.json', this.compilation.context.scratchDir));
189+
// const cssModulesMap = getCssModulesMap(this.compilation);
190+
// // console.log("shouldServer", { cssModulesMap, url });
191+
// return protocol === "file:" && pathname.endsWith(this.extensions[0]) && cssModulesMap[mapKey];
192+
// }
193+
194+
// async intercept(url) {
195+
// console.log('css modules intercept', { url });
196+
// const { pathname, protocol } = url;
197+
// const mapKey = `${protocol}//${pathname}`;
198+
// const cssModulesMap = getCssModulesMap(this.compilation);
199+
// // console.log("@@@@@@", { url, cssModulesMap });
200+
// const cssModule = `export default ${JSON.stringify(cssModulesMap[mapKey].module)}`;
201+
202+
// // console.log("@@@@@@", { cssModule });
203+
// return new Response(cssModule, {
204+
// headers: {
205+
// "Content-Type": this.contentType,
206+
// },
207+
// });
208+
// }
209+
210+
// this happens "first" as the HTML is returned, to find viable references to CSS Modules
211+
// better way than just checking for /?
212+
async shouldIntercept(url) {
163213
const { pathname, protocol } = url;
164214
const mapKey = `${protocol}//${pathname}`;
165-
// // console.log(this.compilation.context.scratchDir)
166-
// // console.log(new URL('./__css-modules-map.json', this.compilation.context.scratchDir));
167215
const cssModulesMap = getCssModulesMap(this.compilation);
168-
// console.log("shouldServer", { cssModulesMap, url });
169-
return protocol === "file:" && pathname.endsWith(this.extensions[0]) && cssModulesMap[mapKey];
216+
217+
return (
218+
url.pathname.endsWith("/") ||
219+
(protocol === "file:" && pathname.endsWith(this.extensions[0]) && cssModulesMap[mapKey])
220+
);
170221
}
171222

172-
async serve(url) {
223+
async intercept(url, request, response) {
173224
const { pathname, protocol } = url;
174225
const mapKey = `${protocol}//${pathname}`;
175226
const cssModulesMap = getCssModulesMap(this.compilation);
176-
// console.log("@@@@@@", { url, cssModulesMap });
177-
const cssModule = `export default ${JSON.stringify(cssModulesMap[mapKey].module)}`;
178227

179-
// console.log("@@@@@@", { cssModule });
180-
return new Response(cssModule, {
181-
headers: {
182-
"Content-Type": this.contentType,
183-
},
184-
});
185-
}
186-
187-
// this happens "first" as the HTML is returned, to find viable references to CSS Modules
188-
// better way than just checking for /?
189-
async shouldIntercept(url) {
190-
return url.pathname.endsWith("/");
191-
}
192-
193-
async intercept(url, request, response) {
194-
const body = await response.text();
195-
const dom = htmlparser.parse(body, { script: true });
196-
const scripts = dom.querySelectorAll("head script");
197-
const sheets = []; // TODO use a map here?
198-
199-
for (const script of scripts) {
200-
const type = script.getAttribute("type");
201-
const src = script.getAttribute("src");
202-
if (src && ["module", "module-shim"].includes(type)) {
203-
// console.log("check this file for CSS Modules", src);
204-
// await resolveForRelativeUrl(new URL(src, import.meta.url this.compilation.context.userWorkspace)
205-
const scriptUrl = new URL(
206-
`./${src.replace(/\.\.\//g, "").replace(/\.\//g, "")}`,
207-
this.compilation.context.userWorkspace,
208-
);
209-
walkAllImportsForCssModules(scriptUrl, sheets, this.compilation);
228+
if (url.pathname.endsWith("/")) {
229+
const body = await response.text();
230+
const dom = htmlparser.parse(body, { script: true });
231+
const scripts = dom.querySelectorAll("head script");
232+
const sheets = []; // TODO use a map here?
233+
234+
for (const script of scripts) {
235+
const type = script.getAttribute("type");
236+
const src = script.getAttribute("src");
237+
if (src && ["module", "module-shim"].includes(type)) {
238+
// console.log("check this file for CSS Modules", src);
239+
// await resolveForRelativeUrl(new URL(src, import.meta.url this.compilation.context.userWorkspace)
240+
const scriptUrl = new URL(
241+
`./${src.replace(/\.\.\//g, "").replace(/\.\//g, "")}`,
242+
this.compilation.context.userWorkspace,
243+
);
244+
walkAllImportsForCssModules(scriptUrl, sheets, this.compilation);
245+
}
210246
}
211-
}
212247

213-
const cssModulesMap = getCssModulesMap(this.compilation);
214-
// console.log({ cssModulesMap });
215-
216-
// for(const cssModule of cssModulesMap) {
217-
// // console.log({ cssModule });
218-
// }
219-
Object.keys(cssModulesMap).forEach((key) => {
220-
sheets.push(cssModulesMap[key].contents);
221-
});
222-
223-
const newBody = body.replace(
224-
"</head>",
225-
`
226-
<style>
227-
${sheets.join("\n")}
228-
</style>
229-
</head>
230-
`,
231-
);
248+
const cssModulesMap = getCssModulesMap(this.compilation);
249+
// console.log({ cssModulesMap });
250+
251+
// for(const cssModule of cssModulesMap) {
252+
// // console.log({ cssModule });
253+
// }
254+
Object.keys(cssModulesMap).forEach((key) => {
255+
sheets.push(cssModulesMap[key].contents);
256+
});
257+
258+
const newBody = body.replace(
259+
"</head>",
260+
`
261+
<style>
262+
${sheets.join("\n")}
263+
</style>
264+
</head>
265+
`,
266+
);
232267

233-
return new Response(newBody);
268+
return new Response(newBody);
269+
} else if (
270+
url.pathname.endsWith("/") ||
271+
(protocol === "file:" && pathname.endsWith(this.extensions[0]) && cssModulesMap[mapKey])
272+
) {
273+
const cssModule = `export default ${JSON.stringify(cssModulesMap[mapKey].module)}`;
274+
275+
return new Response(cssModule, {
276+
headers: {
277+
"Content-Type": this.contentType,
278+
},
279+
});
280+
}
234281
}
235282

236283
async shouldOptimize(url, response) {

src/assets/api-routes.svg

Lines changed: 5 additions & 5 deletions
Loading

src/assets/build-ssg.svg

Lines changed: 7 additions & 7 deletions
Loading

src/assets/github.svg

Lines changed: 1 addition & 1 deletion
Loading

src/assets/html.svg

Lines changed: 4 additions & 4 deletions
Loading

0 commit comments

Comments
 (0)