Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 修复 webpack 5 开启 cache 后跳过 js parser 阶段导致的一系列问题 #14117

Merged
merged 6 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 94 additions & 1 deletion packages/taro-webpack5-runner/src/plugins/TaroNormalModule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,72 @@
import { META_TYPE } from '@tarojs/helper'
import { isEmpty } from 'lodash'
import webpack from 'webpack'

export default class TaroNormalModule extends webpack.NormalModule {
import { componentConfig, componentNameSet, elementNameSet } from '../utils/component'

export class TaroBaseNormalModule extends webpack.NormalModule {
elementNameSet: Set<string>

componentNameSet: Set<string>

collectProps: { [name: string]: string }

constructor (data) {
super(data)

this.collectProps = {}
this.elementNameSet = new Set()
this.componentNameSet = new Set()
}

clear () {
this.collectProps = {}
this.elementNameSet.clear()
this.componentNameSet.clear()
}

serialize (context) {
const { write } = context

write(this.collectProps)
write(this.elementNameSet)
write(this.componentNameSet)

super.serialize(context)
}

deserialize (context) {
const { read } = context

this.collectProps = read()
this.elementNameSet = read()
this.componentNameSet = read()

if (!isEmpty(this.collectProps)) {
for (const key in this.collectProps) {
const attrs = componentConfig.thirdPartyComponents.get(key)
const value = this.collectProps[key]

if (!attrs) continue

value.split('|').forEach(item => {
attrs.add(item)
})
}
}

for (const elementName of this.elementNameSet) {
elementNameSet.add(elementName)
}
for (const componentName of this.componentNameSet) {
componentNameSet.add(componentName)
}

return super.deserialize(context)
}
}

export default class TaroNormalModule extends TaroBaseNormalModule {
name: string
miniType: META_TYPE
constructor (data) {
Expand Down Expand Up @@ -52,3 +117,31 @@ webpack.util.serialization.register(TaroNormalModule, '@tarojs/webpack5-runner/d
return obj
}
})

webpack.util.serialization.register(TaroBaseNormalModule, '@tarojs/webpack5-runner/dist/plugins/TaroNormalModule', 'TaroBaseNormalModule', {
serialize (obj, context) {
obj.serialize(context)
},
deserialize (context) {
const obj = new TaroBaseNormalModule({
// will be deserialized by Module
layer: null,
type: '',
// will be filled by updateCacheModule
resource: '',
context: '',
request: null,
userRequest: null,
rawRequest: null,
loaders: null,
matchResource: null,
parser: null,
parserOptions: null,
generator: null,
generatorOptions: null,
resolveOptions: null
})
obj.deserialize(context)
return obj
}
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import TaroSingleEntryDependency from '../dependencies/TaroSingleEntryDependency'
import { componentConfig } from '../utils/component'
import TaroNormalModule from './TaroNormalModule'
import { componentConfig, componentNameSet, elementNameSet } from '../utils/component'
import TaroNormalModule, { TaroBaseNormalModule } from './TaroNormalModule'

import type { Func } from '@tarojs/taro/types/compile'
import type { Compiler } from 'webpack'
Expand All @@ -23,30 +23,55 @@ function isRenderNode (node, ancestors): boolean {
}

export default class TaroNormalModulesPlugin {
isCache = true

onParseCreateElement: Func | undefined

constructor (onParseCreateElement: Func | undefined) {
this.onParseCreateElement = onParseCreateElement
}

apply (compiler: Compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (_, { normalModuleFactory }) => {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => {
// cache 开启后,会跳过 JavaScript parser 环节,因此需要收集组件信息,在 finishModules 阶段处理
compilation.hooks.finishModules.tap(PLUGIN_NAME, (_) => {
if (!this.isCache) return

for (const name of elementNameSet) {
this.onParseCreateElement?.(name, componentConfig)
}

for (const name of componentNameSet) {
if (name === 'CustomWrapper' && !componentConfig.thirdPartyComponents.get('custom-wrapper')) {
componentConfig.thirdPartyComponents.set('custom-wrapper', new Set())

return
}
}
})

normalModuleFactory.hooks.createModule.tapPromise(PLUGIN_NAME, (data, { dependencies }) => {
const dependency = dependencies[0]
if (dependency instanceof TaroSingleEntryDependency) {
return Promise.resolve(new TaroNormalModule(Object.assign(data,
{ miniType: dependency.miniType, name: dependency.name }
)))
}
return Promise.resolve()
return Promise.resolve(new TaroBaseNormalModule(data))
})

// react 的第三方组件支持
normalModuleFactory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, (parser) => {
parser.hooks.program.tap(PLUGIN_NAME, (ast) => {
this.isCache = false

const currentModule = parser.state.current as TaroBaseNormalModule
currentModule.clear()

walk.ancestor(ast, {
CallExpression: (node, ancestors) => {
const callee = node.callee

if (callee.type === 'MemberExpression') {
if (callee.property.name !== 'createElement') {
return
Expand All @@ -62,9 +87,8 @@ export default class TaroNormalModulesPlugin {
!(nameOfCallee && nameOfCallee.includes('createElementVNode')) &&
!(nameOfCallee && nameOfCallee.includes('createElementBlock')) &&
!(nameOfCallee && nameOfCallee.includes('resolveComponent')) && // 收集使用解析函数的组件名称
// 兼容 Vue 2.0 渲染函数及 JSX
!isRenderNode(node, ancestors)
// TODO: 兼容 vue 2.0 渲染函数及 JSX,函数名 h 与 _c 在压缩后太常见,需要做更多限制后才能兼容
// nameOfCallee !== 'h' && nameOfCallee !== '_c'
) {
return
}
Expand All @@ -73,11 +97,18 @@ export default class TaroNormalModulesPlugin {
const [type, prop] = node.arguments
const componentName = type.name

type.value && this.onParseCreateElement?.(type.value, componentConfig)
if (type.value) {
this.onParseCreateElement?.(type.value, componentConfig)
currentModule.elementNameSet.add(type.value)
}

if (componentName === 'CustomWrapper' && !componentConfig.thirdPartyComponents.get('custom-wrapper')) {
componentConfig.thirdPartyComponents.set('custom-wrapper', new Set())
if (componentName) {
currentModule.componentNameSet.add(componentName)
if (componentName === 'CustomWrapper' && !componentConfig.thirdPartyComponents.get('custom-wrapper')) {
componentConfig.thirdPartyComponents.set('custom-wrapper', new Set())
}
}

if (componentConfig.thirdPartyComponents.size === 0) {
return
}
Expand All @@ -87,9 +118,13 @@ export default class TaroNormalModulesPlugin {
return
}

prop.properties
const props = prop.properties
.filter(p => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name !== 'children' && p.key.name !== 'id')
.forEach(p => attrs.add(p.key.name))
const res = props.map(p => p.key.name).join('|')

props.forEach(p => attrs.add(p.key.name))

currentModule.collectProps[type.value] = res
},
})
})
Expand Down
4 changes: 4 additions & 0 deletions packages/taro-webpack5-runner/src/utils/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export const componentConfig: IComponentConfig = {
thirdPartyComponents: new Map(),
includeAll: false
}

// 用户 cache 功能开启时,记录 parser 过程中的组件信息
export const elementNameSet = new Set()
export const componentNameSet = new Set()