We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
style-loader
style-loader 的功能就一个,在 DOM 里插入一个 <style> 标签,并且将 CSS 写入这个标签内。
DOM
<style>
CSS
简单来说就是这样:
const style = document.createElement('style'); // 新建一个 style 标签 style.type = 'text/css'; style.appendChild(document.createTextNode(content)) // CSS 写入 style 标签 document.head.appendChild(style); // style 标签插入 head 中
稍后会详细分析源码,看看和我们的思路是否一致。
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。
css-loader
loader
关于 style-loader 的 options,这里就不多说了,见 style-loader options .
options
const indexStyle = require('./assets/style/index.css');
webpack
打包完成之后我们打开 html 页面,会看到 <head> 里已经有了 index.css 里的样式内容:
html
<head>
index.css
<style> .container { color: red; background: #999999; } .zelda { width: 260px; height: 100px; } </style>
单独讲一下 injectType 这个配置项,默认值是 styleTag,通过 <style></style> 的形式插入 DOM 中,我们来看看不同的 injectType 的效果。
injectType
styleTag
<style></style>
默认情况下,style-loader 每一次处理引入的样式文件都会在 DOM 上创建一个 <style> 标签,比如此时引入两个样式文件:
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> 标签里。
singletonStyleTag
// 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');
<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。
linkTag
<link rel="stylesheet" href="">
file-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');
<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 这个样式文件。
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()。
_interopRequireDefault
require()
这个方法的作用是:如果引入的是 es6 模块,直接返回,如果是 commonjs 模块,则将引入的内容放在一个对象的 default 属性上,然后返回这个对象。
es6
commonjs
default
module.exports = () => {}; module.exports.pitch = function loader(request) {}
style-loader 的导出方式和普通的 loader 不太一样,默认导出一个空方法,通过 pitch 导出的。
pitch
默认的 loader 都是从右向左像管道一样执行,而 pitch 是从左到右执行的。
为什么 style-loader 需要这样呢?
我们知道默认 loader 的执行是从右向左的,并且会将上一个 loader 处理的结果传递给下一个 loader,如果按照这种默认行为,css-loader 会返回一个 js 字符串给 style-loader。
js
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
看看默认情况:
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}`) 这个方法的作用是将绝对路径转换成相对路径。比如:
_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 的实际内容就是:
content
var content = require("!!../../node_modules/css-loader/dist/cjs.js!./global.css");
也就是在这里才去调用 css-loader 来处理样式文件。
!! 模块前面的两个感叹号的作用是禁用 loader 的配置的,如果不禁用的话会出现无限递归调用的情况。
!!
同样的,update 的实际内容是:
update
var update = require("!../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js")(content, options);
意思也就是调用 injectStylesIntoStyleTage 模块来处理经过 css-loader 处理过的样式内容 content。
injectStylesIntoStyleTage
上述代码都是 style-loader 返回的,真正执行是在 runtime 阶段。
本来都写好了,突然不见了,心痛。
简单地写一下吧,具体的源码见 传送门
将样式插入 DOM 的操作实际是在 runtime 阶段进行的,还是以默认情况举例,看看 injectStylesIntoStyleTage 做了什么。
简单来说,module.exports里最主要的就是 insertStyleElement 和 applyToTag 两个方法,简化一下就是这样的:
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 的主要工作就完成了。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
什么是
style-loader
style-loader
的功能就一个,在DOM
里插入一个<style>
标签,并且将CSS
写入这个标签内。简单来说就是这样:
稍后会详细分析源码,看看和我们的思路是否一致。
如何使用
style-loader
1. 安装
style-loader
2. 配置
webapck
日常的开发中处理样式文件时,一般会使用到
style-loader
和css-loader
这两个loader
。关于
style-loader
的options
,这里就不多说了,见 style-loader options .3. 引入一个样式文件
4. 见证奇迹的时刻
打包完成之后我们打开
html
页面,会看到<head>
里已经有了index.css
里的样式内容:injectType
单独讲一下
injectType
这个配置项,默认值是styleTag
,通过<style></style>
的形式插入DOM
中,我们来看看不同的injectType
的效果。styleTag
默认情况下,
style-loader
每一次处理引入的样式文件都会在DOM
上创建一个<style>
标签,比如此时引入两个样式文件:输出的
DOM
结构为:singletonStyleTag
上面提到默认情况下有几个样式文件就会插入几个
<style>
标签,将injectType
设置为singletonStyleTag
可将所有的样式文件打在同一个<style>
标签里。输出的
DOM
结构为:可以看到,两个样式文件的内容都被放到同一个
<style>
标签里了,并且是按照我们引入样式文件的顺序,似乎还比较符合预期。linkTag
当
injectType
为linkTag
,会通过<link rel="stylesheet" href="">
的形式将样式插入到DOM
中,此时style-loader
接收到的数据应该是样式文件的地址,所以搭配的loader
应该是file-loader
而不是css-loader
。输出的
DOM
结构为:lazyStyleTag, lazySingletonStyleTag
这两种类型的
injectType
区别在于它们是延迟加载的:如果仅仅是像上面一样导入了样式文件,样式是不会插入到
DOM
中的,需要手动使用globalStyle.use()
来延迟加载global.css
这个样式文件。其它的用法就不多说了,自行查看 style-loader。
源码解析
style-loader
主要可以分为:runtime
阶段打包阶段
先看引入依赖部分的代码:
这里定义了一个
_interopRequireDefault
方法,传入的是一个require()
。这个方法的作用是:如果引入的是
es6
模块,直接返回,如果是commonjs
模块,则将引入的内容放在一个对象的default
属性上,然后返回这个对象。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
的内容:根据不同的
injectType
会return
不同的js
代码,在runtime
的时候执行。看看默认情况:
_loaderUtils.default.stringifyRequest(this, `!!${request}`)
这个方法的作用是将绝对路径转换成相对路径。比如:所以
content
的实际内容就是:也就是在这里才去调用
css-loader
来处理样式文件。!!
模块前面的两个感叹号的作用是禁用loader
的配置的,如果不禁用的话会出现无限递归调用的情况。同样的,
update
的实际内容是:意思也就是调用
injectStylesIntoStyleTage
模块来处理经过css-loader
处理过的样式内容content
。上述代码都是
style-loader
返回的,真正执行是在runtime
阶段。runtime
阶段本来都写好了,突然不见了,心痛。简单地写一下吧,具体的源码见 传送门
将样式插入
DOM
的操作实际是在runtime
阶段进行的,还是以默认情况举例,看看injectStylesIntoStyleTage
做了什么。简单来说,
module.exports
里最主要的就是insertStyleElement
和applyToTag
两个方法,简化一下就是这样的:和我们上文猜测差不多是一致的,至此
style-loader
的主要工作就完成了。The text was updated successfully, but these errors were encountered: