Skip to content
Open
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
80 changes: 45 additions & 35 deletions greenwood-plugins/repl-bundler-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,53 @@
import * as rollup from "rollup";
// TODO: depend on these modules first party?
// TODO: depend on these modules first party? Does Greenwood need bumps for these too?
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import type { ResourcePlugin } from "@greenwood/cli";

// function externalizeFsBuiltinRollupPlugin() {
// return {
// name: 'externalize-fs-builtin',
// resolveId(source: string) {
// if (source === 'fs') {
// return source;
// }
// return null;
// },
// load(id: string) {
// if (id === 'fs') {
// return `
// const noop = () => {};
// const fs = {
// readFileSync: noop,
// promises: {
// readFile: noop
// }
// }
// TODO: could we use this? - https://www.npmjs.com/package/rollup-plugin-polyfill-node
function externalizeFsBuiltinRollupPlugin() {
return {
name: "externalize-fs-builtin",
resolveId(source: string) {
if (source === "fs") {
return source;
}
return null;
},
load(id: string) {
if (id === "fs") {
// assumes utf-8 encoding
return `
const fs = {
readFileSync: (url) => {
console.log('fs.readFileSync ->', { url });
const xhr = new XMLHttpRequest();
xhr.open('GET', url.href, false);
xhr.send(null);

// export default fs;
// `;
// }
// return null;
// },
// };
// }
if (xhr.status === 200) {
console.log(xhr.responseText);
return xhr.responseText;
}
},
promises: {
readFile: async function(url) {
console.log('fs.promises.readFile ->', { url });
const response = await fetch(url);
const text = await response.text();
console.log({ url, text });
return text;
},
}
}

export default fs;
`;
}
return null;
},
};
}

class ReplBundlerResource {
extensions: string[];
Expand All @@ -47,11 +64,7 @@ class ReplBundlerResource {
const bundle = await rollup.rollup({
input: url.pathname,
treeshake: false,
plugins: [
// externalizeFsBuiltinRollupPlugin(),
nodeResolve(),
commonjs(),
],
plugins: [externalizeFsBuiltinRollupPlugin(), nodeResolve(), commonjs()],
onLog(level, log) {
// silence circular dependency warnings from sucrase
if (
Expand All @@ -66,9 +79,6 @@ class ReplBundlerResource {
format: "esm",
});

// Create a buffer from the code string to avoid body consumption issues
// const codeBuffer = Buffer.from(output[0].code, 'utf-8');

return new Response(output[0].code, {
headers: {
"Content-Type": "text/javascript",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"lint": "oxlint",
"format": "oxfmt",
"format:check": "oxfmt --check",
"check": "tsgo --ignoreConfig --noEmit --module esnext",
"check": "tsgo --ignoreConfig --noEmit --module esnext --skipLibCheck",
"prepare": "husky",
"postinstall": "patch-package"
},
Expand Down
16 changes: 15 additions & 1 deletion patches/wc-compiler+0.20.0.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
diff --git a/node_modules/wc-compiler/src/jsx-loader.js b/node_modules/wc-compiler/src/jsx-loader.js
index 55b17e2..aae5af4 100644
--- a/node_modules/wc-compiler/src/jsx-loader.js
+++ b/node_modules/wc-compiler/src/jsx-loader.js
@@ -27,7 +27,8 @@ function getParse(html) {
}

export function getParser(moduleURL) {
- const ext = moduleURL.pathname.split('.').pop();
+ // TODO: we only get a Blob URL for the REPL, so will have to craft a "faux" URL
+ const ext = moduleURL?.pathname?.split('.')?.pop();
const isJSX = ext === 'jsx' || ext === 'tsx';

if (!isJSX) {
diff --git a/node_modules/wc-compiler/src/jsx.d.ts b/node_modules/wc-compiler/src/jsx.d.ts
index 4c9286b..48c77cb 100644
index 4c9286b..ca6e96a 100644
--- a/node_modules/wc-compiler/src/jsx.d.ts
+++ b/node_modules/wc-compiler/src/jsx.d.ts
@@ -9,13 +9,15 @@ type ElementAttributes<E extends HTMLElement> = {
Expand Down
2 changes: 1 addition & 1 deletion src/scripts/repl-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ document.addEventListener("DOMContentLoaded", () => {

// once the worker sends back the compiled HTML, update the output editor with the result
worker.onmessage = (result) => {
outputEditor.setValue(prettify(result.data.html));
outputEditor.setValue(prettify(result.data.output));
};

// trigger an initial compilation with the default input contents
Expand Down
191 changes: 10 additions & 181 deletions src/scripts/repl.ts
Original file line number Diff line number Diff line change
@@ -1,140 +1,4 @@
// TODO: have this come from WCC
// import { renderFromInput } from '../../src/wcc.js';
// TODO: have wc-compiler expose DOM shim through exports maps
import "wc-compiler/dom-shim";
import { transform } from "sucrase";
import * as acorn from "acorn";
import * as walk from "acorn-walk";
import { serialize } from "parse5";

// @ts-expect-error
function isCustomElementDefinitionNode(node) {
const { expression } = node;

return (
expression.type === "CallExpression" &&
expression.callee &&
expression.callee.object &&
expression.callee.property &&
expression.callee.object.name === "customElements" &&
expression.callee.property.name === "define"
);
}

// @ts-expect-error
async function getTagName(moduleContents) {
const result = transform(moduleContents, {
transforms: ["typescript", "jsx"],
jsxRuntime: "automatic",
production: true,
});
let tagName;

walk.simple(
acorn.Parser.parse(result.code, {
ecmaVersion: "latest",
sourceType: "module",
}),
{
ExpressionStatement(node) {
if (isCustomElementDefinitionNode(node)) {
// @ts-expect-error
tagName = node.expression.arguments[0].value;
}
},
},
);

return tagName;
}

// @ts-expect-error
async function renderComponentRoots(tree, definitions) {
for (const node of tree.childNodes) {
if (node.tagName && node.tagName.indexOf("-") > 0) {
const { attrs, tagName } = node;

if (definitions[tagName]) {
const { moduleURL } = definitions[tagName];
const elementInstance = await initializeCustomElement(
moduleURL,
tagName,
node,
definitions,
);

if (elementInstance) {
const hasShadow = elementInstance.shadowRoot;

node.childNodes = hasShadow
? [...elementInstance.shadowRoot.childNodes, ...node.childNodes]
: elementInstance.childNodes;
} else {
console.warn(
`WARNING: customElement <${tagName}> detected but not serialized. You may not have exported it.`,
);
}
} else {
console.warn(
`WARNING: customElement <${tagName}> is not defined. You may not have imported it.`,
);
}

// @ts-expect-error
attrs.forEach((attr) => {
if (attr.name === "hydrate") {
definitions[tagName].hydrate = attr.value;
}
});
}

if (node.childNodes && node.childNodes.length > 0) {
await renderComponentRoots(node, definitions);
}

if (node.shadowRoot && node.shadowRoot.childNodes?.length > 0) {
await renderComponentRoots(node.shadowRoot, definitions);
}

// does this only apply to `<template>` tags?
if (node.content && node.content.childNodes?.length > 0) {
await renderComponentRoots(node.content, definitions);
}
}

return tree;
}

async function initializeCustomElement(
// @ts-expect-error
elementURL,
// @ts-expect-error
tagName,
node = {},
// definitions = {}
// @ts-expect-error,
isEntry,
props = {},
) {
// if (!tagName) {
// const depth = isEntry ? 1 : 0;
// registerDependencies(elementURL, definitions, depth);
// }

const element = customElements.get(tagName) ?? (await import(elementURL)).default;
const dataLoader = (await import(elementURL)).getData;
const data = props ? props : dataLoader ? await dataLoader(props) : {};

if (element) {
const elementInstance = new element(data);

Object.assign(elementInstance, node);

await elementInstance.connectedCallback();

return elementInstance;
}
}
import { renderToString } from "wc-compiler";

onmessage = async (e) => {
console.log("Worker: Message received from main script", { e });
Expand All @@ -143,60 +7,25 @@ onmessage = async (e) => {
const wrappingEntryTag = true;

let err;
let html;
let output;

try {
// await renderFromInput(input);

const blobURL = URL.createObjectURL(new Blob([input], { type: "application/javascript" }));
const definitions = {};

await import(blobURL);

const tagName = await getTagName(input);

// @ts-expect-error
definitions[tagName] = {
moduleURL: blobURL,
};

const elementInstance = await initializeCustomElement(blobURL, tagName, {}, true, props);

// in case the entry point isn't valid
if (elementInstance) {
elementInstance.nodeName = tagName ?? "";
elementInstance.tagName = tagName ?? "";
const inputURL = URL.createObjectURL(new Blob([input], { type: "application/javascript" }));
console.log("?????", { input, inputURL });

await renderComponentRoots(
elementInstance.shadowRoot
? {
nodeName: "#document-fragment",
childNodes: [elementInstance],
}
: elementInstance,
definitions,
);
const { html, metadata } = await renderToString(new URL(inputURL), wrappingEntryTag, props);
console.log({ html, metadata });

html =
wrappingEntryTag && tagName
? `
<${tagName}>
${serialize(elementInstance)}
</${tagName}>
`
: serialize(elementInstance);
} else {
console.warn("WARNING: No custom element class found for this entry point.");
}
output = html;
} catch (e) {
console.error(e);
err = e;
}

if (err) {
postMessage(err);
postMessage({ err });
} else {
console.log("Worker: Posting message back to main script", { html });
postMessage({ html });
console.log("Worker: Posting message back to main script", { output });
postMessage({ output });
}
};