TypeScript 中使用 CSS Modules

CSS 的全局性

相当长一段时间 CSS 总是在页面上作为一个全局的存在,以前这个『特性』影响还不算很大,命名上注意一点,比如使用 BEM 也能一定程度上解决问题。

但是随着 web 组件化的需求越来越强烈,CSS 的这种特性开始成为束缚开发者自由飞翔的绳索,每一个 CSS 类名都有可能产生意想不到的冲突,或者被各个组件覆盖来覆盖去,每当修改一个组件时,我们必须谨小慎微,注意是否会在全局环境下产生冲突。

更严重的是,组件化的背景下,JS + 模板 + CSS 才能称为一个完整的组件,每个组件如果单独引用一个 CSS 文件,你只能通过约束类命名来规避不同组件间可能产生的冲突。同时由于 CSS 的全局性,组件的样式可能会互相影响,这完全打破了组件的低耦合高内聚原则。

当然,CSS 并不能算编程语言,不过是一款 DSL,本来我们不能要求它那么多,但是不甘心的程序员们想出了各种方法让 CSS 更像编程语言,从 SASS、Less 到现在有点火的 CSS in JS 都是为了解决这个问题。

而 CSS Modules 的解决思路有所不同,它在编写 CSS 的时候加入了局部作用域/全局作用域的概念。

CSS Modules

CSS Module 的规则非常简单,所有你不指明是全局作用域的都会当初局部作用域来处理。

这里以 SASS 为例,比如你写一段:

.title {
    height: 80px;
    line-height: 80px;
    font-size: 24px;
    color: #0a95bf;
}

出来的样式是:

.title_1hf8_ {
    height: 80px;
    line-height: 80px;
    font-size: 24px;
    color: #0a95bf
}

title 变成了 title_1hf8_ 你大概猜到了,CSS Module 实现局部作用域的方法是把类通过一定规则做个编码。

组件中引用是这样的:

import * as Style from './index.scss';

这样就可以在模板中使用了:

<div v-bind:class="Sytles.title">MD Converter</div>

出来的 html 就是:

<div class="title_1hf8_">MD Converter</div>

这里用的是 TypeScript 和 Vue ,其它地方用法也差不多。

『局部作用域』有了,下面是全局作用域:

:global {

.output {
    background: #fff;
    height: 100%;
    min-height: 100%;
    padding: 1em;
    word-break: break-all;
}

}

把你想作为全局作用域的东西用 :global{} 包起来就可以了。

配合 SASS 和 TypeScript

一般用 CSS Module 使用 Webpack 的 css-loader 即可,这里因为用的是 TypeScript,会有点不一样。

先来个完整的 Webpack 配置文件:

var path = require('path');
var webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    //页面入口文件配置
    entry: {
        "index.entry": __dirname + '/src/js/index.entry.js',
    },

    //入口文件输出配置
    output: {
        filename: '[name].js',
        chunkFilename: "[name].chunk.js",
    },

    externals: {
        "vue": "Vue",
    },
    plugins: [
        new webpack.optimize.UglifyJsPlugin({
          compress: {warnings: false},
          output: {comments: false},
          sourceMap: true
        })
    ],
    module: {
        rules: [
            {
                test: /\.html$/,
                loader: 'text-loader'
            },
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallback: {
                        loader: 'style-loader',
                        options: {
                            insertAt: 'top'
                        }
                    },
                    use: [
                        {
                            loader: 'typings-for-css-modules-loader',
                            options: {
                                modules: true,
                                namedExport: true,
                                camelCase: true,
                                minimize: true,
                                localIdentName: "[local]_[hash:base64:5]"
                            }
                        },
                        {
                            loader: 'sass-loader',
                            options: {
                                outputStyle: 'expanded',
                                sourceMap: true
                            }
                        }
                    ]
                })
            },          
        ],
    },
    plugins: [
        new ExtractTextPlugin({
            filename: (getPath) => {
                return getPath('../css/[name].css').replace('css/js','css');
            },
            allChunks: true
        }),
    ]

};

这里主要用到三个 Loader,分别是 sass-loader,typings-for-css-modules-loader 和 style-loader。

如果不是 TypeScript 把 typings-for-css-modules-loader 换成 css-loader,稍微改下配置即可。

启用 CSS Module 很简单,就一句话 modules: true, 然后下面有个选项 localIdentName: "[local]_[hash:base64:5]" 是用来指定生成类名的规则的,title_1hf8_ 就是根据这个规则来的,你也可以弄得复杂点,比如 [path][name]__[local]--[hash:base64:5]

