Skip to content

refractor service worker #4123

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

Merged
merged 19 commits into from
Dec 3, 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
2 changes: 0 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,4 @@ jobs:
yarn
yarn cypress install
yarn cypress verify
yarn fetch:supporters
yarn fetch:starter-kits
yarn cypress:ci
2 changes: 1 addition & 1 deletion cypress.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"baseUrl": "http://localhost:3000",
"baseUrl": "http://localhost:4200",
"video": false
}
81 changes: 81 additions & 0 deletions cypress/integration/offline_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/server-communication__offline/cypress/integration/offline-spec.js

const goOffline = () => {
cy.log('**go offline**')
.then(() => {
return Cypress.automation('remote:debugger:protocol', {
command: 'Network.enable',
});
})
.then(() => {
return Cypress.automation('remote:debugger:protocol', {
command: 'Network.emulateNetworkConditions',
params: {
offline: true,
latency: -1,
downloadThroughput: -1,
uploadThroughput: -1,
},
});
});
};

const goOnline = () => {
// disable offline mode, otherwise we will break our tests :)
cy.log('**go online**')
.then(() => {
// https://chromedevtools.github.io/devtools-protocol/1-3/Network/#method-emulateNetworkConditions
return Cypress.automation('remote:debugger:protocol', {
command: 'Network.emulateNetworkConditions',
params: {
offline: false,
latency: -1,
downloadThroughput: -1,
uploadThroughput: -1,
},
});
})
.then(() => {
return Cypress.automation('remote:debugger:protocol', {
command: 'Network.disable',
});
});
};

describe('offline', () => {
describe('site', { browser: '!firefox' }, () => {
// make sure we get back online, even if a test fails
// otherwise the Cypress can lose the browser connection
beforeEach(goOnline);
afterEach(goOnline);

it('shows /migrate/ page', () => {
const url = '/migrate/';
const text = 'Migrate';

cy.visit(url);
cy.get('h1').contains(text);

goOffline();

cy.visit(url);
cy.get('h1').contains(text);

// click `guides` link
cy.get('a[title="guides"]').click();
cy.get('h1').contains('Guides');
});

it('open print dialog when accessing /printable url', () => {
const url = '/migrate/printable';
cy.visit(url, {
onBeforeLoad: (win) => {
cy.stub(win, 'print');
},
});
cy.window().then((win) => {
expect(win.print).to.be.calledOnce;
});
});
});
});
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"prebuild": "npm run clean",
"build": "run-s fetch-repos fetch printable content && cross-env NODE_ENV=production webpack --config webpack.ssg.js && run-s clean-printable content && cross-env NODE_ENV=production webpack --config webpack.prod.js",
"postbuild": "npm run sitemap",
"build-test": "npm run build && http-server dist/",
"build-test": "npm run build && http-server --port 4200 dist/",
"test": "npm run lint",
"lint": "run-s lint:*",
"lint:js": "npm run lint-js",
Expand All @@ -56,7 +56,7 @@
"jest": "jest",
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"cypress:ci": "start-server-and-test http-get://localhost:3000 cypress:run"
"cypress:ci": "start-server-and-test build-test http://localhost:4200 cypress:run"
Copy link
Member Author

@chenxsan chenxsan Nov 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're testing against the production site now, I think it makes more sense than testing against development site.

},
"husky": {
"hooks": {
Expand Down Expand Up @@ -106,7 +106,7 @@
"eslint-plugin-react": "^7.21.5",
"front-matter": "^4.0.2",
"html-loader": "^1.3.0",
"html-webpack-plugin": "^4.4.1",
"html-webpack-plugin": "^4.5.0",
"http-server": "^0.12.3",
"husky": "^4.3.0",
"hyperlink": "^4.5.3",
Expand Down Expand Up @@ -151,7 +151,7 @@
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.2.1",
"webpack-merge": "^5.1.4",
"workbox-webpack-plugin": "^5.1.4"
"workbox-webpack-plugin": "^6.0.0"
},
"dependencies": {
"docsearch.js": "^2.5.2",
Expand Down
36 changes: 36 additions & 0 deletions src/PrecacheSsgManifestPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// we need to precache some assets from ssg too
// they're previously handled by require('./src/utilities/find-files-in-dist')(['.css', '.ico', '.svg'])

const { Compilation, sources } = require('webpack');
const getManifestEntriesFromCompilation = require('workbox-webpack-plugin/build/lib/get-manifest-entries-from-compilation');

module.exports = class PrecacheSsgManifestPlugin {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to an external dependency.

apply(compiler) {
compiler.hooks.thisCompilation.tap(
'PrecacheSsgManifestPlugin',
(compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: 'PrecacheSsgManifestPlugin',
stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 10,
},
async () => {
const { sortedEntries } = await getManifestEntriesFromCompilation(
compilation,
{
// we don't want to include all html pages
// as that would take too many storages
// svg excluded as it's already included with InjectManifest
include: [/\.(ico|css)/i, /app-shell/i],
}
);
compilation.emitAsset(
'ssg-manifest.json',
new sources.RawSource(JSON.stringify(sortedEntries))
);
}
);
}
);
}
};
49 changes: 49 additions & 0 deletions src/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { precacheAndRoute, matchPrecache } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { CacheFirst, NetworkOnly } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { setDefaultHandler, setCatchHandler } from 'workbox-routing';
import ssgManifest from '../dist/ssg-manifest.json';

