本文讲解css-loader、style-loader和isomorphic-style-loader的实现原理,而不会讲解它们的具体用法,具体用法请参考其他文档。我是如何知道它们是这么实现的呢?我没有看它们的源代码,而是通过查看webpack打包后生成的代码。为了留下更深刻的印象,就写下了本文。
有个很奇怪的事情,在JS文件中竟然能够导入CSS文件(通过import或require)。这是怎么做到的呢?如果JS文件要想正常被执行,那JS文件中只能包括JS语法,这是毫无疑问的。所以可以推导出,CSS文件肯定被转换成JS文件了,这个就是css-loader做的事情了。
那CSS文件被转换成什么样子了呢?总的来说,原CSS文件的内容被转换成了一个字符串。但这还不够,为撒?一个文件既然能够被导入,那自然需要导出。所以简单推导,css-loader可以将一个css文件转换成如下内容:
module.exports = "原css文件中的样式";
这样CSS文件就成了一个合理的JS文件了,而且可以被其他JS文件引用了。这是css-loader最基本的作用,其他的都是锦上添花,比如压缩、模块化等。
关于css-loader最后就谈一下其中的模块化。模块化是什么意思呢?我们知道css中的类名、动画名等是不具有唯一性约束的,不同的css文件或库很可能造成类名等的冲突。为了不造成冲突,css-loader可以按照一定的规则将类名等转化成具有全局唯一性的名字。即使2个文件中都有叫root的类名,经css-loader转换后,它们的类名可以完全不同,这就是css的模块化。
模块化以后,用户完全不知道被转换后的类名,这样就没法使用了。为了让用户知道类名,css-loader就需要对转换后的内容做些改变。css-loader实际转换后的内容等价于如下伪代码:
module.exports = {
toString: () => “原css文件中的样式(类名被转换了,代码可能也被压缩过)”
locals: {
[原文件中写的类名等]: “被转换后的实际类名等”
}
}
这样通过其中的locals对象,就很容易知道被转换后的类名了。举个例子,假设css原文件中有个类名为home,导入该css文件时用变量s接收,s.locals.home的值就是被转换后的类名了。
有了css-loader,style-loader的实现就很简单了。先通过css-loader把css文件转换成一个对象,假设叫content,该对象就是上面css-loader转换css文件后导出的对象。然后简单粗暴的通过DOM操作将content中的样式插入到style标签中。最后将content.locals导出,方便用户使用类名、动画名等。同时经过css-loader和style-loader转换后的内容可以用如下伪代码表示:
var content = 【css-loader转换css文件后的结果】;
addStylesToDom(content.toString());
module.exports = content.locals;
我们知道了style-loader的原理,它很简单,但又很粗暴。粗暴的是,直接将样式通过DOM操作进行插入。对于浏览器环境则很好,很方便,不需要用户干预。但是对于node环境,这就没法愉快的玩耍了。node环境需要的是将样式插入到动态生成的html字符串中,而不是进行DOM操作。这时就需要用到isomorphic-style-loader,而不是style-loader。
isomorphic-style-loader没有像style-loader那样直接进行DOM操作,而是导出了一些辅助方法,让用户依据实际情况来调用不同的方法。同时经过css-loader和isomorphic-style-loader转换后的内容可以用如下伪代码表示:
var content = 【css-loader转换css文件后的结果】;
// 方便用户使用类名等
exports = module.exports = content.locals || {};
exports._getContent = () => content;
// 方便用户获取样式
exports._getCss = () => context.toString();
// 方便用户将样式插入到DOM中
exports._insertCss = 【作用同上面的addStylesToDom】;
从如上伪代码可以看出,isomorphic-style-loader主要是导出了2个函数,_getCss和_insertCss。让用户根据实际环境来调用,而不是像style-loader那样。在浏览器环境中就可以调用_insertCss(_getCss())来将样式插入到DOM中;在node环境中就不能调用_insertCss,但能调用_getCss获取样式字符串,根据实际需求来使用。