UNPKG

assetgraph

Version:

An auto discovery dependency graph based optimization framework for web pages and applications

151 lines (148 loc) 5.26 kB
module.exports = queryObj => { return function inlineHtmlTemplates(assetGraph) { const relationsToDetach = new Set(); const potentiallyOrphanedAssets = new Set(); for (const htmlAsset of assetGraph.findAssets( Object.assign( { type: 'Html', isInline: false, isFragment: false }, queryObj ) )) { const assetsBySeenIdAttribute = {}; const relationsToInline = []; assetGraph.eachAssetPostOrder( htmlAsset, { type: { $nin: ['HtmlAnchor', 'HtmlMetaRefresh'] } }, asset => { if (asset.type === 'JavaScript') { relationsToInline.push( ...assetGraph.findRelations({ from: asset, type: { $nin: ['JavaScriptGetText', 'JavaScriptTrHtml'] }, to: { type: 'Html', extension: '.ko', isInline: false, isLoaded: true } }) ); } else if ( asset.type === 'Html' && asset.nonInlineAncestor.extension === '.ko' && asset.isFragment ) { relationsToInline.push( ...assetGraph.findRelations({ from: asset, type: 'HtmlInlineScriptTemplate', to: { isLoaded: true } }) ); if (asset.isLoaded) { for (const element of Array.from( asset.parseTree.getElementsByTagName('*') )) { if ( element.hasAttribute('id') && !relationsToInline.some( relationToInline => relationToInline.node === element ) ) { const id = element.getAttribute('id'); if (id) { (assetsBySeenIdAttribute[id] = assetsBySeenIdAttribute[id] || []).push(asset); } } } } } } ); const alreadyInlinedById = {}; for (const htmlInlineScriptTemplate of assetGraph.findRelations({ from: htmlAsset, type: 'HtmlInlineScriptTemplate' })) { const id = htmlInlineScriptTemplate.node.getAttribute('id'); if (id) { alreadyInlinedById[id] = true; } } const document = htmlAsset.parseTree; for (const relationToInline of relationsToInline) { relationsToDetach.add(relationToInline); let id; if (relationToInline.type === 'HtmlInlineScriptTemplate') { id = relationToInline.node.getAttribute('id'); } else { id = relationToInline.to.url .split('/') .pop() .replace(/\..*$/, ''); // Strip .ko or .<localeId>.ko extension } const existingAttributes = relationToInline.type === 'HtmlInlineScriptTemplate' && relationToInline.node.attributes; if (!id || !alreadyInlinedById[id]) { if (id && assetsBySeenIdAttribute[id]) { assetGraph.warn( new Error( `${`inlineTemplates: Inlining ${relationToInline.to.urlOrDescription} ` + `into a <script> in ${htmlAsset.urlOrDescription} ` + `with an id attribute of ${id}, which already exists in these assets:` + '\n '}${assetsBySeenIdAttribute[id] .map(asset => asset.urlOrDescription) .join('\n ')}` ) ); } potentiallyOrphanedAssets.add(relationToInline.to); const node = document.createElement('script'); node.setAttribute('type', 'text/html'); if (id) { node.setAttribute('id', id); } if (existingAttributes) { for (const attribute of Array.from(existingAttributes)) { if (attribute.name !== 'type' && attribute.name !== 'id') { node.setAttribute(attribute.name, attribute.value); } } } document.head.appendChild(node); const htmlInlineScriptTemplate = htmlAsset.addRelation({ type: 'HtmlInlineScriptTemplate', to: relationToInline.to, node }); htmlInlineScriptTemplate.inline(); // These are attached separately for (const template of assetGraph.findRelations({ from: htmlInlineScriptTemplate.to, type: 'HtmlInlineScriptTemplate' })) { template.detach(); } if (id) { alreadyInlinedById[id] = true; } } } } for (const relation of relationsToDetach) { relation.detach(); } // Template assets that had more than one incoming relation to begin with will // be cloned in the .inline() call above. Remove the template assets that have // become orphans: for (const asset of potentiallyOrphanedAssets) { if (assetGraph.findRelations({ to: asset }).length === 0) { assetGraph.removeAsset(asset); } } }; };