大家都清楚在使用webpack構建前端項目時都會使用到sass-loader、less-loader、postcss-loader、css-loader、style-loader,但這些loader在其中起到什么作用呢?本篇主要闡述css-loader與style-loader的作用和實現,加深對loader的理解。
style-loader和css-loader作用是不同的。
1、css-loader
: 加載.css文件
2、style-loader:
使用<style>
將css-loader內部樣式注入到我們的HTML頁面
一、css-loader的作用
1、先講css-loader的作用:css-loader是幫助webpack打包處理css文件的工具
2、css-loader 使用注意項:
(1)使用css-loader必須要配合使用style-loader
(2)css-loader的作用是幫我們分析出各個css文件之間的關系,把各個css文件合並成一段css
(3)style-loader的作用是將css-loader生成的css代碼掛載到頁面的header部分
(4)多個loader配合使用時,處理順序是:從下到上,從右到左 的順序;
3、由於 webpack 只能處理js相關的文件,所以像圖片和css資源是處理不了的,css-loader的作用是將css文件轉換成webpack能夠處理的資源,而style-loader就是幫我們直接將css-loader解析后的內容掛載到html頁面當中
二、css-loader和style-loader是如何配合使用的?
webpack是用JS寫的,運行在node環境,所以默認webpack打包的時候只會處理JS之間的依賴關系!
因為像 .css 這樣的文件不是一個 JavaScript 模塊,你需要配置 webpack 使用 css-loader 或者 style-loader 去合理地處理它們。
css-loader會遍歷css文件,找到所有的url(...)並且處理。style-loader會把所有的樣式插入到你頁面的一個style
1、如果在JS中導入了css,那么就需要使用 css-loader 來識別這個模塊,通過特定的語法規則進行轉換內容最后導出
css-loader
會對 @import
和 url()
進行處理,就像 js
解析 import/require()
一樣,默認生成一個數組存放存放處理后的樣式字符串,並將其導出。
// base.css
.bg { background: #000; } const style = require('./base.css') console.log(style, 'css')
css-loader處理之后導出的是
但是這並不是我們想要的,因為是個數組,頁面是無法直接使用,這時我們需要用到額外一個style-loader來處理。
2、style-loader 是通過一個JS腳本創建一個style標簽,里面包含一些樣式。style-loader是不能單獨使用的,因為它並不負責解析 css 之前的依賴關系,每個loader的功能都是單一的,各自拆分獨立。
style-loader
的作用是把 CSS
插入到 DOM
中,就是處理css-loader
導出的模塊數組,然后將樣式通過style
標簽或者其他形式插入到DOM
中。
配置項injectType
可配置把 styles
插入到 DOM
中的方式。
三、css-loader和style-loader的使用方式
1、內聯方式(不常用)如:import "css-loader!../css/style.css"
2、cli 方式:已棄用
3、配置方式:在webpack.config.js文件中寫明配置信息,module.rules中可以配置多個loader
const path = require('path'); module.exports = { entry: "./src/main.js",//入口文件
output: {//出口文件
path: path.resolve(__dirname, "build"), filename:"bundle.js" }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.less$/, use: [ 'style-loader', 'css-loader', 'less-loader' ] } ] } }
注意:寫法是有順序的
四、less-loader
Less是CSS預處理語言,擴展了CSS語言,增加了變量、Mixin、函數等特性,Less-loader的作用就是將less代碼轉譯為瀏覽器可以識別的CSS代碼。
所以less-loader的原理很簡單,就是調用less庫提供的方法,轉譯less語法后輸出,如下:
// less-loader實現(經簡化)
const less = require('less'); module.exports = function(content) { const callback = this.async(); // 轉譯比較耗時,采用異步方式
const options = this.getOptions(); // 獲取配置文件中less-loader的options
less.render( content, createOptions(options), // less轉譯的配置
(err, output) => { callback(err, output.css); // 將生成的css代碼傳遞給下一個loader
} ); };
五、css-loader
css-loader的作用主要是解析css文件中的@import和url語句,處理css-modules,並將結果作為一個js模塊返回。
假如我們有a.css、b.css、c.css:
// a.css
@import './b.css'; // 導入b.css .a { font-size: 16px; } // b.css
@import './c.css'; // 導入c.css .b { color: red; } // c.css
.c { font-weight: bolder; }
來看看css-loader對a.css的編譯輸出:
// css-loader輸出 exports = module.exports = require("../../../node_modules/css-loader/lib/css-base.js")(false); // imports // 文件需要的依賴js模塊,這里為空 // module
exports.push([ // 模塊導出內容
module.id, ".src-components-Home-index__c--3riXS {\n font-weight: bolder;\n}\n.src-components-Home-index__b--I-yI3 {\n color: red;\n}\n.src-components-Home-index__a--3EFPE {\n font-size: 16px;\n}\n", "" ]); // exports
exports.locals = { // css-modules的類名映射
"c": "src-components-Home-index__c--3riXS", "b": "src-components-Home-index__b--I-yI3", "a": "src-components-Home-index__a--3EFPE" };
可以理解為css-loader將a.css、b.css和c.css的樣式內容以字符串的形式拼接在一起,並將其作為js模塊的導出內容。
// css-loader源碼(經簡化) // https://github.com/webpack-contrib/css-loader/blob/master/src/index.js
import postcss from 'postcss'; module.exports = async function (content, map, meta) { const options = this.getOptions(); // 獲取配置
const plugins = []; // 轉譯源碼所需的postcss插件
shouldUseModulesPlugins(options, this) && plugins.push(modulesPlugins); // 處理css-modules
shouldUseImportPlugin(options, this) && plugins.push(importPlugin); // 處理@import語句
shouldUseURLPlugin(options, this) && plugins.push(urlPlugin); // 處理url()語句
shouldUseIcssPlugin(options, this) && plugins.push(icssPlugin); // 處理icss相關邏輯
if (meta && meta.ast) { // 復用前面loader生成的CSS AST(如postcss-loader)
content = meta.ast.root; } const result = await postcss(plugins).process(content); // 使用postcss轉譯源碼
const importCode = getImportCode(); // 需要導入的依賴語句
const moduleCode = getModuleCode(result); // 模塊導出內容
const exportCode = getExportCode(); // 其他需要導出的信息,如css-modules的類名映射等
const callback = this.async(); // 異步返回
callback(null, `${importCode}${moduleCode}${exportCode}`); };
六、style-loader
經過css-loader的轉譯,我們已經得到了完整的css樣式代碼,style-loader的作用就是將結果以style標簽的方式插入DOM樹中。
直覺上似乎我們只需要像下面這樣返回一段js代碼,將css-loader返回的結果插入DOM就行:
module.exports = function (content) { return ` const style = document.createElement('style'); style.innerHTML = '${content}'; document.head.appendChild(style); `; };
但css-loader返回的不是css樣式代碼的文本,而是一個js模塊的代碼,將這些js代碼直接放進style標里顯然是不行的。
我們來看看style-loader的實現:
// style-loader
import loaderUtils from 'loader-utils'; module.exports = function (content) { // do nothing
}; module.exports.pitch = function (remainingRequest) { /* * 用require語句獲取css-loader返回的js模塊的導出 * 用'!!'前綴跳過配置中的loader,避免重復執行 * 用remainingRequest參數獲取loader鏈的剩余部分,在本例中是css-loader、less-loader * 用loaderUtils的stringifyRequest方法將request語句中的絕對路徑轉為相對路徑 */
const requestPath = loaderUtils.stringifyRequest(this, '!!' + remainingRequest); // 本例中requestPath為: // '!!../node_modules/css-loader/index.js!../node_modules/less-loader/dist/cjs.js!src/styles/index.less'
return ` const content = require(${requestPath}) const style = document.createElement('style'); style.innerHTML = content; document.head.appendChild(style); `; };
style-loader的幾個設計思路:
1、css-loader返回的樣式只能通過其js模塊的運行時得到,故使用require
語句取得
2、normal方法實際上什么都沒做,在pitch方法里中斷loader鏈的執行
,再以inline方式調用了后方的loader來加載當前的less文件
3、如果將pitch中的實現放到normal方法里,就會造成loader鏈執行兩遍
4、如果requestPath中沒有'!!'前綴,就會造成loader鏈被無限循環調用
style-loader的實現邏輯比較繞,也是一個比較經典的pitch
應用,理解了它的原理,就可以是說對loader的調用鏈、執行順序和模塊化輸出等有了一個比較全面的認識,推薦細細體會。