Skip to content

Commit

Permalink
Initial service worker (#140)
Browse files Browse the repository at this point in the history
* Initial service worker

* Don't cache bootstrap

* bootstrap no longer has a hash.

* Handing service worker updates and refactoring loading

* Missed some bits

* Adding constant
  • Loading branch information
jakearchibald committed Apr 24, 2019
1 parent 0bf18bd commit 727f7aa
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 92 deletions.
33 changes: 33 additions & 0 deletions consts-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*/
export default function constsPlugin(consts) {
return {
name: "consts-plugin",
async resolveId(id) {
if (id !== "consts:") {
return;
}
return id;
},
load(id) {
if (id !== "consts:") {
return;
}
return Object.entries(consts)
.map(
([key, value]) => `export const ${key} = ${JSON.stringify(value)};`
)
.join("");
}
};
}
41 changes: 41 additions & 0 deletions resource-list-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*/
const resourceListMarker = "___REPLACE_THIS_WITH_RESOURCE_LIST_LATER";

export default function resourceList() {
return {
name: "dependencygraph",
resolveId(id) {
if (id !== "resource-list:") {
return null;
}
return id;
},
load(id) {
if (id !== "resource-list:") {
return null;
}
return `export default ${resourceListMarker};`;
},
generateBundle(_outputOptions, bundle) {
const resourceListJSON = JSON.stringify(Object.keys(bundle));

for (const item of Object.values(bundle)) {
if (!item.code) {
continue;
}
item.code = item.code.replace(resourceListMarker, resourceListJSON);
}
}
};
}
13 changes: 11 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,27 @@ import { terser } from "rollup-plugin-terser";
import loadz0r from "rollup-plugin-loadz0r";
import dependencyGraph from "./dependency-graph-plugin.js";
import chunkNamePlugin from "./chunk-name-plugin.js";
import resourceListPlugin from "./resource-list-plugin";
import postcss from "rollup-plugin-postcss";
import glsl from "./glsl-plugin.js";
import cssModuleTypes from "./css-module-types.js";
import assetPlugin from "./asset-plugin.js";
import { readFileSync } from "fs";
import constsPlugin from "./consts-plugin.js";

// Delete 'dist'
require("rimraf").sync("dist");

export default {
input: "src/bootstrap.ts",
input: {
bootstrap: "src/bootstrap.ts",
sw: "src/sw/index.ts"
},
output: {
dir: "dist",
format: "amd",
sourcemap: true,
entryFileNames: "[name]-[hash].js",
entryFileNames: "[name].js",
chunkFileNames: "[name]-[hash].js"
},
plugins: [
Expand All @@ -47,6 +52,9 @@ export default {
return name.replace(/-\w/g, val => val.slice(1).toUpperCase());
}
}),
constsPlugin({
version: require("./package.json").version
}),
typescript({
// Make sure we are using our version of TypeScript.
typescript: require("typescript"),
Expand Down Expand Up @@ -87,6 +95,7 @@ export default {
dependencyGraph({
propList: ["facadeModuleId", "fileName", "imports", "code", "isAsset"]
}),
resourceListPlugin(),
terser()
]
};
15 changes: 1 addition & 14 deletions sizereport.config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
const { parse } = require("path");
const rescapeRE = require("escape-string-regexp");

module.exports = {
repo: "GoogleChromeLabs/graviton",
path: "dist/**/*",
branch: "master",
findRenamed(path, newPaths) {
const parsedPath = parse(path);
if (parsedPath.base.startsWith("bootstrap-")) {
const end = /\..*$/.exec(parsedPath.base)[0];
const re = new RegExp(
`^${rescapeRE(parsedPath.dir)}/bootstrap-[^\.]+${end}$`
);
return newPaths.find(path => re.test(path));
}
}
branch: "master"
};
9 changes: 9 additions & 0 deletions src/missing-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ declare module "chunk-name:*" {
export default value;
}

declare module "resource-list:" {
const value: string[];
export default value;
}

declare module "consts:" {
export const version: string;
}

interface Window {
debug?: Promise<typeof import("./services/debug/index.js")>;
}
Expand Down
81 changes: 81 additions & 0 deletions src/offline/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/** Tell the service worker to skip waiting. Resolves once the controller has changed. */
export async function skipWaiting() {
const reg = await navigator.serviceWorker.getRegistration();
if (!reg || !reg.waiting) {
return;
}
reg.waiting.postMessage("skip-waiting");

return new Promise(resolve => {
navigator.serviceWorker.addEventListener(
"controllerchange",
() => resolve(),
{ once: true }
);
});
}

/** Is there currently a waiting worker? */
export let updateReady = false;

/** Wait for an installing worker */
async function installingWorker(
reg: ServiceWorkerRegistration
): Promise<ServiceWorker> {
if (reg.installing) {
return reg.installing;
}
return new Promise<ServiceWorker>(resolve => {
reg.addEventListener("updatefound", () => resolve(reg.installing!), {
once: true
});
});
}

async function watchForUpdate() {
const hasController = !!navigator.serviceWorker.controller;

// If we don't have a controller, we don't need to check for updates – we've just loaded from the
// network.
if (!hasController) {
return;
}

const reg = await navigator.serviceWorker.getRegistration();
// Service worker not registered yet.
if (!reg) {
return;
}

// Look for updates
if (reg.waiting) {
return;
}
const installing = await installingWorker(reg);
await new Promise<void>(resolve => {
installing.addEventListener("statechange", () => {
if (installing.state === "installed") {
resolve();
}
});
});

updateReady = true;
}

/** Set up the service worker and monitor changes */
export async function init() {
const thisURL = new URL(location.href);

if (thisURL.searchParams.has("no-sw")) {
const reg = await navigator.serviceWorker.getRegistration();
if (reg) {
await reg.unregister();
location.reload();
}
return;
}

await navigator.serviceWorker.register("/sw.js");
watchForUpdate();
}
26 changes: 26 additions & 0 deletions src/services/preact-canvas/components/game-loading/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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 { Component, h } from "preact";
import TopBar from "../top-bar/index.js";
import { loading, loadingInner } from "./style.css";

export default class GameLoading extends Component<{}, {}> {
render() {
return (
<div class={loading}>
<TopBar titleOnly />
<div class={loadingInner}>…Loading…</div>
</div>
);
}
}
25 changes: 25 additions & 0 deletions src/services/preact-canvas/components/game-loading/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.loading {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
position: relative;
}

@keyframes fade-in-out {
50% {
opacity: 1;
}
}

.loadingInner {
display: flex;
flex-flow: column;
padding: var(--bar-avoid) var(--side-margin) 0;
box-sizing: border-box;
width: 100%;
text-align: center;
font-size: 2.3rem;
opacity: 0;
animation: fade-in-out 3s ease-in-out infinite 0.5s;
}
30 changes: 2 additions & 28 deletions src/services/preact-canvas/components/intro/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import {
selectArrow as selectArrowStyle,
settingsRow as settingsRowStyle,
startButton as startButtonStyle,
startButtonTextHide,
startButtonTextShow,
startForm as startFormStyle
} from "./style.css";

Expand Down Expand Up @@ -93,22 +91,19 @@ type PresetName = keyof typeof presets;

export interface Props {
onStartGame: (width: number, height: number, mines: number) => void;
loading: boolean;
}

interface State {
presetName: PresetName | "custom";
width: number;
height: number;
mines: number;
longLoad: boolean;
}

// tslint:disable-next-line:max-classes-per-file
export default class Intro extends Component<Props, State> {
state: State = {
presetName: "beginner",
longLoad: false,
...presets.beginner
};

Expand All @@ -119,16 +114,9 @@ export default class Intro extends Component<Props, State> {

componentDidMount() {
window.scrollTo(0, 0);

setTimeout(() => {
this.setState({ longLoad: true });
}, 1000);
}

render(
{ loading }: Props,
{ width, height, mines, presetName, longLoad }: State
) {
render(_props: Props, { width, height, mines, presetName }: State) {
return (
<div class={introStyle}>
<TopBar titleOnly />
Expand Down Expand Up @@ -185,21 +173,7 @@ export default class Intro extends Component<Props, State> {
</NumberField>
</div>
<div class={settingsRowStyle}>
<button class={startButtonStyle} disabled={loading}>
<span
class={
longLoad || !loading
? startButtonTextShow
: startButtonTextHide
}
>
{loading
? longLoad
? "…Loading…"
: "\u00A0" // non-breaking space, to retain height
: "Start"}
</span>
</button>
<button class={startButtonStyle}>Start</button>
</div>
</form>
</div>
Expand Down
17 changes: 0 additions & 17 deletions src/services/preact-canvas/components/intro/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -108,21 +108,4 @@
margin: 0 var(--item-margin) !important;
text-transform: uppercase;
letter-spacing: 0.15rem;
will-change: opacity;
transition: opacity 0.4s ease-in-out;
}

.start-button:disabled {
opacity: 0.7;
}

.start-button-text-hide {
will-change: opacity;
opacity: 0;
transition: opacity 0.2s ease-in-out;
}

.start-button-text-show {
composes: start-button-text-hide;
opacity: 1;
}

0 comments on commit 727f7aa

Please sign in to comment.