前端工程化開發(fā)中“國際化和主題切換”方案小記

1、前言

?這些年來,前端伴隨著Vue和React等等一些框架的出現(xiàn),開發(fā)方式發(fā)生了日新月異的變化。筆者從業(yè)10多年,組件方面使用過早期的微軟Asp.Net的服務(wù)器渲染組件、JAVA體系的JSP模板控件,用過基于Jquery的眾多框架(EasyUI、JqueryUI)組件,ExtJS,Dojo等等。雖然這些前端組件(或者叫做框架)層出不窮,但其基本思想是一致的,都是對瀏覽器端的Dom進(jìn)行操作或者操作的優(yōu)化。誠然這么說,似乎對新框架特點(diǎn)的概括的太籠統(tǒng),尤其在前端工程化開發(fā)大規(guī)模推廣的情況下,現(xiàn)在的新框架和之前的以jquery為主的前端開發(fā)模式已經(jīng)不是一個(gè)級(jí)別的了。一個(gè)信息系統(tǒng)軟件開發(fā)團(tuán)隊(duì)人員的組成,尤其能看出這點(diǎn),前端工程師越來越多,而且對前端的依賴越來越大。一個(gè)技術(shù)或者一個(gè)系統(tǒng)復(fù)雜以后,往往以分而治之來解決面臨的問題,“前后端”分離的開發(fā)方式,也是在近幾年慢慢提出和被很多開發(fā)團(tuán)隊(duì)使用。而且SPA(單頁)系統(tǒng)的出現(xiàn),似乎對理解前端開發(fā)又造成了很大的困擾。

?說了這么多變化,那么現(xiàn)在復(fù)雜的信息系統(tǒng),前端之于業(yè)務(wù)模塊的組成形態(tài)到底有沒有變化呢?;蛘哒f,面對現(xiàn)在流行的微服務(wù)框架,前端的應(yīng)用場景又是如何呢。

?近期正好一個(gè)大的生產(chǎn)系統(tǒng)面臨著前端大改造,系統(tǒng)大概包括一個(gè)平臺(tái)(頁面容器)、若干個(gè)業(yè)務(wù)模塊。各個(gè)業(yè)務(wù)模塊需要以菜單的形式注冊到平臺(tái),且在平臺(tái)內(nèi)部嵌套打開(tab形式,作為一個(gè)整體)。

集成.png

?考察了很多開源開發(fā)框架,往往一個(gè)前端工程,即包含基礎(chǔ)模塊,又包含業(yè)務(wù)模塊,而且一個(gè)系統(tǒng)就一個(gè)SPA,所謂大單頁應(yīng)用。但是,真實(shí)的研發(fā)團(tuán)隊(duì),往往有很多獨(dú)立的微服務(wù)開發(fā)小組組成,每個(gè)團(tuán)隊(duì)有自己的技術(shù)體系。如果按照一個(gè)大單頁的形式組織開發(fā),似乎跨團(tuán)隊(duì)的集成打包交叉很多,而且分塊更新的流程似乎也很復(fù)雜。那么自然最合適的方式是每個(gè)業(yè)務(wù)模塊以一個(gè)SPA應(yīng)用的形式開發(fā),由各個(gè)團(tuán)隊(duì)負(fù)責(zé)(平臺(tái)亦是如此),最終各個(gè)模塊統(tǒng)一以一定形式集成到平臺(tái)內(nèi)。所以很多開源開發(fā)框架往往不太適合復(fù)雜信息系統(tǒng)的研發(fā)方式。在現(xiàn)行架構(gòu)下,無法做到一個(gè)復(fù)雜系統(tǒng)是一個(gè)SPA這種業(yè)態(tài)。

?那么更好的方式,其實(shí)還是類似之前復(fù)雜系統(tǒng)的處理方式,因?yàn)橐筛鱾€(gè)業(yè)務(wù)模塊的內(nèi)嵌,平臺(tái)前端容器其實(shí)還是個(gè)iframe容器,唯一區(qū)別的是,業(yè)務(wù)模塊劃分后,每個(gè)模塊可以以獨(dú)立SPA的形式存在,我想這也是微服務(wù)改造的初衷吧。

?確定了這種模塊組成形式,業(yè)務(wù)模塊和容器模塊的集成,就成了影響系統(tǒng)統(tǒng)一性的關(guān)鍵所在。在這方面,有兩塊內(nèi)容,在前端開發(fā)框架設(shè)計(jì)時(shí)需要著重考慮。這也是今天行文的重點(diǎn),一塊是統(tǒng)一的國際化處理;另一塊的統(tǒng)一的主題切換。

?廢話說了這么多,切入重點(diǎn)。本文以React+Antd+Umijs這一套前端方案為基礎(chǔ),分享演示一個(gè)工程化前端開發(fā)過程中主題切換和國際化處理的方案。先上示例切換效果圖:

theme-change.gif

2、源碼及使用方式

?目前前端基于React和Antd的項(xiàng)目越來越多,而且開源腳手架Umijs的使用似乎也成為中小型項(xiàng)目的必選。先把今天要分享的方案代碼標(biāo)記和說明下:

gitee:https://gitee.com/BeautifulHao/theme-demo-app

//本地演示
git clone https://gitee.com/BeautifulHao/theme-demo-app.git
cd theme-demo-app
yarn install
yarn start

3、國際化方案

?國際化是一個(gè)全局性的設(shè)置,所以作為單頁應(yīng)用,最理想的設(shè)置地方最好是最外層的包裝組件,而結(jié)合umijs,最合適的就是layout組件,或者說是route的最外層嵌套組件設(shè)置全局國際化。react的國際化有兩個(gè)庫,一個(gè)是react-intl,另一個(gè)是react-intl-universal,本文介紹的是react-intl-universal。

ant design 組件國際化

結(jié)合官方的文檔:https://ant.design/docs/react/i18n-cn,列舉項(xiàng)目源碼說明src/layouts/index.js


import React, { PureComponent } from 'react'
//antd國際化包裝組件
import { ConfigProvider } from 'antd';
import intl from 'react-intl-universal';
//antd國際化資源
import zhCN from 'antd/es/locale-provider/zh_CN';
import enUS from 'antd/es/locale-provider/en_US';
//本地國際化資源
import enTranslationsData from '../locale/en-US';
import zhTranslationsData from '../locale/zh-CN';
//國際化鍵常量和基于cookie全局設(shè)置方法
import { Locale_en_US, Locale_zh_CN, getCookieLocale } from '../utils/i18n';

//包含常規(guī)一些國際化字符串
require('intl/locale-data/jsonp/en.js');
require('intl/locale-data/jsonp/zh.js');

const AntdTranslations = {
  [Locale_zh_CN]: zhCN,
  [Locale_en_US]: enUS,
}

class BasicLayout extends PureComponent {

  state = {initDone: false}

  componentWillMount () {
    //通過平臺(tái)容器集設(shè)置的cookie獲取當(dāng)前全局環(huán)境的國際化設(shè)置
    const currentLocale = getCookieLocale();
    //初始化react-intl-universal 組件國際化設(shè)置
    intl.init({
      currentLocale,
      locales: {
        [Locale_zh_CN]: { ...zhTranslationsData },
        [Locale_en_US]: { ...enTranslationsData },
      }
    }).then(() => {
      this.setState({initDone: true});
    });
  }

  render () {
    const currentLocale = getCookieLocale();
    return (
      //按照antd官網(wǎng)設(shè)置國際化包裝器ConfigProvider
      this.state.initDone &&
          <ConfigProvider locale={AntdTranslations[currentLocale]}>{this.props.children}</ConfigProvider>
    )
  }
}

本地國際化資源格式(../locale/en-US):

const enUS ={
  "test.helloworld": "hello world",
  "test.show":"hello,I'm from english context!",
  "test.buttom":"test buttom",
  "test.themeAndLocale":'Please Select The Theme & Locale:',
  "test.pageShow":'page show:'
}

export default enUS

業(yè)務(wù)組件國際化使用

//引入組件
import intl from 'react-intl-universal';
//調(diào)用方法獲取
intl.get('test.themeAndLocale')

4、主題切換方案

