将 css 模块应用于 AngularJS 的更好方法

Better way of applying css-modules to AngularJS

我现在正在使用 css-模块和 AngularJS 1.5 组件。堆栈是 webpack + css-loader?modules=true + angular 1.5 components + pug.

目前我必须执行以下步骤才能在我的哈巴狗模板中使用 css 模块。

// my-component.js

import template from 'my-component.pug';
import styles from 'my-component.css';

class MyComponent {
    constructor($element) {
        $element.addClass('myComponent');        // ------ (1)
        this.styles = styles;                    // ------ (2)
    }
}

angular.module(name, deps)
    .component('my-component', {
        controller: MyComponent,
        template: template,
    });

// my-component.pug
div(class={{ ::$ctrl.styles.fooBar }}) FooBar    // ----- (3)

// my-component.css
.myComponent { background: green; }
.fooBar { color: red; }

有两个问题:

  1. 每个组件都必须注入 $element 并手动设置其 class 名称。这样做的原因是,AngularJS组件标签本身存在于结果HTML中,没有任何classes,这使得CSS变得困难。例如,如果我像这样使用上面的 MyComponent

    <div>
      <my-component></my-component>
    </div>
    

    它将生成以下内容 HTML:

    <div>
      <my-component>
        <div class="my-component__fooBar__3B2xz">FooBar</div>
      </my-component>
    </div>
    

    与ReactJS相比,<my-component>在上面的结果HTML是一个额外的,有时它CSS很难写。 所以我的解决方案是(1),给它加一个class。

  2. 模板中的 class 太长 (3)。我知道这是引用 $ctrl.styles.fooBar 的正确方法,但这太长了。

我理想的解决方案是这样的:

// my-component.js
angular.module(name, deps)
    .component('my-component', {
        controller: MyComponent,
        template: template,
        styles: styles,
    });

// my-component.css
div(css-class="fooBar") FooBar

想法是:

  1. make angular.module().component 支持一个额外的 styles 属性,它会在控制器中自动执行 (2) this.styles = styles;,并应用 (1) $element.addClass()
  2. 指令 css-class$ctrl.styles 应用于元素。

我的问题是,我不知道如何实现上面的想法 1(2 很简单)。如果有人可以就此分享一些信息,我将不胜感激。

我提出了一个我不太满意的解决方案。

Angular 组件可以接受函数作为模板并使用 $element 注入。 doc

If template is a function, then it is injected with the following locals:

  • $element - Current element
  • $attrs - Current attributes object for the element

因此我们可以在模板函数中附加组件 (.myComponent) 的主要 class,然后正则表达式将所有出现的 class 名称替换为已编译的 class名字。

// utils.js
function decorateTemplate(template, styles, className) {
    return ['$element', $element => {
        $element.addClass(styles[className]);
        return template.replace(/$\{(\w+)\}/g, (match, p1) => styles[p1]);
    }];
}

// my-component.js
import style from './my-component.css';
import template from './my-component.pug';
import { decorateTemplate } from 'shared/utils';

class MyComponent {
    // NO NEED to inject $element in constructor
    // constructor($element) { ...
}

angular.module(name, deps)
    .component('myComponent', {
        // decorate the template with styles
        template: decorateTemplate(template, styles, 'myComponent'),
    });

// my-component.pug, with special '${className}' notation
div(class="${fooBar}") FooBar

还有一个地方需要改进,decorateTemplate使用正则表达式替换,模板必须使用特殊符号${className}来指定css-modules class名字。

欢迎提出任何建议。

更新

我更新了我的 decorateTemplate() 函数以利用哈巴狗的特性,因此本地 class 名称可以写成 ._localClassName.

// utils.js
function decorateTemplate(template, styles, className) {

    return ['$element', ($element) => {
        $element.addClass(styles[className]);

        return template.replace(/\sclass="(.+?)"/g, (match, p1) => {
            let classes = p1.split(/\s+/);
            classes = classes.map(className => {
                if (className.startsWith('_')) {
                    let localClassName = className.slice(1);
                    if (styles[localClassName]) {
                        return styles[localClassName];
                    } else {
                        console.warn(`Warning: local class name ${className} not found`);
                        return className;
                    }

                } else {
                    return className;
                }
            });
            return ' class="' + classes.join(' ') + '"';
        });
    }];
}

// my-component.pug
._fooBar FooBar

虽然这更容易,但它并没有消除古怪的符号(本地 class 名称以 _ 开头)和正则表达式替换。

欢迎提出任何建议。