原文地址javascript
webpack loaders 從上手到理解系列
還有這些:css
style-loader
的功能就一個,在 DOM
裏插入一個 <style>
標籤,而且將 CSS
寫入這個標籤內。html
簡單來講就是這樣:vue
const style = document.createElement('style'); // 新建一個 style 標籤
style.type = 'text/css';
style.appendChild(document.createTextNode(content)) // CSS 寫入 style 標籤
document.head.appendChild(style); // style 標籤插入 head 中
複製代碼
稍後會詳細分析源碼,看看和咱們的思路是否一致。java
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
。node
關於 style-loader
的 options
,這裏就很少說了,見 style-loader options .webpack
const indexStyle = require('./assets/style/index.css');
複製代碼
webpack
複製代碼
打包完成以後咱們打開 html
頁面,會看到 <head>
裏已經有了 index.css
裏的樣式內容:git
<style> .container { color: red; background: #999999; } .zelda { width: 260px; height: 100px; } </style>
複製代碼
單獨講一下 injectType
這個配置項,默認值是 styleTag
,經過 <style></style>
的形式插入 DOM
中,咱們來看看不一樣的 injectType
的效果。es6
默認狀況下,style-loader
每一次處理引入的樣式文件都會在 DOM
上建立一個 <style>
標籤,好比此時引入兩個樣式文件:github
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
的主要工做就完成了。