zoukankan      html  css  js  c++  java
  • 在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

    首先查看整个项目目录

    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)来使用绑定类名,结果如下:

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

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

    通过查看原始的HTML代码,我们可以发现,css-loader最后将改名后的每个组件的css,全部以<style>的方式插入到html的header中,便于页面的引用,如下

    以上就是CSS Modules的基本原理

    验证是否存在CSS冲突

    前面我们在attrs组件中添加了一个.header类,值为color:green,接着我们以同样方式在about组件中添加一个.header类,值为 color: red;结果如下:

    attrs组件

    about组件

    可以看到尽管我们定义的原始类名都是.header,但是被css-loader修改后的类名是不一样的,所以在每个组件下定义的类即使同名也是不会发生冲突的,这就是scoped css的原理。

    global css

    我们实现了scoped css,又会问如果我想给全局添加一css怎么办呢?其实很简单,global css的实现引入方式和scoped css并没有区别,只是在类的定义上有区别,如下,在app目录下定义一个app.scss文件,内容如下:

     1 body {
     2     font-size: 20px;
     3 }
     4 
     5 :global {
     6     .index {
     7       display:inline-block;
     8       margin-top: 20px;
     9     }
    10 }

    注意第五行,使用:global定义的就是一个全局类,在项目任何地方都可以使用,比如我们在about组件中定义了一个directive,就可以在这个directive中使用index类,如下:

     1 function aDirective(homeService,$rootScope) {
     2     "ngInject";
     3     return {
     4         restrict: 'E',
     5         template: `name:<input type='text' ng-model='showValue' class='about'>
     6   <br><button ng-click="addName()" class="index">addName</button>`,
     7         link: (scope, element, attrs) => {
     8             scope.addName = () => {
     9                 if(scope.showValue){
    10                     $rootScope.$broadcast('addName',scope.showValue);
    11                 }        
    12             }
    13         }
    14     };
    15 }
    16 
    17 export default aDirective;

    注意第六行,就是直接指定类名为index,结果如下:

    在css modules中使用sass嵌套定义类名

    对于大型项目来讲,很少有直接使用css的,通常都是使用一些CSS预处理器,比如说Sass,在Sass中,通常类之间可以嵌套定义,配合CSS Modules,也是能够灵活使用,比如在about组件中,定义about.scss嵌套类名如下:

     1 //嵌套定义,只需要写类名即可,不需要嵌套调用
     2 .sec {
     3     .about {
     4         display: inline-block;
     5         margin-bottom: 10px;
     6     }
     7 }
     8 
     9 .header {
    10     color: red;
    11 }

    我们定义了一个.sec类,并且在该类下定义了一个嵌套的about类,在html中使用如下:

    1 <navbar></navbar>
    2 <h2 class="{{$ctrl.styles.header}}">{{ $ctrl.name }}</h2>
    3 <section class="{{$ctrl.styles.sec}}">
    4   <event-adirective class="{{$ctrl.styles.about}}"></event-adirective>
    5   <event-bdirective></event-bdirective>
    6 </section>

    注意第4行,我们并没有在使用about类的时候,在前面加上sec,这说明CSS Modules 在处理styles对象的时候认为所有类的类名都是平级关系,尽管它们在定义的时候是嵌套定义的,但是要让这些嵌套类起作用,必须按照嵌套的结构来添加类,结果如下:

    使用CSS Modules的多个css文件合并功能

    由于CSS Modules的主要思想是使用js来处理CSS,那么对于不同的CSS我们可以将其合并成一个styles对象,例如在ctrls组件中,定义两个sass文件,

    a.scss

    1 //多个scss文件合并
    2 .aCtrls{
    3   color: purple;
    4 }

    ctrls.scss

    .ctrls {
        button {
            display: inline-block;
            margin: 20px;
        }
    }

    在ctrls.controller.js中进行合并

     1 let styles={};
     2 import aStyle from './a.scss';
     3 import bStyle from './ctrls.scss';
     4 Object.assign(styles,aStyle,bStyle);
     5 class CtrlsController {
     6   constructor() {
     7     this.name = 'directive通过Controllers通信';
     8     this.styles=styles;
     9   }
    10 }
    11 
    12 export default CtrlsController;

    在1到4行,通过引入不同的styles文件,并将它们合并为一个对象,然后在ctrls.html中使用

     1 <navbar></navbar>
     2 <div>
     3   <h1 class='{{$ctrl.styles.aCtrls}}'>{{ $ctrl.name }}</h1>
     4   <a-ctrl-directive>
     5   <div>
     6     <div>
     7       <b-ctrl-directive class='{{$ctrl.styles.ctrls}}'></b-ctrl-directive>
     8     </div>
     9     </div>
    10   </a-ctrl-directive>
    11 
    12 </div>

    和使用一个sass文件定义的在使用上完全没有任何区别。

    以上就是CSS Modules在Angular1.X项目中的使用,我们还可以结合PostCSS让它在功能上更加强大,进一步使用可以参考这里

  • 相关阅读:
    说下vue工程中代理配置proxy
    说一下登陆页面的实现逻辑
    $router和router区别
    iframe中涉及父子页面跨域问题
    浅析闭包
    用户注册之短信验证
    vue.js(三)
    vue.js(二)
    vue.js(一)
    批量更改会员权限
  • 原文地址:https://www.cnblogs.com/myzhibie/p/5861889.html
Copyright © 2011-2022 走看看