plugin.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /**
  2. * @fileoverview 处理插件
  3. */
  4. const path = require('path')
  5. const through2 = require('through2')
  6. const config = require('./config')
  7. config.plugins.sort((a, b) => {
  8. // editable 置于最后面
  9. if (a === 'editable') return 1
  10. if (b === 'editable') return -1
  11. // markdown 置于最前面
  12. if (a === 'markdown') return -1
  13. if (b === 'markdown') return 1
  14. // 剩余任意顺序
  15. return 0
  16. })
  17. // 提取和替换标签名选择器(组件中仅支持 class 选择器)
  18. const tagSelector = {}
  19. let tagI = 0
  20. if (config.externStyle) {
  21. config.externStyle = config.externStyle.replace(/[^,\s}]+(?=[^}]*{)/g, $ => {
  22. if (!/[a-zA-Z_]/.test($[0])) return $
  23. if (tagSelector[$]) return '.' + tagSelector[$]
  24. tagSelector[$] = '_' + tagI++
  25. return '.' + tagSelector[$]
  26. })
  27. }
  28. module.exports = {
  29. /**
  30. * @description 构建插件
  31. * @param {string} platform 使用平台
  32. */
  33. build (platform) {
  34. const builds = {} // 构建模块
  35. let pluginImports = '' // 插件引入
  36. let plugins = '' // 插件列表
  37. let voidTags = '' // 增加的自闭合标签
  38. let wxml = '' // 要引入到 node.wxml 中的内容
  39. let js = '' // 要引入到 node.js 中的内容
  40. let wxss = config.externStyle // 要引入到 node.wxss 中的内容
  41. const json = {} // 要引入到 node.json 中的内容
  42. // 收集插件中要写入模板文件的内容
  43. for (let i = 0; i < config.plugins.length; i++) {
  44. const plugin = config.plugins[i]
  45. let build = {}
  46. try {
  47. // 专用 build
  48. if (platform === 'uni-app') {
  49. build = require(`../plugins/${plugin}/uni-app/build.js`)
  50. } else {
  51. build = require(`../plugins/${plugin}/miniprogram/build.js`)
  52. }
  53. } catch (e) { }
  54. try {
  55. // 通用 build
  56. build = Object.assign(require(`../plugins/${plugin}/build.js`), build)
  57. } catch (e) { }
  58. // 可以在当前平台使用
  59. if (!build.platform || build.platform.includes(platform)) {
  60. builds[plugin] = build
  61. if (platform === 'uni-app') {
  62. plugins += plugin.replace(/-([a-z])/g, ($, $1) => $1.toUpperCase()) + ','
  63. pluginImports += `import ${plugin.replace(/-([a-z])/g, ($, $1) => $1.toUpperCase())} from './${plugin}/${build.main ? build.main : 'index.js'}'\n`
  64. } else {
  65. plugins += `require('./${plugin}/${build.main ? build.main : 'index.js'}'),`
  66. }
  67. if (build.template) {
  68. wxml += build.template.replace('wx:if', 'wx:elif').replace('v-if', 'v-else-if')
  69. }
  70. if (build.methods) {
  71. for (const method in build.methods) {
  72. js += build.methods[method].toString() + ','
  73. }
  74. }
  75. if (build.usingComponents) {
  76. Object.assign(json, build.usingComponents)
  77. }
  78. if (build.style) {
  79. wxss += build.style
  80. }
  81. }
  82. }
  83. // 加入其他自定义标签
  84. for (const item of config.customElements) {
  85. if (platform === 'uni-app') {
  86. if (item.platforms) {
  87. wxml += '<!-- #ifdef ' + item.platforms.join(' || ').toUpperCase() + ' -->'
  88. }
  89. voidTags += item.name + ','
  90. wxml += '<' + item.name + ' v-else-if="n.name==\'' + item.name + '\'" :class="n.attrs.class" :style="n.attrs.style"'
  91. if (item.attrs) {
  92. for (const attr of item.attrs) {
  93. wxml += ' :' + attr + '="n.attrs'
  94. if (attr.includes('-')) {
  95. wxml += '[\'' + attr + '\']"'
  96. } else {
  97. wxml += '.' + attr + '"'
  98. }
  99. }
  100. }
  101. wxml += ' />'
  102. if (item.platforms) {
  103. wxml += '<!-- #endif -->'
  104. }
  105. } else if (!item.platforms || item.platforms.join(',').toLowerCase().includes(platform)) {
  106. voidTags += item.name + ','
  107. wxml += '<' + item.name + ' wx:elif="{{n.name==\'' + item.name + '\'}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}"'
  108. if (item.attrs) {
  109. for (const attr of item.attrs) {
  110. wxml += ' ' + attr + '="{{n.attrs'
  111. if (attr.includes('-')) {
  112. wxml += '[\'' + attr + '\']}}"'
  113. } else {
  114. wxml += '.' + attr + '}}"'
  115. }
  116. }
  117. }
  118. wxml += ' />'
  119. }
  120. }
  121. return through2.obj(function (file, _, callback) {
  122. if (file.isBuffer()) {
  123. // src 目录
  124. if (file.base.includes('src')) {
  125. let content = file.contents.toString()
  126. if (file.basename === 'index.js' || file.basename === 'mp-html.vue') {
  127. // 注册插件列表
  128. if (platform === 'uni-app') {
  129. content = content.replace(/const\s*plugins\s*=\s*\[\]/, `${pluginImports}const plugins=[${plugins}]`)
  130. } else {
  131. content = content.replace(/plugins\s*=\s*\[\]/, `plugins=[${plugins}]`)
  132. }
  133. } else if (file.basename === 'parser.js') {
  134. // 设置标签名选择器
  135. content = content.replace(/tagSelector\s*=\s*{}/, `tagSelector=${JSON.stringify(tagSelector)}`)
  136. // 设置自闭合标签
  137. .replace(/voidTags\s*:\s*makeMap\('/, 'voidTags: makeMap(\'' + voidTags)
  138. } else if (file.basename === 'node.wxml') {
  139. // 引入模板
  140. content = content.replace(/<!--\s*insert\s*-->/, wxml)
  141. } else if (file.basename === 'node.js') {
  142. // 引入方法
  143. content = content.replace(/methods\s*:\s*{/, 'methods:{' + js)
  144. } else if (file.basename === 'node.wxss') {
  145. // 引入样式
  146. content = wxss + content
  147. } else if (file.basename === 'node.json') {
  148. // 引入组件声明
  149. const comps = JSON.stringify(json).slice(1, -1)
  150. if (comps) {
  151. content = content.replace(/"usingComponents"\s*:\s*{/, '"usingComponents":{' + comps + ',')
  152. }
  153. } else if (file.basename === 'node.vue') {
  154. // 引入 vue
  155. content = content.replace(/<!--\s*insert\s*-->/, wxml)
  156. .replace(/methods\s*:\s*{/, 'methods:{' + js)
  157. .replace('<style>', '<style>' + wxss.replace(/\.[a-zA-Z_][^)}]*?[{,]/g, '/deep/ $&')).replace(/,url/g, ', url')
  158. let importComp = ''
  159. let comps = ''
  160. for (let item in json) {
  161. const val = json[item]
  162. // 插件无法通过这种方式引入
  163. if (val.includes('plugin://')) continue
  164. item = item.replace(/-([a-z])/g, (_, $1) => $1.toUpperCase())
  165. importComp += 'import ' + item + " from '" + val + "'\n"
  166. comps += item + ',\n'
  167. }
  168. content = content.replace('<script>', '<script>\n' + importComp)
  169. .replace(/components\s*:\s*{/, 'components: {\n' + comps)
  170. } else if (file.basename === 'local.html' && wxss) {
  171. // 引入样式
  172. content = '<style>' + wxss + '</style>' + content
  173. }
  174. file.contents = Buffer.from(content)
  175. for (const item in builds) {
  176. if (builds[item].handler) {
  177. builds[item].handler(file, platform)
  178. }
  179. }
  180. } else {
  181. // plugins 目录
  182. const name = file.relative.split(path.sep)[0]
  183. const build = builds[name]
  184. // 本平台不支持使用
  185. if (!build || file.extname === '.md' || file.basename === 'build.js') {
  186. callback()
  187. return
  188. }
  189. // import
  190. if (build.import) {
  191. if (typeof build.import === 'string') {
  192. if (file.relative.includes(build.import)) {
  193. file.import = true
  194. }
  195. } else {
  196. for (let i = 0; i < build.import.length; i++) {
  197. if (file.relative.includes(build.import[i])) {
  198. file.import = true
  199. break
  200. }
  201. }
  202. }
  203. }
  204. if (build.handler) {
  205. build.handler(file, platform)
  206. }
  207. }
  208. }
  209. this.push(file)
  210. callback()
  211. })
  212. },
  213. /**
  214. * @description 引入样式文件到 node.wxss 中
  215. */
  216. importCss () {
  217. let css = ''
  218. return through2.obj(function (file, _, callback) {
  219. if (file.isBuffer()) {
  220. let content = file.contents.toString()
  221. // 要被引入的文件
  222. if (file.import) {
  223. css += content
  224. callback()
  225. return
  226. }
  227. // 引入到对应位置
  228. if (file.basename === 'node.wxss') {
  229. content = css + content
  230. } else if (file.basename === 'node.vue') {
  231. content = content.replace('<style>', '<style>' + css.replace(/\.[a-z_][^)}]+?[{,]/g, '/deep/ $&')).replace(/,url/g, ', url')
  232. } else if (file.basename === 'local.html' && css) {
  233. content = '<style>' + css + '</style>' + content
  234. }
  235. file.contents = Buffer.from(content)
  236. }
  237. this.push(file)
  238. callback()
  239. })
  240. }
  241. }