Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Created April 30, 2019 05:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathantneal/884bd93d064c77ecdeb8825227588050 to your computer and use it in GitHub Desktop.
Save jonathantneal/884bd93d064c77ecdeb8825227588050 to your computer and use it in GitHub Desktop.
Web Components SSR WIP
function getReplacementIdentifiers (globals, types) {
return Object.keys(Object(globals)).reduce(
(object, name) => Object.assign(object, {
[name]: module.exports.createMemberExpressionFromString(globals[name], types)
}),
{}
);
}
function getImportsBySource (globals) {
return Object.keys(Object(globals)).reduce(
(object, src) => Object.keys(globals[src]).reduce(
(object, name) => {
object[name] = object[name] || {};
object[name].source = src;
object[name].name = Array.isArray(globals[src][name]) ? globals[src][name][0] : 'default';
return object;
},
object
),
{}
);
}
function createMemberExpressionFromString (string, types) {
return string.split('.').reduce(
(node, identifier) => node
? types.memberExpression(node, types.identifier(identifier))
: types.identifier(identifier),
null
);
}
function isReplaceableIdentifier (path, replacementIdentifiers) {
return replacementIdentifiers.hasOwnProperty(path.node.name) &&
!path.parentPath.isMemberExpression({ property: path.node })
}
function isImportableIdentifier (path, importIdentifiers) {
return importIdentifiers.hasOwnProperty(path.node.name) &&
!path.parentPath.isMemberExpression({ property: path.node })
}
/*
Example Usage:
babel.transformSync(code, {
plugins: [
[ transformGlobals, options ]
]
});
Example Options:
{
// import window, { document, create as $ } from 'path/to/import.js'
import: { 'path/to/import.js': { window: 'window', document: ['document'], $: ['create'] } }
}
*/
function transformGlobals (api, opts) {
const replacementIdentifiers = getReplacementIdentifiers(opts.replace, api.types);
const importIdentifiers = getImportsBySource(opts.import);
const importNodes = {};
return {
visitor: {
Identifier (path) {
if (isReplaceableIdentifier(path, replacementIdentifiers)) {
const name = path.node.name;
const replacementIdentifier = replacementIdentifiers[name];
path.replaceWith(replacementIdentifier);
}
if (isImportableIdentifier(path, importIdentifiers)) {
const alias = path.node.name;
const source = importIdentifiers[alias].source;
importNodes[source] = importNodes[source] || {};
importNodes[source][alias] = importIdentifiers[alias].name;
}
},
Program: {
exit (path, { file }) {
Object.keys(importNodes).forEach(source => {
const importDeclaration = api.types.importDeclaration(
Object.keys(importNodes[source]).map(
name => importNodes[source][name] === 'default'
? api.types.importDefaultSpecifier(
api.types.identifier(name)
)
: api.types.importSpecifier(
api.types.identifier(name),
api.types.identifier(importNodes[source][name])
)
),
api.types.stringLiteral(source)
);
file.set('ourPath', path.unshiftContainer('body', importDeclaration)[0]);
});
}
}
}
}
}
/*
Example Usage:
createContextualFragment(`<custom-element foo>Hello World</custom-element>`);
*/
function createContextualFragment (innerHTML) {
const documentFragment = document.createDocumentFragment();
documentFragment.appendChild(JSDOM.fragment(innerHTML));
return documentFragment.cloneNode(true);
}
/*
Example Usage:
nodeToString(someElementOrDocumentFragmentOrShadowRoot)
*/
function nodeToString (node) {
return node.shadowRoot
? shadowHostToString(node)
: node.nodeName === 'SLOT' && 'host' in node.getRootNode()
? slotToString(node)
: 'outerHTML' in node
? node.outerHTML
: Object(node.childNodes).length
? nodesToString(node.childNodes)
: node.nodeValue || '';
}
function shadowHostToString (element) {
return `<${element.localName}${attributesToString(element.attributes)}>${
nodesToString(element.shadowRoot.childNodes)
}</${element.localName}>`;
}
function attributesToString (attributes) {
return Object(attributes).length
? ` ${Array.prototype.map.call(
attributes,
attribute => `${attribute.name}${attribute.value ? `="${attribute.value}"` : ''}`
)}`
: '';
}
function nodesToString (childNodes) {
return Array.prototype.map.call(Object(childNodes), nodeToString).join('')
}
function slotToString (element) {
return `<${element.localName}${attributesToString(element)}>${
element.assignedNodes().map(nodeToString).join('')
}</${element.localName}>`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment