UNPKG

7.7 kBJavaScriptView Raw
1'use strict'
2
3var postcss = require('postcss')
4var objectAssign = require('object-assign')
5var pxRegex = require('./lib/pixel-unit-regex')
6var filterPropList = require('./lib/filter-prop-list')
7
8var defaults = {
9 rootValue: 16,
10 unitPrecision: 5,
11 selectorBlackList: [],
12 propList: ['*'],
13 replace: true,
14 mediaQuery: false,
15 minPixelValue: 0
16}
17
18var legacyOptions = {
19 'root_value': 'rootValue',
20 'unit_precision': 'unitPrecision',
21 'selector_black_list': 'selectorBlackList',
22 'prop_white_list': 'propList',
23 'media_query': 'mediaQuery',
24 'propWhiteList': 'propList'
25}
26
27const deviceRatio = {
28 '640': 2.34 / 2,
29 '750': 1,
30 '828': 1.81 / 2
31}
32
33const baseFontSize = 40
34
35const DEFAULT_WEAPP_OPTIONS = {
36 platform: 'weapp',
37 designWidth: 750,
38 deviceRatio
39}
40
41var targetUnit
42
43module.exports = postcss.plugin('postcss-pxtransform', function (options) {
44 options = Object.assign(DEFAULT_WEAPP_OPTIONS, options || {})
45
46 switch (options.platform) {
47 case 'h5': {
48 options.rootValue = baseFontSize * options.designWidth / 640
49 targetUnit = 'rem'
50 break
51 }
52 case 'rn': {
53 options.rootValue = options.deviceRatio[options.designWidth] * 2
54 targetUnit = 'px'
55 break
56 }
57 case 'quickapp': {
58 options.rootValue = 1
59 targetUnit = 'px'
60 break
61 }
62 default: {
63 // mini-program
64 options.rootValue = options.deviceRatio[options.designWidth]
65 targetUnit = 'rpx'
66 }
67 }
68
69 convertLegacyOptions(options)
70
71 var opts = objectAssign({}, defaults, options)
72 var onePxTransform = typeof options.onePxTransform === 'undefined' ? true : options.onePxTransform
73 var pxReplace = createPxReplace(opts.rootValue, opts.unitPrecision,
74 opts.minPixelValue, onePxTransform)
75
76 var satisfyPropList = createPropListMatcher(opts.propList)
77
78 return function (css) {
79 for (var i = 0; i < css.nodes.length; i++) {
80 if (css.nodes[i].type === 'comment') {
81 if (css.nodes[i].text === 'postcss-pxtransform disable') {
82 return
83 } else {
84 break
85 }
86 }
87 }
88
89 // delete code between comment in RN
90 if (options.platform === 'rn') {
91 css.walkComments(comment => {
92 if (comment.text === 'postcss-pxtransform rn eject enable') {
93 let next = comment.next()
94 while (next) {
95 if (next.type === 'comment' && next.text === 'postcss-pxtransform rn eject disable') {
96 break
97 }
98 const temp = next.next()
99 next.remove()
100 next = temp
101 }
102 }
103 })
104 }
105
106 /* #ifdef %PLATFORM% */
107 // 平台特有样式
108 /* #endif */
109 css.walkComments(comment => {
110 const wordList = comment.text.split(' ')
111 // 指定平台保留
112 if (wordList.indexOf('#ifdef') > -1) {
113 // 非指定平台
114 if (wordList.indexOf(options.platform) === -1) {
115 let next = comment.next()
116 while (next) {
117 if (next.type === 'comment' && next.text.trim() === '#endif') {
118 break
119 }
120 const temp = next.next()
121 next.remove()
122 next = temp
123 }
124 }
125 }
126 })
127
128 /* #ifndef %PLATFORM% */
129 // 平台特有样式
130 /* #endif */
131 css.walkComments(comment => {
132 const wordList = comment.text.split(' ')
133 // 指定平台剔除
134 if (wordList.indexOf('#ifndef') > -1) {
135 // 指定平台
136 if (wordList.indexOf(options.platform) > -1) {
137 let next = comment.next()
138 while (next) {
139 if (next.type === 'comment' && next.text.trim() === '#endif') {
140 break
141 }
142 const temp = next.next()
143 next.remove()
144 next = temp
145 }
146 }
147 }
148 })
149
150 css.walkDecls(function (decl, i) {
151 // This should be the fastest test and will remove most declarations
152 if (decl.value.indexOf('px') === -1) return
153
154 if (!satisfyPropList(decl.prop)) return
155
156 if (blacklistedSelector(opts.selectorBlackList,
157 decl.parent.selector)) return
158
159 var value = decl.value.replace(pxRegex, pxReplace)
160
161 // if rem unit already exists, do not add or replace
162 if (declarationExists(decl.parent, decl.prop, value)) return
163
164 if (opts.replace) {
165 decl.value = value
166 } else {
167 decl.parent.insertAfter(i, decl.clone({value: value}))
168 }
169 })
170
171 if (opts.mediaQuery) {
172 css.walkAtRules('media', function (rule) {
173 if (rule.params.indexOf('px') === -1) return
174 rule.params = rule.params.replace(pxRegex, pxReplace)
175 })
176 }
177 }
178})
179
180function convertLegacyOptions (options) {
181 if (typeof options !== 'object') return
182 if (
183 (
184 (typeof options['prop_white_list'] !== 'undefined' &&
185 options['prop_white_list'].length === 0) ||
186 (typeof options.propWhiteList !== 'undefined' &&
187 options.propWhiteList.length === 0)
188 ) &&
189 typeof options.propList === 'undefined'
190 ) {
191 options.propList = ['*']
192 delete options['prop_white_list']
193 delete options.propWhiteList
194 }
195 Object.keys(legacyOptions).forEach(function (key) {
196 if (options.hasOwnProperty(key)) {
197 options[legacyOptions[key]] = options[key]
198 delete options[key]
199 }
200 })
201}
202
203function createPxReplace (rootValue, unitPrecision, minPixelValue, onePxTransform) {
204 return function (m, $1) {
205 if (!$1) return m
206 if (!onePxTransform && parseInt($1, 10) === 1) {
207 return m
208 }
209 var pixels = parseFloat($1)
210 if (pixels < minPixelValue) return m
211 var fixedVal = toFixed((pixels / rootValue), unitPrecision)
212 return (fixedVal === 0) ? '0' : fixedVal + targetUnit
213 }
214}
215
216function toFixed (number, precision) {
217 var multiplier = Math.pow(10, precision + 1)
218 var wholeNumber = Math.floor(number * multiplier)
219 return Math.round(wholeNumber / 10) * 10 / multiplier
220}
221
222function declarationExists (decls, prop, value) {
223 return decls.some(function (decl) {
224 return (decl.prop === prop && decl.value === value)
225 })
226}
227
228function blacklistedSelector (blacklist, selector) {
229 if (typeof selector !== 'string') return
230 return blacklist.some(function (regex) {
231 if (typeof regex === 'string') return selector.indexOf(regex) !== -1
232 return selector.match(regex)
233 })
234}
235
236function createPropListMatcher (propList) {
237 var hasWild = propList.indexOf('*') > -1
238 var matchAll = (hasWild && propList.length === 1)
239 var lists = {
240 exact: filterPropList.exact(propList),
241 contain: filterPropList.contain(propList),
242 startWith: filterPropList.startWith(propList),
243 endWith: filterPropList.endWith(propList),
244 notExact: filterPropList.notExact(propList),
245 notContain: filterPropList.notContain(propList),
246 notStartWith: filterPropList.notStartWith(propList),
247 notEndWith: filterPropList.notEndWith(propList)
248 }
249 return function (prop) {
250 if (matchAll) return true
251 return (
252 (
253 hasWild ||
254 lists.exact.indexOf(prop) > -1 ||
255 lists.contain.some(function (m) {
256 return prop.indexOf(m) > -1
257 }) ||
258 lists.startWith.some(function (m) {
259 return prop.indexOf(m) === 0
260 }) ||
261 lists.endWith.some(function (m) {
262 return prop.indexOf(m) === prop.length - m.length
263 })
264 ) &&
265 !(
266 lists.notExact.indexOf(prop) > -1 ||
267 lists.notContain.some(function (m) {
268 return prop.indexOf(m) > -1
269 }) ||
270 lists.notStartWith.some(function (m) {
271 return prop.indexOf(m) === 0
272 }) ||
273 lists.notEndWith.some(function (m) {
274 return prop.indexOf(m) === prop.length - m.length
275 })
276 )
277 )
278 }
279}