如何在ReactJS中创建用于PWA的动态manifest.json文件?

3
我有一个项目,需要为我的PWA(ReactJS)创建一个动态的manifest.json文件。
请查看下面的代码:

app.service.js:

        function getAppDetails() {
            const requestOptions = {
                method: 'GET',
                headers: authHeader()
            };
            // TODO create dynamic manifest.json file here
            return fetch(`${config.apiUrl}/api/apps`, requestOptions).then(handleResponse);
        }   

        function handleResponse(response) {
            return response.text().then(text => {
                const data = text && JSON.parse(text);
                if (!response.ok) {
                    if (response.status === 401) {
                        // auto logout if 401 response returned from api
                        logout();
                        location.reload(true);
                    } 
                    const error = (data && data.message) || response.statusText;
                    return Promise.reject(error);
                }

                return data;
            });
}

app.actions.js:

function getAppDetails() {
        return dispatch => {
            dispatch(request());

            appService.getAppDetails()
                .then(
                    details => dispatch(success(details)),
                    error => dispatch(failure(error.toString()))
                );
        };

        function request() { return { type: appConstants.GETDETAILS_REQUEST } }
        function success(details) { return { type: appConstants.GETDETAILS_SUCCESS, details } }
        function failure(error) { return { type: appConstants.GETDETAILS_FAILURE, error } }
}

LoginPage.jsx:

    import { appActions } from '../_actions';
    class LoginPage extends React.Component {
        constructor(props) {
            super(props);

            // reset login status
            this.props.dispatch(userActions.logout());

            this.state = {
                email: '',
                password: '',
                isDisabled: false,
                submitted: false
            };

            this.handleChange = this.handleChange.bind(this);
            this.handleSubmit = this.handleSubmit.bind(this);
            this.showSwal = this.showSwal.bind(this);
        }

        componentDidMount() {
            // TODO call the action to add dynamic manifest file to index.html
            this.props.dispatch(aapActions.getAppDetails());
        }

        render() {
            return (
                ................
            );
        }
}

我对这种事情还很陌生。我该如何开始?

1
可能有些人在这里帮助你会比较困难,因为他们可能不确定你遇到了什么问题。你是否尝试过描述的任务,并遇到了一些困难?如果你能够将问题具体化,那可能会帮助别人更好地帮助你。 - halfer
5个回答

12

如果您想创建动态manifest.json,您需要在HTML中使用rel="manifest"的链接标签,但不带有href属性。稍后使用此标记来填充您的清单。如下所示:

如果您想创建动态manifest.json,您需要在 HTML 中使用 rel="manifest" 的链接标签,但不带 href 属性,并在以后使用此标记来填充您的清单。例如:

<!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>title</title>
        <link rel="manifest" id="my-manifest-placeholder">
      </head>
      <body>
        <!-- page content -->
      </body>
    </html>

使用JSON对象设置您的清单

var myDynamicManifest = {
  "name": "Your Great Site",
  "short_name": "Site",
  "description": "Something dynamic",
  "start_url": "<your-url>",
  "background_color": "#000000",
  "theme_color": "#0f4a73",
  "icons": [{
    "src": "whatever.png",
    "sizes": "256x256",
    "type": "image/png"
  }]
}
const stringManifest = JSON.stringify(myDynamicManifest);
const blob = new Blob([stringManifest], {type: 'application/json'});
const manifestURL = URL.createObjectURL(blob);
document.querySelector('#my-manifest-placeholder').setAttribute('href', manifestURL);


很遗憾,这个解决方案在某些情况下无法正常工作 - 这里有一个解释为什么。 - dan
对我来说,这个解决方案似乎在安卓版的Firefox上不起作用。如果href的值是动态设置的,它不会提供"安装"菜单选项。然而,如果应用程序已经通过Chrome安装了,它将显示"在应用中打开"选项。 - viam0Zah
是的,我也一样。 - Bhavin Thummar
还有其他的解决方案吗?它们有效吗? - Bhavin Thummar
@viam0Zah,请查看我回答中链接的FirefoxAndroid问题:https://dev59.com/vCf_s4cB2Jgan1znJWtU#76882058 - Călin Darie

5

受到@Sanjeet Kumar在我的Next.js应用程序中的解决方案启发:

// _document.tsx
<link href="/manifest.json" rel="manifest" id="manifest" />


// _app.tsx
import manifest from "../../public/manifest.json";

const manifestElement = document.getElementById("manifest");
const manifestString = JSON.stringify({
  ...manifest,
  start_url: `${homePagePath}${storeCode}`,
});
manifestElement?.setAttribute(
  "href",
  "data:application/json;charset=utf-8," + encodeURIComponent(manifestString)
);

