generate-component.js 11.6 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 = []
65
  const componentChunkNameMap = {}
fxy060608's avatar
fxy060608 已提交
66 67 68 69 70
  const components = getComponentSet()
  if (components.size) {
    const assets = compilation.assets
    const modules = compilation.modules

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

    Object.keys(assets).forEach(name => {
78 79 80 81 82
      // 判断是不是vue
      const isVueComponent = components.has(name.replace('.js', ''))
      // 独立分包外面的组件,复制到独立分包内,在components中看不到,所以需要单独处理
      const isVueOuterComponent = Boolean(name.endsWith('.js') && name.indexOf(vueOuterComponentSting) >= 0)
      if (isVueComponent || isVueOuterComponent) {
83
        curComponents.push(name.replace('.js', ''))
fxy060608's avatar
fxy060608 已提交
84 85 86 87 88

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

fxy060608's avatar
fxy060608 已提交
89 90 91 92 93
        const chunkName = name.replace('.js', '-create-component')

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

Q
qiang 已提交
97
          if (modulePath.includes('@dcloudio') && isBuiltInComponentPath(modulePath)) {
fxy060608's avatar
fxy060608 已提交
98 99 100
            resource = normalizePath(path.resolve(process.env.UNI_CLI_CONTEXT, modulePath))
          }

101
          moduleId = findComponentModuleId(modules, concatenatedModules, resource, altResource)
fxy060608's avatar
fxy060608 已提交
102 103
        } else {
          const resource = removeExt(path.resolve(process.env.UNI_INPUT_DIR, name))
104
          moduleId = findComponentModuleId(modules, concatenatedModules, resource)
fxy060608's avatar
fxy060608 已提交
105 106 107
        }

        const origSource = assets[name].source()
S
songyu 已提交
108

109 110 111
        if (isVueComponent) {
          componentChunkNameMap[name] = moduleId
        } else if (isVueOuterComponent) {
S
songyu 已提交
112
          const startIndex = name.indexOf(vueOuterComponentSting) + vueOuterComponentSting.length + 1
113 114 115 116
          const rightOriginalComponentName = name.substring(startIndex)
          moduleId = componentChunkNameMap[rightOriginalComponentName]
        }

fxy060608's avatar
fxy060608 已提交
117
        if (origSource.length !== EMPTY_COMPONENT_LEN) { // 不是空组件
fxy060608's avatar
fxy060608 已提交
118 119 120 121 122 123 124
          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 +
125
            `
fxy060608's avatar
fxy060608 已提交
126
;(${globalVar}["${jsonpFunction}"] = ${globalVar}["${jsonpFunction}"] || []).push([
fxy060608's avatar
fxy060608 已提交
127 128 129
    '${chunkName}',
    {
        '${chunkName}':(function(module, exports, __webpack_require__){
fxy060608's avatar
fxy060608 已提交
130
            __webpack_require__('${uniModuleId}')['createComponent'](__webpack_require__(${JSON.stringify(moduleId)}))
fxy060608's avatar
fxy060608 已提交
131 132 133
        })
    },
    [['${chunkName}']]
134
]);
fxy060608's avatar
fxy060608 已提交
135
`
fxy060608's avatar
fxy060608 已提交
136 137
          const newSource = function () {
            return source
fxy060608's avatar
fxy060608 已提交
138
          }
fxy060608's avatar
fxy060608 已提交
139 140
          newSource.__$wrappered = true
          assets[name].source = newSource
fxy060608's avatar
fxy060608 已提交
141 142
        }
      }
143 144
      const styleExtname = getPlatformExts().style
      if (name.endsWith(styleExtname)) {
145
        // 移除部分含有错误引用的 wxss 文件
146 147
        let origSource = assets[name].source()
        origSource = origSource.trim ? origSource.trim() : ''
148 149
        const result = origSource.match(/^@import ["'](.+?)["']$/)
        if (result) {
150
          const stylePath = normalizePath(path.join(path.dirname(name), result[1]))
151 152 153
          if (Object.keys(assets).includes(stylePath)) {
            styleImports[stylePath] = styleImports[stylePath] || []
            styleImports[stylePath].push(name)
154
          } else {
155 156 157
            if (styleImports[name]) {
              styleImports[name].forEach(name => delete assets[name])
              delete styleImports[name]
158 159 160 161 162
            }
            delete assets[name]
          }
        }
      }
P
panyiming.325 已提交
163
      // 处理字节跳动|飞书小程序作用域插槽
164 165 166 167
      const fixExtname = '.fix'
      if (name.endsWith(fixExtname)) {
        const source = assets[name].source()
        const [ownerName, parentName, componentName, slotName] = source.split(',')
168
        const json = assets[ownerName + '.json']
169
        const jsonSource = json && json.source()
170 171
        if (jsonSource) {
          const data = JSON.parse(jsonSource)
172
          const usingComponents = data.usingComponents || {}
173
          const componentPath = normalizePath(path.relative('/', usingComponents[parentName]))
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 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
          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 已提交
215 216
    })
  }
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
  // 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 () {
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
            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',
271
          source: 'Component({onInit(){this.props.onPluginWrap(this)},didUnmount(){this.props.onPluginWrap(this,true)}})'
272 273 274 275 276 277 278 279 280 281 282 283
        },
        {
          ext: 'json',
          source: '{"component":true}'
        }
      ]
      files.forEach(({ ext, source }) => {
        compilation.assets[`${componentName}.${ext}`] = {
          size () {
            return Buffer.byteLength(source, 'utf8')
          },
          source () {
284 285 286 287 288 289
            return source
          }
        }
      })
    }
  }
fxy060608's avatar
fxy060608 已提交
290
  if (process.env.UNI_FEATURE_OBSOLETE !== 'false') {
291 292 293
    if (lastComponents.length) {
      for (const name of lastComponents) {
        if (!curComponents.includes(name)) {
fxy060608's avatar
fxy060608 已提交
294
          removeUnusedComponent(name) // 组件被移除
295 296
        }
      }
fxy060608's avatar
fxy060608 已提交
297 298 299 300 301
    }
    for (const name of curComponents) {
      if (!lastComponents.includes(name)) {
        addComponent(name) // 新增组件
      }
302 303 304 305 306
    }
    lastComponents = curComponents
  }
}

fxy060608's avatar
fxy060608 已提交
307 308 309 310 311
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'))
312
    } catch (e) { }
fxy060608's avatar
fxy060608 已提交
313 314 315
  }
}

316 317
function removeUnusedComponent (name) {
  try {
318 319
    fs.renameSync(path.join(process.env.UNI_OUTPUT_DIR, name + '.json'), path.join(process.env.UNI_OUTPUT_DIR, name +
      '.bak.json'))
320 321
  } catch (e) { }
}