diff --git a/package-lock.json b/package-lock.json index 7b13f870..ef9e8be4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wc-compiler", - "version": "0.3.1", + "version": "0.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wc-compiler", - "version": "0.3.1", + "version": "0.4.1", "license": "MIT", "dependencies": { "acorn": "^8.7.0", @@ -24,6 +24,7 @@ "http-server": "^14.1.0", "jsdom": "^19.0.0", "mocha": "^9.2.2", + "node-fetch": "^3.2.6", "nodemon": "^2.0.15", "prismjs": "^1.28.0", "rehype-autolink-headings": "^6.1.1", @@ -1391,6 +1392,15 @@ "dev": true, "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -2110,6 +2120,29 @@ "dev": true, "license": "MIT" }, + "node_modules/fetch-blob": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", + "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -2250,6 +2283,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -5708,6 +5753,43 @@ "dev": true, "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz", + "integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -7821,6 +7903,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -9150,6 +9241,12 @@ } } }, + "data-uri-to-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz", + "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==", + "dev": true + }, "data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -9656,6 +9753,16 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fetch-blob": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz", + "integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==", + "dev": true, + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -9742,6 +9849,15 @@ "mime-types": "^2.1.12" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, "fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -12094,6 +12210,23 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, + "node-fetch": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz", + "integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==", + "dev": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, "nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -13563,6 +13696,12 @@ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", "dev": true }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index f231b589..00b5193d 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "http-server": "^14.1.0", "jsdom": "^19.0.0", "mocha": "^9.2.2", + "node-fetch": "^3.2.6", "nodemon": "^2.0.15", "prismjs": "^1.28.0", "rehype-autolink-headings": "^6.1.1", diff --git a/src/wcc.js b/src/wcc.js index 4e2e481c..428ba809 100644 --- a/src/wcc.js +++ b/src/wcc.js @@ -6,8 +6,6 @@ import * as walk from 'acorn-walk'; import { parse, parseFragment, serialize } from 'parse5'; import fs from 'fs/promises'; -let definitions; - function getParse(html) { return html.indexOf('') >= 0 || html.indexOf('
') >= 0 || html.indexOf('') >= 0 ? parse @@ -22,12 +20,12 @@ function isCustomElementDefinitionNode(node) { && expression.callee.property.name === 'define'; } -async function renderComponentRoots(tree) { +async function renderComponentRoots(tree, definitions) { for (const node of tree.childNodes) { if (node.tagName && node.tagName.indexOf('-') > 0) { const { tagName } = node; const { moduleURL } = definitions[tagName]; - const elementInstance = await initializeCustomElement(moduleURL, tagName, node.attrs); + const elementInstance = await initializeCustomElement(moduleURL, tagName, node.attrs, definitions); const elementHtml = elementInstance.shadowRoot ? elementInstance.getInnerHTML({ includeShadowRoots: true }) : elementInstance.innerHTML; @@ -39,19 +37,19 @@ async function renderComponentRoots(tree) { } if (node.childNodes && node.childNodes.length > 0) { - await renderComponentRoots(node); + await renderComponentRoots(node, definitions); } // does this only apply to `` tags? if (node.content && node.content.childNodes && node.content.childNodes.length > 0) { - await renderComponentRoots(node.content); + await renderComponentRoots(node.content, definitions); } } return tree; } -async function registerDependencies(moduleURL) { +async function registerDependencies(moduleURL, definitions) { const moduleContents = await fs.readFile(moduleURL, 'utf-8'); walk.simple(acorn.parse(moduleContents, { @@ -65,7 +63,7 @@ async function registerDependencies(moduleURL) { if (!isBareSpecifier) { const dependencyModuleURL = new URL(node.source.value, moduleURL); - await registerDependencies(dependencyModuleURL); + await registerDependencies(dependencyModuleURL, definitions); } }, async ExpressionStatement(node) { @@ -101,8 +99,8 @@ async function getTagName(moduleURL) { return tagName; } -async function initializeCustomElement(elementURL, tagName, attrs = []) { - await registerDependencies(elementURL); +async function initializeCustomElement(elementURL, tagName, attrs = [], definitions = []) { + await registerDependencies(elementURL, definitions); // https://github.com/ProjectEvergreen/wcc/pull/67/files#r902061804 const { pathname } = elementURL; @@ -127,16 +125,15 @@ async function initializeCustomElement(elementURL, tagName, attrs = []) { } async function renderToString(elementURL) { - definitions = []; - + const definitions = []; const elementTagName = await getTagName(elementURL); - const elementInstance = await initializeCustomElement(elementURL); + const elementInstance = await initializeCustomElement(elementURL, undefined, undefined, definitions); const elementHtml = elementInstance.shadowRoot ? elementInstance.getInnerHTML({ includeShadowRoots: true }) : elementInstance.innerHTML; const elementTree = getParse(elementHtml)(elementHtml); - const finalTree = await renderComponentRoots(elementTree); + const finalTree = await renderComponentRoots(elementTree, definitions); const html = elementTagName ? ` <${elementTagName}> ${serialize(finalTree)} @@ -151,14 +148,14 @@ async function renderToString(elementURL) { } async function renderFromHTML(html, elements = []) { - definitions = []; + const definitions = []; for (const url of elements) { - await initializeCustomElement(url); + await initializeCustomElement(url, undefined, undefined, definitions); } const elementTree = getParse(html)(html); - const finalTree = await renderComponentRoots(elementTree); + const finalTree = await renderComponentRoots(elementTree, definitions); return { html: serialize(finalTree), diff --git a/test/cases/node-modules/node-modules.spec.js b/test/cases/node-modules/node-modules.spec.js new file mode 100644 index 00000000..8c9f32b1 --- /dev/null +++ b/test/cases/node-modules/node-modules.spec.js @@ -0,0 +1,33 @@ +/* + * Use Case + * Run wcc against a custom element using a dependency from node-modules + * + * User Result + * Should run without error. + * + * User Workspace + * src/ + * components/ + * events-list.js + * index.js + */ +import { expect } from 'chai'; +import { JSDOM } from 'jsdom'; +import { renderToString } from '../../../src/wcc.js'; + +describe('Run WCC For ', function() { + const LABEL = 'Custom Element w/ a node modules dependency'; + let dom; + + before(async function() { + const { html } = await renderToString(new URL('./src/index.js', import.meta.url)); + + dom = new JSDOM(html); + }); + + describe(LABEL, function() { + it('should not fail when a node module is imported in a custom element', function() { + expect(dom).to.not.be.undefined; + }); + }); +}); \ No newline at end of file diff --git a/test/cases/node-modules/src/components/events-list.js b/test/cases/node-modules/src/components/events-list.js new file mode 100644 index 00000000..0310b89a --- /dev/null +++ b/test/cases/node-modules/src/components/events-list.js @@ -0,0 +1,18 @@ +import fetch from 'node-fetch'; + +class EventsList extends HTMLElement { + async connectedCallback() { + if (!this.shadowRoot) { + const events = await fetch('http://www.analogstudios.net/api/v2/events').then(resp => resp.json()); + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = `