阅读 126

《成为大前端》进阶 - 2. 部署到线上

部署到线上

项目化开发已经完成了,接下来是要将页面部署到在线的服务上,以至于我们的 App 可以在线访问我们的项目

假设我们的公司提供的部署域名为 web.domain.com,那么我们将三个项目部署到如下的路径

news项目来说,访问两个页面的 url 为:

但是以下 2 个问题,会导致开发新需求后页面更新不及时:

  • 上面的链接会有浏览器缓存
  • 上面的链接会有 CDN 更新问题

因此,我们增加版本号到路径中去避免这 2 个问题:

如上,部署news项目时,定义一个版本1,版本每次部署应该是不一样的

部署目标

由于前端页面都是静态的,我们可以用的部署目标地点很多:

  • 阿里云 OSS
  • 腾讯云 OSS
  • AS3 OSS
  • 青云 OSS

就不一一列举了,但为了免费使用,我们这里将使用gitee代码库加上pages服务来部署,这样 是完全免费,并且具备完整的网页访问功能

部署到 gitee

到官方地址:gitee.com/,注册账号后继续后续步…

创建一个 gitee 代码库

填写路径,即库名;选择公开;点击创建

这里我们创建了名为book-to-be-big-fe-deploy,名字无所谓,随意即可

clone 下来用于提交文件部署

部署配置调整

home项目为例,其他项目一样

增加版本控制

package.json

{
    "name": "home",
    ...
    // 增加deploy配置
    "deploy": {
        "version": "1"
    }
}
复制代码

vue.config.js

增加publicPath配置

const packageJSON = require('./package.json')

module.exports = {
  ...
  publicPath: '/book-to-be-big-fe-deploy/' + packageJSON.name + '/' + packageJSON.deploy.version + '/',
  outputDir: 'dist/' + packageJSON.name + '/' + packageJSON.deploy.version + '/',
  ...
}
复制代码

这样调整之后npm run build才能够部署到/home/[version]/目录正确访问

::: tip 注意 前面的配置是依赖于真正部署的位置而配置的,实际应视公司的情况而定 :::

构建项目

home为例

cd path/to/home
npm run build
复制代码

复制到book-to-be-big-fe-deploy

最后三个项目构建和复制完毕会是如下的结构:

部署脚本

为了方便我们写个脚本简化一下上面的操作,并且把脚本放到项目源码的web/deploy目录,实际位置可以更改,只需要 注意脚本的目录和项目、和部署的库的目录关系即可,这里为了方便,就放在这里了

$target = "../../../../book-to-be-big-fe-deploy/"

cd ../home
rm -R dist
npm run build
cp -r -f ./dist/ $target

cd ../news
rm -R dist
npm run build
cp -r -f ./dist/ $target

cd ../weather
rm -R dist
npm run build
cp -r -f ./dist/ $target
复制代码

上面的target变量是看你 clone 部署库的位置而定

最后,使用 git 提交。当然也可以把提交脚本写上,这里不写了

部署pages

进入pages服务页面:

点击部署:

访问

https://[user].gitee.io/[deploy-repo]/home/1/index.html

将user换成你的用户名,deploy-repo换成你的git库名,比如:

https://zzmingo.gitee.io/book-to-be-big-fe-deploy/home/1/index.html

Native 改为线上访问

前面我们讲到了最终项目部署的路径如下:

https://[user].gitee.io/[deploy-repo]/[id]/[version]/[page].html

那么Navigation模块的跳转改为结构化的参数即可:

JSBridge.Navigation.push({
  id: "news",
  page: "index.html"
});
复制代码

只需要传递 2 个参数即可,而其他的:

  • baseUrl = https://[user].gitee.io/[deploy-repo]
  • version

baseUrl应该根据客户端写死,通常部署基础路径是不会基础改动的,而version应该由 客户端查询服务端得到最新的部署版本

那么,baseUrl我们可以以常量的方式定义在客户端中,而version我们使用一个 json 说明 文件来表示,我们命名为manifest.json

客户端使用网络请求不定期更新这个 url 即可

manifest.json

描述了项目对应的最新版本

{
  "projects": [
    {
      "name": "home",
      "version": "1"
    },
    {
      "name": "news",
      "version": "1"
    },
    {
      "name": "weather",
      "version": "1"
    }
  ]
}
复制代码

同样我们也部署到gitee上,让下面这个链接能访问到:

