PWA - ios 添加到桌面功能(踩坑之路)

1,505

背景

最近公司要做一个app内功能的快捷入口,类似支付宝里面的乘车码添加到桌面。目前只要做 ios端。

调研了一下支付宝是怎么做到的, 其实是利用了 safari 的 pwa功能,将编码好的网页内容和图标保存到桌面。点击桌面快捷方式打开网页执行JS,跳转到App对应的功能。 pwa

开搞

Safari可以直接打开一串包含页面内容编码的URL。而且用base64位的码,会解决二次打开的无效的bug。

因为要打包出来一个base64位的码,所以要内联到一个文件里面。所以打算webpack自己搞起来

1.先把webpack架子搭起来
mkdir pwa && cd pwa
npm init -y
npm install webpack webpack-cli --save-dev
2.写一个静态 html 页面

因为我这边想用 HtmlWebpackPlugin 这个插件来 转化,而且设置多语言

先从head 说起吧

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta content="yes" name="apple-touch-fullscreen"> // 全屏
  <meta content="yes" name="apple-mobile-web-app-capable"> // 自动打开app的功能
  <meta content="black" name="apple-mobile-web-app-status-bar-style"> // bar的颜色
  <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,minimal-ui"> // 视图
  <title>你的title</title>
  <link sizes="114x114" rel="apple-touch-icon-precomposed"
    href="你的图片url icon"> // 保存到桌面的图标
  <script> // 设置rem
    document.documentElement.style.fontSize = 100 * document.documentElement.clientWidth / 375 + "px"
  </script>
  <!-- 因为用注入js的方式 文件太大 -->
  <style>你的样式</style>
</head>

在来 body, htmlWebpackPlugin.options.lang 是用于设置多语言的

<body>
  <div id="B_container" class="backguide" style="display:none">
    <div class="tips">
      <% if (htmlWebpackPlugin.options.lang === 'zh-Hans') {%>
        你即将进入
      <% } else if (htmlWebpackPlugin.options.lang === 'en') {%>
        You are about to enter
      <% } %>
    </div>
    <button class="enter" onclick="jumpSchema()">立即进入</button>
  </div>
  <div id="app" style="display:none">
    <div class="title">
      <% if (htmlWebpackPlugin.options.lang === 'zh-Hans') {%>
        添加服务到桌面
      <% } else if (htmlWebpackPlugin.options.lang === 'en') {%>
        Add services to the desktop
      <% } %>
    </div>
  </div>
  <script>
    window.MTSchema = '你要跳转的app的链接';

    function jumpSchema() {
      window.location.href = window.MTSchema;
    }

    function getIOSversion() {
      if (/iP(hone|od|ad)/.test(navigator.platform)) {
        var e = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
        return [parseInt(e[1], 10), parseInt(e[2], 10), parseInt(e[3] || 0, 10)]
      }
    }
    if (window.navigator.standalone) {
        // 通过window.navigator.standalone检测Safari打开的Web应用程序是否全屏显示
      var v = getIOSversion();
      if (13 <= v[0]) {
        // 13以上的系统 二次进入不会自动跳转,显示进入页面 
        document.getElementById("B_container").style.display = "flex"
      } else {
        document.getElementById("app").style.display = "flex"
      }
      window.location.href = window.MTSchema;
    } else {
      document.getElementById("app").style.display = "flex"
    }
  </script>
</body>
3.webpack 配置

由于 url-loader转化html文件 到 base64 是在 HtmlWebpackPlugin 之前执行的,所以注入css script是无效的。 后打算自己写个脚本转化

const path = require("path");
const merge = require("webpack-merge");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const LANG = process.env.LANG;
const NODE_ENV = process.env.NODE_ENV;

let baseConfig = {
  entry: "./src/main.js",
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist")
  },
  devServer: {
    contentBase: false,
    compress: true,
    hot: true,
    host: "0.0.0.0",
    port: 9000
  },
  module: {
    rules: [
      { // 因为转化成js文件太大 废弃 直接使用内联
        test: /\.scss$/,
        use: [
          "style-loader", // 将 JS 字符串生成为 style 节点
          "css-loader", // 将 CSS 转化成 CommonJS 模块
          "sass-loader" // 将 Sass 编译成 CSS,默认使用 Node Sass
        ]
      }
    ]
  },
  plugins: [
    // 这个是重点 我之前想用 url-loader 转化 html 但是发现 url-loader 是先执行了的, 后打算自己写个脚本转化
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      filename: "index.html",
      inject: false, // 不注入
      lang: LANG, // 语言
      minify: {
        collapseWhitespace: true //删除空格、换行
      },
      // 下面是本来想注入的代码 但是导致文件太大
      // inlineSource: '.(js|css)$' // 依赖 HtmlWebpackInlineSourcePlugin
    }),
    // new HtmlWebpackInlineSourcePlugin()
  ]
};
if (NODE_ENV === "development") {
  baseConfig = merge(baseConfig, {
    plugins: [new webpack.HotModuleReplacementPlugin()]
  });
}
module.exports = baseConfig;

4.package.json

设置多语言,运行脚本

"scripts": {
    "build": "rm -rf ./dist && cross-env LANG=zh-Hans NODE_ENV=production webpack --env.lang=zh-Hans && node ./src/toBase64.js",
    "build:en": "rm -rf ./dist && cross-env LANG=en NODE_ENV=production webpack --env.lang=en && node ./src/toBase64.js",
    "dev": "cross-env LANG=en NODE_ENV=development  webpack-dev-server --hot"
}
5.toBase64.js

转化为base64位

const fs = require('fs');
const path = require('path');
const mineType = require('mime-types');
 
let filePath = path.resolve('./dist/index.html');
 
let data = fs.readFileSync(filePath);
data = new Buffer(data).toString('base64');
 
let base64 = 'data:' + mineType.lookup(filePath) + ';base64,' + data;
 
fs.writeFileSync(path.resolve('./dist/index.html'), base64);

总结

感觉确实很不完美, 当然这个还只是能实现的版本, 后续还要做优化。本人小菜鸟一个,有问题希望大家指出,一起成长。