1+ const fs = require ( 'fs' ) ;
2+ const path = require ( 'path' ) ;
3+ const Spritesmith = require ( 'spritesmith' ) ;
4+ const { stringifyRequest } = require ( 'loader-utils' ) ;
5+ var valueParser = require ( 'postcss-value-parser' ) ;
6+
7+ module . exports = function ( source ) {
8+
9+ let outputName = new Date ( ) . getTime ( ) + '_smith.png' ;
10+ // 清除css中的注释
11+ const cleanSource = source . replace ( / \/ \* [ \w \W ] * \* \/ / g, '' )
12+
13+ let imgs = cleanSource . match ( / u r l \( .+ _ s p r i t e .+ \) / g) ;
14+
15+ const callback = this . async ( ) ;
16+
17+ imgs = imgs . map ( item => path . resolve ( this . context , item . replace ( / ( u r l \( .) | ( .\) ) / g, '' ) ) ) ;
18+
19+ Spritesmith . run ( { src : imgs } , async ( err , result ) => {
20+ if ( err ) throw err ;
21+
22+ // 雪碧图尺寸
23+ const imgInfo = result . properties ;
24+
25+ // 子图片的偏移、尺寸信息
26+ const coord = { } ;
27+
28+ for ( key in result . coordinates ) {
29+ let newKey = key . match ( / \w + _ s p r i t e .+ / ) [ 0 ] ;
30+ coord [ newKey ] = result . coordinates [ key ] ;
31+ delete result . coordinates [ key ] ;
32+ }
33+
34+ const emitFile = ( name ) => {
35+ return new Promise ( ( resolve ) => {
36+ fs . mkdir ( this . context + '/sprites' , { recursive : true } , ( err ) => {
37+ if ( err ) {
38+ throw ( err ) ;
39+ }
40+ fs . writeFileSync ( path . resolve ( this . context , 'sprites' , name ) , result . image ) ;
41+ resolve ( ) ;
42+ } ) ;
43+ } ) ;
44+ }
45+ // 当前目录生成雪碧图
46+ await emitFile ( outputName ) ;
47+
48+ let parsedValue = valueParser ( cleanSource ) ;
49+
50+ // 包含所有需要转化的css块
51+ let blocks = { } ;
52+
53+ // 找到当前css块的范围
54+ function findCurrentDeclare ( currentIndex ) {
55+ let beginIndex ;
56+
57+ let endIndex ;
58+
59+ parsedValue . walk ( node => {
60+ if ( / \{ / . test ( node . value ) && node . sourceIndex < currentIndex ) {
61+ beginIndex = node . sourceIndex ;
62+ }
63+ if ( endIndex === undefined && / \} / . test ( node . value ) && node . sourceIndex > currentIndex ) {
64+ endIndex = node . sourceIndex ;
65+ }
66+ } ) ;
67+ if ( beginIndex === undefined || endIndex === undefined ) throw new Error ( `css code error!` ) ;
68+ return {
69+ beginIndex,
70+ endIndex,
71+ currentIndex
72+ } ;
73+ }
74+
75+ parsedValue . walk ( node => {
76+ if ( node . value === 'url' && / _ s p r i t e .+ / . test ( node . nodes [ 0 ] . value ) ) {
77+ blocks [ node . sourceIndex ] = findCurrentDeclare ( node . sourceIndex ) ;
78+ blocks [ node . sourceIndex ] . url = node . nodes [ 0 ] . value . match ( / \w + _ s p r i t e .+ / ) [ 0 ] ;
79+ }
80+ } ) ;
81+
82+ // 转换
83+ for ( const key in blocks ) {
84+ let bgLineStart = 0 ;
85+ let bgLineEnd = 0 ;
86+ let bgszLineStart = 0 ;
87+ let bgszLineEnd = 0 ;
88+ let width ;
89+ let height ;
90+ let x ;
91+ let y ;
92+ let ratio ;
93+ const item = blocks [ key ] ;
94+ // 找到background所在行
95+ parsedValue . walk ( node => {
96+ if ( blocks [ key ] . beginIndex <= node . sourceIndex && blocks [ key ] . endIndex >= node . sourceIndex ) {
97+ if ( bgLineStart !== 0 && bgLineEnd !== 0 ) return ;
98+
99+ if ( bgLineStart === 0 && node . value === 'background' ) {
100+ bgLineStart = node . sourceIndex ;
101+ return ;
102+ }
103+
104+ if ( bgLineStart !== 0 && / ; / . test ( node . value ) ) {
105+ bgLineEnd = node . sourceIndex ;
106+ return ;
107+ }
108+
109+ }
110+ } ) ;
111+ // 找到background-size所在行
112+ parsedValue . walk ( node => {
113+ if ( blocks [ key ] . beginIndex <= node . sourceIndex && blocks [ key ] . endIndex >= node . sourceIndex ) {
114+ if ( bgszLineStart !== 0 && bgszLineEnd !== 0 ) return ;
115+
116+ if ( bgszLineStart === 0 && node . value === 'background-size' ) {
117+ bgszLineStart = node . sourceIndex ;
118+ return ;
119+ }
120+
121+ if ( bgszLineStart !== 0 && / ; / . test ( node . value ) ) {
122+ bgszLineEnd = node . sourceIndex ;
123+ return ;
124+ }
125+
126+ }
127+ } ) ;
128+
129+ // 处理 background-size
130+ parsedValue . walk ( node => {
131+ if ( bgszLineStart <= node . sourceIndex && bgszLineEnd >= node . sourceIndex ) {
132+
133+ // 若background-size只有一个px
134+ if ( width === undefined && / p x ; / . test ( node . value ) ) {
135+ ratio = node . value . match ( / \d + / ) [ 0 ] / coord [ item . url ] . width ;
136+ width = imgInfo . width * ratio + 'px ' ;
137+ height = imgInfo . height * ratio + 'px;' ;
138+ node . value = width + height ;
139+ return ;
140+ }
141+ if ( width === undefined && / p x / . test ( node . value ) ) {
142+ ratio = node . value . match ( / \d + / ) [ 0 ] / coord [ item . url ] . width ;
143+ width = imgInfo . width * ratio + 'px' ;
144+ node . value = width ;
145+ return ;
146+ }
147+ if ( width !== undefined && height === undefined && / p x ; / . test ( node . value ) ) {
148+ height = imgInfo . height * ratio + 'px;' ;
149+ node . value = height ;
150+ return ;
151+ }
152+
153+
154+ }
155+ } ) ;
156+ // 处理 background
157+ if ( ratio ) {
158+ parsedValue . walk ( node => {
159+ if ( bgLineStart <= node . sourceIndex && bgLineEnd >= node . sourceIndex ) {
160+
161+ if ( x === undefined && / p x / . test ( node . value ) ) {
162+ x = - coord [ item . url ] . x * ratio + Number ( node . value . match ( / - ? \d + / ) [ 0 ] ) + 'px' ;
163+ node . value = x ;
164+ return ;
165+ }
166+ if ( x !== undefined && y === undefined && / p x ; / . test ( node . value ) ) {
167+ y = - coord [ item . url ] . y * ratio + Number ( node . value . match ( / - ? \d + / ) [ 0 ] ) + 'px;' ;
168+ node . value = y ;
169+ return ;
170+ }
171+ if ( x !== undefined && y === undefined && / p x / . test ( node . value ) ) {
172+ y = - coord [ item . url ] . y * ratio + Number ( node . value . match ( / - ? \d + / ) [ 0 ] ) + 'px' ;
173+ node . value = y ;
174+ return ;
175+ }
176+ }
177+ } ) ;
178+ }
179+ }
180+ callback ( null , parsedValue . toString ( ) . replace ( / u r l \( .+ _ s p r i t e .+ \) / g, `url(${ stringifyRequest ( this . context , './sprites/' + outputName ) } )` ) ) ;
181+ } ) ;
182+
183+ }
0 commit comments