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 ...
随机推荐
- PHP学习计划
- Ruby on Rails框架开发学习
学习地址:http://www.ixueyun.com/lessons/detail-lessonId-685.html 一.课程概述 软件开发在经历了面向过程编程的阶段,现在正大行其道的是敏捷开发, ...
- HOWTO - Basic MSI安装包在安装运行过程中如何获取完整源路径
有朋友问到如何在一个Windows Installer安装包中获取安装包源路径,就是在安装包运行过程中动态获取*.msi所在完整路径. 这个问题分两类,如果我们的安装包只是一个*.msi安装文件,那么 ...
- 使用VSTS/TFS搭建iOS持续集成环境
TFS 自2015版开始支持跨平台的持续集成环境,通过提供开源的build agent为 Windows / linux / macOS 提供了统一的持续集成环境管理能力.这篇文章给大家介绍一下如何使 ...
- 【转】兼容iOS 10 资料整理
1.Notification(通知) 自从Notification被引入之后,苹果就不断的更新优化,但这些更新优化只是小打小闹,直至现在iOS 10开始真正的进行大改重构,这让开发者也体会到UserN ...
- VMware安装RedHat Linux虚拟机图文详解
创建Red Hat Linux虚拟机 1.打开VMware,开始创建虚拟机 点击菜单[文件]->[新建虚拟机]. 2.默认典型,单击[下一步] 3.选择安装来源 在这里,我们选择安装来源为[安装 ...
- Objective C for Windows
You can use Objective C inside the Windows environment. If you follow these steps, it should be work ...
- 手动安装 atom 扩展包 packages
由于某些原因, 我们下载 atom 扩展时发现速度特别慢, 或者根本无法下载, 那我们可以尝试手动安装 首先, 从 github 上下载(或其它地方) 扩展包, 解压 进入该文件夹, 找到 packa ...
- java生成解析xml的另外两种方法JAXB
JAXB(Java Architecture for XML Binding) 是一个业界的标准,是一项可以根据XML Schema产生Java类的技术.该过程中,JAXB也提供了将XML实例文档反 ...
- 如何配置ssh免密码登录
[TOC] 如果你在管理一堆unix机器,每次登录都要输入密码是挺烦的事情,一方面为了安全我们一般不会将所有机器的密码都设置成一样,另一方面就算一样每次都输入一遍也很麻烦. 这种情况下我们一般是用ss ...