原文地址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
的主要工做就完成了。