首先打开style-loader的package.json,找到main,可以看到它的入口文件即为:dist/index.js,内容如下:`

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 }; }module.exports = () => {};module.exports.pitch = function loader(request) {    // ...}

`其中_interopRequireDefault的作用是:如果引入的是 es6 模块,直接返回,如果是 commonjs 模块,则将引入的内容放在一个对象的 default 属性上,然后返回这个对象。
我首先来看pitch函数,它的内容如下:`

// 获取webpack配置的optionsconst options = _loaderUtils.default.getOptions(this) || {};// (0, func)(),运用逗号操作符,将func的this指向了windows,详情请查看:https://www.jianshu.com/p/cd188bda72df// 调用_schemaUtils是为了校验options,知道其作用就行,这里就不讨论了(0, _schemaUtils.default)(_options.default, options, {    name: 'Style Loader',    baseDataPath: 'options'});// 定义了两个变量,**insert**、**injectType**,不难看出insert的默认值为head,injectType默认值为styleTagconst insert = typeof options.insert === 'undefined' ? '"head"' : typeof options.insert === 'string' ? JSON.stringify(options.insert) : options.insert.toString();const injectType = options.injectType || 'styleTag';switch(injectType){    case 'linkTag':        {            // ...        }    case 'lazyStyleTag':    case 'lazySingletonStyleTag':        {            // ...        }    case 'styleTag':    case 'singletonStyleTag':    default:        {            // ...        }}`

在这里,我们就看默认的就好了,即insert=head,injectType=styleTag`

const isSingleton = injectType === 'singletonStyleTag';const hmrCode = this.hot ? `        // ...    ` : '';return `    // _loaderUtils.default.stringifyRequest这里就不叙述了,主要作用是将绝对路径转换为相对路径    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}`;`

