Skip to content
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

Preload checks for blocking extensions #902

Merged
merged 3 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/linter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import { VideosHaveAltText } from "./rules/VideosHaveAltText";
import { VideosAreSubtitled } from "./rules/VideosAreSubtitled";
import { IsValid } from "./rules/IsValid";
import { TitleMeetsLengthCriteria } from "./rules/TitleMeetsLengthCriteria";
import { RuleConstructor } from "./rule";
import { isArray } from "util";
import { IsTransformedAmp } from "./rules/IsTransformedAmp";
import { ModuleRuntimeUsed } from "./rules/ModuleRuntimeUsed";
import { BlockingExtensionsPreloaded } from "./rules/BlockingExtensionsPreloaded";
import { RuleConstructor } from "./rule";
import { isArray } from "util";

export enum LintMode {
Amp = "amp",
Expand Down Expand Up @@ -128,6 +129,7 @@ function testsForMode(type: LintMode) {
(tests.get(LintMode.PageExperience) || []).concat([
IsValid,
RuntimeIsPreloaded,
BlockingExtensionsPreloaded,
IsTransformedAmp,
ModuleRuntimeUsed,
])
Expand Down
31 changes: 31 additions & 0 deletions packages/linter/src/rules/BlockingExtensionsPreloaded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Context } from "../index";
import { Rule } from "../rule";

const blockingExtension = [
"amp-dynamic-css-classes",
"amp-experiment",
"amp-story",
];

export class BlockingExtensionsPreloaded extends Rule {
run({ $ }: Context) {
const results = [];
blockingExtension.forEach((extension) => {
const scriptPart = `/v0/${extension}-`;
if ($(`script[src*='${scriptPart}']`).length > 0) {
if ($(`link[rel$='preload'][href*='${scriptPart}']`).length == 0) {
results.push(this.warn(`Preload for ${extension} is missing`));
}
}
});
return Promise.all(results);
}
meta() {
return {
url:
"https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp/#optimize-the-amp-runtime-loading",
title: "Render-blocking extensions are preloaded",
info: "",
};
}
}
11 changes: 5 additions & 6 deletions packages/linter/src/rules/RuntimeIsPreloaded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { Rule } from "../rule";

export class RuntimeIsPreloaded extends Rule {
run({ $ }: Context) {
const attr = [
"href='https://cdn.ampproject.org/v0.js'",
"rel='preload'",
"as='script'",
]
const jsAttr = ["href$='/v0.js'", "rel='preload'", "as='script'"]
.map((s) => `[${s}]`)
.join("");
const isPreloaded = $(`link${attr}`).length > 0;
const mjsAttr = ["href$='/v0.mjs'", "rel$='preload'"] // preload or modulepreload
.map((s) => `[${s}]`)
.join("");
const isPreloaded = $(`link${jsAttr}, link${mjsAttr}`).length > 0;
return isPreloaded
? this.pass()
: this.warn(
Expand Down
42 changes: 42 additions & 0 deletions packages/linter/tests/local.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BookendExists } from "../src/rules/BookendExists";
import { TitleMeetsLengthCriteria } from "../src/rules/TitleMeetsLengthCriteria";
import { IsTransformedAmp } from "../src/rules/IsTransformedAmp";
import { ModuleRuntimeUsed } from "../src/rules/ModuleRuntimeUsed";
import { BlockingExtensionsPreloaded } from '../src/rules/BlockingExtensionsPreloaded';

describe(AmpImgAmpPixelPreferred.name, () => {
it(`${AmpImgAmpPixelPreferred.name} - <amp-img height="1" width="1">`, async () => {
Expand Down Expand Up @@ -60,6 +61,38 @@ describe(AmpImgAmpPixelPreferred.name, () => {
});
});


describe(BlockingExtensionsPreloaded.name, () => {
it(`${BlockingExtensionsPreloaded.name} - preload for js and mjs present`, async () => {
return assertPass(
runLocalTest(
BlockingExtensionsPreloaded,
`${__dirname}/local/BlockingExtensionsPreloaded-1/source.html`
)
);
});

it(`${BlockingExtensionsPreloaded.name} - No blocking extensions present`, async () => {
return assertPass(
runLocalTest(
BlockingExtensionsPreloaded,
`${__dirname}/local/BlockingExtensionsPreloaded-2/source.html`
)
);
});

it(`${BlockingExtensionsPreloaded.name} - preload for js and mjs is missing`, async () => {
const results = await runLocalTest(
BlockingExtensionsPreloaded,
`${__dirname}/local/BlockingExtensionsPreloaded-3/source.html`
);
expect(results).toHaveLength(3);
await assertWarn(results[0]);
await assertWarn(results[1]);
await assertWarn(results[2]);
});
});

describe(IsTransformedAmp.name, () => {
it(`${IsTransformedAmp.name} - Transformed AMP detected`, async () => {
return assertPass(
Expand Down Expand Up @@ -138,6 +171,15 @@ describe(RuntimeIsPreloaded.name, () => {
)
);
});

it(`${RuntimeIsPreloaded.name} - <link rel="modulepreload"> is present`, async () => {
return assertPass(
runLocalTest(
RuntimeIsPreloaded,
`${__dirname}/local/RuntimeIsPreloaded-3/source.html`
)
);
});
});

describe(SchemaMetadataIsNews.name, () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!doctype html>
<html lang="en" amp i-amphtml-layout i-amphtml-no-boilerplate transformed="self;v=1" amp-version="2007302351001" class="i-amphtml-singledoc i-amphtml-standalone">
<head>
<meta charset="utf-8">
<style amp-runtime="" i-amphtml-version="012007302351001">html{overflow-x:hidden!important}html.i-amphtml-fie{height:100%!important;width:100%!important}html:not([amp4ads]),html:not([amp4ads]) body{height:auto!important}html:not([amp4ads]) body{margin:0!important}body{-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}html.i-amphtml-singledoc.i-amphtml-embedded{-ms-touch-action:pan-y;touch-action:pan-y}html.i-amphtml-fie>body,html.i-amphtml-singledoc>body{overflow:visible!important}html.i-amphtml-fie:not(.i-amphtml-inabox)>body,html.i-amphtml-singledoc:not(.i-amphtml-inabox)>body{position:relative!important}html.i-amphtml-webview>body{overflow-x:hidden!important;overflow-y:visible!important;min-height:100vh!important}html.i-amphtml-ios-embed-legacy>body{overflow-x:hidden!important;overflow-y:auto!important;position:absolute!important}html.i-amphtml-ios-embed{overflow-y:auto!important;position:static}#i-amphtml-wrapper{overflow-x:hidden!important;overflow-y:auto!important;position:absolute!important;top:0!important;left:0!important;right:0!important;bottom:0!important;margin:0!important;display:block!important}html.i-amphtml-ios-embed.i-amphtml-ios-overscroll,html.i-amphtml-ios-embed.i-amphtml-ios-overscroll>#i-amphtml-wrapper{-webkit-overflow-scrolling:touch!important}#i-amphtml-wrapper>body{position:relative!important;border-top:1px solid transparent!important}#i-amphtml-wrapper+body{visibility:visible}#i-amphtml-wrapper+body .i-amphtml-lightbox-element,#i-amphtml-wrapper+body[i-amphtml-lightbox]{visibility:hidden}#i-amphtml-wrapper+body[i-amphtml-lightbox] .i-amphtml-lightbox-element{visibility:visible}#i-amphtml-wrapper.i-amphtml-scroll-disabled,.i-amphtml-scroll-disabled{overflow-x:hidden!important;overflow-y:hidden!important}amp-instagram{padding:54px 0px 0px!important;background-color:#fff}amp-iframe iframe{box-sizing:border-box!important}[amp-access][amp-access-hide]{display:none}[subscriptions-dialog],body:not(.i-amphtml-subs-ready) [subscriptions-action],body:not(.i-amphtml-subs-ready) [subscriptions-section]{display:none!important}amp-experiment,amp-live-list>[update]{display:none}.i-amphtml-jank-meter{position:fixed;background-color:rgba(232,72,95,0.5);bottom:0;right:0;color:#fff;font-size:16px;z-index:1000;padding:5px}amp-list[resizable-children]>.i-amphtml-loading-container.amp-hidden{display:none!important}amp-list [fetch-error],amp-list[load-more] [load-more-button],amp-list[load-more] [load-more-end],amp-list[load-more] [load-more-failed],amp-list[load-more] [load-more-loading]{display:none}amp-list[diffable] div[role=list]{display:block}amp-story-page,amp-story[standalone]{min-height:1px!important;display:block!important;height:100%!important;margin:0!important;padding:0!important;overflow:hidden!important;width:100%!important}amp-story[standalone]{background-color:#202125!important;position:relative!important}amp-story-page{background-color:#757575}amp-story .amp-active>div,amp-story .i-amphtml-loader-background{display:none!important}amp-story-page:not(:first-of-type):not([distance]):not([active]){transform:translateY(1000vh)!important}amp-autocomplete{position:relative!important;display:inline-block!important}amp-autocomplete>input,amp-autocomplete>textarea{padding:0.5rem;border:1px solid rgba(0,0,0,0.33)}.i-amphtml-autocomplete-results,amp-autocomplete>input,amp-autocomplete>textarea{font-size:1rem;line-height:1.5rem}[amp-fx^=fly-in]{visibility:hidden}
/*# sourceURL=/css/ampdoc.css*/[hidden]{display:none!important}.i-amphtml-element{display:inline-block}.i-amphtml-blurry-placeholder{transition:opacity 0.3s cubic-bezier(0.0,0.0,0.2,1)!important;pointer-events:none}[layout=nodisplay]:not(.i-amphtml-element){display:none!important}.i-amphtml-layout-fixed,[layout=fixed][width][height]:not(.i-amphtml-layout-fixed){display:inline-block;position:relative}.i-amphtml-layout-responsive,[layout=responsive][width][height]:not(.i-amphtml-layout-responsive),[width][height][heights]:not([layout]):not(.i-amphtml-layout-responsive),[width][height][sizes]:not([layout]):not(.i-amphtml-layout-responsive){display:block;position:relative}.i-amphtml-layout-intrinsic,[layout=intrinsic][width][height]:not(.i-amphtml-layout-intrinsic){display:inline-block;position:relative;max-width:100%}.i-amphtml-layout-intrinsic .i-amphtml-sizer{max-width:100%}.i-amphtml-intrinsic-sizer{max-width:100%;display:block!important}.i-amphtml-layout-container,.i-amphtml-layout-fixed-height,[layout=container],[layout=fixed-height][height]:not(.i-amphtml-layout-fixed-height){display:block;position:relative}.i-amphtml-layout-fill,[layout=fill]:not(.i-amphtml-layout-fill){display:block;overflow:hidden!important;position:absolute;top:0;left:0;bottom:0;right:0}.i-amphtml-layout-flex-item,[layout=flex-item]:not(.i-amphtml-layout-flex-item){display:block;position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.i-amphtml-layout-fluid{position:relative}.i-amphtml-layout-size-defined{overflow:hidden!important}.i-amphtml-layout-awaiting-size{position:absolute!important;top:auto!important;bottom:auto!important}i-amphtml-sizer{display:block!important}.i-amphtml-blurry-placeholder,.i-amphtml-fill-content{display:block;height:0;max-height:100%;max-width:100%;min-height:100%;min-width:100%;width:0;margin:auto}.i-amphtml-layout-size-defined .i-amphtml-fill-content{position:absolute;top:0;left:0;bottom:0;right:0}.i-amphtml-replaced-content,.i-amphtml-screen-reader{padding:0!important;border:none!important}.i-amphtml-screen-reader{position:fixed!important;top:0px!important;left:0px!important;width:4px!important;height:4px!important;opacity:0!important;overflow:hidden!important;margin:0!important;display:block!important;visibility:visible!important}.i-amphtml-screen-reader~.i-amphtml-screen-reader{left:8px!important}.i-amphtml-screen-reader~.i-amphtml-screen-reader~.i-amphtml-screen-reader{left:12px!important}.i-amphtml-screen-reader~.i-amphtml-screen-reader~.i-amphtml-screen-reader~.i-amphtml-screen-reader{left:16px!important}.i-amphtml-unresolved{position:relative;overflow:hidden!important}.i-amphtml-select-disabled{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.i-amphtml-notbuilt,[layout]:not(.i-amphtml-element),[width][height][heights]:not([layout]):not(.i-amphtml-element),[width][height][sizes]:not([layout]):not(.i-amphtml-element){position:relative;overflow:hidden!important;color:transparent!important}.i-amphtml-notbuilt:not(.i-amphtml-layout-container)>*,[layout]:not([layout=container]):not(.i-amphtml-element)>*,[width][height][heights]:not([layout]):not(.i-amphtml-element)>*,[width][height][sizes]:not([layout]):not(.i-amphtml-element)>*{display:none}.i-amphtml-notbuilt:not(.i-amphtml-layout-container),[layout]:not([layout=container]):not(.i-amphtml-element),[width][height][heights]:not([layout]):not(.i-amphtml-element),[width][height][sizes]:not([layout]):not(.i-amphtml-element){color:transparent!important;line-height:0!important}.i-amphtml-ghost{visibility:hidden!important}.i-amphtml-element>[placeholder],[layout]:not(.i-amphtml-element)>[placeholder],[width][height][heights]:not([layout]):not(.i-amphtml-element)>[placeholder],[width][height][sizes]:not([layout]):not(.i-amphtml-element)>[placeholder]{display:block}.i-amphtml-element>[placeholder].amp-hidden,.i-amphtml-element>[placeholder].hidden{visibility:hidden}.i-amphtml-element:not(.amp-notsupported)>[fallback],.i-amphtml-layout-container>[placeholder].amp-hidden,.i-amphtml-layout-container>[placeholder].hidden{display:none}.i-amphtml-layout-size-defined>[fallback],.i-amphtml-layout-size-defined>[placeholder]{position:absolute!important;top:0!important;left:0!important;right:0!important;bottom:0!important;z-index:1}.i-amphtml-notbuilt>[placeholder]{display:block!important}.i-amphtml-hidden-by-media-query{display:none!important}.i-amphtml-element-error{background:red!important;color:#fff!important;position:relative!important}.i-amphtml-element-error:before{content:attr(error-message)}i-amp-scroll-container,i-amphtml-scroll-container{position:absolute;top:0;left:0;right:0;bottom:0;display:block}i-amp-scroll-container.amp-active,i-amphtml-scroll-container.amp-active{overflow:auto;-webkit-overflow-scrolling:touch}.i-amphtml-loading-container{display:block!important;pointer-events:none;z-index:1}.i-amphtml-notbuilt>.i-amphtml-loading-container{display:block!important}.i-amphtml-loading-container.amp-hidden{visibility:hidden}.i-amphtml-element>[overflow]{cursor:pointer;position:relative;z-index:2;visibility:hidden;display:initial;line-height:normal}.i-amphtml-element>[overflow].amp-visible{visibility:visible}template{display:none!important}.amp-border-box,.amp-border-box *,.amp-border-box :after,.amp-border-box :before{box-sizing:border-box}amp-pixel{display:none!important}amp-analytics,amp-auto-ads,amp-story-auto-ads{position:fixed!important;top:0!important;width:1px!important;height:1px!important;overflow:hidden!important;visibility:hidden}html.i-amphtml-fie>amp-analytics{position:initial!important}[visible-when-invalid]:not(.visible),form [submit-error],form [submit-success],form [submitting]{display:none}amp-accordion{display:block!important}amp-accordion>section{float:none!important}amp-accordion>section>*{float:none!important;display:block!important;overflow:hidden!important;position:relative!important}amp-accordion,amp-accordion>section{margin:0}amp-accordion>section>:last-child{display:none!important}amp-accordion>section[expanded]>:last-child{display:block!important}
/*# sourceURL=/css/ampshared.css*/</style>
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<link href="https://cdn.ampproject.org/v0.mjs" rel="modulepreload">
<link href="https://cdn.ampproject.org/v0/amp-experiment-0.1.mjs" rel="modulepreload">
<link href="https://cdn.ampproject.org/v0/amp-dynamic-css-classes-0.1.js" rel="preload" as="script">
<script type="module" async src="https://cdn.ampproject.org/v0.mjs" crossorigin="anonymous"></script>
<script nomodule async src="https://cdn.ampproject.org/v0.js"></script>
<script custom-element="amp-experiment" type="module" async src="https://cdn.ampproject.org/v0/amp-experiment-0.1.mjs" crossorigin="anonymous"></script>
<script nomodule async src="https://cdn.ampproject.org/v0/amp-experiment-0.1.js"></script>
<script async custom-element="amp-dynamic-css-classes" src="https://cdn.ampproject.org/v0/amp-dynamic-css-classes-0.1.js"></script>

<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "NewsArticle",
"headline": "Open-source framework for publishing content",
"datePublished": "2015-10-07T12:02:41Z",
"image": [
"logo.jpg"
]
}
</script>
<title>Hello, AMPs</title>
<link rel="canonical" href="http://example.ampproject.org/article.html">
</head>
<body>
<h1>Hello, AMP!</h1>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!doctype html>
<html amp lang="en">
<head>
<meta charset="utf-8">
<link href="https://cdn.ampproject.org/v0.js" rel="preload" as="script">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<title>Hello, AMPs</title>
<link rel="canonical" href="http://example.ampproject.org/article-metadata.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "NewsArticle",
"headline": "Open-source framework for publishing content",
"datePublished": "2015-10-07T12:02:41Z",
"image": [
"logo.jpg"
]
}
</script>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<style amp-custom>
</style>
</head>
<body>
<h1>Hello, AMP!</h1>
</body>
</html>
Loading