1. 程式人生 > 其它 >angular linux 打包不成功_漫談 Angular 定製主題的四種方式

angular linux 打包不成功_漫談 Angular 定製主題的四種方式

技術標籤:angular linux 打包不成功

f7d42cb9adcaf9ee10edc6e14f3ab37d.png

主題定製是提升使用者體驗最常見的一種,前端框架眾多,主題定製方式卻異曲同工,下面來介紹一下 Angular 中實現主題定製的四種方式。

1. webpack loader

React 版本的 Ant Design 使用 less-loader 載入 globalVars 與 modifyVars 變數,並通過 less 的 render 方法傳遞 callback 到 loader 來實現的專案的主題修改功能。

目前絕大部分的 angular 專案同樣使用 webpack 打包方案。顯然,相同的主題修改方案在 angular 中一樣適用。

webpack 打包 less

  • webpack 本身並不具備打包 less 檔案的功能,最終實現該部分功能的是 less-loader,該載入器把 less 轉為 CSS,在 webpack 中每個檔案或模組都是有效的 JS 模組,因此我們還需要 css-loader 將CSS樣式檔案轉換為變成 JS 模組。
  • 這時我們已經有了生成的 dist/style.js,在這個模組中只是將樣式匯出為字串並存放於陣列中,我們需要 style-loader 將該陣列轉換成 style 標籤。
  • 最後我們還需要將 dist/style.js 自動匯入 到 html 中,html-webpack-plugin 可以幫我們實現這部分功能。
  • 除了以上這些 loader,我們可能還需要 autoprefixer、cssnano 和 postcss-loader 等,有興趣的同學可以自行了解。

modifyVars

上面介紹的 less-loader 可以幫忙我們實現主體定製,通過一下這兩個配置,我們就可以把部分樣式抽出變數,通過不同的變數組合成不同的主題:

  1. globalVars:相當於給每個 less 檔案頂部增加一行 @VariableName: xx;
  2. modifyVars:相當於給每個 less 檔案底部增加一行變數 @variable:xx;

custom-webpack

angular-cli 提供了 custom-webpack 的 builder,可以和 angular-cli 合併使用,通過 builder 重寫 webpack 中的 less-loader 的配置,然後利用 modifyVars 實現主題定製。

  1. 安裝 npm i -D @angular-builders/custom-webpack
  2. 在根目錄新建 webpack 配置檔案 extra-webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /.css$/,
                use: [
                    "style-loader",
                    "css-loader"
                ]
            },
            {
                test: /.less$/,
                use : [
                    {
                        loader : "less-loader",
                        options: {
                            modifyVars: {  // 修改主題變數
                                "primary-color": "red"
                            },
                            javascriptEnabled: true
                        }
                    }
                ]
            }
        ]
    }
}

3.在 angular.json 中使用 @angular-builders/custom-webpack:browser

"architect": {
    "build": {
        + "builder": "@angular-builders/custom-webpack:browser",
        - "builder": "@angular-builders/build-angular:browser",
        "options": {
            "customWebpackConfig": {
                "path": "./extra-webpack.config.js"
            },
            "outputPath": "dist/custom-webpack",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
                "src/favicon.ico",
                "src/assets"
            ],
            "styles": [
                "src/styles.less"
            ]
        }
        ...
    }
}

這樣就可以實現 less 原理的主題定製了,當然 custom-webpack 不僅僅可以做到 less-loader 的重寫,它還可以利用 webpack 實現更多功能,具體研究我們在下一篇文章再來探討;

如果你想進一步瞭解在 angular cli 中自定義 webpack 打包的方案,可以參考這篇文章

筆者準備好了可以直接使用的原始碼,方便大家檢視 點選檢視原始碼

純 webpack 打包

如果開發者的專案未使用 Angular CLI,也可以通過同樣的方式實現自己的 webpack 打包器:

  1. 在根目錄新增 webpack.config.js 檔案。
  2. 執行命令 webpack 或者 webpack-dev-serve,即可檢視效果。

筆者準備好了可以直接使用的原始碼,方便大家檢視 點選檢視原始碼

可能很多開發者並不熟悉 less,開發過程中大多用純 CSS,純 CSS 能否實現主題定製了?答案是肯定的,下面我們來探討一下純 CSS 的主題定製。

2. CSS Variable

CSS3 提供了 Variable, 利用 angular Directive 指令,動態修改 CSS Variable,從而得到主題切換的效果。注意:CSS Variable 支援的瀏覽器可以在 這裡 檢視

.element{--main-bg-color: brown;}   // 宣告區域性變數
.element{background-color: var(--main-bg-color);}   // 使用區域性變數
:root { --global-color: #666; --pane-padding: 5px 42px; }   // 宣告全域性變數
.demo{ color: var(--global-color); }  // 使用全域性變數

有了以上的的基礎知識,我們很容易想到如何在 angular 中實現基於 css Variable 的主題切換功能,我們只需要一個 Directive 可以根據 @Input 輸入動態切換 style 即可。

1.建立一個指令:ThemeDirective,用來給需要 CSS 變數的標籤新增樣式

import { Directive, ElementRef, Input, OnChanges } from '@angular/core';
@Directive({
  selector: '[dtTheme]'
})
export class ThemeDirective implements OnChanges {
  @Input('dtTheme') theme: {[prop: string]: string};
  constructor(private el: ElementRef<HTMLElement>) {
  }
  ngOnChanges() {
    Object.keys(this.theme).forEach(prop => {
      this.el.nativeElement.style.setProperty(`--${prop}`, this.theme[prop]);
    });
  }
}

2.建立一個元件:app.component.ts

import { Component } from '@angular/core';
@Component({
  selector   : 'app-root',
  template: `
    <select (input)="setTheme($event.target.value)" title="theme" class="form-control">
      <option value="">- select theme -</option>
      <option>green</option>
      <option>pink</option>
    </select>
    <app-trex [dtTheme]="selectedTheme"></app-trex>
  `,
  styleUrls  : [ './app.component.less' ]
})
export class AppComponent {
  readonly themes = {
    'green': {
      'color-main'        : '#3D9D46',
      'color-main-darken' : '#338942',
      'color-main-darken2': '#286736',
      'color-main-lighten': '#7BBC4D',
      'color-accent'      : '#DC3C2A'
    },
    'pink'   : {
      'color-main'        : '#E05389',
      'color-main-darken' : '#CA3E86',
      'color-main-darken2': '#C13480',
      'color-main-lighten': '#E77A96',
      'color-accent'      : '#208FBC'
    }
  };
  selectedTheme = {};

  setTheme(val) {
    this.selectedTheme = this.themes[val];
  }
}

3.建立一個trex.component.ts元件

import { Component, OnInit, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'dt-trex',
  template: `
    <div class="class1">aaaa</div>
    <div class="class2">bbb</div>
    <div class="class3">ccc</div>
    <div class="class4">ddd</div>
  `,
  styles:`
    .class1{color:var(--color-main, #ff0000);}
    .class2{color:var(--color-main-darken);}
    .class3{color:var(--color-main-darken2);}
    .class4{color:var(--color-main-lighten);}
  `
})
export class TrexComponent {
  constructor() { }
}

CSS 定製主題完成了,筆者準備好了原始碼,方便大家檢視,點選檢視原始碼

但是這種方式有個缺點,瀏覽器最好支援 CSS3 Variable,如果不支援 CSS3 Variable,那麼我還是建議你使用 less 變數。如果你並不想採用 less 的 modifyVars 方式,或者不想重寫 webpack,那麼以下這種方式也許適合你。

3. Angular Configuration

Angular 的元件預設工作在 ViewEncapsulation.Emulated 模式下,在這個模式下,應用程式的dom元素都會附加額外的屬性,而 index.html 被新增的 style 會包含這些屬性,從而做到元件樣式的隔離;但是 component 中的樣式,打包後最後會以 JS 形式出現(原理可檢視上面 “webpack 打包原理”)。

