窺探css-loader與style-loader的作用

記得要微笑發表於2021-11-06

前言

大傢伙都清楚在使用webpack構建前端專案時都會使用到sass-loader、less-loader、postcss-loader、css-loader、style-loader,但這些loader在其中起到什麼作用呢?本篇主要闡述css-loaderstyle-loader的作用和實現,加深對loader的理解。

css-loader

css-loader 會對 @importurl() 進行處理,就像 js 解析 import/require() 一樣,預設生成一個陣列存放存放處理後的樣式字串,並將其匯出。

假如有三個樣式檔案:a.module.scssb.module.scssc.module.scss,它們之間的依賴關係是這樣的:

// a.module.scss
@import './b.module.scss';

.container {
  width: 200px;
  height: 200px;
  background-color: antiquewhite;
}

// b.module.scss
@import url('./c.module.scss');

.text {
  font-size: 16px;
  color: #da2227;
  font-weight: bold;
}

// c.module.scss
.name {
  font-size: 16px;;
  color: #da7777;
  font-weight: bold;
}

index.jsx檔案中引入它們

// index.jsx
import React from 'react';

import styles from './a.module.scss';

const Index = () => {
  return <div className={styles.container}>
    <span className={styles.text}><span className={styles.name}>xxx,</span>hello world!!!</span>
  </div>
}

export default Index;

webpack.config.js構建指令碼

// webpack.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
  entry: {
    index: './src/index.jsx'
  },
  output: {
    filename: 'js/[name].js',
    path: path.resolve(__dirname, './dist'),
    library: 'MyComponent',
    libraryTarget: 'umd',
  },
  resolve: {
    extensions: ['.js', '.jsx', '.tsx'],
  },
  module: {
    rules: [
      { 
        test: /\.(js|jsx)$/, 
        exclude: /node_modules/, 
        loader: "babel-loader" 
      },
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: false, // 禁止css modules
            }
          },
          'sass-loader'
        ]
      },
      { 
        test: /\.(jpg|jpeg|png|gif)$/, 
        use: ['url-loader'] 
      }
    ]
  },
  plugins: [
    new webpack.ProgressPlugin(),
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: ['dist']
    }),
  ],
}

.babelrc配置

// .babelrc
{
  "presets": [
      "@babel/preset-env",
      "@babel/preset-react"
  ],
  "plugins": [
      "@babel/plugin-proposal-class-properties"
  ]
}

package.json配置構建命令"build:dev": "webpack --mode none"。為了方便分析構建之後的程式碼,這裡將mode設定為none

三種mode方式:

development會將 DefinePluginprocess.env.NODE_ENV 的值設定為 development。為模組和 chunk 啟用有效的名,也就是將模組id替換成模組名稱。
image-20211105160434858.png

image-20211105160155947.png
production會將 DefinePluginprocess.env.NODE_ENV 的值設定為 production
為模組和 chunk 啟用確定性的混淆名稱FlagDependencyUsagePlugin
FlagIncludedChunksPluginModuleConcatenationPlugin
NoEmitOnErrorsPluginTerserPlugin
image-20211105160641404.png
none不使用任何預設優化選項

執行yarn build:dev構建,分析生成的檔案

(1)a.module.scss使用@import引入了b.module.scss,被處理放入同一個模組的陣列___CSS_LOADER_EXPORT___中並匯出

image-20211105185844556.png

(2)b.module.scss使用@import url()引入了c.module.scss,但c.module.scss被單獨處理放入另一個模組的陣列___CSS_LOADER_EXPORT___中並匯出

image-20211105190430662.png

(3)a.module.scssb.module.scss被處理放入一個模組陣列,c.module.scss被單獨處理放入另一個模組的陣列,但是b.module.scssc.module.scss是由引入關係的,這個在構建後怎麼關聯依賴的呢?

a.module.scssb.module.scss被處理放入模組id12的陣列,c.module.scss被處理放入模組id15的陣列,模組id12的模組中匯入了模組id15中的樣式陣列,並將其追加到模組id12的陣列中,最後統一以陣列__WEBPACK_DEFAULT_EXPORT__匯出,css-loader處理到這一步就結束了

另外css-loader還提供其他的功能,比如css modules,想要了解可以參照例子開啟css modules構建,窺其原理,此處不作介紹

image-20211105192051078.png

image-20211105191531780.png

css-loader匯出方式

上面說到引入的樣式都被轉化成樣式字串放入模組陣列中,這是預設的處理方式,其實還有另外兩種。

配置項exportType允許匯出樣式為'array''string'或者 'css-style-sheet'可構造樣式(即 CSSStyleSheet), 預設值:'array'

CSSStyleSheet 介面代表一個 CSS 樣式表,並允許檢查和編輯樣式表中的規則列表。它從父型別 StyleSheet 繼承屬性和方法。

一個 CSS 樣式表包含了一組表示規則的 CSSRule 物件。每條 CSS 規則可以通過與之相關聯的物件進行操作,這些規則被包含在 CSSRuleList 內,可以通過樣式表的 cssRules (en-US) 屬性獲取。

例如,CSSStyleRule 物件中的一條規則可能包含這樣的樣式:

h1, h2 {
  font-size: 16pt;
}

style-loader

style-loader的作用是把 CSS 插入到 DOM 中,就是處理css-loader匯出的模組陣列,然後將樣式通過style標籤或者其他形式插入到DOM中。

配置項injectType可配置把 styles 插入到 DOM 中的方式,主要有:

  • styleTag:通過使用多個 <style></style> 自動把 styles 插入到 DOM 中。該方式是預設行為
  • singletonStyleTag:通過使用一個 <style></style> 來自動把 styles 插入到 DOM
  • autoStyleTag:與 styleTag 相同,但是當程式碼在 IE6-9 中執行時,請開啟 singletonStyleTag 模式
  • lazyStyleTag:在需要時使用多個 <style></style>styles 插入到 DOM 中。推薦 lazy style 遵循使用 .lazy.css 作為字尾的命名約定,style-loader 基本用法是使用 .css 作為檔案字尾(其他檔案也一樣,比如:.lazy.less.less)。當使用 lazyStyleTag 時,style-loader 將惰性插入 styles,在需要使用 styles 時可以通過 style.use() / style.unuse() 使 style 可用。
  • lazySingletonStyleTag:通過使用一個 <style></style> 來自動把 styles 插入到 DOM 中,如上提供惰性支援
  • lazyAutoStyleTag:與 lazyStyleTag 相同,但是當程式碼在 IE6-9 中執行時,請開啟lazySingletonStyleTag 模式
  • linkTag:使用多個 <link rel="stylesheet" href="path/to/file.css"> 將 styles 插入到 DOM 中。此 loader 會在執行時使用 JavaScript 動態地插入<link href="path/to/file.css" rel="stylesheet">。要靜態插入<link href="path/to/file.css" rel="stylesheet"> 時請使用MiniCssExtractPlugin

我們以styleTag插入方式進行分析:

上面說到所有的樣式都追加到模組id12的模組陣列中,下面先獲取模組id12的模組陣列,然後生成style標籤將其樣式插入DOM

image-20211105200147804.png

上圖中_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()得到的就是下圖模組返回的update方法,該方法呼叫了其他很多方法將樣式通過style標籤插入DOM

image-20211105200723559.png

參考:

css-loader

style-loader

相關文章