在Angular1.X中使用CSS Modules

在Angular1.5中,增加了一个Component方法,并且定义了组件的若干生命周期hook,在代码规范中也是推崇组件化开发,但是很遗憾的是,CSS模块化组件化的问题并没有得到解决,大部分项目的打包方式还是将所有CSS打包成一个完整的CSS文件,然后插入到html中,这样做的坏处显而易见,如果团队没有良好的CSS代码规范,很容易引起CSS的冲突,本文使用CSS Modules来解决Angular1.X中存在的CSS 冲突问题。

为了便于读者查看并动手操作,我将所有的代码打包成了一个库,首先在本地clone这个库

git clone https://github.com/myzhibie/ng1-css-modules-demo.git

接着安装所需要的依赖

npm install

上述过程如果成功,就可以运行了

gulp serve

在浏览器中查看结果http://localhost:3000

首先查看整个项目目录

在Angular1.X中使用CSS Modules_第1张图片

client文件夹表示客户端代码,common目录下是一些公用组件,components目录下是非公用的业务组件,generator目录下是生成组件的模板文件,webpack.config.js是项目基础的webpack配置文件,根据开发环境会执行添加到dev或者production配置当中去。

CSS Modules是什么,怎么用

关于CSS Modules是什么,这里不多说,只阐述如何在项目中使用,好处也是显而易见的,就是彻底隔离了组件的CSS和全局的CSS,防止冲突。如果需要对CSS Modules有更深的概念上的认识,请查看官网。

配置webpack中的css-loader,启用css-modules.

在项目根目录下webpack.config.js中,查看如下代码

 1 module.exports = {
 2   devtool: 'sourcemap',
 3   entry: {},
 4   module: {
 5     loaders: [
 6        { test: /\.js$/, exclude: [/app\/lib/, /node_modules/], loader: 'ng-annotate!babel' },
 7        { test: /\.html$/, loader: 'raw' },
 8        { test: /\.scss$/, loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!sass' },
 9        { test: /\.css$/, loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' }
10     ]
11   },
12   // resolve:{
13   //   modulesDirectories:[bootstrapPath]
14   // },
15   plugins: [
16     // Injects bundles in your index.html instead of wiring all manually.
17     // It also adds hash to all injected assets so we don't have problems
18     // with cache purging during deployment.
19     new HtmlWebpackPlugin({
20       template: 'client/index.html',
21       inject: 'body',
22       hash: true
23     }),
24 
25     // Automatically move all modules defined outside of application directory to vendor bundle.
26     // If you are using more complicated project structure, consider to specify common chunks manually.
27     new webpack.optimize.CommonsChunkPlugin({
28       name: 'vendor',
29       minChunks: function (module, count) {
30         return module.resource && module.resource.indexOf(path.resolve(__dirname, 'client')) === -1;
31       }
32     })
33   ]
34 };

注意查看第8和第9行,对于css和scss文件的处理,使用css-loader并启用了css-modules,importLoaders=1代码该文件被CSS-loader处理之后还会被Style-loader再次处理一次,localIdentName指定了scope css名字生成的规则,这里是[name]代表组件所在目录名,[local]表示本来定义的css名称,[hash:base64:5]代表由css-loader生成的哈希值。这样定义之后,每次遇到css或者sass文件,css-loader都会按照我们指定的方式将组件的css名称改名并引用到html中。

添加Scoped css(Sass)

所谓的scoped css,就是指局部css,在我们的项目中可以理解为单个组件自己的css,不与全局css相混淆。在client/app/components目录下,选定attrs组件,添加attrs.scss,内容如下:

1 //local scope css 定义 
2 .attrs{
3   color:red;
4 } 
5 .header{
6   color:green;
7 }

接着在attrs.controller.js中引入这个sass文件,如下:

1 import styles from "./attrs.scss";
2 class AttrsController {
3   constructor() {
4     this.name = 'Directives利用attrs通信';
5     this.styles=styles;
6   }
7 }
8 
9 export default AttrsController;

第一行import引入进来我们刚才写的css,然后将它赋值给该controller的一个属性叫styles,接着在html中引用我们定义的styles,如下:

1 <navbar>navbar>
2 <div>
3   <h2 class="{{$ctrl.styles.header}}">{{ $ctrl.name }}h2>
4   <directive-b directive-a>directive-b>
5 div>

注意第3行,我们直接使用controller的styles对象加上我们定义的类名(header)来使用绑定类名,结果如下:

在Angular1.X中使用CSS Modules_第2张图片

可以看到css-loader将我们定义的类名改成了我们指定的模式,并在html中引用了该类,可以查看我们引入的styles对象如下:

在Angular1.X中使用CSS Modules_第3张图片

基本原理就是css-loader按照我们指定的模式修改每个组件的类名,并在我们import的时候将其打包到js中的一个对象,这个对象的key值是我们定义的原始类名,键值是修改后的类名。

通过查看原始的HTML代码,我们可以发现,css-loader最后将改名后的每个组件的css,全部以