<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【工程化】深入淺出 CSS Modules

          共 13689字,需瀏覽 28分鐘

           ·

          2021-04-20 23:41

          CSS Modules 是什么?

          官方文檔的介紹如下:

          A CSS Modules is a CSS file in which all class names and animation names are scoped locally by default.

          所有的類名和動畫名稱默認都有各自的作用域的 CSS 文件。CSS Modules 并不是 CSS 官方的標準,也不是瀏覽器的特性,而是使用一些構建工具,比如 webpack,對 CSS 類名和選擇器限定作用域的一種方式(類似命名空間)

          本文來介紹一下 CSS Modules 的簡單使用,以及 CSS Modules 的實現(xiàn)原理(CSS-loader 中的實現(xiàn))

          CSS Modules 的簡單使用

          項目搭建以及配置

          新建一個項目,本文的 Demo[1]

          npx create-react-app learn-css-modules-react
          cd learn-css-modules-react
          # 顯示 webpack 的配置
          yarn eject

          看到 config/webpack.config.js,默認情況下,React 腳手架搭建出來的項目,只有 .module.css 支持模塊化,如果是自己搭建的話,可以支持 .css 文件的后綴等

          // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
          // using the extension .module.css
          {
            test: cssModuleRegex,
            use: getStyleLoaders({
              importLoaders1,
              sourceMap: isEnvProduction
                ? shouldUseSourceMap
                : isEnvDevelopment,
              modules: {
                getLocalIdent: getCSSModuleLocalIdent,
              },
            }),
          }

          其中 getStyleLoaders 函數(shù),可以看到 css-loader 的配置

          const getStyleLoaders = (cssOptions, preProcessor) => {
            const loaders = [
              // ...
              {
                loaderrequire.resolve('css-loader'),
                options: cssOptions,
              },
              // ...
            ];
            // ...
            return loaders;
          };

          我們就基于這個環(huán)境當做示例進行演示

          局部作用域

          之前的樣式

          首先,我們將 App.css 修改成 App.module.css,然后導入 css,并設置(這里有個小知識點,實際上 CSS Modules 推薦的命名是駝峰式,主要是這樣的話,使用對象 style.className 就可以訪問。如果是如下,就需要 styles['***-***'])

          import styles from './App.module.css';

          // ...
          <header className={styles['App-header']}></header>

          就會根據(jù)特定的規(guī)則生成相應的類名

          這個命名規(guī)則是可以通過 CSS-loader 進行配置,類似如下的配置:

          module: {
            loaders: [
              // ...
              {
                test/\.css$/,
                loader"style-loader!css-loader?modules&localIdentName=[path][name]---[local]---[hash:base64:5]"
              },
            ]
          }

          全局作用域

          默認情況下,我們發(fā)現(xiàn),在 css modules 中定義的類名必須得通過類似設置變量的方式給 HTML 設置(如上示例所示)

          那么我能像其他的 CSS 文件一樣直接使用類名(也就是普通的設置方法),而不是編譯后的哈希字符串么?

          使用 :global 的語法,就可以聲明一個全局的規(guī)則

          :global(.App-link) {
            color#61dafb;
          }

          這樣就可以在 HTML 中直接跟使用普通的 CSS 一樣了

          但這里感覺就是 CSS Modules 給開發(fā)者留的一個后門,我們這樣的 CSS,還是直接放在普通 .css 文件中比較好,我理解這就是 React 為什么對待 .css 和 .module.css 不同后綴進行不同的處理的原因

          Class 的組合

          在 CSS Modules 中,一個選擇器可以繼承另一個選擇器的規(guī)則,這稱為 "組合"(["composition"](https://github.com/css-modules/css-modules#composition ""composition""))

          比如,我們定義一個 font-red,然后在 .App-header 中使用 composes: font-red; 繼承

          .font-red {
            color: red;
          }

          .App-header {
            composes: font-red;
            /* ... */
          }

          輸入其他的模塊

          不僅僅可以同一個文件中的,還可以繼承其他文件中的 CSS 規(guī)則

          定義一個 another.module.css

          .font-blue {
            color: blue;
          }

          在 App.module.css 中

          .App-header {
            /* ... */
            composes: font-blue from './another.module.css';
            /* ... */
          }

          使用變量

          我們還可以使用變量,定義一個 colors.module.css

          @value blue: #0c77f8;

          在 App.module.css 中

          @value colors: "./colors.module.css";
          @value blue from colors;

          .App-header {
            /* ... */
            color: blue;
          }

          使用小結

          總體而言,CSS Modules 的使用偏簡單,上手非常的快,接下來我們看看 Webpack 中 CSS-loader 是怎么實現(xiàn) CSS Modules

          CSS Modules 的實現(xiàn)原理

          從 CSS Loader 開始講起

          lib/processCss.js

          var pipeline = postcss([
           ...
           modulesValues,
           modulesScope({
              // 根據(jù)規(guī)則生成特定的名字
            generateScopedNamefunction(exportName{
             return getLocalIdent(options.loaderContext, localIdentName, exportName, {
              regExp: localIdentRegExp,
              hashPrefix: query.hashPrefix || "",
              context: context
             });
            }
           }),
           parserPlugin(parserOptions)
          ]);

          主要看 modulesValuesmodulesScope 方法,實際上這兩個方法又是來自其他兩個包

          var modulesScope = require("postcss-modules-scope");
          var modulesValues = require("postcss-modules-values");

          postcss-modules-scope

          這個包主要是實現(xiàn)了 CSS Modules 的樣式隔離(Scope Local)以及繼承(Extend)

          它的代碼比較簡單,基本一個文件完成,源碼可以看這里 [2],這里會用到 postcss 處理 AST 相關,我們大致了解它的思想即可

          默認的命名規(guī)則

          實際上,假如你沒有設置任何的規(guī)則時候會根據(jù)如下進行命名

          // 生成 Scoped name 的方法(沒有傳入的時候的默認規(guī)則)
          processor.generateScopedName = function (exportedName, path{
            var sanitisedPath = path.replace(/\.[^\.\/\\]+$/'').replace(/[\W_]+/g'_').replace(/^_|_$/g'');
            return '_' + sanitisedPath + '__' + exportedName;
          };

          這種寫法在很多的源碼中我們都可以看到,以后寫代碼的時候也可以采用

          var processor = _postcss2['default'].plugin('postcss-modules-scope'function (options{
            // ...
            return function (css{
              // 如果有傳入,則采用傳入的命名規(guī)則
              // 否則,采用默認定義的 processor.generateScopedName
              var generateScopedName = options && options.generateScopedName || processor.generateScopedName;
            }
            // ...
          })

          前置知識 —— postcss 遍歷樣式的方法

          css ast 主要有 3 種父類型

          • AtRule: @xxx 的這種類型,如 @screen,因為下面會提到變量的使用 @value
          • Comment: 注釋
          • Rule: 普通的 css 規(guī)則

          還有幾個個比較重要的子類型:

          • decl:指的是每條具體的 css 規(guī)則
          • rule:作用于某個選擇器上的 css 規(guī)則集合

          不同的類型進行不同的遍歷

          • walk: 遍歷所有節(jié)點信息,無論是 atRule、rule、comment 的父類型,還是 ruledecl 的子類型
          • walkAtRules:遍歷所有的 atRule
          • walkComments:遍歷注釋
          • walkDecls
          • walkRules

          作用域樣式的實現(xiàn)

          // Find any :local classes
          // 找到所有的含有 :local 的 classes
          css.walkRules(function (rule{
            var selector = _cssSelectorTokenizer2['default'].parse(rule.selector);
            // 獲取 selector
            var newSelector = traverseNode(selector);
            rule.selector = _cssSelectorTokenizer2['default'].stringify(newSelector);
            // 遍歷每一條規(guī)則,假如匹配到則將類名等轉(zhuǎn)換成作用域名稱
            rule.walkDecls(function (decl{
              var tokens = decl.value.split(/(,|'[^']*'|"[^"]*")/);
              tokens = tokens.map(function (token, idx{
                if (idx === 0 || tokens[idx - 1] === ',') {
                  var localMatch = /^(\s*):local\s*\((.+?)\)/.exec(token);
                  if (localMatch) {
                    // 獲取作用域名稱
                    return localMatch[1] + exportScopedName(localMatch[2]) + token.substr(localMatch[0].length);
                  } else {
                    return token;
                  }
                } else {
                  return token;
                }
              });
              decl.value = tokens.join('');
            });
          });

          css.walkRules 遍歷所有節(jié)點信息,無論是 atRule、rule、comment 的父類型,還是 ruledecl 的子類型,獲取 selector

          // 遞歸遍歷節(jié)點,找到目標節(jié)點
          function traverseNode(node{
            switch (node.type) {
              case 'nested-pseudo-class':
                if (node.name === 'local') {
                  if (node.nodes.length !== 1) {
                    throw new Error('Unexpected comma (",") in :local block');
                  }
                  return localizeNode(node.nodes[0]);
                }
                /* falls through */
              case 'selectors':
              case 'selector':
                var newNode = Object.create(node);
                newNode.nodes = node.nodes.map(traverseNode);
                return newNode;
            }
            return node;
          }

          walkDecls 遍歷每一條規(guī)則,生成相應的 Scoped Name

          // 生成一個 Scoped Name
          function exportScopedName(name{
            var scopedName = generateScopedName(name, css.source.input.from, css.source.input.css);
            exports[name] = exports[name] || [];
            if (exports[name].indexOf(scopedName) < 0) {
              exports[name].push(scopedName);
            }
            return scopedName;
          }

          關于實現(xiàn) composes 的組合語法,有點類似,不再贅述

          postcss-modules-values

          這個庫的主要作用是在模塊文件之間傳遞任意值,主要是為了實現(xiàn)在 CSS Modules 中能夠使用變量

          它的實現(xiàn)也是只有一個文件,具體查看這里 [3]

          查看所有的 @value 語句,并將它們視為局部變量或?qū)氲模詈蟊4娴?definitions 對象中

          /* Look at all the @value statements and treat them as locals or as imports */
          // 查看所有的 @value 語句,并將它們視為局部變量還是導入的
          css.walkAtRules('value', atRule => {
            // 類似如下的寫法
            // @value primary, secondary from colors 
            if (matchImports.exec(atRule.params)) {
              addImport(atRule)
            } else {
              // 處理定義在文件中的 類似如下
              // @value primary: #BF4040;
            // @value secondary: #1F4F7F;
              if (atRule.params.indexOf('@value') !== -1) {
                result.warn('Invalid value definition: ' + atRule.params)
              }

              addDefinition(atRule)
            }
          })

          假如是導入的,調(diào)用的 addImport 方法

          const addImport = atRule => {
            // 如果有 import 的語法
            let matches = matchImports.exec(atRule.params)
            if (matches) {
              let [/*match*/, aliases, path] = matches
              // We can use constants for path names
              if (definitions[path]) path = definitions[path]
              let imports = aliases.replace(/^\(\s*([\s\S]+)\s*\)$/'$1').split(/\s*,\s*/).map(alias => {
                let tokens = matchImport.exec(alias)
                if (tokens) {
                  let [/*match*/, theirName, myName = theirName] = tokens
                  let importedName = createImportedName(myName)
                  definitions[myName] = importedName
                  return { theirName, importedName }
                } else {
                  throw new Error(`@import statement "${alias}" is invalid!`)
                }
              })
              // 最后會根據(jù)這個生成 import 的語法
              importAliases.push({ path, imports })
              atRule.remove()
            }
          }

          否則則直接 addDefinition,兩個的思想大致我理解都是找到響應的變量,然后替換

          // 添加定義
          const addDefinition = atRule => {
            let matches
            while (matches = matchValueDefinition.exec(atRule.params)) {
              let [/*match*/, key, value] = matches
              // Add to the definitions, knowing that values can refer to each other
              definitions[key] = replaceAll(definitions, value)
              atRule.remove()
            }
          }

          總結

          CSS Modules 并不是 CSS 官方的標準,也不是瀏覽器的特性,而是使用一些構建工具,比如 webpack,對 CSS 類名和選擇器限定作用域的一種方式(類似命名空間)。通過 CSS Modules,我們可以實現(xiàn) CSS 的局部作用域,Class 的組合等功能。最后我們知道 CSS Loader 實際上是通過兩個庫進行實現(xiàn)的。其中, postcss-modules-scope —— 實現(xiàn) CSS Modules 的樣式隔離(Scope Local)以及繼承(Extend)和 postcss-modules-values —— 在模塊文件之間傳遞任意值

          參考

          • 開發(fā) postcss 插件 [4]
          • CSS Modules[5]
          • CSS Modules 用法教程 [6]

          參考資料

          [1]

          Demo: https://github.com/GpingFeng/learn-css-modules

          [2]

          這里: https://github.com/css-modules/postcss-modules-scope/blob/master/src/index.js

          [3]

          查看這里: https://github.com/css-modules/postcss-modules-values/blob/master/src/index.js

          [4]

          開發(fā) postcss 插件: http://echizen.github.io/tech/2017/10-29-develop-postcss-plugin

          [5]

          CSS Modules: https://github.com/css-modules/css-modules

          [6]

          CSS Modules 用法教程: http://www.ruanyifeng.com/blog/2016/06/css_modules.html


          瀏覽 91
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲天堂高清无码 | 成人黄片影院网站 | 免费看片18+ | 人妻熟女一区二区三区APP下载 | 丁香五月欧美 |