TypeScript 中使用 CSS Modules

Bob-Chen發表於2017-09-23

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 程式碼地址:
github.com/bob-chen/md

碎碎念

記錄一些所思所想,寫寫科技與人文,寫寫生活狀態,寫寫讀書心得,主要是扯淡和感悟。
歡迎關注,交流。

微信公眾號:程式設計師的詩和遠方

公眾號ID : MonkeyCoder-Life

參考

www.ruanyifeng.com/blog/2016/0…

github.com/camsong/blo…

相關文章