-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.js
159 lines (125 loc) · 4.84 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
'use strict';
const postcss = require('postcss');
const mediaParser = require('postcss-media-query-parser').default;
const valueParser = require('postcss-value-parser');
const DPI_RATIO = {
x: 96,
dppx: 96,
dpcm: 2.54,
dpi: 1
};
// convert all sizes to dpi for sorting
const convertSize = (size, decl) => {
if (!size) {
return DPI_RATIO.x;
}
const m = size.match(/^([0-9|\.]+)(.*?)$/);
if (m && DPI_RATIO[m[2]]) {
const dpi = m[1] * DPI_RATIO[m[2]];
return {
dpi: Math.floor(dpi),
pxRatio: Math.floor(dpi / DPI_RATIO.x * 100) / 100
};
}
throw decl.error('Incorrect size value', { word: m && m[2] });
};
const stringify = chunk => valueParser.stringify(chunk);
const parseValue = (value, decl) => {
const valueChunks = valueParser(value).nodes;
const imageSetChunks = valueChunks.shift().nodes;
const sizes = imageSetChunks
.filter(chunk => chunk.type === 'word')
.map(chunk => convertSize(stringify(chunk), decl));
const urls = imageSetChunks
.filter(chunk => chunk.type === 'function' || chunk.type === 'string')
.map(chunk => {
const str = stringify(chunk);
return chunk.type === 'string' ? `url(${str})` : str;
});
const suffix = valueChunks.length ?
valueChunks
.map(stringify)
.join('') :
'';
return {
images: {
size: sizes,
url: urls
},
suffix
};
};
module.exports = postcss.plugin('postcss-image-set-polyfill', () =>
css => {
css.walkDecls(/^(background-image|background)$/, decl => {
// ignore nodes we already visited
if (decl.__visited) {
return;
}
// make sure we have image-set
if (!decl.value || decl.value.indexOf('image-set') === -1) {
return;
}
const commaSeparatedValues = postcss.list.comma(decl.value);
const mediaQueryList = {};
const parsedValues = commaSeparatedValues.map(value => {
const result = {};
if (value.indexOf('image-set') === -1) {
result.default = value;
return result;
}
const parsedValue = parseValue(value, decl);
const images = parsedValue.images;
const suffix = parsedValue.suffix;
result.default = images.url[0] + suffix;
// for each image add a media query
if (images.url.length > 1) {
for (let i = 0, len = images.url.length; i < len; i++) {
const size = images.size[i].dpi;
if (size !== DPI_RATIO.x) {
if (!mediaQueryList[size]) {
mediaQueryList[size] = images.size[i].pxRatio;
}
result[size] = images.url[i] + suffix;
} else {
result.default = images.url[i] + suffix;
}
}
}
return result;
});
// add the default image to the decl
decl.value = parsedValues.map(val => val.default).join(',');
// check for the media queries
const media = decl.parent.parent.params;
const parsedMedia = media && mediaParser(media);
Object.keys(mediaQueryList)
.sort()
.forEach(size => {
const minResQuery = `(min-resolution: ${size}dpi)`;
const minDPRQuery = `(-webkit-min-device-pixel-ratio: ${mediaQueryList[size]})`;
const paramStr = parsedMedia ?
parsedMedia.nodes
.map(queryNode => `${queryNode.value} and ${minDPRQuery}, ${queryNode.value} and ${minResQuery}`)
.join(',') :
`${minDPRQuery}, ${minResQuery}`;
const atrule = postcss.atRule({
name: 'media',
params: paramStr
});
// clone empty parent with only relevant decls
const parent = decl.parent.clone({
nodes: []
});
const d = decl.clone({
value: parsedValues.map(val => val[size] || val.default).join(',')
});
// mark nodes as visited by us
d.__visited = true;
parent.append(d);
atrule.append(parent);
decl.root().append(atrule);
});
});
}
);