@mpxjs/webpack-plugin 2.8.22 → 2.8.23-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/lib/config.js +14 -0
  3. package/lib/dependencies/AddEntryDependency.js +24 -0
  4. package/lib/dependencies/ResolveDependency.js +4 -0
  5. package/lib/index.js +35 -5
  6. package/lib/loader.js +40 -0
  7. package/lib/platform/template/wx/component-config/button.js +14 -2
  8. package/lib/platform/template/wx/component-config/image.js +4 -0
  9. package/lib/platform/template/wx/component-config/input.js +4 -0
  10. package/lib/platform/template/wx/component-config/rich-text.js +4 -0
  11. package/lib/platform/template/wx/component-config/scroll-view.js +4 -0
  12. package/lib/platform/template/wx/component-config/switch.js +4 -0
  13. package/lib/platform/template/wx/component-config/text.js +4 -0
  14. package/lib/platform/template/wx/component-config/textarea.js +5 -0
  15. package/lib/platform/template/wx/component-config/view.js +4 -0
  16. package/lib/platform/template/wx/index.js +121 -1
  17. package/lib/resolve-loader.js +4 -1
  18. package/lib/runtime/components/tenon/getInnerListeners.js +314 -0
  19. package/lib/runtime/components/tenon/tenon-button.vue +305 -0
  20. package/lib/runtime/components/tenon/tenon-image.vue +61 -0
  21. package/lib/runtime/components/tenon/tenon-input.vue +104 -0
  22. package/lib/runtime/components/tenon/tenon-rich-text.vue +21 -0
  23. package/lib/runtime/components/tenon/tenon-scroll-view.vue +124 -0
  24. package/lib/runtime/components/tenon/tenon-switch.vue +91 -0
  25. package/lib/runtime/components/tenon/tenon-text-area.vue +77 -0
  26. package/lib/runtime/components/tenon/tenon-text.vue +64 -0
  27. package/lib/runtime/components/tenon/tenon-view.vue +93 -0
  28. package/lib/runtime/optionProcessor.tenon.js +388 -0
  29. package/lib/style-compiler/index.js +1 -1
  30. package/lib/style-compiler/plugins/hm.js +20 -0
  31. package/lib/template-compiler/compiler.js +11 -2
  32. package/lib/tenon/index.js +104 -0
  33. package/lib/tenon/processJSON.js +356 -0
  34. package/lib/tenon/processScript.js +263 -0
  35. package/lib/tenon/processStyles.js +21 -0
  36. package/lib/tenon/processTemplate.js +133 -0
  37. package/lib/utils/get-relative-path.js +25 -0
  38. package/package.json +5 -2
package/README.md CHANGED
@@ -13,6 +13,6 @@ module.exports = {
13
13
  new mpxWebpackPlugin({
14
14
  mode: 'wx'
15
15
  })
16
- ],
16
+ ]
17
17
  }
18
18
  ```
package/lib/config.js CHANGED
@@ -356,6 +356,20 @@ module.exports = {
356
356
  templatePrefix: 'module.exports = \n'
357
357
  }
358
358
  },
359
+ tenon: {
360
+ directive: {
361
+ if: 'v-if',
362
+ elseif: 'v-else-if',
363
+ else: 'v-else'
364
+ },
365
+ wxs: {
366
+ tag: 'wxs',
367
+ module: 'module',
368
+ src: 'src',
369
+ ext: '.wxs',
370
+ templatePrefix: 'module.exports = \n'
371
+ }
372
+ },
359
373
  qa: {
360
374
  typeExtMap: {
361
375
  json: '.json',
@@ -0,0 +1,24 @@
1
+ const NullDependency = require('webpack/lib/dependencies/NullDependency')
2
+
3
+ class AddEntryDependency extends NullDependency {
4
+ constructor ({ context, dep, name }) {
5
+ super()
6
+ this.__addEntryParams = [context, dep, name]
7
+ }
8
+
9
+ get type () {
10
+ return 'mpx add entry'
11
+ }
12
+
13
+ // updateHash (hash) {
14
+ // super.updateHash(hash)
15
+ // hash.update(this.childCompileEntryModule.identifier())
16
+ // }
17
+ }
18
+
19
+ AddEntryDependency.Template = class AddEntryDependencyTemplate {
20
+ apply () {
21
+ }
22
+ }
23
+
24
+ module.exports = AddEntryDependency
@@ -75,6 +75,10 @@ ResolveDependency.Template = class ResolveDependencyTemplate {
75
75
  getContent (dep) {
76
76
  const { resolved = '', compilation } = dep
77
77
  const publicPath = compilation.outputOptions.publicPath || ''
78
+ // for tenon
79
+ if (dep.compilation.__mpx__.mode === 'tenon') {
80
+ return `getRelativePath(currentURL, ${JSON.stringify(resolved)}) + '.js'`
81
+ }
78
82
  return JSON.stringify(publicPath + resolved)
79
83
  }
80
84
  }
package/lib/index.js CHANGED
@@ -317,7 +317,7 @@ class MpxWebpackPlugin {
317
317
  let splitChunksPlugin
318
318
  let splitChunksOptions
319
319
 
320
- if (this.options.mode !== 'web') {
320
+ if (this.options.mode !== 'web' && this.options.mode !== 'tenon') {
321
321
  const optimization = compiler.options.optimization
322
322
  optimization.runtimeChunk = {
323
323
  name: (entrypoint) => {
@@ -1073,7 +1073,27 @@ class MpxWebpackPlugin {
1073
1073
  }
1074
1074
  })
1075
1075
 
1076
- // 处理跨平台转换
1076
+ // processing for tenon-store
1077
+ if (mpx.mode === 'tenon') {
1078
+ let TENON_STORE_ID = 0
1079
+ parser.hooks.call.for('imported var').tap('MpxWebpackPlugin', (expr) => {
1080
+ if (['createStore', 'createStoreWithThis'].includes(expr.callee.name)) {
1081
+ const current = parser.state.current
1082
+ const storeOptions = expr.arguments.length && expr.arguments[0]
1083
+ if (storeOptions) {
1084
+ current.addDependency(new InjectDependency({
1085
+ content: 'Object.assign(',
1086
+ index: storeOptions.range[0]
1087
+ }))
1088
+ current.addDependency(new InjectDependency({
1089
+ content: `, { __store_id: ${TENON_STORE_ID++} })`,
1090
+ index: storeOptions.range[1]
1091
+ }))
1092
+ }
1093
+ }
1094
+ })
1095
+ }
1096
+
1077
1097
  if (mpx.srcMode !== mpx.mode) {
1078
1098
  // 处理跨平台全局对象转换
1079
1099
  const transGlobalObject = (expr) => {
@@ -1197,7 +1217,7 @@ class MpxWebpackPlugin {
1197
1217
  name: 'MpxWebpackPlugin',
1198
1218
  stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONS
1199
1219
  }, () => {
1200
- if (mpx.mode === 'web') return
1220
+ if (mpx.mode === 'web' || mpx.mode === 'tenon') return
1201
1221
 
1202
1222
  if (this.options.generateBuildMap) {
1203
1223
  const pagesMap = compilation.__mpx__.pagesMap
@@ -1435,21 +1455,31 @@ try {
1435
1455
  if (mpx.mode === 'web') {
1436
1456
  const mpxStyleOptions = queryObj.mpxStyleOptions
1437
1457
  const firstLoader = loaders[0] ? toPosix(loaders[0].loader) : ''
1438
- const isPitcherRequest = firstLoader.includes('vue-loader/lib/loaders/pitcher')
1458
+ const isPitcherRequest = firstLoader.includes('vue-loader/lib/loaders/pitcher') || firstLoader.includes('@hummer/tenon-loader/dist/pitcher.js')
1439
1459
  let cssLoaderIndex = -1
1440
1460
  let vueStyleLoaderIndex = -1
1441
1461
  let mpxStyleLoaderIndex = -1
1462
+ let tenonStyleLoaderIndex = -1
1442
1463
  loaders.forEach((loader, index) => {
1443
1464
  const currentLoader = toPosix(loader.loader)
1444
1465
  if (currentLoader.includes('css-loader') && cssLoaderIndex === -1) {
1445
1466
  cssLoaderIndex = index
1446
1467
  } else if (currentLoader.includes('vue-loader/lib/loaders/stylePostLoader') && vueStyleLoaderIndex === -1) {
1447
1468
  vueStyleLoaderIndex = index
1469
+ } else if (currentLoader.includes('@hummer/tenon-style-loader/dist/index.js') && tenonStyleLoaderIndex === -1) {
1470
+ tenonStyleLoaderIndex = index
1448
1471
  } else if (currentLoader.includes(styleCompilerPath) && mpxStyleLoaderIndex === -1) {
1449
1472
  mpxStyleLoaderIndex = index
1450
1473
  }
1451
1474
  })
1452
- if (mpxStyleLoaderIndex === -1) {
1475
+ if (mpx.mode === 'tenon' && mpxStyleLoaderIndex === -1) {
1476
+ if (tenonStyleLoaderIndex > -1 && !isPitcherRequest) {
1477
+ loaders.splice(tenonStyleLoaderIndex + 1, 0, {
1478
+ loader: normalize.lib('style-compiler/index.js'),
1479
+ options: (mpxStyleOptions && JSON.parse(mpxStyleOptions)) || {}
1480
+ })
1481
+ }
1482
+ } else if (mpxStyleLoaderIndex === -1) {
1453
1483
  let loaderIndex = -1
1454
1484
  if (cssLoaderIndex > -1 && vueStyleLoaderIndex === -1) {
1455
1485
  loaderIndex = cssLoaderIndex
package/lib/loader.js CHANGED
@@ -11,6 +11,7 @@ const processJSON = require('./web/processJSON')
11
11
  const processScript = require('./web/processScript')
12
12
  const processStyles = require('./web/processStyles')
13
13
  const processTemplate = require('./web/processTemplate')
14
+ const processForTenon = require('./tenon/index')
14
15
  const getJSONContent = require('./utils/get-json-content')
15
16
  const normalize = require('./utils/normalize')
16
17
  const getEntryName = require('./utils/get-entry-name')
@@ -127,6 +128,45 @@ module.exports = function (content) {
127
128
  return callback(e)
128
129
  }
129
130
  }
131
+
132
+ if (mode === 'tenon') {
133
+ if (ctorType === 'app' && !queryObj.app) {
134
+ const request = addQuery(this.resource, { app: true })
135
+ output += `
136
+ import App from ${stringifyRequest(request)}
137
+ import * as Tenon from '@hummer/tenon-vue'
138
+
139
+ Tenon.render(App)\n`
140
+ // 直接结束loader进入parse
141
+ this.loaderIndex = -1
142
+ return callback(null, output)
143
+ }
144
+ if (ctorType === 'page' && queryObj.tenon) {
145
+ console.log(resourcePath)
146
+ const request = addQuery(resourcePath, { page: true })
147
+ output += `
148
+ import page from ${stringifyRequest(request)}
149
+ import * as Tenon from '@hummer/tenon-vue'
150
+
151
+ Tenon.render(page)\n`
152
+ this.loaderIndex = -1
153
+ return callback(null, output)
154
+ }
155
+ return processForTenon({
156
+ mpx,
157
+ loaderContext,
158
+ isProduction,
159
+ queryObj,
160
+ filePath,
161
+ parts,
162
+ ctorType,
163
+ autoScope,
164
+ componentsMap,
165
+ moduleId,
166
+ callback
167
+ })
168
+ }
169
+
130
170
  // 处理mode为web时输出vue格式文件
131
171
  if (mode === 'web') {
132
172
  if (ctorType === 'app' && !queryObj.isApp) {
@@ -28,6 +28,8 @@ module.exports = function ({ print }) {
28
28
  const ttEventLog = print({ platform: 'bytedance', tag: TAG_NAME, isError: false, type: 'event' })
29
29
  const webPropLog = print({ platform: 'web', tag: TAG_NAME, isError: false })
30
30
  const webEventLog = print({ platform: 'web', tag: TAG_NAME, isError: false, type: 'event' })
31
+ const tenonPropLog = print({ platform: 'tenon', tag: TAG_NAME, isError: false })
32
+ const tenonEventLog = print({ platform: 'tenon', tag: TAG_NAME, isError: false, type: 'event' })
31
33
  const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false })
32
34
  const wxPropValueLog = print({ platform: 'wx', tag: TAG_NAME, isError: false, type: 'value' })
33
35
 
@@ -37,6 +39,10 @@ module.exports = function ({ print }) {
37
39
  el.isBuiltIn = true
38
40
  return 'mpx-button'
39
41
  },
42
+ tenon (tag, { el }) {
43
+ el.isBuiltIn = true
44
+ return 'tenon-button'
45
+ },
40
46
  props: [
41
47
  {
42
48
  test: 'open-type',
@@ -131,13 +137,18 @@ module.exports = function ({ print }) {
131
137
  },
132
138
  {
133
139
  test: /^(open-type|lang|session-from|send-message-title|send-message-path|send-message-img|show-message-card|app-parameter)$/,
134
- web: webPropLog
140
+ web: webPropLog,
141
+ tenon: tenonPropLog
135
142
  },
136
143
  {
137
144
  test: /^(size|type|plain|loading|form-type|hover-class|hover-stop-propagation|hover-start-time|hover-stay-time|use-built-in)$/,
138
145
  web (prop, { el }) {
139
146
  // todo 这部分能力基于内部封装实现
140
147
  el.isBuiltIn = true
148
+ },
149
+ tenon (prop, { el }) {
150
+ // todo 这部分能力基于内部封装实现
151
+ el.isBuiltIn = true
141
152
  }
142
153
  },
143
154
  {
@@ -174,7 +185,8 @@ module.exports = function ({ print }) {
174
185
  },
175
186
  {
176
187
  test: /^(getuserinfo|contact|error|launchapp|opensetting|getphonenumber)$/,
177
- web: webEventLog
188
+ web: webEventLog,
189
+ tenon: tenonEventLog
178
190
  }
179
191
  ]
180
192
  }
@@ -13,6 +13,10 @@ module.exports = function ({ print }) {
13
13
  el.isBuiltIn = true
14
14
  return 'mpx-image'
15
15
  },
16
+ tenon (tag, { el }) {
17
+ el.isBuiltIn = true
18
+ return 'tenon-image'
19
+ },
16
20
  props: [
17
21
  {
18
22
  test: /^show-menu-by-longpress$/,
@@ -20,6 +20,10 @@ module.exports = function ({ print }) {
20
20
  el.isBuiltIn = true
21
21
  return 'mpx-input'
22
22
  },
23
+ tenon (tag, { el }) {
24
+ el.isBuiltIn = true
25
+ return 'tenon-input'
26
+ },
23
27
  props: [
24
28
  {
25
29
  test: /^(cursor-spacing|auto-focus|adjust-position|hold-keyboard)$/,
@@ -12,6 +12,10 @@ module.exports = function ({ print }) {
12
12
  el.isBuiltIn = true
13
13
  return 'mpx-rich-text'
14
14
  },
15
+ tenon (tag, { el }) {
16
+ el.isBuiltIn = true
17
+ return 'tenon-rich-text'
18
+ },
15
19
  props: [
16
20
  {
17
21
  test: /^(space)$/,
@@ -19,6 +19,10 @@ module.exports = function ({ print }) {
19
19
  el.isBuiltIn = true
20
20
  return 'mpx-scroll-view'
21
21
  },
22
+ tenon (tag, { el }) {
23
+ el.isBuiltIn = true
24
+ return 'tenon-scroll-view'
25
+ },
22
26
  props: [
23
27
  {
24
28
  test: /^(enable-flex|scroll-anchorin|refresher-enabled|refresher-threshold|refresher-default-style|refresher-background|refresher-triggered|enhanced|bounces|show-scrollbar|paging-enabled|fast-deceleratio)$/,
@@ -10,6 +10,10 @@ module.exports = function ({ print }) {
10
10
  el.isBuiltIn = true
11
11
  return 'mpx-switch'
12
12
  },
13
+ tenon (tag, { el }) {
14
+ el.isBuiltIn = true
15
+ return 'tenon-switch'
16
+ },
13
17
  props: [
14
18
  {
15
19
  test: /^type$/,
@@ -19,6 +19,10 @@ module.exports = function ({ print }) {
19
19
  return 'span'
20
20
  }
21
21
  },
22
+ tenon (tag, { el }) {
23
+ el.isBuiltIn = true
24
+ return 'tenon-text'
25
+ },
22
26
  props: [
23
27
  {
24
28
  test: /^(decode|user-select)$/,
@@ -22,6 +22,11 @@ module.exports = function ({ print }) {
22
22
  el.isBuiltIn = true
23
23
  return 'mpx-textarea'
24
24
  },
25
+ tenon (tag, { el }) {
26
+ // form全量使用内建组件
27
+ el.isBuiltIn = true
28
+ return 'tenon-textarea'
29
+ },
25
30
  props: [
26
31
  {
27
32
  test: /^(auto-focus|fixed|cursor-spacing|cursor|show-confirm-bar|selection-start|selection-end|adjust-position|hold-keyboard|disable-default-padding|confirm-type)$/,
@@ -21,6 +21,10 @@ module.exports = function ({ print }) {
21
21
  return 'div'
22
22
  }
23
23
  },
24
+ tenon (tag, { el }) {
25
+ el.isBuiltIn = true
26
+ return 'tenon-view'
27
+ },
24
28
  qa (tag) {
25
29
  return 'div'
26
30
  },
@@ -10,7 +10,7 @@ const normalize = require('../../../utils/normalize')
10
10
 
11
11
  module.exports = function getSpec ({ warn, error }) {
12
12
  const spec = {
13
- supportedModes: ['ali', 'swan', 'qq', 'tt', 'web', 'qa', 'jd', 'dd'],
13
+ supportedModes: ['ali', 'swan', 'qq', 'tt', 'web', 'qa', 'jd', 'dd', 'tenon'],
14
14
  // props预处理
15
15
  preProps: [],
16
16
  // props后处理
@@ -24,6 +24,15 @@ module.exports = function getSpec ({ warn, error }) {
24
24
  value: parsed.result
25
25
  }
26
26
  }
27
+ },
28
+ tenon ({ name, value }) {
29
+ const parsed = parseMustache(value)
30
+ if (parsed.hasBinding) {
31
+ return {
32
+ name: name === 'animation' ? 'v-' + name : ':' + name,
33
+ value: parsed.result
34
+ }
35
+ }
27
36
  }
28
37
  }
29
38
  ],
@@ -86,6 +95,16 @@ module.exports = function getSpec ({ warn, error }) {
86
95
  name: 'v-for',
87
96
  value: `(${itemName}, ${indexName}) in ${parsed.result}`
88
97
  }
98
+ },
99
+ tenon ({ value }, { el }) {
100
+ const parsed = parseMustache(value)
101
+ const attrsMap = el.attrsMap
102
+ const itemName = attrsMap['wx:for-item'] || 'item'
103
+ const indexName = attrsMap['wx:for-index'] || 'index'
104
+ return {
105
+ name: 'v-for',
106
+ value: `(${itemName}, ${indexName}) in ${parsed.result}`
107
+ }
89
108
  }
90
109
  },
91
110
  {
@@ -111,6 +130,25 @@ module.exports = function getSpec ({ warn, error }) {
111
130
  name: ':key',
112
131
  value
113
132
  }
133
+ },
134
+ tenon ({ value }, { el }) {
135
+ // vue的template中不能包含key,对应于小程序中的block
136
+ if (el.tag === 'block') return false
137
+ const itemName = el.attrsMap['wx:for-item'] || 'item'
138
+ const keyName = value
139
+ if (value === '*this') {
140
+ value = itemName
141
+ } else {
142
+ if (isValidIdentifierStr(keyName)) {
143
+ value = `${itemName}.${keyName}`
144
+ } else {
145
+ value = `${itemName}['${keyName}']`
146
+ }
147
+ }
148
+ return {
149
+ name: ':key',
150
+ value
151
+ }
114
152
  }
115
153
  },
116
154
  {
@@ -121,6 +159,9 @@ module.exports = function getSpec ({ warn, error }) {
121
159
  },
122
160
  web () {
123
161
  return false
162
+ },
163
+ tenon () {
164
+ return false
124
165
  }
125
166
  },
126
167
  {
@@ -163,6 +204,49 @@ module.exports = function getSpec ({ warn, error }) {
163
204
  }
164
205
  ]
165
206
  }
207
+ },
208
+ tenon ({ value }, { el }) {
209
+ el.hasEvent = true
210
+ const attrsMap = el.attrsMap
211
+ const tagRE = /\{\{((?:.|\n|\r)+?)\}\}(?!})/
212
+ const stringify = JSON.stringify
213
+ const match = tagRE.exec(value)
214
+ if (match) {
215
+ const modelProp = attrsMap['wx:model-prop'] || 'value'
216
+ const modelEvent = attrsMap['wx:model-event'] || 'input'
217
+ const modelValuePathRaw = attrsMap['wx:model-value-path']
218
+ const modelValuePath = modelValuePathRaw === undefined ? 'value' : modelValuePathRaw
219
+ const modelFilter = attrsMap['wx:model-filter']
220
+ let modelValuePathArr
221
+ try {
222
+ modelValuePathArr = JSON5.parse(modelValuePath)
223
+ } catch (e) {
224
+ if (modelValuePath === '') {
225
+ modelValuePathArr = []
226
+ } else {
227
+ modelValuePathArr = modelValuePath.split('.')
228
+ }
229
+ }
230
+ const modelValue = match[1].trim()
231
+ return [
232
+ {
233
+ name: ':' + modelProp,
234
+ value: modelValue
235
+ },
236
+ {
237
+ name: 'mpxModelEvent',
238
+ value: modelEvent
239
+ },
240
+ {
241
+ name: 'mpxModelEventId',
242
+ value: Math.random().toString(36).slice(3, 11)
243
+ },
244
+ {
245
+ name: '@mpxModel',
246
+ value: `__model(${stringifyWithResolveComputed(modelValue)}, $event, ${stringify(modelValuePathArr)}, ${stringify(modelFilter)})`
247
+ }
248
+ ]
249
+ }
166
250
  }
167
251
  },
168
252
  {
@@ -217,6 +301,14 @@ module.exports = function getSpec ({ warn, error }) {
217
301
  name: ':class',
218
302
  value: parsed.result
219
303
  }
304
+ },
305
+ tenon ({ name, value }) {
306
+ const dir = this.test.exec(name)[1]
307
+ const parsed = parseMustache(value)
308
+ return {
309
+ name: ':' + dir,
310
+ value: parsed.result
311
+ }
220
312
  }
221
313
  },
222
314
  // 通用指令
@@ -274,6 +366,17 @@ module.exports = function getSpec ({ warn, error }) {
274
366
  name: 'v-' + dir,
275
367
  value: parsed.result
276
368
  }
369
+ },
370
+ tenon ({ name, value }) {
371
+ let dir = this.test.exec(name)[1]
372
+ const parsed = parseMustache(value)
373
+ if (dir === 'elif') {
374
+ dir = 'else-if'
375
+ }
376
+ return {
377
+ name: 'v-' + dir,
378
+ value: parsed.result
379
+ }
277
380
  }
278
381
  },
279
382
  // 事件
@@ -346,6 +449,23 @@ module.exports = function getSpec ({ warn, error }) {
346
449
  name: rPrefix + rEventName + meta.modifierStr,
347
450
  value
348
451
  }
452
+ },
453
+ tenon ({ name, value }, { eventRules, el }) {
454
+ const match = this.test.exec(name)
455
+ const prefix = match[1]
456
+ const eventName = match[2]
457
+ const modifierStr = match[3] || ''
458
+ const meta = {
459
+ modifierStr
460
+ }
461
+ // 记录event监听信息用于后续判断是否需要使用内置基础组件
462
+ el.hasEvent = true
463
+ const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'web', meta })
464
+ const rEventName = runRules(eventRules, eventName, { mode: 'web' })
465
+ return {
466
+ name: rPrefix + rEventName + meta.modifierStr,
467
+ value
468
+ }
349
469
  }
350
470
  },
351
471
  // 无障碍
@@ -1,3 +1,6 @@
1
1
  module.exports = function () {
2
- return `module.exports = __mpx_resolve_path__(${JSON.stringify(this.resource)})`
2
+ return `
3
+ var currentURL = global.currentPagePath
4
+ var getRelativePath = require('@mpxjs/webpack-plugin/lib/utils/get-relative-path').getRelativePath
5
+ module.exports = __mpx_resolve_path__(${JSON.stringify(this.resource)})`
3
6
  }