去掉多余的代码,可以清晰的看到pitch方法实际上最后返回了一个字符串,该字符串就是编译后在浏览器执行的代码,让我们来看看它在浏览器是如何操作的:
首先调用require方法获取css文件的内容,将其赋值给content,如果content是字符串,则将content赋值为数组,即:[[module.id], content, ''],接着我们覆盖了options的insert、singleton属性,由于我们暂时只看默认的,所以insert=head,singleton=false;再往下面看,我们又使用require方法引用了runtime/injectStyleIntoStyleTag.js,它返回一个函数,我们将content和options传递给该函数,并立即执行它:`

module.exports = function (list, options) {    options = options || {};    options.attributes = typeof options.attributes === 'object' ? options.attributes : {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style>    // tags it will allow on a page    if (!options.singleton && typeof options.singleton !== 'boolean') {        options.singleton = isOldIE();    }        var styles = listToStyles(list, options);    addStylesToDom(styles, options);    return function update(newList) {        // ...    };};

可以看到,该函数的主要内容即为

var styles = listToStyles(list, options);addStylesToDom(styles, options);

我们先来看看listToStyles做了什么

function listToStyles(list, options) {    var styles = [];    var newStyles = {};    for (var i = 0; i < list.length; i++) {        var item = list[i];        // 回过头去看就知道,item实际上等于[[module.id, content, '']],其中content即为css文件的内容        var id = options.base ? item[0] + options.base : item[0];        var css = item[1];        var media = item[2]; // ''        var sourceMap = item[3]; // undefined        var part = {            css: css,            media: media,            sourceMap: sourceMap        };        if (!newStyles[id]) {            styles.push(newStyles[id] = {                id: id,                parts: [part]            });        } else {            newStyles[id].parts.push(part);        }    }    return styles;}

这段代码很简单,将传递进来的内容转换为了styles数组,接下来看看addStylesToDom函数:

// 在文件顶部,定义了stylesInDom对象,主要是用来记录已经被加入DOM中的stylesvar stylesInDom = {};function addStylesToDom(styles, options) {    for (var i = 0; i < styles.length; i++) {        var item = styles[i];        var domStyle = stylesInDom[item.id];        var j = 0;        // 判断当前style是否加入DOM中        if (domStyle) {            domStyle.refs++;            // 如果加入,首先循环已加入DOM的parts,并调用其函数,这里我们比较疑惑,但是往下看两行我们就知道这个函数从哪儿来了            for (; j < domStyle.parts.length; j++) {                domStyle.parts[j](item.parts[j]);            }            // 除了上面循环的,如果传进来的style还有则说明又新增的,调用addStyle方法并将其返回值放入domStyle的parts中            // 这里就知道了parts中存放的是addStyle,且是一个函数            for (; j < item.parts.length; j++) {                domStyle.parts.push(addStyle(item.parts[j], options));            }        } else {            // 如果没有加入DOM中,则依次调用addStyle并存入数组parts中,并将当前的style存入stylesInDom对象中            var parts = [];            for (; j < item.parts.length; j++) {                parts.push(addStyle(item.parts[j], options));            }            stylesInDom[item.id] = {                id: item.id,                refs: 1,                parts: parts            };        }    }}

其中的关键还是在于addStyle函数

var singleton = null;var singletonCounter = 0;function addStyle(obj, options) {    var style;    var update;    var remove;    // 默认singleton为false,所以暂时不考虑if的内容了    if (options.singleton) {        var styleIndex = singletonCounter++;        style = singleton || (singleton = insertStyleElement(options));        update = applyToSingletonTag.bind(null, style, styleIndex, false);        remove = applyToSingletonTag.bind(null, style, styleIndex, true);    } else {        style = insertStyleElement(options);        update = applyToTag.bind(null, style, options);        remove = function remove() {            removeStyleElement(style);        };    }    update(obj);    return function updateStyle(newObj) {        if (newObj) {            if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {                return;            }            update(obj = newObj);        } else {            remove();        }    };}

可以看到它返回一个函数,其主要内容是判断传入的对象是否与原对象相等,如果相等,则什么都不做,否则调用update函数,如果对象为空,则调用remove函数。而update与remove是在else中被赋值的,在赋值之前,我们首先看insertStyleElement函数:

var getTarget = function getTarget() {    var memo = {};    return function memorize(target) {        if (typeof memo[target] === 'undefined') {            var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself            if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {                try {                    // This will throw an exception if access to iframe is blocked                    // due to cross-origin restrictions                    styleTarget = styleTarget.contentDocument.head;                } catch (e) {                    // istanbul ignore next                    styleTarget = null;                }            }            memo[target] = styleTarget;        }        return memo[target];    };}();function insertStyleElement(options) {    var style = document.createElement('style');    if (typeof options.attributes.nonce === 'undefined') {        var nonce = typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null;        if (nonce) {            options.attributes.nonce = nonce;        }    }    Object.keys(options.attributes).forEach(function (key) {        style.setAttribute(key, options.attributes[key]);    });    if (typeof options.insert === 'function') {            options.insert(style);    } else {        var target = getTarget(options.insert || 'head');        if (!target) {            throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");        }        target.appendChild(style);    }    return style;}

上面函数很简单,创建一个style标签,并将其插入insert中,即head中,回到之前的地方,我们定义了update和remove,之后我们手动调用update函数,即applyToTag

function applyToTag(style, options, obj) {    var css = obj.css;    var media = obj.media;    var sourceMap = obj.sourceMap;    if (media) {        style.setAttribute('media', media);    }    if (sourceMap && btoa) {        css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */");    } // For old IE    /* istanbul ignore if  */    if (style.styleSheet) {        style.styleSheet.cssText = css;    } else {        while (style.firstChild) {            style.removeChild(style.firstChild);        }        style.appendChild(document.createTextNode(css));    }}

这段代码很简单,即给刚创建的style标签更新内容,而remove函数指向removeStyleElement函数

function removeStyleElement(style) {    // istanbul ignore if    if (style.parentNode === null) {        return false;    }    style.parentNode.removeChild(style);}

`即删除styleDOM结构

总结一下,style-loader会返回一个字符串,而在浏览器中调用时,会将创建一个style标签,将其加入head中,并将css的内容放入style中,同时每次该文件更新也会相应的更新Style结构,如果该css文件内容被删除,则style的内容也会被相应的删除,总体来说,style-loader做了一件非常简单的事:在 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 中

`