《成为大前端》进阶 - 3. 开发环境

425

前面我们完成了项目化在线部署,完成后,通常我们会进入新的版本迭代阶段,比如,我们的weather项目, 现在要增加PM2.5具体数值显示这个需求。

于是,我们遇到一个问题就是前端然后在部署的情况下,开发环境的情况下确保代码运行良好,虽然我们可以在chrome下 确定页面样式问题,但是假设天气数据是来源于我们的服务端,服务端需要我们提供移动客户端的登录态才能调用,这时候 App里就需要提供开发环境。

简单的说:在开发环境下我们打开天气的页面时最好访问的是我们webpack开发服务器

实现方式:移动客户端可以打包一个开发环境包,这个包运行起来可以配置指向webpack服务端

Android开发环境

一般打开开发环境的方式有多种:

  • 提供稳定的UI入口进入
  • 摇一摇进入
  • 开发环境悬浮按钮

这里我们使用摇一摇进入

代码编写

MainActivity.kt

摇一摇代码放在MainActivity

class MainActivity : WebActivity(), ShakeSensor.OnShakeListener {

    private lateinit var shakeSensor: ShakeSensor

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 初始化摇一摇传感器
        if (BuildConfig.DEBUG) {
            shakeSensor = ShakeSensor(this)
            shakeSensor.shakeListener = this
            shakeSensor.register()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 销毁
        if (BuildConfig.DEBUG) {
            shakeSensor.unregister()
        }
    }

    override fun getLoadUrl(): String {
        return WebManager.getWebUrl("home", "index.html")
    }

    override fun onShakeComplete(event: SensorEvent) {
        // 摇一摇显示开发环境
        AlertDialog.Builder(this)
            .setTitle("打开开发环境?")
            .setItems(arrayOf("取消", "确定")) { _, index ->
                if (index == 1) {
                    gotoDevSettings()
                }
            }
            .create()
            .show()
    }

    private fun gotoDevSettings() {
        // 摇一摇点确定后去DevSettingsActivity
        startActivity(Intent(this, DevSettingsActivity::class.java))
    }
}

::: tip 注意 这里用到了Android的传感器,实现摇一摇的动作代码在: ShakeSensor :::

DevSettingsActivity.kt

这次我们实现界面使用Android的新功能PreferenceScreen

首先,像添加okio库依赖一样添加preference依赖,这个库是官方提供的:

implementation("androidx.preference:preference:1.1.0-alpha04")

DevSettingsActivity:

package com.example.tobebigfe.web

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import com.example.tobebigfe.R

class DevSettingsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dev_settings)
        title = "开发设置"
        supportFragmentManager
            .beginTransaction()
            .add(R.id.settings_container, DevSettingsFragment())
            .commit()
    }
}

class DevSettingsFragment : PreferenceFragmentCompat() {
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.dev_preferences, rootKey)
    }
}

layout/activity_dev_settings.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/settings_container" xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

</FrameLayout>

xml/dev_preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <PreferenceCategory app:key="webDev" android:title="Web开发环境">
        <SwitchPreferenceCompat
            app:key="webDev.enabled"
            app:title="开启开发模式"/>
        <EditTextPreference
            app:key="webDev.project"
            app:title="项目"
            app:useSimpleSummaryProvider="true"/>
        <EditTextPreference
            app:key="webDev.server"
            app:title="服务器"
            app:useSimpleSummaryProvider="true" />
    </PreferenceCategory>

</PreferenceScreen>

WebManager.kt

object WebManager {
    ...

    private lateinit var preferences: SharedPreferences

    // 初始化
    fun init(context: Context) {
        preferences = PreferenceManager.getDefaultSharedPreferences(context)
        ...
    }

    // 根据id和page获取正式的url
    fun getWebUrl(id: String, page: String): String {
        // 判断设置的是不是开发项目
        if (BuildConfig.DEBUG && preferences.isWebDevEnabled() && id == preferences.webDevProject()) {
            return "http://${preferences.webDevServer()}/$id/dev/$page?t=" + System.currentTimeMillis()
        }
        return "${WebConst.WEB_BASE_URL}$id/${versionMap[id]}/$page"
    }

    ...

}

private fun SharedPreferences.isWebDevEnabled(): Boolean {
    return getBoolean("webDev.enabled", false)
}

private fun SharedPreferences.webDevProject(): String? {
    return getString("webDev.project", null)
}

private fun SharedPreferences.webDevServer(): String? {
    return getString("webDev.server", null)
}

最终代码结构

 

运行结果

摇一摇:

Android日志:

::: tip 注意 配置好的是本地的一个服务器地址和weather项目,所以进weather时加载的是开发环境的链接 :::

扩展阅读

iOS开发环境

学习 SwiftPackage 添加依赖

Swift Package Manager 是苹果提供的官方依赖管理器,并且 XCode11 之后对这个支持非常好

