解析style-loader源码

728 阅读2分钟

执行debugger;

const loaderAPI = () => {};
loaderAPI.pitch = function loader(request) {
  debugger;
  const options = this.getOptions(_options.default);
  const injectType = options.injectType || "styleTag";
  const esModule = typeof options.esModule !== "undefined" ? options.esModule : true;
  const runtimeOptions = {};
  ....`

style-loader是通过loader.pitch函数往源代码中插入dom操作来执行的,webpack执行loader的顺序是从右往左,但是执行pitch函数是从左到右执行 然后就是执行一些绑定参数的操作 最后通过return 返回一段代码字符串:

" import API from "!../../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js";

import domAPI from "!../../../node_modules/style-loader/dist/runtime/styleDomAPI.js";

import insertFn from "!../../../node_modules/style-loader/dist/runtime/insertBySelector.js";

import setAttributes from "!../../../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js"; 

import insertStyleElement from "!../../../node_modules/style-loader/dist/runtime/insertStyleElement.js";

import styleTagTransformFn from "!../../../node_modules/style-loader/dist/runtime/styleTagTransform.js";

import content, * as namedExport from "!!../../../node_modules/css-loader/dist/cjs.js!./a.css";

var options = {}; 

options.styleTagTransform = styleTagTransformFn; options.setAttributes = setAttributes;

options.insert = insertFn.bind(null, "head");

options.domAPI = domAPI;

options.insertStyleElement = insertStyleElement;

var update = API(content, options);

export * from "!!../../../node_modules/css-loader/dist/cjs.js!./a.css"; export default content && content.locals ? content.locals : undefined; "

重点在于这段:

import content, * as namedExport from "!!../../../node_modules/css-loader/dist/cjs.js!./a.css";

pitch函数返回值的时候是会阻断后面loader的执行的,但这时候通过inline-loader写法再次引入css-loader来解析css文件,使得css-loader能再次运行,这里的!!会忽略前置、后置、普通loader的执行,也就是阻止style-loader第二次执行;此时loader执行完成,往源码中插入了dom操作,此时我们来看var update = API(content, options);打断点

module.exports = function injectStylesIntoStyleTag(list, options) {
    debugger;
  options = options || {};
  list = list || [];
  var lastIdentifiers = modulesToDom(list, options);
  return function update(newList) {
    ...

image.png

function modulesToDom(list, options) {
  var idCountMap = {};
  var identifiers = [];

  for (var i = 0; i < list.length; i++) {
    var item = list[i];
    var id = options.base ? item[0] + options.base : item[0];
    var count = idCountMap[id] || 0;
    var identifier = "".concat(id, " ").concat(count);
    idCountMap[id] = count + 1;
    var indexByIdentifier = getIndexByIdentifier(identifier);
    var obj = {
      css: item[1],
      media: item[2],
      sourceMap: item[3],
      supports: item[4],
      layer: item[5]
    };

    if (indexByIdentifier !== -1) {
      stylesInDOM[indexByIdentifier].references++;
      stylesInDOM[indexByIdentifier].updater(obj);
    } else {
      var updater = addElementStyle(obj, options);
      options.byIndex = i;
      stylesInDOM.splice(i, 0, {
        identifier: identifier,
        updater: updater,
        references: 1
      });
    }

    identifiers.push(identifier);
  }

  return identifiers;
}
function addElementStyle(obj, options) {
  var api = options.domAPI(options);
  api.update(obj);

  var updater = function updater(newObj) {
    if (newObj) {
      if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap && newObj.supports === obj.supports && newObj.layer === obj.layer) {
        return;
      }

      api.update(obj = newObj);
    } else {
      api.remove();
    }
  };

  return updater;
}
function domAPI(options) {
  var styleElement = options.insertStyleElement(options);
  return {
    update: function update(obj) {
      apply(styleElement, options, obj);
    },
    remove: function remove() {
      removeStyleElement(styleElement);
    }
  };
}
function insertStyleElement(options) {
  var element = document.createElement("style");
  options.setAttributes(element, options.attributes);
  options.insert(element, options.options);
  return element;
}
function insertBySelector(insert, style) {
  var target = getTarget(insert);

  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);
}
function styleTagTransform(css, styleElement) {
  if (styleElement.styleSheet) {
    styleElement.styleSheet.cssText = css;
  } else {
    while (styleElement.firstChild) {
      styleElement.removeChild(styleElement.firstChild);
    }

    styleElement.appendChild(document.createTextNode(css));
  }
}

然后就是常见的往head插入style元素的操作