New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
POC: Adzerk Fast Fetch with template based preferential render #12765
Conversation
This reverts commit 2134f8c.
} | ||
|
||
AMP.extension('amp-ad-template', '0.1', AMP => { | ||
AMP.registerElement('amp-ad-template', AmpAdTemplate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should not be an extension. it can be just another file in amp-a4a/0.1/
folder for code sharing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/** @const {string} */ | ||
const TAG = 'amp-ad-template'; | ||
|
||
export class AmpAdTemplate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming: AmpAdTemplates
since this is more like a template manager class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/** | ||
* Fetch and parse template from AMP cache. Result is stored in global in | ||
* order to reduce overhead when template is used multiple times. | ||
* @param {number} templateId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed, let's use a templateUrl string.
For example,
Template canonical URL: https://adserver.com/amp_template_1
=>
Template proxy URL https://adserver-com.cdn.ampproject.org/a/s/adserver.com/amp_template_1
The domain name in the proxy URL serves as namespace and the path serves as an ID.
It avoids all the hassles for ad network to submit templates and manage IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* @param {!Element} element Parent element containing template. | ||
* @param {!Window=} opt_window | ||
*/ | ||
populateTemplate(templateValues, element, opt_window) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming nitpick: render()
can we merge element and opt_window?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
import {dev} from '../../../src/log'; | ||
import {getMode} from '../../../src/mode'; | ||
import {getAmpdoc} from '../../../src/service'; | ||
import {Bind} from '../../amp-bind/0.1/bind-impl'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will compile the whole amp-bind extension into this extension
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can I avoid doing so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to get the binder from service.
@choumx correct me if I'm wrong
try: adoptServiceForEmbedIfEmbeddable()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think this is the way. I get the following error:
Error: Service amp-bind not found on parent or doesn't implement EmbeddableService.
Even if this did work, I'd still need some way to call setState()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should retrieve services from the corresponding function in src/services.js --
in this case, bindForDocOrNull
.
* @param {number} templateId | ||
* @return {!CachedTemplateDef} | ||
*/ | ||
retrieveTemplate(templateId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming nitpick: fetch(templateUrl)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
}; | ||
const cacheKeys = /**@type {!Array<number>}*/ | ||
(Object.keys(this.templateCache_)); | ||
if (cacheKeys.length > 5) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe worth to implement a separate helper class: LcuCache?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
this.ampCreativeJson_ = null; | ||
|
||
ampAdTemplate = ampAdTemplate || | ||
new AmpAdTemplate(this.win, this.parseTemplate_.bind(this)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of passing in parseTemplate function in constructor, can you do:
AmpAdTemplates.fetch(url).then(t => {
this.parseTemplate_(t);
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
class LRUCache { | ||
/** @param {number} capacity */ | ||
constructor(capacity) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can pass in a generator
function in constructor. Then we only need one public method for the LRUCache: get()
Also, worth to extract the code into src/utils/cache.js
for sharing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really sure how a generator
would fit in here. Can you give an example of how you imagine the cache being used? If it's not too strong a preference on your end, I'd prefer to keep it with get/put.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class LRUCache {
constructor(capacity, generator);
get(id) {
...
if (!this.cache_[id]) {
this.cache_[id] = generator(id);
}
return this.cache_[id];
}
}
something like this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm still not understanding. Who calls next()
, and how does this eliminate the need for put()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is holding up release. Let's change this in a future PR. I don't think anyone will be using this code for a while anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it will be used like:
constructor() {
this.cache_ = new LRUCache(5, url => {
return Services.xhrFor(this.win_).fetchText(url);
});
}
...
fetch(url) {
return this.cache_.get(url);
}
there are other more important comments not addressed in this PR, so realistically, i don't think it should hold the release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to mark some of the comments as done. Will address the others shortly.
There is a couple of remaining comments unaddressed. Please reply to each of them when done. |
Is the service called amp-bind or bind? I'm not with my laptop. Please
check.
…On Jan 23, 2018 16:39, "glevitzky" ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In extensions/amp-ad-template/0.1/amp-ad-template.js
<#12765 (comment)>:
> + *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {urls} from '../../../src/config';
+import {Services} from '../../../src/services';
+import {dev} from '../../../src/log';
+import {getMode} from '../../../src/mode';
+import {getAmpdoc} from '../../../src/service';
+import {Bind} from '../../amp-bind/0.1/bind-impl';
Don't think this is the way. I get the following error:
Error: Service amp-bind not found on parent or doesn't implement
EmbeddableService.
Even if this did work. I'd still need some way to call setState().
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#12765 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ANd2kEZEUlW0s9btXemvVNfDJoaqYuafks5tNnutgaJpZM4RZ0M3>
.
--
You received this message because you are subscribed to the Google Groups
"amphtml-eng-github" group.
To unsubscribe from this group and stop receiving emails from it, send an
email to ***@***.***
To post to this group, send email to ***@***.***
To view this discussion on the web visit https://groups.google.com/a/
google.com/d/msgid/amphtml-eng-github/ampproject/amphtml/
pull/12765/review/91032891%40github.com
<https://groups.google.com/a/google.com/d/msgid/amphtml-eng-github/ampproject/amphtml/pull/12765/review/91032891%40github.com?utm_medium=email&utm_source=footer>
.
--
You received this message because you are subscribed to the Google Groups
"amphtml-eng" group.
To unsubscribe from this group and stop receiving emails from it, send an
email to ***@***.***
To post to this group, send email to ***@***.***
To view this discussion on the web visit https://groups.google.com/a/
google.com/d/msgid/amphtml-eng/ampproject/amphtml/pull/
12765/review/91032891%40github.com
<https://groups.google.com/a/google.com/d/msgid/amphtml-eng/ampproject/amphtml/pull/12765/review/91032891%40github.com?utm_medium=email&utm_source=footer>
.
|
Yep, you will need setState.
…On Jan 23, 2018 16:52, "Hongfei Ding (丁鸿飞)" ***@***.***> wrote:
Is the service called amp-bind or bind? I'm not with my laptop. Please
check.
On Jan 23, 2018 16:39, "glevitzky" ***@***.***> wrote:
> ***@***.**** commented on this pull request.
> ------------------------------
>
> In extensions/amp-ad-template/0.1/amp-ad-template.js
> <#12765 (comment)>:
>
> > + *
> + * http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS-IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +import {urls} from '../../../src/config';
> +import {Services} from '../../../src/services';
> +import {dev} from '../../../src/log';
> +import {getMode} from '../../../src/mode';
> +import {getAmpdoc} from '../../../src/service';
> +import {Bind} from '../../amp-bind/0.1/bind-impl';
>
> Don't think this is the way. I get the following error:
>
> Error: Service amp-bind not found on parent or doesn't implement
> EmbeddableService.
>
> Even if this did work. I'd still need some way to call setState().
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <#12765 (comment)>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ANd2kEZEUlW0s9btXemvVNfDJoaqYuafks5tNnutgaJpZM4RZ0M3>
> .
>
> --
> You received this message because you are subscribed to the Google Groups
> "amphtml-eng-github" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to ***@***.***
> To post to this group, send email to ***@***.***
> To view this discussion on the web visit https://groups.google.com/a/go
> ogle.com/d/msgid/amphtml-eng-github/ampproject/amphtml/pull
> /12765/review/91032891%40github.com
> <https://groups.google.com/a/google.com/d/msgid/amphtml-eng-github/ampproject/amphtml/pull/12765/review/91032891%40github.com?utm_medium=email&utm_source=footer>
> .
>
> --
> You received this message because you are subscribed to the Google Groups
> "amphtml-eng" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to ***@***.***
> To post to this group, send email to ***@***.***
> To view this discussion on the web visit https://groups.google.com/a/go
> ogle.com/d/msgid/amphtml-eng/ampproject/amphtml/pull/12765/
> review/91032891%40github.com
> <https://groups.google.com/a/google.com/d/msgid/amphtml-eng/ampproject/amphtml/pull/12765/review/91032891%40github.com?utm_medium=email&utm_source=footer>
> .
>
|
Doesn't seem to matter. Using 'bind' instead of 'amp-bind', I get `Error:
Service bind not found on parent or doesn't implement EmbeddableService.`.
On Tue, Jan 23, 2018 at 7:53 PM, amphtml-team <notifications@github.com>
wrote:
… Yep, you will need setState.
On Jan 23, 2018 16:52, "Hongfei Ding (丁鸿飞)" ***@***.***>
wrote:
> Is the service called amp-bind or bind? I'm not with my laptop. Please
> check.
>
> On Jan 23, 2018 16:39, "glevitzky" ***@***.***> wrote:
>
>> ***@***.**** commented on this pull request.
>> ------------------------------
>>
>> In extensions/amp-ad-template/0.1/amp-ad-template.js
>> <#12765 (comment)
>:
>>
>> > + *
>> + * http://www.apache.org/licenses/LICENSE-2.0
>> + *
>> + * Unless required by applicable law or agreed to in writing, software
>> + * distributed under the License is distributed on an "AS-IS" BASIS,
>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
>> + * See the License for the specific language governing permissions and
>> + * limitations under the License.
>> + */
>> +
>> +import {urls} from '../../../src/config';
>> +import {Services} from '../../../src/services';
>> +import {dev} from '../../../src/log';
>> +import {getMode} from '../../../src/mode';
>> +import {getAmpdoc} from '../../../src/service';
>> +import {Bind} from '../../amp-bind/0.1/bind-impl';
>>
>> Don't think this is the way. I get the following error:
>>
>> Error: Service amp-bind not found on parent or doesn't implement
>> EmbeddableService.
>>
>> Even if this did work. I'd still need some way to call setState().
>>
>> —
>> You are receiving this because you are subscribed to this thread.
>> Reply to this email directly, view it on GitHub
>> <#12765 (comment)
>,
>> or mute the thread
>> <https://github.com/notifications/unsubscribe-auth/
ANd2kEZEUlW0s9btXemvVNfDJoaqYuafks5tNnutgaJpZM4RZ0M3>
>> .
>>
>> --
>> You received this message because you are subscribed to the Google
Groups
>> "amphtml-eng-github" group.
>> To unsubscribe from this group and stop receiving emails from it, send
an
>> email to ***@***.***
>> To post to this group, send email to ***@***.***
>> To view this discussion on the web visit https://groups.google.com/a/go
>> ogle.com/d/msgid/amphtml-eng-github/ampproject/amphtml/pull
>> /12765/review/91032891%40github.com
>> <https://groups.google.com/a/google.com/d/msgid/amphtml-
eng-github/ampproject/amphtml/pull/12765/review/91032891%
40github.com?utm_medium=email&utm_source=footer>
>> .
>>
>> --
>> You received this message because you are subscribed to the Google
Groups
>> "amphtml-eng" group.
>> To unsubscribe from this group and stop receiving emails from it, send
an
>> email to ***@***.***
>> To post to this group, send email to ***@***.***
>> To view this discussion on the web visit https://groups.google.com/a/go
>> ogle.com/d/msgid/amphtml-eng/ampproject/amphtml/pull/12765/
>> review/91032891%40github.com
>> <https://groups.google.com/a/google.com/d/msgid/amphtml-
eng/ampproject/amphtml/pull/12765/review/91032891%
40github.com?utm_medium=email&utm_source=footer>
>> .
>>
>
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#12765 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ATkVidBGzwQu317ZzG5l3NmwovAgiUbAks5tNn8TgaJpZM4RZ0M3>
.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes!
examples/adzerk.amp.html
Outdated
<div fallback></div> | ||
</amp-ad> | ||
|
||
<!--<amp-ad width=300 height=250 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uncomment them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* amp-mustache. AMP creative response will consist of the following JSON | ||
* object with two fields: | ||
* | ||
* - ampCreativeTemplateId: number value for template ID. Template must already |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we follow the naming in the design doc?
templateUrl,
data,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
*/ | ||
getTemplateProxyUrl_(url) { | ||
const loc = parseUrl(url); | ||
const hostClean = startsWith(loc.host, 'www.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to remove www.
.
const hostClean = startsWith(loc.host, 'www.') | ||
? loc.host.slice(4) | ||
: loc.host; | ||
return loc.protocol + '//' + hostClean.replace('.', '-') + '.' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please refer to AMP cache URL format
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably won't fix until morning, but besides removing 'www', what's wrong with this format?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe not clear from the code, but this converts https://adserver.com/amp_template_1
to https://adserver-com.cdn.ampproject.org/a/s/adserver.com/amp_template_1
as mentioned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Converting the AMP document domain from IDN (Punycode) to UTF-8.
- Replacing every "-" (dash) with "--"(2 dashes).
- Replacing every "." (dot) with a "-" (dash).
- Converting back to IDN (Punycode).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* string value used to dynamically update the template | ||
* | ||
* Additionally, ad response must include header indicating AMP creative | ||
* template response: AMP-template-amp-creative: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of having the header value to be true
, we can put amp-mustache
, to differentiate templating languages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
*/ | ||
getTemplateProxyUrl_(url) { | ||
const loc = parseUrl(url); | ||
return loc.protocol + '//' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
proxy URL is always https
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
checkStillCurrent(); | ||
this.ampCreativeJson_ = /** @type {!AmpTemplateCreativeDef} */ | ||
(tryParseJson(body) || {}); | ||
const proxyUrl = getMode(this.win).localDev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you move the proxy URL logic into AmpAdTemplates for sharing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const loc = parseUrl(url); | ||
return loc.protocol + '//' + | ||
loc.hostname.replace(/-/g, '--').replace(/\./g, '-') + | ||
'.' + urls.cdn.slice(8) + '/a/s/' + loc.hostname + loc.pathname; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use /c/s/
for now, before AMP cache supports /a/s/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. good to go, thanks for the work!
/** | ||
* Fetch and parse template from AMP cache. Result is stored in global in | ||
* order to reduce overhead when template is used multiple times. | ||
* @param {string} templateUrl CDN Proxy URL to template. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
canonical URL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
build-system/app.js
Outdated
`0.1/data/${match[2]}.template`; | ||
fs.readFileAsync(filePath).then(file => { | ||
res.setHeader('Content-Type', 'application/json'); | ||
res.setHeader('AMP-template-amp-creative', 'true'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
amp-mustache
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
build-system/app.js
Outdated
pc.cwd() + '/extensions/amp-ad-network-adzerk-impl/0.1/data/' + match[1]; | ||
fs.readFileAsync(filePath).then(file => { | ||
res.setHeader('Content-Type', 'application/json'); | ||
res.setHeader('AMP-template-amp-creative', 'true'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
amp-mustache
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
…oject#12765) * POC: Adzerk Fast Fetch with template based preferential render * fix dep check * fix test failures * Using templating service. * yarn.lock revert * yarn.lock * yarn.lock * Attempt ampproject#4 at yarn fix... * Test fix. * Update mock templates to use <template>s. * Reverting mode-object. * Yarrrrn * Moving toward amp-bind. * amp-bind integration. * Removing the need for amp-state. * Revert "Removing the need for amp-state." This reverts commit 2134f8c. * Modifying transformed template. * Factored our ad templating into new extension. * Deleting test file. * Lint fixes and other minutiea. * Added comments. * PR feedback. * Removed unused function. * Added tests for templates and cache. * Adding tests. * presubmit fixes. * Reverting to amp-mustache. * Url transforming, uncommenting, and other feedback. * Removing duplicate files. * Moving url transform to amp-ad-templates. * Test fixes * header values + minor comment change.
For #12903 |
…oject#12765) * POC: Adzerk Fast Fetch with template based preferential render * fix dep check * fix test failures * Using templating service. * yarn.lock revert * yarn.lock * yarn.lock * Attempt #4 at yarn fix... * Test fix. * Update mock templates to use <template>s. * Reverting mode-object. * Yarrrrn * Moving toward amp-bind. * amp-bind integration. * Removing the need for amp-state. * Revert "Removing the need for amp-state." This reverts commit 2134f8c. * Modifying transformed template. * Factored our ad templating into new extension. * Deleting test file. * Lint fixes and other minutiea. * Added comments. * PR feedback. * Removed unused function. * Added tests for templates and cache. * Adding tests. * presubmit fixes. * Reverting to amp-mustache. * Url transforming, uncommenting, and other feedback. * Removing duplicate files. * Moving url transform to amp-ad-templates. * Test fixes * header values + minor comment change.
Proof of concept in order to determine feasibility of template based AMP creatives for adzerk. AMP cache portions TBD.
AMP ad Fast Fetch implementation for adzerk network allowing for preferential render via mustache template. Template must have previously been pushed to AMP cache (yet to be implemented) in order to guarantee it is A4A valid.
Items to be addressed:
Original PR: #12616
/cc @ampproject/a4a