generate-component.js 10.8 KB
Newer Older
1
const fs = require('fs')
fxy060608's avatar
fxy060608 已提交
2 3 4
const path = require('path')
const {
  removeExt,
5 6
  normalizePath,
  getPlatformExts
fxy060608's avatar
fxy060608 已提交
7 8
} = require('@dcloudio/uni-cli-shared')
const {
9
  getComponentSet
fxy060608's avatar
fxy060608 已提交
10 11
} = require('@dcloudio/uni-cli-shared/lib/cache')

fxy060608's avatar
fxy060608 已提交
12 13 14 15
const {
  isBuiltInComponentPath
} = require('@dcloudio/uni-cli-shared/lib/pages')

fxy060608's avatar
fxy060608 已提交
16 17 18 19
const {
  restoreNodeModules
} = require('../shared')

fxy060608's avatar
fxy060608 已提交
20 21
const EMPTY_COMPONENT_LEN = 'Component({})'.length

fxy060608's avatar
fxy060608 已提交
22
const uniPath = normalizePath(require('@dcloudio/uni-cli-shared/lib/platform').getMPRuntimePath())
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

function findModule (modules, resource, altResource) {
  return modules.find(
    module => {
      let moduleResource = module.resource
      if (
        !moduleResource ||
        (
          moduleResource.indexOf('.vue') === -1 &&
          moduleResource.indexOf('.nvue') === -1
        )
      ) {
        return
      }
      moduleResource = removeExt(module.resource)
      return moduleResource === resource || moduleResource === altResource
    }
  )
}

function findModuleId (modules, resource, altResource) {
  const module = findModule(modules, resource, altResource)
  return module && module.id
}

function findModuleIdFromConcatenatedModules (modules, resource, altResource) {
  const module = modules.find(module => {
    return findModule(module.modules, resource, altResource)
  })
  return module && module.id
}

function findComponentModuleId (modules, concatenatedModules, resource, altResource) {
  return findModuleId(modules, resource, altResource) ||
    findModuleIdFromConcatenatedModules(concatenatedModules, resource, altResource) ||
    resource
}