https://[user].gitee.io/[deploy-repo]/manifest.json

前端代码修改

homeApp.vue

methods: {
  onClickNews() {
    JSBridge.Navigation.push({
      id: 'news',
      page: 'index.html'
    })
  },
  onClickWeather() {
    JSBridge.Navigation.push({
      id: 'weather',
      page: 'index.html'
    })
  },
}
复制代码

newsIndex.vue

methods: {
  ...
  onClickNewsItem(item) {
    JSBridge.Navigation.push({
      page: 'detail.html',
      params: {
        id: item.id,
      },
    })
  }
}
复制代码

Native 改为线上访问(Android)

使用 gradle 添加外部依赖库

在开始之前,介绍如何添加外部依赖库,帮助更深理解 Android 开发基础。

gradle 是 java 体系的开发构建工具,通过插件化提供不同的项目使用,比如 Android 使用 Android Gralde Plugin

和 js 有 npm 维护第三方依赖一样,java体系也有它的第三方库提供方,但是来源有很多,比如

  • jcenter
  • mavenCentual
  • bintray
  • google
  • aliyun

但他们都遵循 maven 的管理方式,提供标准的协议,以至于 gradle 也能配置和安装依赖

添加来源

刚开始的篇章,我们就稍微提到了一点,添加来源,为了加快国内速度,我们添加了aliyun的源

添加依赖

项目的依赖写在另一个文件中,如图:

这次,我们添加一个依赖,以方便我们写代码:

dependencies {
    ...
    implementation("com.squareup.okio:okio:2.4.3")
}
复制代码

添加后注意点右上角的sync,相当于npm install

sync 完毕后就安装好了依赖

::: tip 提示 安装的是okio库,这个库有利于我们编写读写文件的代码 :::

代码编写

manifest.json

客户端可以打包一份manifest.json进App里,以打开Web链接时知道使用什么版本

新的java包容纳代码

由于我们这里写的代码功能和jsbridge有一定的区别,因此我们使用新的包容纳代码,包名为web

WebConst.kt

package com.example.tobebigfe.web

object WebConst {
    // 定义baseUrl
    val WEB_BASE_URL = "https://zzmingo.gitee.io/book-to-be-big-fe-deploy/"

}
复制代码

WebManager.kt

package com.example.tobebigfe.web

import android.content.Context
import okio.buffer
import okio.source
import org.json.JSONObject
import java.io.IOException
import java.io.InputStream

/*
管理项目化之后的逻辑:
1. manifest.json
2. 正式的url的拼接
 */
object WebManager {

    // 项目名 => 版本
    private val versionMap = mutableMapOf<String, String>()

    // 初始化
    fun init(context: Context) {
        // 1. 从assets中获取manifest.json的InputStream
        val input = context.assets.open("manifest.json")
        // 2. 读取完整内容
        val manifestStr = readAll(input)
        // 3. 从json一步步解析到versionMap
        val manifest = JSONObject(manifestStr)
        val projects = manifest.getJSONArray("projects")
        for (i in 0 until projects.length()) {
            val prj = projects.getJSONObject(i)
            versionMap[prj.getString("name")] = prj.getString("version")
        }
    }

    // 根据id和page获取正式的url
    fun getWebUrl(id: String, page: String): String {
        return "${WebConst.WEB_BASE_URL}$id/${versionMap[id]}/$page"
    }

    // 工具函数,读取一个InputStream的所有内容,这里使用到了okio库,非常方便
    @Throws(IOException::class)
    private fun readAll(input: InputStream): String {
        return input.source().use { fileSource ->
            fileSource.buffer().use { buffer ->
                buffer.readUtf8()
            }
        }
    }

}
复制代码

MyApplication.kt

WebManager在某个时机需要初始化(init),这里我们选择app打开时就初始化,因此我们需要定义自己的Application

package com.example.tobebigfe

import android.app.Application
import com.example.tobebigfe.web.WebManager

// 继承android的Application
class MyApplication : Application() {

    // 覆盖onCreate,这样App启动时会调用,这个方法会比Activity初始化还有早
    override fun onCreate() {
        super.onCreate()

        // 初始化WebManager
        WebManager.init(this)
    }
}
复制代码

AndroidManifest.xml中注册使用我们定义的MyApplication

<application
    android:name=".MyApplication"
    ...>
    ...
</application>
复制代码

这样App启动时就使用这个类了

