converter.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /**
  2. * @fileoverview 将微信端的代码转换到各个平台
  3. */
  4. const through2 = require('through2')
  5. // api 前缀
  6. const prefix = {
  7. weixin: {
  8. wxml: 'wx:',
  9. js: 'wx.'
  10. },
  11. qq: {
  12. wxml: 'qq:',
  13. js: 'qq.'
  14. },
  15. baidu: {
  16. wxml: 's-',
  17. js: 'swan.'
  18. },
  19. alipay: {
  20. wxml: 'a:',
  21. js: 'my.'
  22. },
  23. toutiao: {
  24. wxml: 'tt:',
  25. js: 'tt.'
  26. }
  27. }
  28. // 文件名后缀
  29. const suffix = {
  30. weixin: {
  31. wxml: '.wxml',
  32. wxss: '.wxss'
  33. },
  34. qq: {
  35. wxml: '.qml',
  36. wxss: '.qss'
  37. },
  38. baidu: {
  39. wxml: '.swan',
  40. wxss: '.css'
  41. },
  42. alipay: {
  43. wxml: '.axml',
  44. wxss: '.acss'
  45. },
  46. toutiao: {
  47. wxml: '.ttml',
  48. wxss: '.ttss'
  49. }
  50. }
  51. /**
  52. * @description 取出两个括号之间的内容
  53. * @param {string} content 总内容
  54. * @param {number} i 开始位置
  55. */
  56. function getSection (content, i) {
  57. let j = i + 1
  58. let num = 1
  59. const start = content[i]
  60. const end = start === '(' ? ')' : '}'
  61. while (num) {
  62. if (content[j] === start) {
  63. num++
  64. } else if (content[j] === end) {
  65. num--
  66. }
  67. j++
  68. }
  69. return content.substring(i, j)
  70. }
  71. /**
  72. * @description 处理不同小程序平台间的差异
  73. * @param {string} platform 使用平台
  74. */
  75. module.exports = function (platform) {
  76. if (platform !== 'uni-app') {
  77. platform = platform.split('-')[1]
  78. }
  79. return through2.obj(function (file, _, callback) {
  80. if (file.isBuffer()) {
  81. let content = file.contents.toString()
  82. if (platform === 'uni-app') {
  83. if (file.extname === '.js') {
  84. content = content.replace(/\.properties/g, '')
  85. }
  86. } else {
  87. // wxml 文件处理
  88. if (file.extname === '.wxml') {
  89. content = content.replace(/wx:/g, prefix[platform].wxml) // 替换 api 前缀
  90. file.extname = suffix[platform].wxml // 修改后缀名
  91. if (platform === 'qq') {
  92. // wxs 转为 qs
  93. content = content.replace(/<wxs/g, '<qs').replace(/<\/wxs/g, '</qs')
  94. } else if (platform === 'baidu') {
  95. content = content.replace(/s-if=['"]{{(\S+)}}['"]/g, 's-if="$1"') // s-if 和 s-for 后不加 {{}}
  96. .replace(/s-for=['"]{{(\S+)}}['"]/g, 's-for="$1"')
  97. .replace(/data="(.*?)"/g, 'data="{$1}"')
  98. } else if (platform === 'alipay') {
  99. content = content.replace('block-size', 'handle-size')
  100. .replace(/longpress/g, 'longTap')
  101. .replace(/bind([\S])/g, (_, $1) => { // bindevent 转为 onEvent
  102. return 'on' + $1.toUpperCase()
  103. }).replace(/catch([\S])/g, (_, $1) => { // catchevent 转为 catchEvent
  104. return 'catch' + $1.toUpperCase()
  105. })
  106. }
  107. } else if (file.extname === '.js') {
  108. // js 文件处理
  109. // 替换 api 前缀
  110. content = content.replace(/wx\./g, prefix[platform].js)
  111. // 支付宝格式转换
  112. if (platform === 'alipay') {
  113. // 将 aa.triggerEvent('bb', cc) 替换为 aa.props.onBb && aa.props.onBb(cc)
  114. content = content.replace(/([a-zA-Z0-9._]+).triggerEvent\(['"](\S+?)['"],*/g, function (_, $1, $2) {
  115. const method = `${$1}.props.on${$2[0].toUpperCase()}${$2.slice(1)}`
  116. return `${method}&&${method}(`
  117. })
  118. // 转换 showToast
  119. let i = content.indexOf('.showToast')
  120. while (i !== -1) {
  121. i += 10
  122. const section = getSection(content, i)
  123. content = content.substr(0, i) + section.replace('title', 'content') + content.substr(i + section.length)
  124. i = content.indexOf('.showToast', i)
  125. }
  126. // 转换 showActionSheet
  127. i = content.indexOf('.showActionSheet')
  128. while (i !== -1) {
  129. i += 16
  130. const section = getSection(content, i)
  131. content = content.substr(0, i) + section.replace('itemList', 'items') + content.substr(i + section.length)
  132. i = content.indexOf('.showActionSheet', i)
  133. }
  134. // 转换 setClipboardData
  135. i = content.indexOf('.setClipboardData')
  136. while (i !== -1) {
  137. i += 17
  138. const section = getSection(content, i)
  139. content = content.substr(0, i - 4) + section.replace('data', 'text') + content.substr(i + section.length)
  140. i = content.indexOf('.setClipboardData', i)
  141. }
  142. // 组件格式转换
  143. if (content.includes('Component({')) {
  144. // 替换生命周期
  145. content = content.replace('created:', 'didMount:')
  146. .replace('attached:', 'didMount:')
  147. .replace('detached:', 'didUnmount:')
  148. // 将 properties 字段转为 props 格式
  149. i = content.indexOf('{', content.indexOf('properties:'))
  150. let props
  151. let propsStr = '{'
  152. const objStr = getSection(content, i)
  153. // 取出整个 properties 字段
  154. eval('props = ' + objStr) // eslint-disable-line
  155. for (const item in props) {
  156. if (!props[item]) continue
  157. propsStr += item + ':'
  158. if (props[item].value) {
  159. // 设置了默认值
  160. if (typeof props[item].value === 'boolean') {
  161. propsStr += props[item].value ? '!0' : '!1'
  162. } else {
  163. propsStr += props[item].value
  164. }
  165. } else {
  166. // 没有设置默认值
  167. const type = props[item].type || props[item]
  168. if (type === String) {
  169. propsStr += '""'
  170. } else if (type === Boolean) {
  171. propsStr += '!1'
  172. } else if (type === Number) {
  173. propsStr += '0'
  174. } else if (type === Object) {
  175. propsStr += '{}'
  176. } else if (type === Array) {
  177. propsStr += '[]'
  178. }
  179. }
  180. propsStr += ','
  181. }
  182. content = content.substr(0, i) + propsStr.substring(0, propsStr.length - 1) + '}' + content.substr(i + objStr.length)
  183. }
  184. content = content.replace(/properties/g, 'props')
  185. .replace(/\.setNavigationBarTitle/g, '.setNavigationBar')
  186. } else {
  187. content = content.replace(/\.properties/g, '.data')
  188. }
  189. } else if (file.extname === '.wxss') {
  190. // wxss 文件处理
  191. file.extname = suffix[platform].wxss // 修改后缀名
  192. }
  193. }
  194. file.contents = Buffer.from(content)
  195. }
  196. this.push(file)
  197. callback()
  198. })
  199. }