Skip to content

Commit

Permalink
Add mechanism for arbitrary head tags (mainmatter#659)
Browse files Browse the repository at this point in the history
* dedicated class for writing head tags
* render head tags in SSR build
* add static meta tags
* add og:title meta tag
* add canonical url tag
* fix object iteration
* add article published time meta tag
* render meta date for blog posts
* format
* fix prerender build
  • Loading branch information
marcoow authored and pichfl committed Jul 30, 2019
1 parent c00fd83 commit b70f70f
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
lib/generate-blog-components/lib/files/**/*
lib/generate-blog/lib/templates/**/*
lib/global-css/css/vendor/normalize.css
src/ui/components/Header/template.hbs
2 changes: 1 addition & 1 deletion ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class SimplabsApp extends GlimmerApp {
});
let ssrTree = this.ssrTree();

let appTree = new MergeTrees([jsTree, ssrTree]);
let appTree = new MergeTrees([jsTree, ssrTree], { overwrite: true });
return new Rollup(appTree, {
rollup: {
input: 'ssr/index.js',
Expand Down
7 changes: 5 additions & 2 deletions lib/component-generation/prepare-templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ module.exports = function prepareTemplates(folder) {
.reduce((acc, folder) => {
let component = path.basename(folder);
acc[component] = ['template', 'stylesheet'].reduce((acc, template) => {
let source = fs.readFileSync(path.join(folder, `${template}.hbs`)).toString();
acc[template] = handlebars.compile(source);
let file = path.join(folder, `${template}.hbs`);
if (fs.existsSync(file)) {
let source = fs.readFileSync(file).toString();
acc[template] = handlebars.compile(source);
}
return acc;
}, {});
return acc;
Expand Down
4 changes: 1 addition & 3 deletions lib/generate-blog/lib/components-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,12 @@ module.exports = class ComponentsBuilder extends BaseComponentsBuilder {

_writePostComponent(post, related) {
let data = this._preparePostTemplateData(post);
data.componentName = post.componentName;
if (related) {
data.related = this._preparePostTemplateData(related);
}
let componentTemplate = this.templates.post.template(data);

data = {
componentName: post.componentName,
};
let componentCssBlock = this.templates.post.stylesheet(data);

this.writeComponent(post.componentName, componentTemplate, componentCssBlock);
Expand Down
1 change: 1 addition & 0 deletions lib/generate-blog/lib/templates/post/template.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<div>
<HeadTag @name='meta' @keys=\{{hash property="article:published_time"}} @values=\{{hash content="{{isoDate}}"}} />
<script type="application/ld+json">
{
"@type": "BlogPosting",
Expand Down
3 changes: 3 additions & 0 deletions lib/head-data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ module.exports = {
<meta name="language" content="en" />
<meta name="content-language" content="en" />
<meta name="publisher" content="simplabs GmbH" />
<meta property="fb:admins" content="699569440119973" />
<meta property="og:site_name" content="simplabs" />
<meta name="twitter:site" content="@simplabs">
<link type="application/atom+xml" rel="alternate" href="https://simplabs.com/feed.xml" title="simplabs Blog" />
`;
} else {
Expand Down
4 changes: 2 additions & 2 deletions scripts/prerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ let server = express();
server.get('*', async function(req, res, next) {
if (req.headers.accept && req.headers.accept.includes('text/html')) {
let origin = `${req.protocol}://${req.headers.host}`;
let { body, title } = await renderer.render(origin, req.url);
let { body, head } = await renderer.render(origin, req.url);
let shoeboxBundlePreloads = buildShoeboxBundlePreloads(body);
let html = HTML.replace('<div id="app"></div>', body)
.replace(/<title>[^<]*<\/title>/, `<title>${title}</title>`)
.replace(/<head>/, `<head>\n${head}`)
.replace('<link', `${shoeboxBundlePreloads}\n<link`);
res.send(html);
} else {
Expand Down
13 changes: 13 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { classnames } from '@css-blocks/glimmer/dist/cjs/src/helpers/classnames'
import { concat } from '@css-blocks/glimmer/dist/cjs/src/helpers/concat';
import { ComponentManager, setPropertyDidChange } from '@glimmer/component';
import App from './main';
import HeadTags from './utils/head-tags';
import hash from './utils/helpers/hash';

const containerElement = document.getElementById('app');
const hasSSRBody = !!document.querySelector('[data-has-ssr-response]');
Expand Down Expand Up @@ -53,13 +55,24 @@ app.registerInitializer({
'lazyRegistration',
`utils:/${app.rootName}/lazy-registration/main`,
);

registry.register(
`utils:/${app.appName}/head-tags/main`,
HeadTags
);
registry.registerInjection(
`component`,
'headTags',
`utils:/${app.appName}/head-tags/main`,
);
}
});

app.registerInitializer({
initialize(registry) {
register(registry, `helper:/${app.rootName}/components/-css-blocks-classnames`, classnames);
register(registry, `helper:/${app.rootName}/components/-css-blocks-concat`, concat);
register(registry, `helper:/${app.rootName}/components/hash`, hash);
}
});

Expand Down
17 changes: 17 additions & 0 deletions src/ui/components/HeadTag/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Component from '@glimmer/component';

export default class HeadTag extends Component {
constructor(options) {
super(options);

this.setMetaTags();
}

public willDestroy() {
this.headTags.remove(this.args.name, this.args.keys);
}

private setMetaTags() {
this.headTags.write(this.args.name, this.args.keys, this.args.values, this.args.content);
}
}
3 changes: 3 additions & 0 deletions src/ui/components/HeadTag/stylesheet.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:scope {
block-name: HeadTag;
}
Empty file.
21 changes: 16 additions & 5 deletions src/ui/components/Simplabs/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default class Simplabs extends Component {
let options: INavigoHooks = {
after: () => {
this._setPageTitle(title);
this._setCanonicalUrl(path);
if (!this.appState.isSSR) {
window.scrollTo(0, 0);
}
Expand Down Expand Up @@ -206,11 +207,21 @@ export default class Simplabs extends Component {
}

private _setPageTitle(title) {
if (this.appState.isSSR) {
this.document.title = formatPageTitle(title);
} else {
document.title = formatPageTitle(title);
}
this.headTags.write('title', {}, {}, formatPageTitle(title));
this.headTags.write('meta', {
name: 'twitter:title',
property: 'og:title',
}, {
content: title,
});
}

private _setCanonicalUrl(path) {
this.headTags.write('meta', {
property: 'og:url',
}, {
content: `https://simplabs.com${path}`,
});
}

private _injectActiveComponentState() {
Expand Down
49 changes: 49 additions & 0 deletions src/utils/head-tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export default class HeadTags {


public static create(): HeadTags {
return new HeadTags();
}
private document: HTMLDocument = window.document;

public write(tagName, keyAttrs = {}, contentAttrs = {}, textContent = null) {
let element = this.getElement(tagName, keyAttrs);

let attrs = {
...keyAttrs,
...contentAttrs
};
for (let attr of Object.keys(attrs)) {
element.setAttribute(attr, attrs[attr]);
}
if (textContent) {
element.textContent = textContent;
}

this.document.head.appendChild(element);
}

public remove(tagName, keyAttrs = {}) {
let element = this.getElement(tagName, keyAttrs);

this.document.head.removeChild(element);
}

private getElement(tagName, keyAttrs): Element {
let selector = buildSelector(tagName, keyAttrs);
let element = this.document.querySelector(selector);
if (!element) {
element = this.document.createElement(tagName);
}
return element;
}
}

function buildSelector(tagName, attrs): string {
let selector = `head > ${tagName}`;
if (Object.keys(attrs).length > 0) {
let attrSelectors = Object.keys(attrs).map((attr) => `[${attr}="${attrs[attr]}"]`);
selector = `${selector}${attrSelectors.join('')}`;
}
return selector;
}
3 changes: 3 additions & 0 deletions src/utils/helpers/hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function(params, named) {
return named;
}
22 changes: 21 additions & 1 deletion ssr/ssr-application.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import DynamicScope from './dynamic-scope';
import SSRComponentManager from './ssr-component-manager';
import SSRDOMTreeConstruction from './ssr-dom-tree-construction';
import SSRHeadTags from './ssr-head-tags';
import hash from '../src/utils/helpers/hash';

import { classnames } from '@css-blocks/glimmer/dist/cjs/src/helpers/classnames';
import { concat } from '@css-blocks/glimmer/dist/cjs/src/helpers/concat';
Expand Down Expand Up @@ -59,6 +61,10 @@ export default class SSRApplication extends Application {
`helper:/${rootName}/components/-css-blocks-concat`
] = concat;

registry._resolver.registry._entries[
`helper:/${rootName}/components/hash`
] = hash;

// inject appendOperations into environment in order to get working createElement and setAttribute.
registry.register(
`domTreeConstruction:/${rootName}/main/main`,
Expand Down Expand Up @@ -92,6 +98,20 @@ export default class SSRApplication extends Application {
`component-manager:/${rootName}/component-managers/main`,
SSRComponentManager
);
registry.register(
`utils:/${rootName}/head-tags/main`,
SSRHeadTags
);
registry.registerInjection(
`utils:/${rootName}/head-tags/main`,
'document',
`document:/${rootName}/main/main`
);
registry.registerInjection(
`component`,
'headTags',
`utils:/${rootName}/head-tags/main`,
);
},
});
this.initialize();
Expand Down Expand Up @@ -134,7 +154,7 @@ export default class SSRApplication extends Application {
let document = this.document as any;
return {
body: this.serializer.serializeChildren(document.body) as string,
title: document.title as string
head: this.serializer.serializeChildren(document.head) as string
};
}
}
34 changes: 34 additions & 0 deletions ssr/ssr-head-tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// tslint:disable-next-line:no-var-requires
const SimpleDOM = require('simple-dom');

export default class SSRHeadTags {
private document: SimpleDOM.Document;

constructor(options) {
this.document = options.document;
}

public static create(options): SSRHeadTags {
return new SSRHeadTags(options);
}

public write(tagName, keyAttrs = {}, contentAttrs = {}, textContent = null) {
let element = this.document.createElement(tagName);

let attrs = {
...keyAttrs,
...contentAttrs
};
for (let attr in attrs) {
element.setAttribute(attr, attrs[attr]);
}

if (textContent) {
let text = this.document.createTextNode(textContent);
element.appendChild(text);
}

this.document.head.appendChild(element);
}
}
}
1 change: 0 additions & 1 deletion tests/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Simplabs</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">

Expand Down

0 comments on commit b70f70f

Please sign in to comment.