浅析css在webpack的编译过程

447 阅读3分钟

环境配置

本文按照less-loader、css-loader、style-loader的顺序处理css

创建example文件夹,内容如下

// webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  devtool: false,
  entry: "./src/testLess.js",
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
  optimization: {
    runtimeChunk: true,
  },
};

// package.json

{
  "name": "example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^6.7.3",
    "html-webpack-plugin": "^5.5.0",
    "less-loader": "^11.1.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.72.1",
    "webpack-cli": "^4.9.2"
  }
}


// src/test.less

.content {
  width: 100%;
  &-item {
    height: 100%;
  }
}

// src/testLess.js
import less from "./test.less";

console.log(less);

断点调试

style-loader

首先进到了style.pitch

1676878314074.png

request的意思是用 C:\Users\mjgao\css-webpack\example\node_modules\css-loader\dist\cjs.js(就是css-loader)和C:\Users\mjgao\css-webpack\example\node_modules\less-loader\dist\cjs.js(less-loader)去处理C:\Users\mjgao\css-webpack\example\src\test.less这个文件

之后进入到字符串拼接,拼接出来的字符串内容如下

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!../node_modules/less-loader/dist/cjs.js!./test.less";
      
      

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!../node_modules/less-loader/dist/cjs.js!./test.less";
export default content && content.locals ? content.locals : undefined;

由于pitch返回了这段内容,此次loader过程结束,本文结束

虽然loader结束了,但是webpack在生成上面这段代码的时候,会分析内容

仔细看这段代码,这些import是webpack的内联loader,感叹号代表对这个文件禁用loader(不然webpack会对这个文件也进行loader操作) webpack.docschina.org/configurati…

上面这段代码很明显指明了用css-loaderless-loader去处理test.less这个文件

所以此时会执行第二次loader————内联loader

由于css-loader和less-loader并没有pitch,所以执行loader函数,首先执行less-loader

less-loader

简略代码,去掉日志,sourcemap等等其他内容

async function lessLoader(source) {
  const options = this.getOptions(_options.default);
  const callback = this.async();
  let result;
  try {
    result = await implementation.render(data, lessOptions);
  } catch (error) {
    if (error.filename) {
      this.addDependency(_path.default.normalize(error.filename));
    }
    callback(new _LessError.default(error));
    return;
  }
  const {
    css,
    imports
  } = result;
  imports.forEach(item => {
    if ((0, _utils.isUnsupportedUrl)(item)) {
      return;
    }
    const normalizedItem = _path.default.normalize(item);

    if (_path.default.isAbsolute(normalizedItem)) {
      this.addDependency(normalizedItem);
    }
  });
  callback(null, css);
}

image.png

可以看到,传进去的source就是test.less的内容

const callback = this.async()这里用了异步的回调

result = await implementation.render(data, lessOptions);这里是loader的处理

image.png

处理完的结果就是css内容了

imports.forEach是将每个less文件都监听,因为less-loader会整合所有的less,若单个文件改动,需要全部重新构建一遍,

在开发过程中会发现如果文件出错了,修改后编译得非常快,这是因为抛出异常的时候,监听了这个文件

最后异步调用callback,将处理结果传给webpack

css-loader

可以看见content就是less-loader处理后的内容

1676887250089.png

由于less-loader的callback是异步的,所以css-loader也需要异步

css-loader的作用就是将css处理成js

// Imports
import ___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___ from "../node_modules/css-loader/dist/runtime/noSourceMaps.js";
import ___CSS_LOADER_API_IMPORT___ from "../node_modules/css-loader/dist/runtime/api.js";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___);
// Module
___CSS_LOADER_EXPORT___.push([module.id, ".content {\n  width: 100%;\n}\n.content-item {\n  height: 100%;\n}\n", ""]);
// Exports
export default ___CSS_LOADER_EXPORT___;

这就是css-loader的处理结果,最后生成的结果还要再经过webpack的处理

总结

style-loader的作用就是生成一份字符串代码给webpack,此时loader就结束了,后面webpack分析引用的时候发现这份代码还存在调用loader,于是就再一次启用loader

后面网页请求的时候调用style-loader生成的那段代码,所有的东西就都能访问到了

image.png

这是执行main.js的时候的html

var update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_node_modules_less_loader_dist_cjs_js_test_less__WEBPACK_IMPORTED_MODULE_6__["default"], options);

执行到这句之后,页面就有style标签了

对应的应该是字符串代码var update = API(content, options);

image.png