polymer-build
Version:
A library of Gulp build tasks
85 lines (76 loc) • 2.98 kB
text/typescript
import * as dom5 from 'dom5/lib/index-next';
import {predicates as p} from 'dom5/lib/index-next';
import * as parse5 from 'parse5';
import * as url from 'url';
import File = require('vinyl');
import {AsyncTransformStream, getFileContents} from './streams';
const attrValueMatches = (attrName: string, regex: RegExp) => {
return (node: parse5.ASTNode) => {
const attrValue = dom5.getAttribute(node, attrName);
return attrValue != null && regex.test(attrValue);
};
};
const webcomponentsLoaderRegex = /\bwebcomponents\-(loader|lite|bundle)\.js\b/;
const webcomponentsLoaderMatcher = p.AND(
p.hasTagName('script'), attrValueMatches('src', webcomponentsLoaderRegex));
/**
* Wraps `addCustomElementsEs5Adapter()` in a `stream.Transform`.
*/
export class CustomElementsEs5AdapterInjector extends
AsyncTransformStream<File, File> {
constructor() {
super({objectMode: true});
}
protected async *
_transformIter(files: AsyncIterable<File>): AsyncIterable<File> {
for await (const file of files) {
if (file.contents === null || file.extname !== '.html') {
yield file;
continue;
}
const contents = await getFileContents(file);
const updatedContents = addCustomElementsEs5Adapter(contents);
if (contents === updatedContents) {
yield file;
} else {
const updatedFile = file.clone();
updatedFile.contents = Buffer.from(updatedContents, 'utf-8');
yield updatedFile;
}
}
}
}
/**
* Please avoid using this function because the API is likely to change. Prefer
* the interface provided by `PolymerProject.addCustomElementsEs5Adapter`.
*
* When compiling ES6 classes down to ES5 we need to include a special shim so
* that compiled custom elements will still work on browsers that support native
* custom elements.
*
* TODO(fks) 03-28-2017: Add tests.
*/
export function addCustomElementsEs5Adapter(html: string): string {
// Only modify this file if we find a web components polyfill. This is a
// heuristic to identify the entry point HTML file. Ultimately we should
// explicitly transform only the entry point by having the project config.
if (!webcomponentsLoaderRegex.test(html)) {
return html;
}
const parsed = parse5.parse(html, {locationInfo: true});
const script = dom5.query(parsed, webcomponentsLoaderMatcher);
if (!script) {
return html;
}
// Collect important dom references & create fragments for injection.
const loaderScriptUrl = dom5.getAttribute(script, 'src')!;
const adapterScriptUrl =
url.resolve(loaderScriptUrl, 'custom-elements-es5-adapter.js');
const es5AdapterFragment = parse5.parseFragment(`
<script>if (!window.customElements) { document.write('<!--'); }</script>
<script type="text/javascript" src="${adapterScriptUrl}"></script>
<!--! do not remove -->
`);
dom5.insertBefore(script.parentNode!, script, es5AdapterFragment);
return parse5.serialize(parsed);
}