本文是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。只有在spritestrue时,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.pngsprite.admin.png;如果配置split:false,boi只会编译输出一个sprites图片文件sprite.icons.png
    • retina - Boolean,是否识别分辨率标识,默认为true。分辨率标识指的是类似@2x的文件名标识,比如存在两个图标文件logo.pnglogo@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功能的技术选型如下:

4. 实现方案

上文第二节中提到了boi实现sprites功能的设计原则和工作模式。用户在配置API中指定图标文件的路径

,boi以此路径作为区分图标与非图标文件的标识;并且支持识别分辨率标识进行单独编译。

在配置postcss时,要注意以下几点:

  1. 使用less/sass等css预编译器时postcss的执行时机问题;
  2. 通过路径进行图标文件合法性过滤;
  3. 以子目录名称和分辨率标识为基础的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实现方案的更多相关文章

  1. CSS3与页面布局学习笔记(五)——Web Font与CSS Sprites(又称CSS精灵、雪碧图)技术

    一.web font web font是应用在web中的一种字体技术,在CSS中使用font-face定义新的字体.先了解操作系统中的字体: a).安装好操作系统后,会默认安装一些字体,这些字体文件描 ...

  2. CSS Sprites (CSS图像拼合技术)教程工具

    什么是CSS Sprites? “Sprite”(精灵)这个词在计算机图形学中有它独特的定义,由于游戏.视频等画质越来越高,必须有一种技术可以智能的处理材质和贴图,并且要 同时保持画面流畅.“Spri ...

  3. 基于webpack的前端工程化开发解决方案探索(一):动态生成HTML(转)

    1.什么是工程化开发 软件工程的工程化开发概念由来已久,但对于前端开发来说,我们没有像VS或者eclipse这样量身打造的IDE,因为在大多数人眼中,前端代码无需编译,因此只要一个浏览器来运行调试就行 ...

  4. CSS预编译与PostCSS以及Webpack构建CSS综合方案

    CSS全称Cascading Style Sheets(层叠样式表),用来为HTML添加样式,本质上是一种标记类语言.CSS前期发展非常迅速,1994年哈肯·维姆·莱首次提出CSS,1996年12月W ...

  5. 前端性能优化--图片处理(Css Sprites 与 base64)

    前言: 近期研究着前端性能的优化方面的知识,并以博客记之.之前有相同系列的文章(前端性能优化--图片懒加载(lazyload image)),这次继续是关于图片的处理,css sprites 和 ba ...

  6. 基于 Webpack 引入 jquery 插件的笔记

    如果都是基于 webpack(npm 上有包),那就非常顺利: import $ from 'jquery' import 'jquery-modal/jquery.modal.min.css' im ...

  7. 基于 Webpack 4 搭建 Vue 开发环境

    自从工作之后,就已经很久没有写过博客了.时间被分割得比较碎,积累了一段时间的学习成果,才写下了这篇博客. 之前有写过 Webpack4 的文章,但是都比较偏入门,唯一的一篇实战篇 -- 基于Webpa ...

  8. react&webpack使用css、less && 安装原则 --- 从根本上解决问题。

    在webpack-react项目中,css的使用对于不同人有不同的选择,早起是推荐在jsx文件中使用 css inline js的,但是这种方法要写很多对象来表示一个一个的标签,并且对于这些对象,我们 ...

  9. 基于webpack搭建vue-cli以及webstorm的设置

    N1.检查本地电脑是否安装node和npm 安装vue-cli的前提是已经安装了npm 是否安装npm检查命令 npm -v 如果出现版本号,说明已经安装了npm 查看node版本号 node -v ...

随机推荐

  1. 转:SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

  2. 神舟K650c i7(W350STQ)上成功装好Mac OS X 10.9,兼谈如何安装WinXP、7、8.1、OSX、Ubuntu五系统(Chameleon、MBR)

    作者:zyl910 参考教程——http://bbs.pcbeta.com/viewthread-1432534-1-4.html笔记本SNB和IVY平台Win7/Win8/Win8.1安装OS X ...

  3. [算法导论]BFS @ Python

    class Graph: def __init__(self): self.V = [] class Vertex: def __init__(self, x): self.key = x self. ...

  4. Hadoop学习-生态体系(ecosystem)概览

    0. 大背景 全球No.1搜索引擎公司谷歌(Google)面临每天海量搜索引擎数据的问题,经过长时间的实践积累, 谷歌形成了自己的大数据框架,但是并没有开源,而是发表了一篇论文,阐述了自己的思想,在论 ...

  5. python排序算法的实现-插入

    1.算法: 设有一组关键字{ K 1 , K 2 ,…, K n }:排序开始就认为 K 1 是一个有序序列:让 K 2 插入上述表长为 1 的有序序列,使之成为一个表长为 2 的有序序列:然后让 K ...

  6. 倒计时,js

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  7. Linux 输出文件列数,拼接文件

    如果我只想看看文件的前几行,每行的字段数(列数),我的文件已tab作为分隔符(这个可以自己指定),其具体命令如下: head fileName | awk -F'\t' '{print NF}' 如果 ...

  8. 怎么删除github上的仓库

    1.到你的个人中心.点击你的个人账号.下图的红色部分 2.点击repositories(仓库),选择你要删除的项目 3.code这一行导航栏 最后的一个. setting 4.下拉页面到最下面 Del ...

  9. 算法 - 求两个自然数的最小公倍数(C++)

    //************************************************************************************************** ...

  10. mingw 环境编译 liburl故障一例

    环境是 windows 10,已经安装 mingw,并设置好mingw 和 msys的环境变量 C:\Users\cracker>set |grep MinGW Path=C:\Program ...