PWA - ios 新增到桌面功能(踩坑之路)

任志鵬鵬發表於2020-01-08

背景

最近公司要做一個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);
複製程式碼

總結

感覺確實很不完美, 當然這個還只是能實現的版本, 後續還要做優化。本人小菜鳥一個,有問題希望大家指出,一起成長。

相關文章