翻译自:https://github.com/dojo/frame...css
Dojo 部件最适合做为简单的组件,每一个组件处理单一职责。它们应该尽量的封装和模块化,以提升可重用性,同时避免与应用程序使用的其余组件出现冲突。html
可使用常规的 CSS 为部件设置样式,可是为了达到封装和复用的目标,每一个部件应该维护各自的 CSS 模块(CSS module),该模块与部件的源代码存放在各自的文件中。这样就能够独立地设置各部件的样式,而不会与应用程序其余地方使用的类名冲突。node
Dojo 界定出如下几类样式,每一类都表明了企业 web 应用程序中的样式需关注的不一样方面和粒度:git
未主题化的部件样式 (_粒度:_ 单个部件)github
主题化的部件样式 (_粒度:_ 单个部件)web
theme
中间件中的 theme.classes(css)
API,传入须要主题化的 CSS,并在渲染时使用返回的类名。部件的使用者能够按需覆写部分或全部类。应用于多个部件的样式 (_粒度:_ 应用程序范围)npm
应用于多个部件的样式,这些部件既能够是不一样类型的部件,也能够是单个部件的多个实例。这些样式为应用程序中全部可主题化的部件提供一致的可视化外观。可经过如下几种机制提供(或引用)全局样式:json
variables.css
文件,供其余样式导入和引用如上述列表所示,不管是跨整个应用程序,仍是单个样式类中的单条样式规则,Dojo 为应用程序开发者提供了几种互相补充的机制来提供或重写 CSS 样式类。浏览器
Dojo 借助 CSS Modules,既提供了 CSS 的全部灵活性,又引入了本地化样式类的额外优点,防止大型应用程序中无心间的样式冲突。Dojo 也为每一个 CSS 模块生成类型定义文件,容许部件用与导入其余 TypeScript 模块类似的方式来导入 CSS 模块,并以类型安全的方式引用 CSS 类名,同时在设计期间可使用 IDE 自动完成功能。安全
部件的 CSS 模块文件应该使用 .m.css
扩展名,并约定 CSS 模块的文件名要与关联的部件名保持一致。具备此扩展名的文件会被看成 CSS 模块来处理,而不是普通的 CSS 文件。
如下是部件的 CSS 模块文件:
src/styles/MyWidget.m.css
.myWidgetClass { font-variant: small-caps; } .myWidgetExtraClass { font-style: italic; }
在对应的部件中使用此样式,以下所示:
src/widgets/MyWidget.ts
import { create, tsx } from '@dojo/framework/core/vdom'; import * as css from '../styles/MyWidget.m.css'; const factory = create(); export default factory(function MyWidget() { return <div classes={[css.myWidgetClass, css.myWidgetExtraClass]}>Hello from a Dojo widget!</div>; });
在构建的应用程序中查看示例部件中的 CSS 类时,它们不会直接包含 myWidgetClass
和 myWidgetExtraClass
,而是通过混淆处理的 CSS 类名,相似于 MyWidget-m__myWidgetClass__33zN8
和 MyWidget-m__myWidgetExtraClass___g3St
。
混淆后的类名专用于 MyWidget
元素,而这是由 Dojo 的 CSS 模块化构建流程决定的。有了这种机制,则同一个应用程序的其余部件也可使用 myWidgetClass
类名,即便具备不一样的样式规则,也不会在每组样式间出现任何冲突。
警告: 混淆处理的 CSS 类名是不稳定的,可能会随着应用程序的构建而更改,因此开发人员不能显式地引用它们(例如试图在应用程序的其余位置定位一个元素)。
Dojo 可使用现代的 CSS 特性,例如自定义属性和 var()
,来提取和集中管理应用程序中的通用样式属性。
没必要在每一个部件的 CSS 模块中为颜色或字体设置相同的值,而是经过提取自定义属性,在每一个 CSS 模块中引用该属性名,而后在集中一处的 CSS :root
伪类中设置值。这种隔离更易于维护跨整个应用程序的公共样式。
例如:
src/themes/variables.css
:root { /* different sets of custom properties can be used if an application supports more than one possible theme */ --light-background: lightgray; --light-foreground: black; --dark-background: black; --dark-foreground: lightgray; --padding: 32px; }
src/themes/myDarkTheme/MyWidget.m.css
@import '../variables.css'; .root { margin: var(--padding); color: var(--dark-foreground); background: var(--dark-background); }
注意,在一个页面中,:root
伪类是全局的,可是由于 Dojo 使用了 CSS 模块,则可能会在应用程序的多处指定 :root
属性。可是 Dojo 没法保证 CSS 模块的处理顺序,所以为了确保 :root
中属性的一致性,建议在应用程序的代码中只有一处 :root
定义,统一放在 variables.css
文件中。这个集中存放的变量文件是一个常规的 CSS 文件(不是一个 CSS 模块),当 CSS 模块须要使用自定义属性值时,可使用 @import
导入。
Dojo 默认的构建流程按原样将自定义属性输出到应用程序的样式表中。对于最新的浏览器来讲,这样作没有问题;但当使用的浏览器没有实现 CSS 自定义属性标准(如 IE)时,就会出现问题。为了解决这个问题,可使用遗留模式(dojo build app --legacy
)来构建应用程序,这种状况下,Dojo 会在构建期间解析自定义属性的值,并复制到输出的样式表中。一个值将包含原来的 var()
引用,第二个值是专为旧版浏览器解析的值,当没法处理 var()
时就使用解析后的值。
将主题应用到 Dojo 部件后,部件的默认样式类会彻底被主题提供的样式类覆盖。当只须要经过主题修改样式类中的一部分属性,而其他属性依然使用默认值时,就会出现问题。
Dojo 应用程序中的 CSS 模块文件可使用 composes:
功能将样式从一个类选择器应用到另外一个类选择器。当经过调整现有的主题来建立一个新主题时,或者在单个主题中提取通用的样式属性时(注意,提取单个属性的值是更标准的作法是 CSS 自定义属性),这个功能是颇有用的。
警告: composes:
功能可能会比较脆弱,例如当扩展一个不受当前应用程序控制的第三方主题时。第三方主题所作的任何更改,均可能会破坏基于 composes
功能的应用程序主题,且这样的破坏很难定位和解决。
可是,在大型应用程序中仔细使用此功能会颇有用。好比,集中管理一组公共属性:
src/themes/common/ButtonBase.m.css
.buttonBase { margin-right: 10px; display: inline-block; font-size: 14px; text-align: left; background-color: white; }
src/themes/myBlueTheme/MyButton.m.css
.root { composes: buttonBase from '../common/ButtonBase.m.css'; background-color: blue; }
因为 Dojo 应用程序中的样式主要是针对单个部件的,所以不须要使用复杂的选择器。在 Dojo 中为应用程序设置样式时应尽量简单,开发人员可经过如下几条简单的建议作到这一点:
维护封装的部件样式
避免嵌套选择器
避免使用 BEM 命名规范
!important
Dojo 应用程序须要一种方法,来为全部部件展现一致的外观,这样用户就能够总体地把握和使用应用程序功能,而不是认为将东拼西凑的元素混搭在网页中。这一般要根据公司或产品的营销主题来指定颜色、布局或字体等实现的。
考虑让部件支持主题须要作两方面的准备:
theme
中间件,const factory = create({ theme })
theme.classes(css)
返回的一个或多个部件样式类。按惯例,当开发的部件须要分发时,还须要考虑第三点要求(Dojo 部件库中的部件都遵循此约定):
root
的样式类。这样当在自定义主题中覆写第三方可主题化部件的样式时,就能以一致的方式定位到顶层节点。theme
中间件是从 @dojo/framework/core/middleware/theme
模块中导入的。
theme.classes
方法theme.classes
将部件的 CSS 类名转换应用程序或部件的主题类名。
theme.classes<T extends ClassNames>(css: T): T;
theme
中间件属性theme
(可选)
classes
(可选)
下面是一个可主题化部件的 CSS 模块文件:
src/styles/MyThemeableWidget.m.css
/* requirement 4, i.e. this widget is intended for wider distribution, therefore its outer-most VDOM element uses the 'root' class: */ .root { font-family: sans-serif; } /* widgets can use any variety of ancillary CSS classes that are also themeable */ .myWidgetExtraThemeableClass { font-variant: small-caps; } /* extra 'fixed' classes can also be used to specify a widget's structural styling, which is not intended to be overridden via a theme */ .myWidgetStructuralClass { font-style: italic; }
在相应的可主题化的部件中使用这些样式:
src/widgets/MyThemeableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom'; import theme from '@dojo/framework/core/middleware/theme'; import * as css from '../styles/MyThemeableWidget.m.css'; /* requirement 1: */ const factory = create({ theme }); export default factory(function MyThemeableWidget({ middleware: { theme } }) { /* requirement 2 */ const { root, myWidgetExtraThemeableClass } = theme.classes(css); return ( <div classes={[ /* requirement 3: */ root, myWidgetExtraThemeableClass, css.myWidgetExtraThemeableClass ]} > Hello from a themed Dojo widget! </div> ); });
部件也能导入和引用多个 CSS 模块,除了本指南的其它部分介绍的基于 CSS 的方法(CSS 自定义属性 和 CSS 模块化组合功能)以外,这提供了另外一种经过 TypeScript 代码来提取和复用公共样式属性的方法。
扩展上述示例:
src/styles/MyThemeCommonStyles.m.css
.commonBase { border: 4px solid black; border-radius: 4em; padding: 2em; }
src/widgets/MyThemeableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom'; import theme from '@dojo/framework/core/middleware/theme'; import * as css from '../styles/MyThemeableWidget.m.css'; import * as commonCss from '../styles/MyThemeCommonStyles.m.css'; const factory = create({ theme }); export default factory(function MyThemeableWidget({ middleware: { theme } }) { const { root } = theme.classes(css); const { commonBase } = theme.classes(commonCss); return <div classes={[root, commonBase, css.myWidgetExtraThemeableClass]}>Hello from a themed Dojo widget!</div>; });
部件的使用者能够将一个有效的主题传给部件实例的 theme
属性,来重写特定部件实例的主题。当须要在应用程序的不一样部分以多种方式显示给定的部件时,这个功能就能派上用场。
例如,在可主题化部件示例的基础上构建:
src/themes/myTheme/styles/MyThemeableWidget.m.css
.root { color: blue; }
src/themes/myThemeOverride/theme.ts
import * as myThemeableWidgetCss from './styles/MyThemeableWidget.m.css'; export default { 'my-app/MyThemeableWidget': myThemeableWidgetCss };
src/widgets/MyApp.tsx
import { create, tsx } from '@dojo/framework/core/vdom'; import MyThemeableWidget from './src/widgets/MyThemeableWidget.tsx'; import * as myThemeOverride from '../themes/myThemeOverride/theme.ts'; const factory = create(); export default factory(function MyApp() { return ( <div> <MyThemeableWidget /> <MyThemeableWidget theme={myThemeOverride} /> </div> ); });
此处,渲染了两个 MyThemeableWidget
实例,若是指定了应用程序范围的主题,则第一个部件会使用此主题,不然使用部件的默认样式。相比之下,第二个部件始终使用 myThemeOverride
中定义的主题。
主题机制提供了一种简便的方式,为应用程序中的每一个部件统一应用自定义样式,但当用户但愿为给定的部件实例应用额外的样式时,在这种场景下主题机制就不够灵活。
能够经过可主题化部件的 classes
属性来传入额外的样式类。这些样式类是追加的,不会重写部件已有的样式类,它们的目的是对已经存在的样式进行细粒度的调整。提供的每一组额外的样式类都须要按照两个级别的 key 进行分组:
例如,额外的样式类属性的类型定义为:
type ExtraClassName = string | null | undefined | boolean; interface Classes { [widgetThemeKey: string]: { [baseClassName: string]: ExtraClassName[]; }; }
做为一个提供额外样式类的示例,下面调整 Dojo combobox 实例,以及其中的子部件 text input。此操做会将 combobox 使用的 text input 控件的背景色以及其自身面板的背景色改成蓝色。combobox 控件面板中的下拉箭头也会变为红色:
src/styles/MyComboBoxStyleTweaks.m.css
.blueBackground { background-color: blue; } .redArrow { color: red; }
src/widgets/MyWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom'; import ComboBox from '@dojo/widgets/combobox'; import * as myComboBoxStyleTweaks from '../styles/MyComboBoxStyleTweaks.m.css'; const myExtraClasses = { '@dojo/widgets/combobox': { controls: [myComboBoxStyleTweaks.blueBackground], trigger: [myComboBoxStyleTweaks.redArrow] }, '@dojo/widgets/text-input': { input: [myComboBoxStyleTweaks.blueBackground] } }; const factory = create(); export default factory(function MyWidget() { return ( <div> Hello from a tweaked Dojo combobox! <ComboBox classes={myExtraClasses} results={['foo', 'bar']} /> </div> ); });
注意,部件的做者负责显式地将 classes
属性传给全部的要使用样式类的子部件,由于 Dojo 自己没法将这个属性注入给或自动传给子部件。
要为应用程序中全部可主题化的部件指定一个主题,可在应用程序顶层部件中使用 theme
中间件中的 theme.set
API。要设置默认的或初始的主题,则在调用 theme.set
以前要先使用 theme.get
进行确认。
例如,为应用程序设置一个初始主题:
src/App.tsx
import { create, tsx } from '@dojo/framework/core/vdom'; import theme from '@dojo/framework/core/middleware/theme'; import myTheme from '../themes/MyTheme/theme'; const factory = create({ theme }); export default factory(function App({ middleware: { theme }}) { // if the theme isn't set, set the default theme if (!theme.get()) { theme.set(myTheme); } return ( // the application's widgets ); });
有关导入的 myTheme
结构说明,请参考编写主题。
请注意,使用可主题化的部件时,若是没有显示指定主题(例如,没有使用 theme.set
设置一个默认主题,也没有显式地重写部件实例的主题或样式类),则每一个部件都使用默认的样式规则。
若是使用一个彻底独立分发的主题(/learn/styling/working-with-themes#distributing-themes),应用程序还须要将囊括主题的 index.css
文件集成到自身的样式中来。在项目的 main.css
文件中导入。
src/main.css
@import '@{myThemePackageName}/{myThemeName}/index.css';
与之相比,另外一种使用外部构建主题的部份内容的方法是经过主题组合功能(/learn/styling/working-with-themes#composing-off-dojo-themes)实现的。
theme
中间件中的 .set(theme)
函数用于在整个应用程序级别更改当前激活的主题。为 .set
传入所需的主题,这将让应用程序树中全部可主题化的部件失效,并使用新的主题从新渲染。
src/widgets/ThemeSwitcher.tsx
import { create, tsx } from '@dojo/framework/core/vdom'; import theme from '@dojo/framework/core/middleware/theme'; import myTheme from '../themes/MyTheme/theme'; import alternativeTheme from '../themes/MyAlternativeTheme/theme'; const factory = create({ theme }); export default factory(function ThemeSwitcher({ middleware: { theme } }) { return ( <div> <button onclick={() => { theme.set(myTheme); }} > Use Default Theme </button> <button onclick={() => { theme.set(alternativeTheme); }} > Use Alternative Theme </button> </div> ); });
Dojo 的主题框架使用“部件主题 key” 概念将重写的样式与对应部件的相关样式关联。覆盖的样式一般在主题中指定;但若是须要,也能够直接传给 theme
中间件的 classes
属性。
一个部件的主题 key 的确切格式为:
{package-name}/{widget-css-module-name}
其中 package-name
是项目 package.json
中 name
属性的值,widget-css-module-name
是部件使用的主 CSS 模块的文件名(_不包括_ .m.css
扩展名)。
给定以下项目:
package.json
{ "name": "my-app" }
遵循部件的 CSS 模块命名规范时,一个 src/widgets/MyWidget.ts
使用的 CSS 模块名相似于 src/styles/MyWidget.m.css
。所以 MyWidget
的主题 key 为:
my-app/MyWidget
此处,部件的名称与其 CSS 模块文件的名称相同,可是开发人员要注意,不要将部件的主题 key 误认为就是部件的 TypeScript 类名。
再看第二个部件,没有遵循 CSS 模块的命名规范,如 src/widgets/BespokeWidget.ts
使用的 CSS 模块为 src/styles/BespokeStyleSheet.m.css
,则部件的主题 key 应改成:
my-app/BespokeStyleSheet
主题就是一个 TypeScript 模块,会导出一个默认对象,其中将部件主题 key 映射到导入的类型化的 CSS 模块上。主题中的 CSS 模块与部件直接使用的常规模块相同。一旦应用程序应用了主题,则主题定义对象中的主题 key 标识的每个部件的样式,都会被主题 key 对应的 CSS 模块中的样式覆盖。
如下是 MyWidget
部件完整主题中的一个简单示例(使用默认的 CSS 模块 MyWidget.m.css
),位于 my-app
项目中:
src/themes/myTheme/styles/MyWidget.m.css
.root { color: blue; }
src/themes/myTheme/theme.ts
import * as myThemedWidgetCss from './styles/MyWidget.m.css'; export default { 'my-app/MyWidget': myThemedWidgetCss };
此处,MyWidget
遵循命名规范,将主样式类命名为 root
,这样 myTheme
就能够被 src/themes/myTheme/styles/MyWidget.m.css
CSS 模块中的 root
类覆盖掉。
经过主题 key my-app/MyWidget
,主题将新的 root
样式类关联到 MyWidget
上。当应用 myTheme
主题后,MyWidget
会将其颜色设置为蓝色,且不会再接收其初始 CSS 模块的 root
类中定义的其余样式。
应用程序的主题可能须要包含第三方部件使用的样式,好比Dojo 自带部件库中提供的样式。
@dojo/cli-create-theme
中提供了一些工具,使用 dojo create theme
CLI 命令,能为第三方部件快速生成主题脚手架。可经过如下方式在应用程序中安装:
npm install --save-dev @dojo/cli-create-theme
而后在项目根目录下按以下方式使用:
dojo create theme -n {myThemeName}
运行此命令,会在询问两个问题后开始建立 myThemeName
主题:
What Package to do you want to theme?
@dojo/widgets
。本命令会继续询问更多的包,直到用户结束此操做。Which of the {third-party-package} theme files would you like to scaffold?
命令成功执行后,会在当前项目中建立几个文件:
src/themes/{myThemeName}/theme.ts
src/themes/{myThemeName}/{third-party-package}/path/to/{selectedWidget}.m.css
为全部 {selectedWidget}
建立的主题 CSS 模块都提供了可主题化的 CSS 选择器,而后就能够为 {myThemeName}
填充合适的样式规则。
任何包含 theme
目录的第三方包都是兼容的,其中既包含部件的 CSS 模块文件(*.m.css
),也包含对应的编译后的定义文件(*.m.css.js
- 详情参见分发主题)。
例如:
node_modules └── {third-party-package} └── theme │ {widget}.m.css │ {widget}.m.css.js
Dojo 的 cli-build-theme
提供了一个 CLI 命令,构建的主题可分发给多个应用程序使用。它会建立出以各类不一样方式使用主题所需的全部文件。
注意,当使用 dojo create theme
搭建新的主题时,并不须要使用 dojo build theme
,由于全部相关文件都已就位。这主要用于使用 @dojo/cli-build-app
或 @dojo/cli-build-widget
构建项目时来构建主题。
要使用此工具,在须要主题化的项目下安装 @dojo/cli-build-theme
:
npm install --save-dev @dojo/cli-build-theme
而后构建主题,请运行命令,并指定一个主题名以及一个可选的发布版本号:
dojo build theme --name={myThemeName} --release={releaseVersion}
若是没有指定 release
,则会使用 package.json
中的当前版本号。
运行该命令后,会在项目中建立一个 dist/src/{myThemeName}
文件夹,其中包含:
index.js
文件,会被导入并用于主题化应用程序或兼容的部件 .m.css
)。这些文件能够经过主题组合功能直接引用,可用于基于新构建的主题来建立出本身主题的全部应用程序。assets
文件夹,包含主题文件夹中的全部字体和图片。index.css
文件,若是要使用整个主题,则须要将其导入到应用程序的 main.css
中。支持在自定义元素(custom elements)上使用主题的其余文件:
{name}-{release}.js
文件,会使用全局的注册器注册主题(使用 <script>
标签添加)。{name}-{release}.css
文件,使用 <link rel="stylesheet">
标签添加。@dojo/themes
包提供了一组当即可用的主题,涵盖了 Dojo 自带部件库的全部部件。能够按原样使用主题库,或者做为基础组合出完整的应用程序主题。
@dojo/themes
,好比使用 npm i @dojo/themes
命令。而后,对于常规的 Dojo 应用程序:在项目的 main.css
文件中导入主题的 CSS 文件:
@import '~@dojo/themes/dojo/index.css';
导入主题的 TypeScript 模块,而后使用:
import theme from '@dojo/themes/dojo'; render() { return w(Button, { theme }, [ 'Hello World' ]); }
若是尝试在 Custom elements 中使用它,则安装完 @dojo/themes
以后:
在 index.html
中添加 Custom elements 专用的主题 CSS 文件:
<link rel="stylesheet" href="node_modules/@dojo/themes/dojo/dojo-{version}.css" />
在 index.html
中添加 Custom elements 专用的主题 JS 文件:
<script src="node_modules/@dojo/themes/dojo/dojo-{version}.js"></script>
一旦在项目中安装了 @dojo/themes
,就可将其做为扩展应用程序主题的基础,在新的主题中使用 CSS 模块化的组合功能来包含相关的组件。
@dojo/themes
中也包含一个 拥有 :root
的 variables.css
文件,若是扩展的应用程序主题须要在新主题的某处引用 Dojo 内置的属性,则能够导入该文件。
下面是一个 @dojo/widgets/button
使用新主题的示例,扩展自 @dojo/themes
,将按钮的背景色改成绿色,而其余的主题样式属性保持不变:
src/themes/myTheme/theme.ts
import * as myButton from './myButton.m.css'; export default { '@dojo/widgets/button': myButton };
src/themes/myTheme/myButton.m.css
@import '@dojo/themes/dojo/variables.css'; .root { composes: root from '@dojo/themes/dojo/button.m.css'; background-color: var(--dojo-green); }