Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

style-loader, 我以为我懂了,其实我错了 #79

Open
closertb opened this issue Aug 3, 2021 · 0 comments
Open

style-loader, 我以为我懂了,其实我错了 #79

closertb opened this issue Aug 3, 2021 · 0 comments

Comments

@closertb
Copy link
Owner

closertb commented Aug 3, 2021

关于样式编译

因为webpack编译的思想是万无皆可JS,意旨所有web项目关联的资源文件,都可以通过js关联起来。然而又由于图片,样式这些本来和js八竿子打不到一起的,所以就有了各种loader来解决他们的关联性问题;

说到webpack的样式编译,总有几个loader是不能错过的,比如less-loader, css-loader,style-loader,随便上一段常用配置:

const lessLoader = {
  test: /\.less$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        modules: true,
      },
    },
    'less-loader',
  ],
}

以上文件就是告诉webpack,在遇到文件名以.less结尾的文件,先用less-loader编译,再用css-loader编译,最后用style-loader,曾经我是用这样一段js伪代码来概述的:

// 伪代码
const res = styleLoader(cssLoader(lessLoader('xxx.less')))

其实这个伪代码,一点都不严谨.

都是loader,但作用却大相径庭

style-loader 其实与 css-loader, less-loader的作用是有区别的,后者其实承担的是模块化与语法转译这一块;而style-loader这一类(还有常用的mini-css-extract-plugin)承担的是粘结剂功能,就是将js中的css加载到html中,从而使样式生效;

20210802224636

随意严谨一点的伪代码应该这样写:

const cssContent = cssLoader(lessLoader('xxx.less'))
// 通过styleTag插入css 字符串到head中
styleLoader(cssContent,  document.head)

好像文章写到这里就应该结束了,但实际上好戏才开始。

曾经我以为,style-loader是在构建时,就将样式插入到了html中,但实际上我错了,而且这一错就是五年(曾经你以为的,不过是你的无知:送给自己),正确的答案是css被构建到了js文件中,然后在js文件加载时,通过style-loader提供的方法将其加载到html中。

简单来讲,曾经我以为这个loader是个构建时,但他其实是一个运行时。

怎么判断呢,很简单,就像如何判断一个网站是否是服务端渲染一样,看网站首页请求获取的html文件是否含样式(也可以直接通过source面板查看html文件, 不是elements)。

之所以会产生这个误判,是我直接平移了mini-css-extract-plugin的构建思想,这个plugin就剥离了JS中的样式,形成了一个新的文件,然后再把样式地址插入到html头中,这是个完完全全的构建时。

而最近为什么会突然关注到这个点,是因为我们在调一个微组件的方案,在组件成功加载时,发现样式丢失,看看下图的惨不忍睹:
20210803081706

经过debug,发现样式确实被打包进了文件,只是没有被加载到style标签中,第一直觉就是缺少loader,而后查看构建工具,发现样式编译配置只到了css-loader这一级,缺少style-loader

从构建结果看样式加载过程

如果到这文章就结束了,似乎就太不像我了,毕竟刨根问底(xuxudaodao)的我才是真的我.接下来,通过一段实例代码,来看webpack构建,是怎样实现样式加载的。

示例代码:

// style.less代码
@font-size: 28px;

.Demo {
  box-sizing: border-box;
  :global {
    .demo-title {
      font-size: @font-size;
    }
  }
}
import React from 'react';
// 引入样式
import style from './style.less';

export default function Demo() {
  return (
    <div className={style.Demo}>
      <h3 className="emo-title">this is a demo</h3>
    </div>
  );
}

然后通过webpack,采用style-loader的方式构建,为了方便阅读,没有做代码压缩和丑化。

然后从入口可以看见样式引入是这样的:

// 注释无用代码有删减
__webpack_require__.d(__webpack_exports__, "default", function() { return Demo; });
var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
// 引入样式
var _style_less__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./src/demo/style.less");
var _style_less__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_style_less__WEBPACK_IMPORTED_MODULE_1__);


function Demo() {
  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
    className: _style_less__WEBPACK_IMPORTED_MODULE_1___default.a.Demo
  }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h3", {
    className: "emo-title"
  }, "this is a demo"));
}

可以看见,在入口文件,有一个从./src/demo/style.less模块的样式引入,并赋值给了_style_less__WEBPACK_IMPORTED_MODULE_1___default变量;

然后顺藤摸瓜,继续看一下style.less模块长什么样:

// "./src/demo/style.less" 模块
function(module, exports, __webpack_require__) {
  var api = __webpack_require__("../../../../.def/def_modules/.builders/@ali/builder-cook/node_modules/[email protected]@style-loader/dist/runtime/injectStylesIntoStyleTag.js");
  var content = __webpack_require__("../../../../.def/def_modules/.builders/@ali/builder-cook/node_modules/[email protected]@css-loader/index.js?!../../../../.def/def_modules/.builders/@ali/builder-cook/node_modules/[email protected]@postcss-loader/src/index.js?!../../../../.def/def_modules/.builders/@ali/builder-cook/node_modules/[email protected]@less-loader/dist/cjs.js?!./src/demo/style.less");
  content = content.__esModule ? content.default : content;

  if (typeof content === 'string') {
    content = [[module.i, content, '']];
  }

  var options = {};
  options.insert = "head";
  options.singleton = false;

  var update = api(content, options);
  module.exports = content.locals || {};
}

这这一块的代码就可以看出,这一个模块是一个承上(css引入)启下(插入html);

承上通过从less-loader/dist/cjs.js?!./src/demo/style.less这个模块引入, 而插入html则是通过从injectStylesIntoStyleTag导入了一个方法,从方法名就可以知道他的作用就是通过style标签插入css样式.

这里面还有一个点,就是最后的导出,模块最后将content.locals导出,继续根据线索去看看content到底是什么:

function(module, exports, __webpack_require__) {
  exports = module.exports = __webpack_require__("../../../../.def/def_modules/.builders/@ali/builder-cook/node_modules/[email protected]@css-loader/lib/css-base.js")(false);

  exports.push([module.i, "._1rfoVjSya7b-iuQB2_qKPh {\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n}\n._1rfoVjSya7b-iuQB2_qKPh .demo-title {\n  font-size: 28px;\n}\n", ""]);

  exports.locals = {
    "Demo": "_1rfoVjSya7b-iuQB2_qKPh"
  };
}

通过上面的代码,就可以看出exports.locals的作用就是css的模块化

至此,真相大白....

写在最后

我似乎特别享受这种顺藤摸瓜的前端(ruozhi)侦探感觉, 这让以前对我来说的一个黑盒被打开了,最后还是那句话,前端里面真的没什么黑魔化,基本就是红宝书里面的知识,只要能静下心来细品: 魔法终究只是一个特别好的想法而已。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant