pwa+webpack,初探與踩坑

lhyt發表於2018-05-20

0.前言

我們都知道pwa是一個新技術.,依靠快取,離線了還能正常跑,而且秒開。我把以前原生寫的小遊戲遷移到react,再遷移到webpack+react,最後再升級到pwa。具體介紹不多說,我們開始擼吧。

1.webpack

webpack攻略有很多,不囉嗦了,簡單介紹一些重點。記住幾個點:入口entry、出口output、外掛plugins、模組載入器loader。接下來你一個完整的專案的相關操作至少要包含這些。

還有一個就是path模組,專門讀取路徑的,做一切的配置前,首先把路徑搞好吧:

//一般我們就是這樣子做的
var path = require("path");
var ROOT_PATH = path.resolve(__dirname);//當前主入口目錄
var SRC_PATH = path.resolve(ROOT_PATH,"src");//src,你寫的程式碼在這裡
var DIST_PATH = path.resolve(ROOT_PATH,"dist");//打包結果
var COMP_PATH = path.resolve(SRC_PATH,"component");//vue、react都有的component

//然後我們的配置裡面
var config = {
    mode:'development',
    entry: path.resolve(__dirname, './src/index.js'),//webpack把主入口html變成js,然後注入html
    output:{
        path:DIST_PATH,
        filename:"bundle.js"
    },
}
複製程式碼

模組載入器,一般我們不用前處理器的話,繼續在config裡面新增配置,這樣子就基本滿足需求

    module:{
        rules:[
            {
                test:/\.(es6|js)$/,//考慮到es6
                use:[
                    {
                        loader:"babel-loader",
                    }
                ],
                exclude:/node_modules/   //不把nodemodules考慮進去
            },
            {
                test:/\.(css)$/,
                use:[
                    {
                        loader:"style-loader"
                    },
                    {
                        loader:"css-loader"
                    }
                ],
                exclude:/node_modules/
            },
            {
                test:/\.(png|jpeg|jpg|gif)$/,
                use:[
                    {
                        loader:"url-loader",
                    }
                ],
                exclude:/node_modules/
            }
        ]
    }
複製程式碼

對於外掛,我們一般就用htmlWebpackPlugin和熱更新就差不多了

    plugins:[
        new webpack.HotModuleReplacementPlugin(),
        new htmlWebpackPlugin({
             title: 'game',
             template: path.resolve(__dirname, './index.html'),
             //bunld.js會注入裡面
             inject: true
        }),
        new OfflinePlugin() //這是pwa用的,等下講到
    ]
複製程式碼

還有一個伺服器:

var server = new WebpackDevServer(webpack(config), {
    contentBase: path.resolve(__dirname, './dist'), //預設會以根資料夾提供本地伺服器,這裡指定資料夾
    historyApiFallback: true, //這是history路由,如果設定為true,所有的跳轉將指向index.html
    port: 9090, //預設8080
    publicPath: "/", //本地伺服器所載入的頁面所在的目錄
    hot: true, //熱更新
    inline: true, //實時重新整理
    historyApiFallback: true //不跳轉
});
server.listen(9090, 'localhost', function (err) {
    if (err) throw err
})
複製程式碼

哦,對了,列舉一下require清單和package.json:

var webpack = require("webpack");
var path = require("path");
var htmlWebpackPlugin = require("html-webpack-plugin");
var webpackDelPlugin = require("webpack-del-plugin");
var WebpackDevServer = require('webpack-dev-server');
var ROOT_PATH = path.resolve(__dirname);
var SRC_PATH = path.resolve(ROOT_PATH,"src");
var DIST_PATH = path.resolve(ROOT_PATH,"dist");
var TEM_PATH = path.resolve(SRC_PATH,"component");
var  OfflinePlugin = require('offline-plugin')


//package.json
{
  "name": "pwawebpack",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "jquery": "^3.3.1",
    "react-scripts": "1.1.1"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-react-transform": "^3.0.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-react-hmre": "^1.1.1",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "offline-plugin": "^5.0.3",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-transform-hmr": "^1.0.4",
    "style-loader": "^0.21.0",
    "url-loader": "^1.0.1",
    "webpack": "^4.8.3",
    "webpack-cli": "^2.1.3",
    "webpack-del-plugin": "0.0.2",
    "webpack-dev-server": "^3.1.4",
    "webpack-notifier": "^1.6.0"
  },
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config webpack.config.js"
  }
}
複製程式碼

為了快點看到pwa+webpack的效果,那我們eslint、test就不寫了

2.pwa

我們就拿百度到的那些例子說吧,一個正常的pwa,由index.html、一個css、一個manifest.json、一個sw.js。我們要啟動一個pwa,這是必備的。 其實,是不是看起來有點像谷歌瀏覽器的擴充套件?有沒有試過自己寫谷歌瀏覽器外掛,比如遮蔽廣告的、個人工具的、某些網站收藏夾等等外掛。畢竟一家人,所以看起來也有點像。

html:

<head>
  <title>PWA</title>
  <meta name="viewport" content="width=device-width, user-scalable=no" />
  <link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
  <h1>1</h1>
</body>
複製程式碼

manifest.json:其實和自己寫的瀏覽器擴充套件差不多,就是一些關於名字、樣式、logo的配置

{
  "name": "PWA",
  "short_name": "p",
  "display": "standalone",
  "start_url": "/",
  "theme_color": "#0000ff",
  "background_color": "#00ff00",
  "icons": [
    {
      "src": "logo.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ]
}
複製程式碼

sw具體介紹 點這裡 生命週期的話,也不多說了,幾個階段:解析Parsed、安裝Installed、啟用Activated,中間失敗的話直接跳到廢棄Redundant階段,然後我們監聽這些事件,我們直接看效果。

var cacheStorageKey = 'v1'

var cacheList = [
  '/',
  "index.html",
  "main.css",
  "logo.png"
]

self.addEventListener('install', e => {
  e.waitUntil(
    caches.open(cacheStorageKey)
    .then(cache => cache.addAll(cacheList))
    .then(() => self.skipWaiting())
  )
})

self.addEventListener('fetch', function(e) {
  e.respondWith(
    caches.match(e.request).then(function(response) {
      if (response != null) {
        return response
      }
      return fetch(e.request.url)
    })
  )
})

self.addEventListener('activate', function(e) {
  e.waitUntil(
    Promise.all(
      caches.keys().then(cacheNames => {
        return cacheNames.filter(name => 
          name !== cacheStorageKey
        ).map(name=>caches.delete(name))
      })
    ).then(() => {
      return self.clients.claim()
    })
  )
})
複製程式碼

注意了,pwa需要https或者localhost,因為這東西能把你本地的檔案都拉取了,那也有可能幹其他事情,所以必須是要在安全的情況下跑的。還有,是不是發現改了html、js檔案,清空快取都不更新呢?其實改一下sw就可以了,manifest做應用快取也是,改個版本號,或者加個空格就行。

3.基於webpack的pwa

文件見官網

我們不用配置就可以跑起來,但是配置裡面有些地方需要注意的而且不能亂改,自行看文件。配置常用的是:caches(預設全部快取,也可以自己設定),externals(陣列形式,表示其他資源如cdn),excludes(陣列形式,除了哪些不能被快取),autoUpdate(多久後更新,預設一小時)

我們使用offline-plugin這個外掛,只需要在外掛裡面直接引入即可:

 plugins: [
    // ... 其他外掛
    new OfflinePlugin()
  ]
複製程式碼

接著在我們的入口檔案index.js加入:

import * as OfflinePluginRuntime from 'offline-plugin/runtime';
OfflinePluginRuntime.install();
複製程式碼

這樣子直接跑webpack就ok了,試一下谷歌瀏覽器offline模式,你會發現還是能跑:

1