61
let lastComponents = []
fxy060608's avatar
fxy060608 已提交
62
// TODO 解决方案不太理想
fxy060608's avatar
fxy060608 已提交
63
module.exports = function generateComponent (compilation, jsonpFunction = 'webpackJsonp') {
64
  const curComponents = []
fxy060608's avatar
fxy060608 已提交
65 66 67 68 69
  const components = getComponentSet()
  if (components.size) {
    const assets = compilation.assets
    const modules = compilation.modules

70
    const concatenatedModules = modules.filter(module => module.modules)
fxy060608's avatar
fxy060608 已提交
71
    const uniModuleId = modules.find(module => module.resource && normalizePath(module.resource) === uniPath).id
72
    const styleImports = {}
73
    const fixSlots = {}
fxy060608's avatar
fxy060608 已提交
74 75 76

    Object.keys(assets).forEach(name => {
      if (components.has(name.replace('.js', ''))) {
77
        curComponents.push(name.replace('.js', ''))
fxy060608's avatar
fxy060608 已提交
78 79 80 81 82

        if (assets[name].source.__$wrappered) {
          return
        }

fxy060608's avatar
fxy060608 已提交
83 84 85 86 87
        const chunkName = name.replace('.js', '-create-component')

        let moduleId = ''
        if (name.indexOf('node-modules') === 0) {
          const modulePath = removeExt(restoreNodeModules(name))
fxy060608's avatar
fxy060608 已提交
88
          let resource = normalizePath(path.resolve(process.env.UNI_INPUT_DIR, '..', modulePath))
fxy060608's avatar
fxy060608 已提交
89
          const altResource = normalizePath(path.resolve(process.env.UNI_INPUT_DIR, modulePath))
fxy060608's avatar
fxy060608 已提交
90

Q
qiang 已提交
91
          if (modulePath.includes('@dcloudio') && isBuiltInComponentPath(modulePath)) {
fxy060608's avatar
fxy060608 已提交
92 93 94
            resource = normalizePath(path.resolve(process.env.UNI_CLI_CONTEXT, modulePath))
          }

95
          moduleId = findComponentModuleId(modules, concatenatedModules, resource, altResource)
fxy060608's avatar
fxy060608 已提交
96 97
        } else {
          const resource = removeExt(path.resolve(process.env.UNI_INPUT_DIR, name))
98
          moduleId = findComponentModuleId(modules, concatenatedModules, resource)
fxy060608's avatar
fxy060608 已提交
99 100 101
        }

        const origSource = assets[name].source()
fxy060608's avatar
fxy060608 已提交
102
        if (origSource.length !== EMPTY_COMPONENT_LEN) { // 不是空组件
fxy060608's avatar
fxy060608 已提交
103 104 105 106 107 108 109
          const globalVar = process.env.UNI_PLATFORM === 'mp-alipay' ? 'my' : 'global'
          // 主要是为了解决支付宝旧版本, Component 方法只在组件 js 里有,需要挂在 my.defineComponent
          let beforeCode = ''
          if (process.env.UNI_PLATFORM === 'mp-alipay') {
            beforeCode = ';my.defineComponent || (my.defineComponent = Component);'
          }
          const source = beforeCode + origSource +
110
            `
fxy060608's avatar
fxy060608 已提交
111
;(${globalVar}["${jsonpFunction}"] = ${globalVar}["${jsonpFunction}"] || []).push([
fxy060608's avatar
fxy060608 已提交
112 113 114
    '${chunkName}',
    {
        '${chunkName}':(function(module, exports, __webpack_require__){
fxy060608's avatar
fxy060608 已提交
115
            __webpack_require__('${uniModuleId}')['createComponent'](__webpack_require__(${JSON.stringify(moduleId)}))
fxy060608's avatar
fxy060608 已提交
116 117 118
        })
    },
    [['${chunkName}']]
119
]);
fxy060608's avatar
fxy060608 已提交
120
`
fxy060608's avatar
fxy060608 已提交
121 122
          const newSource = function () {
            return source
fxy060608's avatar
fxy060608 已提交
123
          }
fxy060608's avatar
fxy060608 已提交
124 125
          newSource.__$wrappered = true
          assets[name].source = newSource
fxy060608's avatar
fxy060608 已提交
126 127
        }
      }
128 129
      const styleExtname = getPlatformExts().style
      if (name.endsWith(styleExtname)) {
130
        // 移除部分含有错误引用的 wxss 文件
131 132
        let origSource = assets[name].source()
        origSource = origSource.trim ? origSource.trim() : ''
133 134
        const result = origSource.match(/^@import ["'](.+?)["']$/)
        if (result) {
135
          const stylePath = normalizePath(path.join(path.dirname(name), result[1]))
136 137 138
          if (Object.keys(assets).includes(stylePath)) {
            styleImports[stylePath] = styleImports[stylePath] || []
            styleImports[stylePath].push(name)
139
          } else {
140 141 142
            if (styleImports[name]) {
              styleImports[name].forEach(name => delete assets[name])
              delete styleImports[name]
143 144 145 146 147
            }
            delete assets[name]
          }
        }
      }
P
panyiming.325 已提交
148
      // 处理字节跳动|飞书小程序作用域插槽
149 150 151 152
      const fixExtname = '.fix'
      if (name.endsWith(fixExtname)) {
        const source = assets[name].source()
        const [ownerName, parentName, componentName, slotName] = source.split(',')
153
        const json = assets[ownerName + '.json']
154
        const jsonSource = json && json.source()
155 156
        if (jsonSource) {
          const data = JSON.parse(jsonSource)
157
          const usingComponents = data.usingComponents || {}
158
          const componentPath = normalizePath(path.relative('/', usingComponents[parentName]))
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
          const slots = fixSlots[componentPath] = fixSlots[componentPath] || {}
          const slot = slots[slotName] = slots[slotName] || {}
          slot[componentName] = '/' + name.replace(fixExtname, '')
          delete assets[name]

          const jsonFile = assets[`${componentPath}.json`]
          if (jsonFile) {
            const oldSource = jsonFile.__$oldSource || jsonFile.source()
            const sourceObj = JSON.parse(oldSource)
            Object.values(slots).forEach(components => {
              const usingComponents = sourceObj.usingComponents = sourceObj.usingComponents || {}
              Object.assign(usingComponents, components)
            })
            delete sourceObj.componentGenerics
            const source = JSON.stringify(sourceObj, null, 2)
            jsonFile.source = function () {
              return source
            }
            jsonFile.__$oldSource = oldSource
          }

          const templateFile = assets[`${componentPath}${getPlatformExts().template}`]
          if (templateFile) {
            const oldSource = templateFile.__$oldSource || templateFile.source()
            let templateSource
            Object.keys(slots).forEach(name => {
              const reg = new RegExp(`<${name} (.+?)></${name}>`)
              templateSource = oldSource.replace(reg, string => {
                const props = string.match(reg)[1]
                return Object.keys(slots[name]).map(key => {
                  return `<block tt:if="{{generic['${name.replace(/^scoped-slots-/, '')}']==='${key}'}}"><${key} ${props}></${key}></block>`
                }).join('')
              })
            })
            templateFile.source = function () {
              return templateSource
            }
            templateFile.__$oldSource = oldSource
          }
        }
      }
fxy060608's avatar
fxy060608 已提交
200 201
    })
  }
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
  // fix mp-qq https://github.com/dcloudio/uni-app/issues/2648
  const appJsonFile = compilation.assets['app.json']
  if (process.env.UNI_PLATFORM === 'mp-qq' && appJsonFile) {
    const obj = JSON.parse(appJsonFile.source())
    if (obj && obj.usingComponents && !Object.keys(obj.usingComponents).length) {
      const componentName = 'fix-2648'
      obj.usingComponents[componentName] = `/${componentName}`
      const source = JSON.stringify(obj, null, 2)
      appJsonFile.source = function () {
        return source
      }
      const files = [
        {
          ext: 'qml',
          source: '<!-- https://github.com/dcloudio/uni-app/issues/2648 -->'
        },
        {
          ext: 'js',
          source: 'Component({})'
        },
        {
          ext: 'json',
          source: '{"component":true}'
        }
      ]
      files.forEach(({ ext, source }) => {
        compilation.assets[`${componentName}.${ext}`] = {
          size () {
            return Buffer.byteLength(source, 'utf8')
          },
          source () {
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
            return source
          }
        }
      })
    }
  }
  // fix mp-alipay plugin
  if (process.env.UNI_PLATFORM === 'mp-alipay' && appJsonFile) {
    const obj = JSON.parse(appJsonFile.source())
    if (obj && obj.usingComponents && !Object.keys(obj.usingComponents).length) {
      const componentName = 'plugin-wrapper'
      obj.usingComponents[componentName] = `/${componentName}`
      const source = JSON.stringify(obj, null, 2)
      appJsonFile.source = function () {
        return source
      }
      const files = [
        {
          ext: 'axml',
          source: '<slot></slot>'
        },
        {
          ext: 'js',
          source: 'Component({onInit(){this.props.onPluginWrap(this)},didUnmount(){this.props.onPluginWrap(this,false)}})'
        },
        {
          ext: 'json',
          source: '{"component":true}'
        }
      ]
      files.forEach(({ ext, source }) => {
        compilation.assets[`${componentName}.${ext}`] = {
          size () {
            return Buffer.byteLength(source, 'utf8')
          },
          source () {
269 270 271 272 273 274
            return source
          }
        }
      })
    }
  }
fxy060608's avatar
fxy060608 已提交
275
  if (process.env.UNI_FEATURE_OBSOLETE !== 'false') {
276 277 278
    if (lastComponents.length) {
      for (const name of lastComponents) {
        if (!curComponents.includes(name)) {
fxy060608's avatar
fxy060608 已提交
279
          removeUnusedComponent(name) // 组件被移除
280 281
        }
      }
fxy060608's avatar
fxy060608 已提交
282 283 284 285 286
    }
    for (const name of curComponents) {
      if (!lastComponents.includes(name)) {
        addComponent(name) // 新增组件
      }
287 288 289 290 291
    }
    lastComponents = curComponents
  }
}

fxy060608's avatar
fxy060608 已提交
292 293 294 295 296
function addComponent (name) {
  const bakJson = path.join(process.env.UNI_OUTPUT_DIR, name + '.bak.json')
  if (fs.existsSync(bakJson)) {
    try {
      fs.renameSync(bakJson, path.join(process.env.UNI_OUTPUT_DIR, name + '.json'))
297
    } catch (e) { }
fxy060608's avatar
fxy060608 已提交
298 299 300
  }
}

301 302
function removeUnusedComponent (name) {
  try {
fxy060608's avatar
fxy060608 已提交
303 304
    fs.renameSync(path.join(process.env.UNI_OUTPUT_DIR, name + '.json'), path.join(process.env.UNI_OUTPUT_DIR, name +
      '.bak.json'))
305 306
  } catch (e) { }
}