4
使用这种方法会出现“清单:属性'start_url'被忽略,URL无效”的错误提示。 - SushiWaUmai
@SushiWaUmai 这可能是由于您定义了start_url的方式不正确 - 它应该按照这里所解释的设置 - 例如,/.https://example.org/path。您可以使用Lighthouse PWA分析工具来验证它是否设置正确。 - dan
尝试使用 useEffect 将其设置在组件内似乎没有起作用,是否可以根据查询参数动态更改它? - Tayyab Ferozi
@SushiWaUmai,请查看我回答中有关相对URL的注意事项 https://dev59.com/vCf_s4cB2Jgan1znJWtU#76882058 - Călin Darie

4

我在使用Blob时遇到了一些问题,在安卓设备上它的表现不如预期。下面的答案可以解决这个问题。

 var myDynamicManifest = {
  "name": "Your Great Site",
  "short_name": "Site",
  "description": "Something dynamic",
  "start_url": "<your-url>",
  "background_color": "#000000",
  "theme_color": "#0f4a73",
  "icons": [
      {
        src: "icon_size_36.png",
        sizes: "36x36",
        type: "image/png",
      },
      {
        src: "icon_size_48.png",
        sizes: "48x48",
        type: "image/png",
      },
      {
        src: "icon_size_72.png",
        sizes: "72x72",
        type: "image/png",
      },
      {
        src: "icon_size_96.png",
        sizes: "96x96",
        type: "image/png",
      },
      {
        src: "icon_size_144.png",
        sizes: "144x144",
        type: "image/png",
      },
      {
        src: "icon_size_192.png",
        sizes: "192x192",
        type: "image/png",
      },
      {
        src: "icon_size_512.png",
        sizes: "512x512",
        type: "image/png",
      },
    ]
  }
  const link = document.createElement("link");
  link.rel = "manifest";    
  const stringManifest = JSON.stringify(myDynamicManifest);
  link.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(stringManifest))
  document.head.appendChild(link);

我相信即使采用这种方法在Android上仍然存在问题 - 如果您更新manifest.json,可能无法应用更新。请参阅我在此处的评论(https://dev59.com/vCf_s4cB2Jgan1znJWtU#71246165)。 - dan

3
很不幸,关于使用 blob: 或者 data: URL 的建议在一些情况下并不能正确地起作用,因为 manifest.json 的 URL 应该是静态的

请不要更改 web 应用清单文件的名称或位置,这样做可能会阻止浏览器更新您的 PWA。

正如那篇文章所解释的那样,有一个特殊的机制可以更新 manifest.json(通过非常特定的条件触发),它不应该通过 URL 名称进行更新。
此外,即使应用程序通过 Lighthouse 检查,Android 也可能不再将其视为完整的(WebAPK-installable)PWA。在这种情况下,它仍然“安装”为普通 PWA,并在应用启动图标角落显示一个丑陋的 Chrome 图标。我花了很多时间试图理解原因,最后偶然看到了那篇文章并找到了解释。
因此,请按以下方式之一执行:
  1. 从完全静态的 URL 服务于动态的 manifest.json,或者
  2. 基于某个属性(例如主机名)确定 href,它在应用程序的整个生命周期内都不会改变(因此可以为不同的客户端提供不同的清单变体,但每个客户端的版本都将具有静态 URL)。

你是什么意思?你上面提供的解决方案实际上就是通过不同的data:载荷来改变href属性... - Shockwaver
谢谢留言,我的意思更多是从动态获取URL的角度来看。我已经更新了答案,使其更加清晰明了。 - dan

0
从javascript开始,使用<link rel="manifest">并设置href可以正常工作,支持data:blob: URL。
已知问题:
- 相对路径的start_urlscope和图标无法正常工作。它们将被解释为相对于blob:或data: URL,这是无效的。症状:忽略了Manifest属性'start_url'。解决方案:构建绝对URL,例如对于与前端具有相同源的URL,我已经使用了...
    new URL(`/my/absolute/path/?evensomeparameter=${valueVariable}`,
   window.location.origin).href
  • Content-Security-Policy 可能会阻止从 blob:data: 加载清单。症状:在控制台中,您会发现错误消息,指出 refused to load manifest from 'blob:https://my.domain.com/blah' because it violates the following Content Security Policy directive:。解决方案:您可以在 Content Security Policy 中启用 manifest-src blob:manifest-src data:。这可能来自响应头 - 请参阅负载均衡器 / CDN / 服务器配置 - 或者来自页面上的 <meta 标签指令 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
  • 2022 年更新的问题指出,在 Android 上,Firefox 只在文档加载时读取清单,因此延迟设置 href 不起作用 https://github.com/mozilla-mobile/fenix/issues/16672

只是为了完整起见,还有一种选择:利用存储在cookie中的会话数据并在服务器端生成清单。但要注意允许的域名-请参阅上面的内容安全策略。并确定您希望从哪里提供静态资源,如图标和启动画面。最好使用绝对URL。

https://medium.com/limehome-engineering/create-a-pwa-app-manifest-dynamically-spa-angular-1627260e0390


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接