这次我们要依赖的是:github.com/neoneye/Swi…

一般我们选择Up to Next Minor

点 Finish

最后成功引入 SwiftyFORM

代码编写

一般打开开发环境的方式有多种:

  • 提供稳定的 UI 入口进入
  • 摇一摇进入
  • 开发环境悬浮按钮

这里我们使用摇一摇进入

ViewController.swift

摇一摇代码放在ViewController

class ViewController : WebViewController {
    
    override func getLoadUrl() -> String {
        return WebManager.shared.getWebUrl(id: "home", page: "index.html")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        /**
        开启摇动感应
        */
        UIApplication.shared.applicationSupportsShakeToEdit = true
        becomeFirstResponder()
    }
    
    override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        // 识别到摇一摇
        if motion == .motionShake {
            showDevAction()
        }
    }
    
    private func showDevAction() {
        let actionSheet = UIAlertController(title: "摇一摇", message: nil, preferredStyle: .actionSheet)
        let cancel = UIAlertAction(title: "取消", style: .cancel)
        actionSheet.addAction(cancel)
        let webDev = UIAlertAction(title: "进入开发环境?", style: .default) { action in
            let navVC = UINavigationController(rootViewController: DevSettingsController())
            self.present(navVC, animated: true, completion: nil)
        }
        actionSheet.addAction(webDev)
        self.present(actionSheet, animated: true, completion: nil)
    }
    
}

DevSettingsController.swift

这次我们实现界面使用SwiftyFORM

import Foundation
import UIKit
import SwiftyFORM

class DevSettingsController : FormViewController {
    
    lazy var settings: UserDefaults = { return WebManager.shared.settings }()
    
    lazy var enabledSwitch: SwitchFormItem = {
        let instance = SwitchFormItem()
        instance.title = "开启"
        instance.setValue(settings.bool(forKey: "enabled"), animated: false)
        instance.switchDidChangeBlock = {
            self.settings.setValue($0, forKey: "enabled")
        }
        return instance
    }()
    
    lazy var projectTextField: TextFieldFormItem = {
        let instance = TextFieldFormItem()
        instance.title = "项目"
        instance.placeholder = "未设置"
        
        instance.textAlignment = .right
        instance.value = self.settings.string(forKey: "project") ?? ""
        instance.textDidChangeBlock = {
            self.settings.setValue($0, forKey: "project")
        }
        return instance
    }()
    
    lazy var serverTextField: TextFieldFormItem = {
        let instance = TextFieldFormItem()
        instance.title = "服务器"
        instance.placeholder = "未设置"
        instance.textAlignment = .right
        instance.value = self.settings.string(forKey: "server") ?? ""
        instance.textDidChangeBlock = {
            self.settings.setValue($0, forKey: "server")
        }
        return instance
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "开发环境"
        let leftCloseBtn = UIBarButtonItem(title: "关闭", style: .plain, target: self, action: #selector(onClickClose))
        navigationItem.leftBarButtonItem = leftCloseBtn
    }
    
    @objc private func onClickClose() {
        self.dismiss(animated: true, completion: nil)
    }
    
    override func populate(_ builder: FormBuilder) {
        builder += SectionHeaderTitleFormItem().title("Web开发环境")
        builder += enabledSwitch
        builder += projectTextField
        builder += serverTextField
    }
    
}

WebManager.kt

class WebManager {
    ...
    
    public let settings = UserDefaults(suiteName: "WebDev")!
    ...
    
    // 根据id和page获取正式的url
    func getWebUrl(id: String, page: String) -> String {
        // 如果环境打开了
        if settings.bool(forKey: "enabled"),
            let server = settings.string(forKey: "server"),
            let project = settings.string(forKey: "project"),
            project == id
        {
            return "http://\(server)/\(project)/dev/\(page)"
        }
        
        let version = versionDict[id] ?? "1"
        return "\(WebConst.WEB_BASE_URL)\(id)/\(version)/\(page)"
    }
    
}

最终代码结构

 

运行结果

模拟器激活摇一摇,真实设备就用手摇:



查看日志:

::: tip 注意 配置好的是本地的一个服务器地址和weather项目,所以进weather时加载的是开发环境的链接 :::

扩展阅读

前端开发环境配置

weather项目的vue.config.js

const packageJSON = require("./package.json");

let version = packageJSON.deploy.version;
let publicBasePath = "/book-to-be-big-fe-deploy/";

// dev环境改变version和publicBasePath以适配开发环境的链接地址
if (process.env.NODE_ENV === "development") {
  version = "dev";
  publicBasePath = "/";
}

module.exports = {
  devServer: {
    host: "0.0.0.0",
    port: 8083
  },
  publicPath: publicBasePath + packageJSON.name + "/" + version + "/",
  outputDir: "dist/" + packageJSON.name + "/" + version + "/"
};

开发环境完成,其他项目配置做法一样,甚至可以出一个 vue 项目模板