Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancement/wcc 0.15.0 and HTML Web Components #1284

Draft
wants to merge 2 commits into
base: release/0.30.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"lint:js": "eslint \"*.js\" \"./packages/**/**/*.js\" \"./test/*.js\" \"./www/**/**/*.js\"",
"lint:ts": "eslint \"./packages/**/**/*.ts\"",
"lint:css": "stylelint \"./www/**/*.js\", \"./www/**/*.css\"",
"lint": "ls-lint && yarn lint:js && yarn lint:css"
"lint": "ls-lint && yarn lint:js && yarn lint:css",
"postinstall": "patch-package"
},
"resolutions": {
"lit": "^3.1.0"
Expand All @@ -50,6 +51,7 @@
"jsdom": "^16.5.0",
"lerna": "^3.16.4",
"mocha": "^9.1.3",
"patch-package": "^8.0.0",
"rimraf": "^2.6.3",
"stylelint": "^13.8.0",
"stylelint-a11y": "^1.2.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
prerender: true
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Use Case
* Run Greenwood and correctly prerender "HTML" Web Components.
* https://blog.jim-nielsen.com/2023/html-web-components/
*
* User Result
* Should generate a static Greenwood build with the expected prerender "HTML" Web Components content.
*
* User Command
* greenwood build
*
* User Config
*
* {
* prerender: true,
* }
*
* User Workspace
* src/
* components/
* picture-frame.js
* index.html
*/
import chai from 'chai';
import { JSDOM } from 'jsdom';
import path from 'path';
import { runSmokeTest } from '../../../../../test/smoke-test.js';
import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js';
import { Runner } from 'gallinago';
import { fileURLToPath, URL } from 'url';

const expect = chai.expect;

describe('Build Greenwood With: ', function() {
const LABEL = 'Prerendering with HTML Web Components';
const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
const outputPath = fileURLToPath(new URL('.', import.meta.url));
let runner;

before(function() {
this.context = {
publicDir: path.join(outputPath, 'public')
};
runner = new Runner(false, true);
});

describe(LABEL, function() {
before(function() {
runner.setup(outputPath, getSetupFiles(outputPath));
runner.runCommand(cliPath, 'build');
});

runSmokeTest(['public'], LABEL);

describe('Prerender HTML Web Component', function() {
let dom;
let pictureFrame;

before(async function() {
dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html'));
pictureFrame = dom.window.document.querySelectorAll('app-picture-frame');
});

it('should not have any <template> tags within the document', function() {
expect(dom.window.document.querySelectorAll('template').length).to.equal(0);
});

it('should only have one <app-picture-frame> tag', function() {
expect(pictureFrame.length).to.equal(1);
});

it('should have the expected title attribute content in the heading of HTML', () => {
const heading = pictureFrame[0].querySelectorAll('.picture-frame .heading');

expect(heading.length).to.equal(1);
expect(heading[0].textContent).to.equal('Greenwood');
});

it('should have the expected image from userland in the HTML', () => {
const img = pictureFrame[0].querySelectorAll('.picture-frame img');

expect(img.length).to.equal(1);
expect(img[0].getAttribute('alt')).to.equal('Greenwood logo');
expect(img[0].getAttribute('src')).to.equal('/assets/greenwood.png');
});
});
});

after(function() {
runner.teardown(getOutputTeardownFiles(outputPath));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default class PictureFrame extends HTMLElement {
connectedCallback() {
const title = this.getAttribute('title');

this.innerHTML = `
<div class="picture-frame">
<h6 class="heading">${title}</h6>
${this.innerHTML}
</div>
`;
}
}

customElements.define('app-picture-frame', PictureFrame);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en" prefix="og:http://ogp.me/ns#">

<head>
<script type="module" src="./components/picture-frame.js"></script>
</head>

<body>
<app-picture-frame title="Greenwood">
<img src="/assets/greenwood.png" alt="Greenwood logo" />
</app-picture-frame>
</body>

</html>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
* greenwood build
*
* User Config
* import { greenwoodPluginImportCss } from '@greenwood/plugin-import-css';
*
* {
* prerender: true,
Expand Down
90 changes: 90 additions & 0 deletions patches/wc-compiler+0.14.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
diff --git a/node_modules/wc-compiler/src/wcc.js b/node_modules/wc-compiler/src/wcc.js
index 35884d4..e37a4a4 100644
--- a/node_modules/wc-compiler/src/wcc.js
+++ b/node_modules/wc-compiler/src/wcc.js
@@ -32,16 +32,27 @@ async function renderComponentRoots(tree, definitions) {
const { tagName } = node;

if (definitions[tagName]) {
+ console.log('renderComponentRoots', { tagName });
const { moduleURL } = definitions[tagName];
- const elementInstance = await initializeCustomElement(moduleURL, tagName, node.attrs, definitions);
- const elementHtml = elementInstance.shadowRoot
+ console.log({ node });
+ const elementInstance = await initializeCustomElement(moduleURL, tagName, node, definitions);
+ const hasShadow = elementInstance.shadowRoot;
+ const elementHtml = hasShadow
? elementInstance.getInnerHTML({ includeShadowRoots: true })
: elementInstance.innerHTML;
const elementTree = parseFragment(elementHtml);
+ const hasLight = elementTree.childNodes > 0;

- node.childNodes = node.childNodes.length === 0
+ console.log('elementHtml', { elementHtml });
+ console.log('elementTree', { elementTree });
+ console.log('elementTree.childNodes', elementTree.childNodes);
+ console.log('node.childNodes', node.childNodes);
+
+ node.childNodes = node.childNodes.length === 0 && hasLight > 0 && !hasShadow
? elementTree.childNodes
- : [...elementTree.childNodes, ...node.childNodes];
+ : hasShadow
+ ? [...elementTree.childNodes, ...node.childNodes]
+ : elementTree.childNodes;
} else {
console.warn(`WARNING: customElement <${tagName}> is not defined. You may not have imported it yet.`);
}
@@ -138,7 +149,10 @@ async function getTagName(moduleURL) {
return tagName;
}

-async function initializeCustomElement(elementURL, tagName, attrs = [], definitions = [], isEntry, props = {}) {
+async function initializeCustomElement(elementURL, tagName, node = {}, definitions = [], isEntry, props = {}) {
+ const { attrs = [], childNodes = [] } = node;
+ console.log('initializeCustomElement', { node });
+
if (!tagName) {
const depth = isEntry ? 1 : 0;
registerDependencies(elementURL, definitions, depth);
@@ -157,6 +171,41 @@ async function initializeCustomElement(elementURL, tagName, attrs = [], definiti

if (element) {
const elementInstance = new element(data); // eslint-disable-line new-cap
+ let innerHTML = elementInstance.innerHTML || '';
+
+ // TODO
+ // 1. Needs to be recursive
+ // 2. ~~Needs to handle attributes~~
+ // 3. Needs to handle duplicate content
+ // 4. Needs to handle self closing tags
+ // 5. handle all node types
+ childNodes.forEach((child) => {
+ const { nodeName, attrs = [] } = child;
+
+ if (nodeName !== '#text') {
+ innerHTML += `<${nodeName}`;
+
+ if (attrs.length > 0) {
+ attrs.forEach(attr => {
+ innerHTML += ` ${attr.name}="${attr.value}"`;
+ });
+ }
+
+ innerHTML += '>';
+
+ child.childNodes.forEach((c) => {
+ if (c.nodeName === '#text') {
+ innerHTML += c.value;
+ }
+ });
+
+ innerHTML += `</${nodeName}>`;
+ }
+ });
+
+ console.log({ innerHTML });
+ elementInstance.innerHTML = innerHTML;
+ console.log('=================');

attrs.forEach((attr) => {
elementInstance.setAttribute(attr.name, attr.value);
Loading
Loading