boi剖析 - 基于webpack的css sprites实现方案
本文是58到家前端工程化集成解决方案boi的博文系列之一。boi是基于webpack打造的一站式前端工程化解决方案,现已开源Github。
作为前端构建工具不可或缺的一个环节,自动生成css sprites图片不仅仅能够减少频繁的人工操作,还能够避免多人协作时对同一个sprites图片维护过程中因个人原因引起的图片不规范问题。58到家前端工程化解决方案boi的自动css sprites功能基于webpack实现,本文记录一下实现方案的各个细节以及需要注意的地方。
1. 功能需求
css sprites的功能需求简单说就是将style中引用的散列小图标合并成一张sprites图片。从功能角度来讲比较单一,从实现角度来讲需要具备以下几点:
- 对style文件进行资源依赖分析,能够得出style中引用的图片资源;
- style文件引用的图片并非都是图标,其他的比如背景图等资源不应该被sprites合并。所以必须有明确的标识可以区分图标与非图标资源。
对于第一点,webpack本身就具备依赖分析的功能,所以无需自行实现。那么如何设计明确的标识以便区分资源类型呢?
2. 用户至上的设计原则
上文提到的资源标识,我们首先看一下业内的同类产品是如何实现的。以fis为例,请看以下代码:
li.list-1::before {
background-image: url('./img/list-1.png?__sprite');
}
li.list-2::before {
background-image: url('./img/list-2.png?__sprite');
}
fis的css sprites功能要求开发者在style代码中添加__sprite标识,fis通过识别这个标识来区分资源类型。这种模式的优点是可以精确地进行定位,而且对图标文件的路径没有强制要求,可以将图标文件与其他资源文件混合存放。但是,在代码中书写标识,首先需要具体的业务开发人员时刻注意不要遗漏;其次,这种模式实质上是对代码的一种“绑架”,代码中存在与业务无关的内容并且可移植性不高。
作为框架,所有方案都应该遵循用户至上的设计原则:
- 配置API语义化,一目了然;
- 减少代码绑架,减少代码中存在与业务无关的内容,以便代码的高可移植性;
- 提供高级配置API,方便用户进行自定义。
基于以上原则,boi在设计配置API时尽量做到了语义化,并且style代码中不存在任何与业务无关的内容。以下代码是boi配置css sprites功能的demo:
boi.spec('style',{
sprites: true,
spritesConfig: {
dir: 'assets/image/icons',
split: true,
retina: true,
postcssSpritesOpts: null
}
});
与sprites功能相关的配置项细节如下:
sprites-Boolean,是否开启自动sprites功能,默认false。只有在sprites为true时,spritesConfig才会生效;spritesConfig-Object,功能配置细节:dir-String,图标文件的目录路径,默认为undefined。boi以路径作为区分图标与非图标资源的标识,也就是说参与自动sprites的图标文件必须存放于独立的目录下,比如'assets/image/icons';split-Boolean,是否识别子目录并且每个子目录分别编译为sprites图片,默认为true。比如上述代码对应的项目中存在图标目录'assets/image/icons',在此目录下又存在两个子目录'assets/image/icons/index'和'assets/image/icons/admin',分别存在index页面和admin页面的图标文件。如果配置split:true,boi将会编译输出两个sprites图片sprite.index.png和sprite.admin.png;如果配置split:false,boi只会编译输出一个sprites图片文件sprite.icons.png。retina-Boolean,是否识别分辨率标识,默认为true。分辨率标识指的是类似@2x的文件名标识,比如存在两个图标文件logo.png和logo@2x.png并且style文件中对两张图标都有引用,如下:
@media screen and (max-width:780px){
.logo{
background-image: url(../assets/icons/logo.png)
}
}
@media screen and (min-width:781px and max-width:900px){
.logo{
background-image: url(../assets/icons/logo@2x.png)
}
}
如果配置`retina:true`,boi将把两种分辨率的图片分别合并为一张sprites图片,否则会编译到同一张sprites图片里。详细内容可以参考[boi-example-css-sprites](https://github.com/boijs/boi-example-css-sprites)。
postcssSpritesOpts-Object,默认为null。boi使用postcss-sprites作为实现css sprites的技术选型。postcssSpritesOpts是提供给用户自定义postcss-sprites相关功能的,这个配置项一般情况下是不需要用户操作的。如果遇到上文提到的配置项不能满足的应用场景,用户可以通过此API直接对postcss-sprites进行配置。
3. 技术选型
boi实现css sprites功能的技术选型如下:
- 构建内核: webpack;
- 资源编译loader:postcss-loader
- sprites功能实现: postcss-sprites
4. 实现方案
上文第二节中提到了boi实现sprites功能的设计原则和工作模式。用户在配置API中指定图标文件的路径
,boi以此路径作为区分图标与非图标文件的标识;并且支持识别分辨率标识进行单独编译。
在配置postcss时,要注意以下几点:
- 使用less/sass等css预编译器时postcss的执行时机问题;
- 通过路径进行图标文件合法性过滤;
- 以子目录名称和分辨率标识为基础的sprites图片命名规则。
下文将分别介绍boi针对上述问题的具体解决方案。
4.1 与css预编译器综合使用
postcss并非只支持原始的css语法,同时也支持less和sass等预编译语法。webpack根据loader的先后顺序从右至左依次进行编译,比如:
{
test: /\.less$/,
loader: 'css!less'
}
webpack对less文件的编译顺序为:less->css->style。那么在使用postcss时应该在哪一步执行呢?
虽然postcss支持less和sass,笔者也并不推荐直接使用postcss去编译less和sass。一方面是因为postcss支持的预编译器类型有限;另一方面即使postcss支持所有预编译语言,考虑到用户配置预编译器的多样性,如果对不同编译器分派不同的postcss插件势必会造成boi框架体积的臃肿。
基于上述的考虑,postcss-loader的位置就已经确定了:在预编译loader之后,css-loader之前。如下:
{
test: /\.less$/,
loader: 'css!postcss!less'
}
之所以在css-loader之前还有另外一个原因, postcss-sprites将散列的图标合并成sprites之后首先要将生成的sprites图片存放于一个临时目录内,然后在通过css-loader进行资源依赖解析并编译到统一的dest目录中。所以中间有一个暂存的过程,必须通过css-loader进行依赖解析才能得到最终的结果。
4.2 合法性过滤
boi通过路径进行图标合法性标识,首先根据用户的配置创建验证正则:
const REG_SPRITES_NAME = new RegExp([
path.posix.normalize(spritesConfig.dir).replace(/^\.*/, '').replace(/\//, '\\/'),
'\\/\.+\\.',
_.isArray(config.image.extType) ? '(' + config.image.extType.join('|') +')' : config.image.extType,
'\$'
].join(''), 'i');
然后配置postcss-sprites的filterBy钩子函数进行合法性验证:
filterBy: (image) => {
if (!REG_SPRITES_NAME.test(image.url)) {
return Promise.reject();
}
return Promise.resolve();
}
4.3 分组规则
分组的依据有两个:目录名称和分辨率标识。首先需要根据用户的配置创建目录名称验证和分辨率标识验证的正则:
// 合法的散列图path
const REG_SPRITES_PATH = new RegExp([
path.posix.normalize(spritesConfig.dir).replace(/^\.*/, '').replace(/\//, '\\/'),
'\\/(.*?)\\/.*'
].join(''), 'i');
// 合法的retina标识
const REG_SPRITES_RETINA = new RegExp([
'@(\\d+)x\\.',
_.isArray(config.image.extType) ? '(' + config.image.extType.join('|') +')' : config.image.extType,
].join(''), 'i');
然后通过postcss-sprites的groupBy钩子函数进行分组规则制定:
groupBy: (image) => {
let groups = null;
let groupName = undefined;
if (spritesConfig && spritesConfig.split) {
groups = REG_SPRITES_PATH.exec(image.url);
groupName = groups ? groups[1] : 'icons';
} else {
groupName = 'icons';
}
if (spritesConfig && spritesConfig.retina) {
image.retina = true;
image.ratio = 1;
let ratio = REG_SPRITES_RETINA.exec(image.url);
if (ratio) {
ratio = ratio[1];
while (ratio > 10) {
ratio = ratio / 10;
}
image.ratio = ratio;
image.groups = image.groups.filter((group) => {
return ('@' + ratio + 'x') !== group;
});
groupName += '@' + ratio + 'x';
}
}
return Promise.resolve(groupName);
}
上述代码包括以下逻辑:
- 如果用户配置
split:true,boi会对子目录进行正则验证,如果存在子目录将会单独分组;若不存子目录子默认分组名称为'icons'; - 如果用户配置
retina:true,boi会验证图标文件名是否包含分辨率标识,如果存在则将groupName加上类似'@2x'的后缀。
各位可能注意到上述代码中以下的部分比较怪异:
image.groups = image.groups.filter((group) => {
return ('@' + ratio + 'x') !== group;
});
postcss-sprites识别到图标存在分辨率标识会生成单独的分组名称,如果不进行上述过滤的话,最终生成的sprites图片名称类似sprites.@2x.icons.png。以上过滤是为了将@2x分组删除,以便编译后的文件名更具语义化,比如sprites.icons@2x.png。
5. 开源代码
各位可以结合源码/lib/config/genConfig/mp/style.js理解本文的内容。
boi剖析 - 基于webpack的css sprites实现方案的更多相关文章
- CSS3与页面布局学习笔记(五)——Web Font与CSS Sprites(又称CSS精灵、雪碧图)技术
一.web font web font是应用在web中的一种字体技术,在CSS中使用font-face定义新的字体.先了解操作系统中的字体: a).安装好操作系统后,会默认安装一些字体,这些字体文件描 ...
- CSS Sprites (CSS图像拼合技术)教程工具
什么是CSS Sprites? “Sprite”(精灵)这个词在计算机图形学中有它独特的定义,由于游戏.视频等画质越来越高,必须有一种技术可以智能的处理材质和贴图,并且要 同时保持画面流畅.“Spri ...
- 基于webpack的前端工程化开发解决方案探索(一):动态生成HTML(转)
1.什么是工程化开发 软件工程的工程化开发概念由来已久,但对于前端开发来说,我们没有像VS或者eclipse这样量身打造的IDE,因为在大多数人眼中,前端代码无需编译,因此只要一个浏览器来运行调试就行 ...
- CSS预编译与PostCSS以及Webpack构建CSS综合方案
CSS全称Cascading Style Sheets(层叠样式表),用来为HTML添加样式,本质上是一种标记类语言.CSS前期发展非常迅速,1994年哈肯·维姆·莱首次提出CSS,1996年12月W ...
- 前端性能优化--图片处理(Css Sprites 与 base64)
前言: 近期研究着前端性能的优化方面的知识,并以博客记之.之前有相同系列的文章(前端性能优化--图片懒加载(lazyload image)),这次继续是关于图片的处理,css sprites 和 ba ...
- 基于 Webpack 引入 jquery 插件的笔记
如果都是基于 webpack(npm 上有包),那就非常顺利: import $ from 'jquery' import 'jquery-modal/jquery.modal.min.css' im ...
- 基于 Webpack 4 搭建 Vue 开发环境
自从工作之后,就已经很久没有写过博客了.时间被分割得比较碎,积累了一段时间的学习成果,才写下了这篇博客. 之前有写过 Webpack4 的文章,但是都比较偏入门,唯一的一篇实战篇 -- 基于Webpa ...
- react&webpack使用css、less && 安装原则 --- 从根本上解决问题。
在webpack-react项目中,css的使用对于不同人有不同的选择,早起是推荐在jsx文件中使用 css inline js的,但是这种方法要写很多对象来表示一个一个的标签,并且对于这些对象,我们 ...
- 基于webpack搭建vue-cli以及webstorm的设置
N1.检查本地电脑是否安装node和npm 安装vue-cli的前提是已经安装了npm 是否安装npm检查命令 npm -v 如果出现版本号,说明已经安装了npm 查看node版本号 node -v ...
随机推荐
- MySQL数据库定义与操作语言
文章为作者原创,未经许可,禁止转载. -Sun Yat-sen University 冯兴伟 实验1.1 数据库定义 (1)实验目的 理解和掌握数据库DDL语言,能够熟练地使用SQL DDL语句 ...
- Linux系统中CPU使用率查询常用的5个命令
在程序开发中,我们一般都是在Linux系统上进行开发,因此对Linux系统的维护工作很重要.在Linux系统维护中,我们需要经常查看的就是cpu的使用率,分析系统的整体运行情况.那CPU使用率怎么查询 ...
- android如何播放和录制音频
视频录制功能正在走来,在Androidsdk中有与之相关的类:android.media.MediaRecorder.当然,因为模拟器上没有提供必要的硬件设施,所以在学习过程中并不能实现.Media能 ...
- Docker实践(4)—network namespace与veth pair
network namespace 创建network namespace # ip netns add blue # ip netns list blue 添加网口到namespace 先创建v ...
- Android系列---JSON数据解析
您可以通过点击 右下角 的按钮 来对文章内容作出评价, 也可以通过左下方的 关注按钮 来关注我的博客的最新动态. 如果文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦 如果您对文章内 ...
- Android UI系列-----Dialog对话框
您可以通过点击 右下角 的按钮 来对文章内容作出评价, 也可以通过左下方的 关注按钮 来关注我的博客的最新动态. 如果文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦 如果您对文章内 ...
- ubuntu 16.04 vnc server
安装方法 https://help.ubuntu.com/community/VNC/Servers#vino 系统默认自带的,通过搜索Remote Desktop来找到它 配置好之后,设定一下密码 ...
- 转:TinyXM--优秀的C++ XML解析器
读取和设置xml配置文件是最常用的操作,试用了几个C++的XML解析器,个人感觉TinyXML是使用起来最舒服的,因为它的API接口和Java的十分类似,面向对象性很好. TinyXML是一个开源的解 ...
- 如何做一份能忽悠投资人的PPT
游侠近日发布的一款电动汽车引发全民吐槽,被人们嘲讽为“靠一份PPT忽悠投资人”.这类情形可以回溯至去年的锤子手机发布会.如今,吐槽的开始散去,我们可以静下心来吸收点干货,我们对比了锤子手机发布会的PP ...
- 软件包管理 之 RPM 基础 《RPM 的介绍和应用》
RPM 是 Red Hat Package Manager 的缩写,本意是Red Hat 软件包管理,顾名思义是Red Hat 贡献出来的软件包管理:在Fedora .Redhat.Mandriva. ...