angular linux 打包不成功_漫談 Angular 定製主題的四種方式
技術標籤:angular linux 打包不成功
主題定製是提升使用者體驗最常見的一種,前端框架眾多,主題定製方式卻異曲同工,下面來介紹一下 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 可以幫忙我們實現主體定製,通過一下這兩個配置,我們就可以把部分樣式抽出變數,通過不同的變數組合成不同的主題:
- globalVars:相當於給每個 less 檔案頂部增加一行 @VariableName: xx;
- modifyVars:相當於給每個 less 檔案底部增加一行變數 @variable:xx;
custom-webpack
angular-cli 提供了 custom-webpack 的 builder,可以和 angular-cli 合併使用,通過 builder 重寫 webpack 中的 less-loader 的配置,然後利用 modifyVars 實現主題定製。
- 安裝
npm i -D @angular-builders/custom-webpack
- 在根目錄新建 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 打包器:
- 在根目錄新增 webpack.config.js 檔案。
- 執行命令 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,緊跟在元件內聯和外部樣式之後,屬於全域性樣式。
下面簡單介紹一下這種方式的實現流程:
- 配置全域性樣式 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 中
}
下面來介紹一下實現這種主題定製的流程:點選檢視原始碼
- 配置 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混用,流程比較複雜