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 ...
随机推荐
- Tomcat 集群模式下 Session 更新 Bug (redis memcached 及tomcat自已的集群)
从 excel 中导入数据入系统,我们用的是先上传文件至服务器再分析所上传的文件逐行导入. 就是执行了一循环,在当前循环位置标识一下客户端就知道执行的进度了,以前的方式 是用 session.setA ...
- 【linux】如何将Vim打造成一个成熟的IDE
如果你稍微写过一点代码,就能知道“集成开发环境”(IDE)是多么的便利.不管是Java.C还是Python,当IDE会帮你检查语法.后台编译,或者自动导入你需要的库时,写代码就变得容易许多.另外,如果 ...
- Android的Activity屏幕切换动画(一)-左右滑动切换
(国内知名Android开发论坛eoe开发者社区推荐:http://www.eoeandroid.com/) Android的Activity屏幕切换动画(一)-左右滑动切换 在Android开发过程 ...
- 通过Web.config实现301重定向
通过Web.config实现301重定向 IIS7以上可以通过修改Web.config实现IIS设置 现在我们通过Web.config实现301重定向 <system.webServer> ...
- python排序算法的实现-冒泡
1.算法描述: (1)共循环 n-1 次 (2)每次循环中,如果 前面的数大于后面的数,就交换 (3)设置一个标签,如果上次没有交换,就说明这个是已经好了的. 2.代码 #!/usr/bin/pyth ...
- hibernate 映射 多对一
一对多和上文讲的多对一两种映射关系,其实就是站在相反的角度考虑同样的事情. 一对多和多对一映射原理是一样的,都在多的一端加入一个外键指向一的一端.也就是说,在关系数据库的表中,他们的表及表字段都是一样 ...
- .NET Core:面向未来的开源跨平台开发技术
作为一种全新的开源和跨平台的开发平台,.NET Core 历经两年多的开发,终于在于2016年6月27日针对所有主流服务器和桌面操作系统发布 1.0 RTM 版本..NET Core 是一种通用开发平 ...
- Swift 自动布局框架-SnapKit
官方网址:http://snapkit.io/ Github: https://github.com/SnapKit/SnapKit SnapKit is a DSL to make Auto Lay ...
- UIRefreshControl的使用
注意: 1.需要在ios6.0之后的版本中使用 2.UIRefreshControl目前只能用于UITableViewController,如果用在其他ViewController中,运行时会错误(即 ...
- 允许CEF跨域访问iframe
默认情况下,如果嵌入本地Web页面,并在页面内部使用iframe来显示一个在线页面,加载的过程中会触发一个未捕获异常,虚函数CefV8ContextHandler::OnUncaughtExcepti ...