// Precache assets built with webpack
precacheAndRoute(self.__WB_MANIFEST);

precacheAndRoute(ssgManifest);

// Precache manifest.json as ssgManifest couldn't catch it
precacheAndRoute([
{
url: '/manifest.json',
revision: '1', // manually update needed when content changed
},
]);

// Cache Google Fonts
registerRoute(
/https:\/\/fonts\.gstatic\.com/,
new CacheFirst({
cacheName: 'google-fonts-cache',
plugins: [
// Ensure that only requests that result in a 200 status are cached
new CacheableResponsePlugin({
statuses: [200],
}),
new ExpirationPlugin({
// Cache for one year
maxAgeSeconds: 60 * 60 * 24 * 365,
maxEntries: 30,
}),
],
})
);

setDefaultHandler(new NetworkOnly());
setCatchHandler(({ event }) => {
switch (event.request.destination) {
case 'document':
return matchPrecache('/app-shell/index.html');
default:
return Response.error();
}
});
12 changes: 9 additions & 3 deletions webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,23 @@ module.exports = () => ({
test: /\.woff2?$/,
type: 'asset/resource',
generator: {
filename: 'font/[hash][ext][query]'
filename: 'font/[name].[hash][ext][query]'
}
},
{
test: /\.(jpg|jpeg|png|ico)$/i,
type: 'asset/resource'
type: 'asset/resource',
generator: {
filename: '[name].[hash][ext][query]'
}
},
{
test: /\.svg$/i,
type: 'asset/resource',
exclude: [path.resolve(__dirname, 'src/styles/icons')]
exclude: [path.resolve(__dirname, 'src/styles/icons')],
generator: {
filename: '[name].[hash][ext][query]'
}
},
{
test: /\.svg$/i,
Expand Down
45 changes: 8 additions & 37 deletions webpack.prod.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Import External Dependencies
const { merge } = require('webpack-merge');
const OptimizeCSSAssetsPlugin = require('css-minimizer-webpack-plugin');
const { GenerateSW } = require('workbox-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
const path = require('path');

// Load Common Configuration
const common = require('./webpack.common.js');

// find [css, ico, svg] versioned (hashed) files emitted by SSG run
const hashedAssetsBySSGRun = require('./src/utilities/find-files-in-dist')(['.css', '.ico', '.svg']);

module.exports = env => merge(common(env), {
mode: 'production',
target: 'web',
Expand Down Expand Up @@ -43,40 +41,13 @@ module.exports = env => merge(common(env), {
]
},
plugins: [
new GenerateSW({
skipWaiting: true,
clientsClaim: true,
new InjectManifest({
swSrc: path.join(__dirname, 'src/sw.js'),
swDest: 'sw.js',
exclude: [/icon_.*\.png/, /printable/, '/robots.txt', ...hashedAssetsBySSGRun],
additionalManifestEntries: [
{
url: '/app-shell/index.html',
revision: new Date().getTime().toString() // dirty hack
},
{
url: '/manifest.json',
revision: '1'
},
...hashedAssetsBySSGRun.map(url => ({
url: '/' + url, // prepend the publicPath
revision: null
}))
],
navigateFallback: '/app-shell/index.html',
navigateFallbackDenylist: [/printable/],
runtimeCaching: [
{
urlPattern: /https:\/\/fonts\.gstatic\.com/, // cache google fonts for one year
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts',
expiration: {
maxAgeSeconds: 60 * 60 * 24 * 365,
maxEntries: 30
}
}
}
],
// exclude license
exclude: [
/license\.txt/i
]
})
]
});
4 changes: 3 additions & 1 deletion webpack.ssg.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const contentTree = require('./src/_content.json');

// Load Common Configuration
const common = require('./webpack.common.js');
const PrecacheSsgManifestPlugin = require('./src/PrecacheSsgManifestPlugin');

// content tree to path array
const paths = [
Expand All @@ -34,7 +35,7 @@ module.exports = env => merge(common(env), {
index: './server.jsx'
},
output: {
filename: '.server/[name].js',
filename: '.server/[name].[contenthash].js',
libraryTarget: 'umd'
},
optimization: {
Expand Down Expand Up @@ -131,5 +132,6 @@ module.exports = env => merge(common(env), {
},
],
}),
new PrecacheSsgManifestPlugin()
]
});
Loading