UNPKG

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