原文地址:Webpack Loader源碼導讀之css-loadercss
在上一篇Webpack Loader源碼導讀之less-loader咱們介紹了less-loadernode
本篇是Webpack Loader源碼導讀系列中關於css-loader的解讀,主要闡述loader的工做,及部份配置項做用。webpack
源碼 v0.28.8,lib目錄以下:git
lib
|____compile-exports.js
|____createResolver.js
|____css-base.js
|____getImportPrefix.js
|____getLocalIdent.js
|____loader.js
|____localsLoader.js
|____processCss.js
複製代碼
css-loader有兩個入口文件lib/loader.js
和lib/localsLoader.js
github
名稱 | 類型 | 默認值 | 描述 |
---|---|---|---|
root | String | / | 解析 URL 的路徑,以 / 開頭的 URL 不會被轉譯 |
url | Boolean | true | 啓用/禁用 url() 處理 |
alias | Object | {} | 建立別名更容易導入一些模塊 |
import | Boolean | true | 啓用/禁用 @import 處理 |
modules或module | Boolean | false | 啓用/禁用 CSS 模塊 |
sourceMap | Boolean | false | 啓用/禁用 Sourcemap |
camelCase | Boolean或String | false | 以駝峯化式命名導出類名 |
importLoaders | Number | 0 | 在 css-loader 前應用的 loader 的數量 |
localIdentName | String | [hash:base64] | 配置生成的標識符(ident) |
各個配置項的做用,在下面走讀代碼的過程咱們會舉例說明去做用;web
不管是loader.js仍是localsLoader.js,都會先解析loader選項,而後執行processCss編譯css文件,他們的區別在於對編譯結果的處理不通,首先咱們先看看processCss作了什麼處理。正則表達式
先跑個示例 b.cssjson
@value colorYellow: yellow;
:local(.className) {
background: red;
color: colorYellow;
}
:local(.subClass) {
composes: className;
background: blue;
}
複製代碼
a.csssass
@value colorYellow from './b.css';
:local(.aClass) {
composes: className from './b.css';
background: colorYellow;
}
.app {
font-size: 14px;
}
複製代碼
loader配置,爲了便於看編譯結果,咱們配置了extract-text-webpack-pluginbash
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: "css-loader",
options: {
minimize: true,
sourceMap: true,
modules: true,
localIdentName: '[hash:base64]'
}
}
]
})
}
複製代碼
首先是通過一波postcss(v5.2.17)的處理, 先將輸入內容作一次parse(代碼在postcss/lib/parser.js中),提取出一些關鍵字,轉成指定對象用於後面解析,轉成以下格式的對象:
{
"raws": {
"semicolon": false,
"after": "\n"
},
"type": "root",
"nodes": [
{
"raws": {
"before": "",
"between": "",
"afterName": " "
},
"type": "atrule",
"name": "value",
"source": {
"start": {
"line": 1,
"column": 1
},
"input": {
"css": "@value colorYellow from './b.css';\n\n:local(.aClass) {\n composes: className from './b.css';\n background: colorYellow;\n}\n\n.app {\n font-size: 14px;\n}\n",
"file": "/css-loader!/Users/yzf/webpack-tuition/loaders/babel/src/a.css"
},
"end": {
"line": 1,
"column": 34
}
},
"params": "colorYellow from './b.css'"
},
...
],
"source": {
"input": {
"css": "@value colorYellow from './b.css';\n\n:local(.aClass) {\n composes: className from './b.css';\n background: colorYellow;\n}\n\n.app {\n font-size: 14px;\n}\n",
"file": "/css-loader!/Users/yzf/webpack-tuition/loaders/babel/src/a.css"
},
"start": {
"line": 1,
"column": 1
}
}
}
複製代碼
其中nodes的類型包括root(跟節點)、atrule(@
規則)、decl(聲明)、comment(註釋)和rule(普通規則)幾種類型 而後這個nodes會通過一系列插件處理,在插件處理過程當中會常常見到walkAtRules、walkRules、walkDecls和walkComments幾個方法,這幾個方法代碼在postcss/lib/container.js
中, 顧名思義,這幾個方法分別是用來解析這幾種不一樣規則的,如walkAtRules('value',callback)
意思就是解析@value
規則 在css-loader中使用到了以下幾個插件
var pipeline = postcss([
modulesValues,
localByDefault({
mode: options.mode,
rewriteUrl: function(global, url) {
if(parserOptions.url){
url = url.trim();
if(!url.replace(/\s/g, '').length || !loaderUtils.isUrlRequest(url, root)) {
return url;
}
if(global) {
return loaderUtils.urlToRequest(url, root);
}
}
return url;
}
}),
extractImports(),
modulesScope({
generateScopedName: function generateScopedName (exportName) {
return customGetLocalIdent(options.loaderContext, localIdentName, exportName, {
regExp: localIdentRegExp,
hashPrefix: query.hashPrefix || "",
context: context
});
}
}),
parserPlugin(parserOptions)
]);
複製代碼
接下來咱們來了解下這些插件都作了什麼事情
第一個插件是modulesValues(postcss-modules-values v1.3.0),其做用是解析變量@value,如b.css中 定義了@value colorYellow: yellow;
在後面就能夠使用color: colorYellow;
,效果等同color: yellow;
,在a.css中也能夠從b.css導入該值@value colorYellow from './b.css';
;
第二個插件是localByDefault(postcss-modules-local-by-default v1.2.0),該插件的做用與css-loader的配置項modules有關; 若是modules配置爲true,則該插件會給每一個類名前加:local
,這樣在js中import s from './a.css'
時獲得的s值爲{ colorYellow: 'yellow', aClass: '_3RfWl8Fjg9j10HraIxvVwo _2WlYzvzC-urSx4y6mIOOFM', app: '_2fkqRy5LeEcw20RyY_eLpM' }
, 不然爲{ colorYellow: 'yellow', aClass: '_3RfWl8Fjg9j10HraIxvVwo _2WlYzvzC-urSx4y6mIOOFM' }
;區別在於a.css中app這個class,在示例代碼中.app
前面沒加:local
則導出的對象中不包含app, 可是modules設置爲true時本插件會默認給app加上local,因此導出的對象中就有app。
第三個插件是extractImports(postcss-modules-extract-imports v1.1.0),看a.css中的代碼,該插件的做用是將
:local(.aClass) {
composes: className from './b.css';
background: colorYellow;
}
複製代碼
轉成
:import("./b.css"){
className: i__imported_className_0;
}
:local(.aClass) {
composes: i__imported_className_0;
background: colorYellow;
}
複製代碼
第四個插件是modulesScope(postcss-modules-scope v1.1.0),該插件的做用就是export出js中可以引入的對象,會將
:local(.aClass) {
composes: i__imported_className_0;
background: colorYellow;
}
複製代碼
轉成
:export {
aClass: _3RfWl8Fjg9j10HraIxvVwo
}
._3RfWl8Fjg9j10HraIxvVwo {
composes: i__imported_className_0;
background: colorYellow;
}
複製代碼
這裏暫時不會處理composes。其中轉換出來的類名,如_3RfWl8Fjg9j10HraIxvVwo
是根據配置項localIdentName: '[hash:base64]'
決定的,若是配置的是 localIdentName: '[local]'
,則類名不會變,即仍是aClass
。
最後一個插件是parserPlugin,這個代碼就在css-loader/src/processCss.js
中,是css-loader對前面編譯結果作的最後處理。 咱們給demo增長一個c.css,而後在a.css中導入@import "./c.css"
,這個插件作了如下事情:
var icss = icssUtils.extractICSS(css);
從nodes中提取出每一個文件的:import與:export信息,:import的內容暫存到imports和importItems中// imports
{
"$i__const_colorYellow_0": 1, // 值爲importItems中的索引
"$i__imported_className_0": 2
}
// importItems
[
{
"url": "./c.css",
"mediaQuery": ""
},
{
"url": "./b.css",
"export": "colorYellow"
},
{
"url": "./b.css",
"export": "className"
}
]
複製代碼
而後根據imports和importItems將exports從
{
"colorYellow": "i__const_colorYellow_0",
"aClass": "_3RfWl8Fjg9j10HraIxvVwo i__imported_className_0",
"app": "_2fkqRy5LeEcw20RyY_eLpM"
}
複製代碼
轉換成
{
"colorYellow": "___CSS_LOADER_IMPORT___1___", // 1,2即爲importItems中的索引
"aClass": "_3RfWl8Fjg9j10HraIxvVwo ___CSS_LOADER_IMPORT___2___",
"app": "_2fkqRy5LeEcw20RyY_eLpM"
}
複製代碼
i__const_colorYellow_0
都替換成___CSS_LOADER_IMPORT___1___
形式的;通過全部插件處理之後結果是這樣的(固然中間還有個minimize配置爲true時會走cssnano壓縮,這裏略過了):
/* a.css */
._3RfWl8Fjg9j10HraIxvVwo{background:___CSS_LOADER_IMPORT___1___}._2fkqRy5LeEcw20RyY_eLpM{font-size:14px}
/* b.css */
._2WlYzvzC-urSx4y6mIOOFM{background:red;color:#ff0}._2ZjxOCWmD5GtQv4c-EHJ1g{background:blue}
/* c.css */
._2W2YIQ3PA5I9QGXroo7b2m{display:block}
複製代碼
對於上面的處理結果,還存在着___CSS_LOADER_IMPORT___1___
這樣的內容,顯然還不是最終結果,回到咱們的入口文件loader.js
看看最後的處理, 固然,若是你使用的loader是css-loader/locals
,則入口文件是localsLoader.js
。 loader.js最後要作的就是拼出最後module.exports要導出去的模塊,將依賴的模塊經過正則表達式/___CSS_LOADER_IMPORT___([0-9]+)___/g
及前面解析出來的importItems
替換成require("-!../node_modules/css-loader/index.js?{\"minimize\":true,\"sourceMap\":true,\"modules\":true,\"localIdentName\":\"[hash:base64]\"}!./b.css").locals["colorYellow"]
格式的 最終導出模塊,在js中能夠直接import進來獲得一個對象
/* a.css */
exports = module.exports = require("../node_modules/css-loader/lib/css-base.js")(true);
// imports
exports.i(require("-!../node_modules/css-loader/index.js?{\"minimize\":true,\"sourceMap\":true,\"modules\":true,\"localIdentName\":\"[hash:base64]\"}!./c.css"), "");
exports.i(require("-!../node_modules/css-loader/index.js?{\"minimize\":true,\"sourceMap\":true,\"modules\":true,\"localIdentName\":\"[hash:base64]\"}!./b.css"), undefined);
// module
exports.push([module.id, "._3RfWl8Fjg9j10HraIxvVwo{background:"
+ require("-!../node_modules/css-loader/index.js?{\"minimize\":true,\"sourceMap\":true,\"modules\":true,\"localIdentName\":\"[hash:base64]\"}!./b.css").locals["colorYellow"]
+ "}._2fkqRy5LeEcw20RyY_eLpM{font-size:14px}", "", {"version":3,"sources":["/Users/yzf/webpack-tuition/loaders/babel/src/a.css"],"names":[],"mappings":"AAGA,yBAEI,sCAAwB,CAC3B,AAED,yBACI,cAAgB,CACnB","file":"a.css","sourcesContent":["@import \"./c.css\";\n@value colorYellow from './b.css';\n\n:local(.aClass) {\n composes: className from './b.css';\n background: colorYellow;\n}\n\n.app {\n font-size: 14px;\n}\n"],"sourceRoot":""}]);
// exports
exports.locals = {
"colorYellow": "" + require("-!../node_modules/css-loader/index.js?{\"minimize\":true,\"sourceMap\":true,\"modules\":true,\"localIdentName\":\"[hash:base64]\"}!./b.css").locals["colorYellow"] + "",
"aClass": "_3RfWl8Fjg9j10HraIxvVwo " + require("-!../node_modules/css-loader/index.js?{\"minimize\":true,\"sourceMap\":true,\"modules\":true,\"localIdentName\":\"[hash:base64]\"}!./b.css").locals["className"] + "",
"app": "_2fkqRy5LeEcw20RyY_eLpM"
};
/* b.css */
exports = module.exports = require("../node_modules/css-loader/lib/css-base.js")(true);
// imports
// module
exports.push([module.id, "._2WlYzvzC-urSx4y6mIOOFM{background:red;color:#ff0}._2ZjxOCWmD5GtQv4c-EHJ1g{background:blue}", "", {"version":3,"sources":["/Users/yzf/webpack-tuition/loaders/babel/src/b.css"],"names":[],"mappings":"AAEA,yBACI,eAAgB,AAChB,UAAmB,CACtB,AAED,yBAEI,eAAiB,CACpB","file":"b.css","sourcesContent":["@value colorYellow: yellow;\n\n:local(.className) {\n background: red;\n color: colorYellow;\n}\n\n:local(.subClass) {\n composes: className;\n background: blue;\n}\n"],"sourceRoot":""}]);
// exports
exports.locals = {
"colorYellow": "yellow",
"className": "_2WlYzvzC-urSx4y6mIOOFM",
"subClass": "_2ZjxOCWmD5GtQv4c-EHJ1g _2WlYzvzC-urSx4y6mIOOFM"
};
/* c.css */
exports = module.exports = require("../node_modules/css-loader/lib/css-base.js")(true);
// imports
// module
exports.push([module.id, "._2W2YIQ3PA5I9QGXroo7b2m{display:block}", "", {"version":3,"sources":["/Users/yzf/webpack-tuition/loaders/babel/src/c.css"],"names":[],"mappings":"AAAA,yBACI,aAAe,CAClB","file":"c.css","sourcesContent":[".test {\n display: block;\n}\n"],"sourceRoot":""}]);
// exports
exports.locals = {
"test": "_2W2YIQ3PA5I9QGXroo7b2m"
};
複製代碼
/* 合併後main.css */
._2W2YIQ3PA5I9QGXroo7b2m{display:block}._2WlYzvzC-urSx4y6mIOOFM{background:red;color:#ff0}._2ZjxOCWmD5GtQv4c-EHJ1g{background:blue}._3RfWl8Fjg9j10HraIxvVwo{background:yellow}._2fkqRy5LeEcw20RyY_eLpM{font-size:14px}
/*# sourceMappingURL=main.css.map*/
複製代碼
若是是在服務端使用css-loader/locals
則不搭配ExtractTextPlugin,處理結果爲
/* a.css */
module.exports = {
"colorYellow": "" + require("-!../node_modules/css-loader/locals.js??ref--1-0!./b.css")["colorYellow"] + "",
"aClass": "_3RfWl8Fjg9j10HraIxvVwo " + require("-!../node_modules/css-loader/locals.js??ref--1-0!./b.css")["className"] + "",
"app": "_2fkqRy5LeEcw20RyY_eLpM"
};
/* b.css */
module.exports = {
"colorYellow": "yellow",
"className": "_2WlYzvzC-urSx4y6mIOOFM",
"subClass": "_2ZjxOCWmD5GtQv4c-EHJ1g _2WlYzvzC-urSx4y6mIOOFM"
};
複製代碼
c.css沒有模塊導出
這個解析過程有點長,可是css-loader對css處理的主要過程基本都提到了,咱們也可以知道通過這個loader之後樣式變成什麼樣,導出了什麼模塊;
固然,上面提到的@import @value composes等特性在使用less或者sass等其餘css預編譯時是用不到的,由於他們有本身的語法,咱們通常不會去使用這些特性;
css-loader處理完之後,在實際使用時咱們在最後都會再通過style-loader處理,有時搭配ExtractTextPlugin,那麼這兩個loader或插件又作了什麼呢?咱們下篇見。
若是喜歡請點贊,歡迎關注個人博客hiihl。