原文地址javascript
webpack loaders
系列文章:css
style-loader
style-loader
的功能就一個,在 DOM
裏插入一個 <style>
標籤,而且將 CSS
寫入這個標籤內。html
簡單來講就是這樣:java
const style = document.createElement('style'); // 新建一個 style 標籤 style.type = 'text/css'; style.appendChild(document.createTextNode(content)) // CSS 寫入 style 標籤 document.head.appendChild(style); // style 標籤插入 head 中
稍後會詳細分析源碼,看看和咱們的思路是否一致。node
style-loader
style-loader
npm install style-loader --save-dev
webapck
// webpack.config.js module.exports = { module: { rules: [ { test: /\.(css)$/, use: [ { loader: 'style-loader', options: {}, }, { loader: 'css-loader' }, ], }, ], }, };
平常的開發中處理樣式文件時,通常會使用到 style-loader
和 css-loader
這兩個 loader
。webpack
關於 style-loader
的 options
,這裏就很少說了,見 style-loader options .git
const indexStyle = require('./assets/style/index.css');
webpack
打包完成以後咱們打開 html
頁面,會看到 <head>
裏已經有了 index.css
裏的樣式內容:es6
<style> .container { color: red; background: #999999; } .zelda { width: 260px; height: 100px; } </style>
單獨講一下 injectType
這個配置項,默認值是 styleTag
,經過 <style></style>
的形式插入 DOM
中,咱們來看看不一樣的 injectType
的效果。github
默認狀況下,style-loader
每一次處理引入的樣式文件都會在 DOM
上建立一個 <style>
標籤,好比此時引入兩個樣式文件:web
const globalStyle = require('./assets/style/global.css'); const indexStyle = require('./assets/style/index.css');
輸出的 DOM
結構爲:
<style> html, body { height: 100%; } #app { background: #ffffff; } </style> <style> .container { color: red; } .zelda { width: 260px; height: 100px; } </style>
上面提到默認狀況下有幾個樣式文件就會插入幾個 <style>
標籤,將 injectType
設置爲 singletonStyleTag
可將全部的樣式文件打在同一個 <style>
標籤裏。
// config { test: /\.(css)$/, use: [ { loader: 'style-loader', options: { injectType: 'singletonStyleTag', }, }, { loader: 'css-loader' }, ], } // js const globalStyle = require('./assets/style/global.css'); const indexStyle = require('./assets/style/index.css');
輸出的 DOM
結構爲:
<style> html, body { height: 100%; } #app { background: #ffffff; } .container { background: #f5f5f5; } .container { color: red; background: #999999; } .zelda { width: 260px; height: 100px; } </style>
能夠看到,兩個樣式文件的內容都被放到同一個 <style>
標籤裏了,而且是按照咱們引入樣式文件的順序,彷佛還比較符合預期。
當 injectType
爲 linkTag
,會經過 <link rel="stylesheet" href="">
的形式將樣式插入到 DOM
中,此時 style-loader
接收到的數據應該是樣式文件的地址,因此搭配的 loader
應該是 file-loader
而不是 css-loader
。
// config { test: /\.(css)$/, use: [ { loader: 'style-loader', options: { injectType: 'linkTag', }, }, { loader: 'file-loader' }, ], } // js const globalStyle = require('./assets/style/global.css'); const indexStyle = require('./assets/style/index.css');
輸出的 DOM
結構爲:
<head> <link rel="stylesheet" href="f2742027f8729dc63bfd46029a8d0d6a.css"> <link rel="stylesheet" href="34cd6c668a7a596c4bedad32a39832cf.css"> </head>
這兩種類型的 injectType
區別在於它們是延遲加載的:
// config { test: /\.(css)$/, use: [ { loader: 'style-loader', options: { injectType: 'lazyStyleTag', }, }, { loader: 'css-loader' }, ], } // js const globalStyle = require('./assets/style/global.css'); const indexStyle = require('./assets/style/index.css'); // globalStyle.use();
若是僅僅是像上面同樣導入了樣式文件,樣式是不會插入到 DOM
中的,須要手動使用 globalStyle.use()
來延遲加載 global.css
這個樣式文件。
其它的用法就很少說了,自行查看 style-loader。
style-loader
主要能夠分爲:
runtime
階段先看引入依賴部分的代碼:
var _path = _interopRequireDefault(require("path")); var _loaderUtils = _interopRequireDefault(require("loader-utils")); var _schemaUtils = _interopRequireDefault(require("schema-utils")); var _options = _interopRequireDefault(require("./options.json")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
這裏定義了一個 _interopRequireDefault
方法,傳入的是一個 require()
。
這個方法的做用是:若是引入的是 es6
模塊,直接返回,若是是 commonjs
模塊,則將引入的內容放在一個對象的 default
屬性上,而後返回這個對象。
module.exports = () => {}; module.exports.pitch = function loader(request) {}
style-loader
的導出方式和普通的 loader
不太同樣,默認導出一個空方法,經過 pitch
導出的。
默認的 loader
都是從右向左像管道同樣執行,而 pitch
是從左到右執行的。
爲何 style-loader
須要這樣呢?
咱們知道默認 loader
的執行是從右向左的,而且會將上一個 loader
處理的結果傳遞給下一個 loader
,若是按照這種默認行爲,css-loader
會返回一個 js
字符串給 style-loader
。
style-loader
的做用是將 CSS
代碼插入到 DOM
中,若是按照順序從 css-loader
接收到一個 js
字符串的話,就沒法獲取到真實的 CSS
樣式了。因此正確的作法是先執行 style-loader
,在它裏面去執行 css-loader
,拿到通過處理的 CSS
內容,再插入到 DOM
中。
接下來看看 loader
的內容:
// 獲取 webpack 配置裏的 options const options = _loaderUtils.default.getOptions(this) || {}; // 校驗 options (0, _schemaUtils.default)(_options.default, options, { name: 'Style Loader', baseDataPath: 'options' }); // style 標籤插入的位置,默認是 head const insert = typeof options.insert === 'undefined' ? '"head"' : typeof options.insert === 'string' ? JSON.stringify(options.insert) : options.insert.toString(); // 設置以哪一種方式插入 DOM 中 // 詳情見這個:https://github.com/webpack-contrib/style-loader#injecttype const injectType = options.injectType || 'styleTag'; switch (injectType) { case 'linkTag': {} case 'lazyStyleTag': case 'lazySingletonStyleTag': {} case 'styleTag': case 'singletonStyleTag': default: {} }
根據不一樣的 injectType
會 return
不一樣的 js
代碼,在 runtime
的時候執行。
看看默認狀況:
return `var content = require(${_loaderUtils.default.stringifyRequest(this, `!!${request}`)}); if (typeof content === 'string') { content = [[module.id, content, '']]; } var options = ${JSON.stringify(options)} options.insert = ${insert}; options.singleton = ${isSingleton}; var update = require(${_loaderUtils.default.stringifyRequest(this, `!${_path.default.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`)})(content, options); if (content.locals) { module.exports = content.locals; } ${hmrCode}`;
_loaderUtils.default.stringifyRequest(this, `!!${request}`)
這個方法的做用是將絕對路徑轉換成相對路徑。好比:
import css from './asset/style/global.css'; // 此時傳遞給 style-loader 的 request 會是 request = '/test-loader/node_modules/css-loader/dist/cjs.js!/test-loader/assets/style/global.css'; // 轉換 _loaderUtils.default.stringifyRequest(this, `!!${request}`); // result: "!!../../node_modules/css-loader/dist/cjs.js!./global.css"
因此 content
的實際內容就是:
var content = require("!!../../node_modules/css-loader/dist/cjs.js!./global.css");
也就是在這裏纔去調用 css-loader
來處理樣式文件。
!!
模塊前面的兩個感嘆號的做用是禁用 loader
的配置的,若是不由用的話會出現無限遞歸調用的狀況。
一樣的,update
的實際內容是:
var update = require("!../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js")(content, options);
意思也就是調用 injectStylesIntoStyleTage
模塊來處理通過 css-loader
處理過的樣式內容 content
。
上述代碼都是 style-loader
返回的,真正執行是在 runtime
階段。
runtime
階段原本都寫好了,忽然不見了,心痛。
簡單地寫一下吧,具體的源碼見 傳送門
將樣式插入 DOM
的操做實際是在 runtime
階段進行的,仍是以默認狀況舉例,看看 injectStylesIntoStyleTage
作了什麼。
簡單來講,module.exports
裏最主要的就是 insertStyleElement
和 applyToTag
兩個方法,簡化一下就是這樣的:
module.exports = (list, options) => { options = options || {}; const styles = listToStyles(list, options); addStylesToDom(styles, options); } function insertStyleElement(options) { var style = document.createElement('style'); Object.keys(options.attributes).forEach(function (key) { style.setAttribute(key, options.attributes[key]); }); return style; } function applyToTag(style, options, obj) { var css = obj.css; var media = obj.media; if (media) { style.setAttribute('media', media); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { while (style.firstChild) { style.removeChild(style.firstChild); } style.appendChild(document.createTextNode(css)); } }
和咱們上文猜想差很少是一致的,至此 style-loader
的主要工做就完成了。