简单解释下三个 Loader 的作用:

  1. sass-loader 的作用当然是把 SASS 文件编译成 CSS 文件;
  2. typings-for-css-modules-loader 是在 css-loader 上包了一层,它的选项完全兼容 css-loader。除此之外,它会为每个 SASS 文件生成对应的 xxx.scss.d.ts 的解释文件,这样在 TypeScript 中就可以正确解析,编辑器里面也能有非常友好的代码提示。
  3. style-loader 就是把样式使用 <style> 标签打到页面上。

整个过程就是,读到一个 SCSS 文件,丢给 sass-loader 处理成 css,然后给 typings-for-css-modules-loader 生成 xxx.scss.d.ts 文件并且把 css 处理成 JavaScript 可以使用的样子(这步其实是 css-loader 在处理,为啥要把 css 文件处理成 JavaScript 可以用的样子呢,因为 webpack 只能处理 JavaScript,所以需要做转换),最后把处理好的给 style-loader,页面加载的时候就会打到页面上。

其实 loader 的本质就是 anything to JavaScript,因为 Webpack 只处理 JavaScript。记住这一点,就对为什么要用这个 loader 那个 loader 有个清晰的认识了。

在用 TypeScript 写 Vue 组件的时候,定义组件时,可以 require 一个 html 作为 template:

@Component({
    template: require('./index.html'),
})

为啥可以 require html 文件呢,因为上面的 webpack 配置中有这句:

            {
                test: /\.html$/,
                loader: 'text-loader'
            },

把 html 文件用 text-loader 处理了,把 require html 变成一段 text,利用 Webpack 模板和代码优雅分离。

其它

除此之外,CSS Modules 还有定义变量,继承别的类,import 其它模块等特性,不过这些 SASS 大多也有。

最后上一个简单的例子,TypeScript + Vue 的 Markdown 简单编辑器。

demo 代码地址: https://github.com/bob-chen/md

参考

http://www.ruanyifeng.com/blog/2016/06/css_modules.html

https://github.com/camsong/blog/issues/5

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3e7zfmubrl44s

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CodingBlock

Eclipse的快捷键及常用设置

本篇文章转自:eclipse快捷键及各种设置 1、提示键配置 一般默认情况下,Eclipse ,MyEclipse 的代码提示功能是比Microsoft Vis...

2605
来自专栏移动开发之家

GSYVideoPlayer项目说明

项目目前UI层大部分方法和变量都是protect,虽然就封装性而言这并不是很好,但你可以继承后快捷实现你的自定义。

1113
来自专栏每日一篇技术文章

go - os包 彻头彻尾用法使用

os包提供了操作系统函数的不依赖平台的接口。设计为Unix风格的,虽然错误处理是go风格的;失败的调用会返回错误值而非错误码。通常错误值里包含更多信息。例如,如...

2966
来自专栏从零开始学自动化测试

Selenium2+python自动化24-js处理富文本

前言 上一篇Selenium2+python自动化23-富文本(自动发帖)解决了富文本上iframe问题,其实没什么特别之处,主要是iframe的切换,...

2935
来自专栏技术小讲堂

ASP.NET AJAX(3)__UpdatePanel

今天也不知道写不写的完了,最近闲下来了,却感冒了,早上起来都不会说话了,不过幸亏咱不是靠嘴皮子过活了,哎~~~~窃喜吧 上一篇简单写到UpdatePanel的一...

3615
来自专栏zhisheng

使用 CodeMirror 打造属于自己的在线代码编辑器

前提 写这个的目的是因为之前项目里用到过 CodeMirror,觉得作为一款在线代码编辑器还是不错,也看到过有些网站用到过在线代码编辑,当然我不知道他们是用什么...

9007
来自专栏狮乐园

codereview-s8

之后再efficiencyView方法中调用stopPropagation方法阻止事件冒泡

903
来自专栏祝威廉

React问题三则

这两天在用 Blueprint+React+ServiceFramework+MySQL 为主要组件开发一个小玩具,由衷的喜欢React了。为啥呢?非常后端,其...

811
来自专栏韩东吉的Unity杂货铺

零基础入门 21: UGUI Inputfield

因为一些外部原因,以后文章的发布只会在公众号内推送,取消了在蛮牛专栏的文章更新,望蛮牛小伙伴周知,关注微信公众号,可以第一时间收到新分享的推送通知。

3102
来自专栏葬爱家族

微信小程序WePY开发框架简介

微信小程序入门门槛低、开发周期短、代码编写灵活、传播速度快等优点让微信小程序迅速火爆,开发者纷纷涌入,任何语言开发者一旦多了,就会有新的框架出来,WePY就是一...

2962

扫码关注云+社区

领取腾讯云代金券