因此如果想實現主題定製,實際上是需要打多個 angular 的生成包,不過值得高興的是 angular-cli 原生支援同時生成多個 package,我們可以配置 light 和 dark 變數檔案,利用 angular-cli 的 builder 打多個主題包,然後利用路由切換不同的主題。

  • ViewEncapsulation.Emulated(預設)樣式將被包裝到 style 標籤中,推送到 head 標籤,並唯一標識,以便與元件的模板匹配,樣式將僅用於同一組件中的模板。
  • ViewEncapsulation.ShadowDom 全域性樣式都不會影響後代元件
  • ViewEncapsulation.Native 已棄用
  • ViewEncapsulation.None 樣式包裹在 style 標籤中並推送到 head,緊跟在元件內聯和外部樣式之後,屬於全域性樣式。

下面簡單介紹一下這種方式的實現流程:

  1. 配置全域性樣式 style.less

注意:customize_theme 是資料夾名稱,存放於 src/product-configurations/styles/(light|dark)下,利用 angular.json 中的 stylePreprocessorOptions(允許新增額外的基準路徑,這些基準路徑將被檢查予以匯入,Import ‘customize_theme’,可以成功匯入,再也不用寫很長的../../相對路徑)

@import 'customize_theme';

2. 配置 angular.json

注意:升級到 angular8.0 後,configurations 中的 key(如 ligth-theme)不能包含“:”(踩坑),原因這裡檢視

"configurations": {
    "light-theme": {
        "stylePreprocessorOptions": {
            "includePaths": [
                "src/styles",
                "src/product-configurations/styles/light"
            ]
        }
    },
    "dark-theme": {
        "stylePreprocessorOptions": {
            "includePaths": [
                "src/styles",
                "src/product-configurations/styles/dark"
            ]
        }
    }
    ...
}

3. 配置 packge.json

{
  "name": "app",
  "version": "0.0.1",
  "scripts": {
       "build:light": "ng build --project=app-build --configuration=light-theme",
       "build:dark": "ng build --project=app-build --configuration=dark-theme"
  }
  ...
}

這種方式缺點很明顯,需要打包後切換不同語言包,打包時間翻倍,且需要路由來控制語言切換,每次切換語言都要重新載入,效能上比較浪費。既然如此,如何避免這些缺陷了?下面來介紹一種既簡單又效能好的方式。

4. :host-context()

:host-context() 是 webComponents 下的 selector,很多人可能都沒有使用過,但是卻是相對而言最適合的主題切換方式。注意 :host-context() 支援的瀏覽器可以在 這裡 檢視

:host-context(.theme-light) h2{
   // 基於當前元件向上查詢 .theme-light,有則應用到元件的 h2 中
}

下面來介紹一下實現這種主題定製的流程:點選檢視原始碼

  1. 配置 angular.json,暴露兩個主題檔案
"styles": [ "src/light.less","src/dark.less" ]

2. 修改 dark.less 和 light.less 檔案

@html-selector: html;
@primary-color: blue;
@html-selector: html;
@primary-color: red;

3. 配置全域性樣式 styles.less 檔案

.themeMixin(@rules) {
  :host-context(.dark) {
    @import "theme-dark";
    @rules();
  }
  :host-context(.light) {
    @import "theme-light";
    @rules();
  }
}

4. 配置 app.component.less 應用

@import "../styles";
.themeMixin({
  p {
    color: @primary-color;
  }
});

5. 在瀏覽器中給 body 新增 class='dark|light',即可看到效果。

以上方式可以實現 less 的主題動態切換,無需打包和設定路由,但是 :host-context() 和 :host 混用,會有些問題,具體可檢視這裡。

其他元件中有主題概念,需要用 themeMixin 包起來使用,此外 @html-selector 變數可以實現兩種主題共同存在,如果你需要的話。

對比以上四種方式

  • webpack loader:瀏覽器都支援,需多次打包,支援:host混用,流程比較複雜;
  • CSS Variable:Chrome 49以上、FireFox 31以上、Safari 9.1以上、IE不支援,1次打包,支援:host混用,流程簡單直接
  • Angular Configuration:瀏覽器都支援,需多次打包,支援:host混用,流程簡單直接
  • :host-context():Chrome 54以上、opera 41以上,FireFox 、Safari、IE不支援,1次打包 ,不支援:host混用,流程比較複雜