可行方案介紹

  • css換膚:生成多套主題css,在頁面加載時(shí)根據(jù)主題選擇加載指定css

  • less換膚:采用動(dòng)態(tài)Less機(jī)制,通過js動(dòng)態(tài)編譯less變量修改css(可參考:less換膚

css主題切換介紹

從效果來說,動(dòng)態(tài)Less換膚優(yōu)于css換膚(頁面不刷新),但對于單個(gè)業(yè)務(wù)模塊而言,雖然作為SPA應(yīng)用,但是模塊內(nèi)多個(gè)功能地址都將作為菜單獨(dú)立打開,每次加載一個(gè)頁面都將動(dòng)態(tài)編譯一次less,性能將有很大的消耗。所以在行文開頭說明的復(fù)雜系統(tǒng)的場景下less換膚不是一個(gè)最優(yōu)的選擇。那么css換膚呢,綜合考量了下,的確還是能夠達(dá)到預(yù)期的效果的,尤其是antd一個(gè)主色就能搞定一個(gè)主題的場景(antd),而且主題切換往往就是antd皮膚切換。所以要實(shí)現(xiàn)主題切換,就得從antd下手。

生成多份主題文件

Antd 的樣式 CSS 文件可以通過 link 標(biāo)簽引入,而該項(xiàng)目又要求提供多套Antd 的主題樣式。首先想到的 能否直接去官網(wǎng)能否直接定制并下載 CSS 文件。然而答案是 否定 的。但是作為業(yè)界優(yōu)秀的開源項(xiàng)目,Antd 提供了自定義定制主題樣式的方法,在官網(wǎng) 定制主題 就有詳細(xì)的說明,然而 Antd 卻只提供了 LESS 樣式定制的功能,并沒有提供現(xiàn)成的生成 CSS 樣式文件定制并下載的功能。

那么自然可行的辦法就是:

  • 獲取官方標(biāo)準(zhǔn)的 LESS 主文件
  //一般就在當(dāng)前項(xiàng)目下
  node_modules/antd/dist/antd.less
  • 隨后自定義一份 LESS 文件,引入該主文件后,根據(jù)需求指定的樣式變量進(jìn)行覆蓋
  @import "../node_modules/antd/dist/antd.less";   
// Import Ant Design styles by less entry
  
  @primary-color: #1890ff;
  • 利用 lessc 工具最終編譯出所需要的 CSS 文件
lessc -js theme-demo-app-default.less ../public/theme/theme-demo-app-default.css

根據(jù)上面的思路,我們只需要多做幾份不同@primary-color顏色的css文件即可。


多主題.png

多主題2.png

加載指定主題css

從單頁應(yīng)用入口文件index.html下手,在頁面加載之初去加載指定css,結(jié)合umijs的html模板文件,我們可以按照如下方式處理:document.ejs

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Theme-Demo Application</title>
  <script>
    window.publicPathString = "<%= context.publicPath %>"
    document.title = "<%= context.config.plugins[0][1].title %>"
  </script>
  <script src="<%= context.publicPath %>theme/append-theme.js"></script>
</head>
<body>
  <div id="root"></div>
</body>
</html>
// append-theme.js
//根據(jù)cookie動(dòng)態(tài)添加sup-ui的樣式文件
var themeHandle = {
  getCookie: function(name) {
    name = name + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for(var i = 0; i <ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) === ' ') {
            c = c.substring(1);
         }
         if (c.indexOf(name) === 0) {
            return c.substring(name.length, c.length);
         }
     }
    return undefined;
  },
  appendTheme: function() {
    var publicPath = window.publicPathString;
    var theme = this.getCookie('theme');
    theme = theme ? theme : 'default'
    var link = document.createElement('link');
    link.type = 'text/css';
    link.id = 'theme-' + theme;
    link.rel = 'stylesheet';
    link.href = publicPath + 'theme/theme-demo-app-' + theme + '.css';
    document.getElementsByTagName('head')[0].appendChild(link);
  },
};

themeHandle.appendTheme();

主要的過程就是從cookie中獲取指定主題設(shè)置,然后加載不同的antd主題css.

參考:

http://www.itdecent.cn/p/35e0581629d2
https://boycgit.github.io/customize-antd-css/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容