From f88a9e298261c64b3eb411ba4778ca23efd75287 Mon Sep 17 00:00:00 2001 From: DK the Human Date: Thu, 26 Sep 2019 02:27:01 -0700 Subject: [PATCH] feat(gatsby-plugin-manifest): Update manifest on navigation (#17426) With the addition of the localize option in gatsby-plugin-manifest (#13471), multiple manifests can be generated and served depending on the path of the URL. While the correct manifest is served on initial page load, the link to the manifest is never updated even if the user navigates to a page that should include a different manifest. Co-authored-by: Ward Peeters --- .../src/__tests__/gatsby-browser.js | 73 +++++++++++++++++++ .../src/__tests__/gatsby-ssr.js | 1 + .../src/gatsby-browser.js | 18 +++++ .../gatsby-plugin-manifest/src/gatsby-node.js | 31 ++++++-- .../gatsby-plugin-manifest/src/gatsby-ssr.js | 20 +---- .../src/get-manifest-pathname.js | 23 ++++++ 6 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 packages/gatsby-plugin-manifest/src/__tests__/gatsby-browser.js create mode 100644 packages/gatsby-plugin-manifest/src/gatsby-browser.js create mode 100644 packages/gatsby-plugin-manifest/src/get-manifest-pathname.js diff --git a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-browser.js b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-browser.js new file mode 100644 index 0000000000000..3f55921e0f365 --- /dev/null +++ b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-browser.js @@ -0,0 +1,73 @@ +describe(`gatsby-plugin-manifest`, () => { + const pluginOptions = { + name: `My Website`, + start_url: `/`, + localize: [ + { + start_url: `/es/`, + lang: `es`, + }, + ], + } + let onRouteUpdate + + beforeEach(() => { + global.__PATH_PREFIX__ = `` + global.__MANIFEST_PLUGIN_HAS_LOCALISATION__ = true + onRouteUpdate = require(`../gatsby-browser`).onRouteUpdate + document.head.innerHTML = `` + }) + + afterAll(() => { + delete global.__MANIFEST_PLUGIN_HAS_LOCALISATION__ + }) + + test(`has manifest in head`, () => { + const location = { + pathname: `/`, + } + onRouteUpdate({ location }, pluginOptions) + expect(document.head).toMatchInlineSnapshot(` + + + + `) + }) + + test(`changes href of manifest if navigating to a localized app`, () => { + const location = { + pathname: `/es/`, + } + // add default lang + pluginOptions.lang = `en` + onRouteUpdate({ location }, pluginOptions) + expect(document.head).toMatchInlineSnapshot(` + + + + `) + }) + + test(`keeps default manifest if not navigating to a localized app`, () => { + const location = { + pathname: `/random-path/`, + } + // add default lang + pluginOptions.lang = `en` + onRouteUpdate({ location }, pluginOptions) + expect(document.head).toMatchInlineSnapshot(` + + + + `) + }) +}) diff --git a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js index 65a004a13f61e..359d102f8a46b 100644 --- a/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js +++ b/packages/gatsby-plugin-manifest/src/__tests__/gatsby-ssr.js @@ -112,6 +112,7 @@ describe(`gatsby-plugin-manifest`, () => { it(testName, () => { onRenderBody(args, { start_url: `/`, + lang: `en`, localize: [ { start_url: `/de/`, diff --git a/packages/gatsby-plugin-manifest/src/gatsby-browser.js b/packages/gatsby-plugin-manifest/src/gatsby-browser.js new file mode 100644 index 0000000000000..9280241922bd8 --- /dev/null +++ b/packages/gatsby-plugin-manifest/src/gatsby-browser.js @@ -0,0 +1,18 @@ +/* global __MANIFEST_PLUGIN_HAS_LOCALISATION__ */ +import { withPrefix as fallbackWithPrefix, withAssetPrefix } from "gatsby" +import getManifestForPathname from "./get-manifest-pathname" + +// when we don't have localisation in our manifest, we tree shake everything away +if (__MANIFEST_PLUGIN_HAS_LOCALISATION__) { + const withPrefix = withAssetPrefix || fallbackWithPrefix + + exports.onRouteUpdate = function({ location }, pluginOptions) { + const { localize } = pluginOptions + const manifestFilename = getManifestForPathname(location.pathname, localize) + + const manifestEl = document.head.querySelector(`link[rel="manifest"]`) + if (manifestEl) { + manifestEl.setAttribute(`href`, withPrefix(manifestFilename)) + } + } +} diff --git a/packages/gatsby-plugin-manifest/src/gatsby-node.js b/packages/gatsby-plugin-manifest/src/gatsby-node.js index ff888fb5fb9ca..d9faf2877e38f 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-node.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-node.js @@ -84,20 +84,26 @@ exports.onPostBootstrap = async ( cacheModeOverride = { cache_busting_mode: `name` } } - return makeManifest(cache, reporter, { - ...manifest, - ...locale, - ...cacheModeOverride, - }) + return makeManifest( + cache, + reporter, + { + ...manifest, + ...locale, + ...cacheModeOverride, + }, + true + ) }) ) } activity.end() } -const makeManifest = async (cache, reporter, pluginOptions) => { +const makeManifest = async (cache, reporter, pluginOptions, shouldLocalize) => { const { icon, ...manifest } = pluginOptions - const suffix = pluginOptions.lang ? `_${pluginOptions.lang}` : `` + const suffix = + shouldLocalize && pluginOptions.lang ? `_${pluginOptions.lang}` : `` // Delete options we won't pass to the manifest.webmanifest. delete manifest.plugins @@ -196,3 +202,14 @@ const makeManifest = async (cache, reporter, pluginOptions) => { JSON.stringify(manifest) ) } + +exports.onCreateWebpackConfig = ({ actions, plugins }, pluginOptions) => { + actions.setWebpackConfig({ + plugins: [ + plugins.define({ + __MANIFEST_PLUGIN_HAS_LOCALISATION__: + pluginOptions.localize && pluginOptions.localize.length, + }), + ], + }) +} diff --git a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js index 3db615e8c5bfb..cc972edfb5445 100644 --- a/packages/gatsby-plugin-manifest/src/gatsby-ssr.js +++ b/packages/gatsby-plugin-manifest/src/gatsby-ssr.js @@ -2,8 +2,8 @@ import React from "react" import { withPrefix as fallbackWithPrefix, withAssetPrefix } from "gatsby" import fs from "fs" import { createContentDigest } from "gatsby-core-utils" - import { defaultIcons, addDigestToPath } from "./common.js" +import getManifestForPathname from "./get-manifest-pathname" // TODO: remove for v3 const withPrefix = withAssetPrefix || fallbackWithPrefix @@ -14,20 +14,6 @@ exports.onRenderBody = ( { setHeadComponents, pathname = `/` }, { localize, ...pluginOptions } ) => { - if (Array.isArray(localize)) { - const locales = pluginOptions.start_url - ? localize.concat(pluginOptions) - : localize - const manifest = locales.find(locale => - RegExp(`^${locale.start_url}.*`, `i`).test(pathname) - ) - pluginOptions = { - ...pluginOptions, - ...manifest, - } - if (!pluginOptions) return false - } - // We use this to build a final array to pass as the argument to setHeadComponents at the end of onRenderBody. let headComponents = [] @@ -66,14 +52,14 @@ exports.onRenderBody = ( } } - const suffix = pluginOptions.lang ? `_${pluginOptions.lang}` : `` + const manifestFileName = getManifestForPathname(pathname, localize) // Add manifest link tag. headComponents.push( ) diff --git a/packages/gatsby-plugin-manifest/src/get-manifest-pathname.js b/packages/gatsby-plugin-manifest/src/get-manifest-pathname.js new file mode 100644 index 0000000000000..2cadc49a33249 --- /dev/null +++ b/packages/gatsby-plugin-manifest/src/get-manifest-pathname.js @@ -0,0 +1,23 @@ +/** + * Get a manifest filename depending on localized pathname + * + * @param {string} pathname + * @param {Array<{start_url: string, lang: string}>} localizedManifests + * @return string + */ +export default (pathname, localizedManifests) => { + const defaultFilename = `manifest.webmanifest` + if (!Array.isArray(localizedManifests)) { + return defaultFilename + } + + const localizedManifest = localizedManifests.find(app => + pathname.startsWith(app.start_url) + ) + + if (!localizedManifest) { + return defaultFilename + } + + return `manifest_${localizedManifest.lang}.webmanifest` +}