Skip to content

Commit

Permalink
Updates resource injector to inject inline styles.
Browse files Browse the repository at this point in the history
Refs #41.

The resource injector now processes the input HTML to look for any inline style annotations replaces them with a `<style />` tag containing the contents of the file.
  • Loading branch information
dgp1130 committed May 1, 2022
1 parent 42c22c6 commit d7f49f9
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/resource_injector/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ ts_library(
deps = [
":config",
"//common:fs",
"//common:prerender_annotation_walker",
"//common/models:prerender_annotation",
"@npm//node-html-parser",
],
)
Expand All @@ -69,6 +71,7 @@ ts_library(
":config",
":injector",
"//common:fs",
"//common/models:prerender_annotation",
"@npm//@types/jasmine",
],
)
Expand Down
31 changes: 30 additions & 1 deletion packages/resource_injector/injector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as fs from 'rules_prerender/common/fs';
import { HTMLElement, parse } from 'node-html-parser';
import { InjectorConfig, InjectScript, InjectStyle } from 'rules_prerender/packages/resource_injector/config';
import { isInlineStyle } from 'rules_prerender/common/models/prerender_annotation';
import { AnnotationNode, walkAllAnnotations } from 'rules_prerender/common/prerender_annotation_walker';

/**
* Parses the given HTML document and injects all the resources specified by the
Expand All @@ -26,7 +28,7 @@ export async function inject(html: string, config: InjectorConfig):
},
});

// Inject all resources.
// Inject all static resources.
for (const action of config) {
switch (action.type) {
case 'script': {
Expand All @@ -41,6 +43,9 @@ export async function inject(html: string, config: InjectorConfig):
}
}

// Inject `<style />` tags for each inline style annotation in the document.
await replaceInlineStyleAnnotations(walkAllAnnotations(root));

// Return the new document.
return root.toString();
}
Expand Down Expand Up @@ -115,6 +120,30 @@ async function injectStyle(root: HTMLElement, { path }: InjectStyle):
style.insertAdjacentHTML('afterend', '\n');
}

/**
* Replaces all inline style annotations with real `<style />` tags for the referenced
* file.
*/
async function replaceInlineStyleAnnotations(
nodes: Generator<AnnotationNode, void, void>,
): Promise<void> {
for (const node of nodes) {
const { annotation } = node;

// Only inline styles should still be in the HTML, everything else should have
// been extracted already.
if (!isInlineStyle(annotation)){
throw new Error(`Injector found an annotation which is not an inline style (actually ${
annotation.type}). This should have been handled earlier in the pipeline.\n${
JSON.stringify(annotation, null, 4)}`);
}

const inlineStyle = new HTMLElement('style', {});
inlineStyle.set_content(await fs.readFile(annotation.path, 'utf-8'));
node.replace(inlineStyle);
}
}

function assertNever(value: never): never {
throw new Error(`Unexpected call to \`assertNever()\` with value: ${
JSON.stringify(value)}`);
Expand Down
85 changes: 85 additions & 0 deletions packages/resource_injector/injector_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'jasmine';

import * as fs from 'rules_prerender/common/fs';
import { createAnnotation, StyleScope } from 'rules_prerender/common/models/prerender_annotation';
import { InjectorConfig } from 'rules_prerender/packages/resource_injector/config';
import { inject } from 'rules_prerender/packages/resource_injector/injector';

Expand Down Expand Up @@ -195,5 +196,89 @@ describe('injector', () => {
</html>
`.trim());
});

it('inlines style annotations', async () => {
const annotation = createAnnotation({
type: 'style',
path: 'foo.css',
scope: StyleScope.Inline,
});

const input = `
<!DOCTYPE html>
<html>
<head>
<title>Some title</title>
</head>
<body>
<h2>Hello, World!</h2>
<!-- ${annotation} -->
</body>
</html>
`.trim();

spyOn(fs, 'readFile').and.resolveTo('.foo { color: red; }');

const injected = await inject(input, []);
expect(fs.readFile).toHaveBeenCalledWith('foo.css', 'utf-8');

expect(injected).toBe(`
<!DOCTYPE html>
<html>
<head>
<title>Some title</title>
</head>
<body>
<h2>Hello, World!</h2>
<style>.foo { color: red; }</style>
</body>
</html>
`.trim());
});

it('throws on any annotations other than inline style annotations', async () => {
// Should fail when given a script annotation.
const scriptAnnotation = createAnnotation({
type: 'script',
path: 'foo.js',
});
const inputWithScriptAnnotation = `
<!DOCTYPE html>
<html>
<head>
<title>Some title</title>
</head>
<body>
<h2>Hello, World!</h2>
<!-- ${scriptAnnotation} -->
</body>
</html>
`.trim();
await expectAsync(inject(inputWithScriptAnnotation, []))
.toBeRejectedWithError(
/Injector found an annotation which is not an inline style/);

// Should fail when given a global style annotation.
const globalStyleAnnotation = createAnnotation({
type: 'style',
path: 'foo.css',
scope: StyleScope.Global,
});
const inputWithGlobalStyleAnnotation = `
<!DOCTYPE html>
<html>
<head>
<title>Some title</title>
</head>
<body>
<h2>Hello, World!</h2>
<!-- ${globalStyleAnnotation} -->
</body>
</html>
`.trim();
await expectAsync(inject(inputWithGlobalStyleAnnotation, []))
.toBeRejectedWithError(
/Injector found an annotation which is not an inline style/);
});
});
});

0 comments on commit d7f49f9

Please sign in to comment.