MainActivity.kt

class MainActivity : WebActivity() {

    override fun getLoadUrl(): String {
        // return "http://192.168.31.101:8081/index.html"

        // 使用WebManager获取正式的url
        return WebManager.getWebUrl("home", "index.html") 
    }
}
复制代码

JSBridgeNavigation.kt

class JSBridgeNavigation... {

    private fun open(callbackId: String, arg: JSONObject) {
        val intent = Intent(activity, WebActivity::class.java)

        // 如果前端传递了id参数,说明使用项目名跳转方式
        if (arg.has("id")) {
            val id = arg.getString("id")
            val page = arg.getString("page")
            val url = WebManager.getWebUrl(id, page)
            intent.putExtra("url", url)
        }
        // 否则有url参数的情况
        else if (arg.has("url")) {
            intent.putExtra("url", arg.getString("url"))
        }

        if (arg.has("params")) {
            val params = arg.get("params") as JSONObject?
            params?.let {
                intent.putExtra("params", it.toString())
            }
        }

        activity.startActivity(intent)
    }
}
复制代码

最后代码结构

 

运行效果

运行效果和使用本地开发服务器时是一样的,只是所有都使用的在线地址。接下来还有两个话题:

  • 提供开发环境
  • 在线版本更新

Native 改为线上访问(iOS)

代码编写

新的Group容纳代码

由于我们这里写的代码功能和JSBridge有一定的区别,因此我们使用新的包容纳代码,包名为Web

客户端可以打包一份manifest.json进App里,以打开Web链接时知道使用什么版本

WebConst.swift

import Foundation

struct WebConst {
    
    public static let WEB_BASE_URL = "https://zzmingo.gitee.io/book-to-be-big-fe-deploy/"
    
}
复制代码

WebManager.swift

import Foundation

/*
管理项目化之后的逻辑:
1. manifest.json
2. 正式的url的拼接
 */
class WebManager {
    
    // 单例模式
    public static let shared = WebManager()
    // 项目名 => 版本
    private var versionDict = [String:String]()
    
    // 初始化
    init() {
        // 读取解析manifest.json的过程
        let path = Bundle.main.path(forResource: "manifest.json", ofType: "json")!
        let data = FileManager.default.contents(atPath: path)!
        let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String:Any?]
        let projects = json["projects"] as! [[String:Any?]]
        projects.forEach { item in
            versionDict[item["name"] as! String] = item["version"] as? String
        }
    }
    
    // 根据id和page获取正式的url
    func getWebUrl(id: String, page: String) -> String {
        let version = versionDict[id] ?? "1"
        return "\(WebConst.WEB_BASE_URL)\(id)/\(version)/\(page)"
    }
    
}
复制代码

MainActivity.swift

import UIKit

class ViewController : WebViewController {
    
    override func getLoadUrl() -> String {
        return WebManager.shared.getWebUrl(id: "home", page: "index.html")
    }
    
}
复制代码

JSBridgeNavigation.swift

class JSBridgeNavigation ... {

    ...
    
    private func open(callbackId: String, arg: [String : Any?]) {
        guard let vc = self.viewController else { return }
        guard let url = parseWebUrl(arg) else { return }
        let newVC = WebViewController()
        newVC.url = url
        if let params = arg["params"] as? [String:Any?] {
            newVC.params = params
        }
        vc.present(newVC, animated: true, completion: nil)
    }
    
    private func push(callbackId: String, arg: [String : Any?]) {
        guard let vc = self.viewController else { return }
        guard let navVC = vc.navigationController else { return }
        guard let url = parseWebUrl(arg) else { return }
        let newVC = WebViewController()
        newVC.url = url
        if let params = arg["params"] as? [String:Any?] {
            newVC.params = params
        }
        navVC.pushViewController(newVC, animated: true)
    }
    
    private func parseWebUrl(_ arg: [String: Any?]) -> String? {
        if let id = arg["id"] as? String, let page = arg["page"] as? String {
            return WebManager.shared.getWebUrl(id: id, page: page)
        }
        else if let url = arg["url"] as? String {
            return url
        } else {
            return nil
        }
    }

    ...
}
复制代码

最后代码结构

 

运行效果

运行效果和使用本地开发服务器时是一样的,只是所有都使用的在线地址。接下来还有两个话题:

  • 提供开发环境
  • 在线版本更新
关注下面的标签,发现更多相似文章
评论