Skip to content

Commit

Permalink
Merge pull request #73 from eleme/feature/enhance_wechat_to_web_support
Browse files Browse the repository at this point in the history
转 Web 功能增强及完善,优化对各类边界情况的兼容与编译支持及社区组件库支持
  • Loading branch information
BboyZaki committed Jul 17, 2023
2 parents aedf820 + d9b82d8 commit 84103ef
Show file tree
Hide file tree
Showing 70 changed files with 727 additions and 314 deletions.
9 changes: 7 additions & 2 deletions packages/core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,13 @@ export function createApp<T extends IData, G extends IData>(
* 模拟全局 App 构造函数, 用于不存在 App 构造函数的环境, 如 小程序插件
*/
globalApp?: (options: IData) => any
/** 暴露给 App 调用的方法,可调用传入的 $hooks 执行所需逻辑,如 调用 $hooks.pause 暂定所有生命周期函数的执行 */
onHooksCreated?: ($hooks: Record<string, any>) => any
/**
* 暴露给 App 调用的方法,可调用传入的 $hooks 执行所需逻辑,如
* - 调用 $hooks.pause 暂定所有生命周期函数的执行,并将调用堆栈保存
* - 调用 $hooks.resume 恢复并之前之前被暂停的 hooks
* - 手动触发某些 hook
*/
onHooksCreated?: ($hooks: MorHooks) => any
}
): any {
logger.time('createApp-init')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,31 @@ const TAB_BAR_RULES_TO_ALIPAY = {
}
} as TransformRules

// 支付宝分包配置兼容转换
const transformAlipaySubpackages = function (
subpackages: { root: string; pages: string[] }[]
) {
if (!subpackages?.length) return subpackages
return subpackages.map(function (sub) {
if (!sub.root) return sub
return {
...sub,
// 移除 root 最后的 /
// 原因 支付宝不支持
root: sub.root.replace(/\/$/, '')
}
})
}

const APP_RULES_TO_ALIPAY = {
subPackages: 'subPackages',
subPackages: {
to: 'subPackages',
fn: transformAlipaySubpackages
},
subpackages: {
to: 'subPackages',
fn: transformAlipaySubpackages
},
window: {
to: 'window',
fn(window, config, options): Record<string, any> {
Expand Down
80 changes: 78 additions & 2 deletions packages/plugin-compiler-alipay/src/plugins/SjsParserPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ import {
} from '@morjs/utils'
import { isSimilarTarget } from '../constants'

/**
* 从 module.exports.xxx 中提取 xxx 变量名
*/
export function getModuleExportsName(content: string): string[] {
const result = []
const regex = /module\.exports\.(\w+)/gim
const matchResult = content.match(regex)

if (!matchResult) return result

matchResult.map((param) => {
const name = param.split('.')[2]
result.push(name)
})

return result
}

/**
* 支付宝 sjs 文件转译
*/
Expand All @@ -20,10 +38,17 @@ export default class AlipayCompilerSjsParserPlugin implements Plugin {
runner.hooks.beforeRun.tap(this.name, () => {
const { sourceType, target } = runner.userConfig

// 仅当 sjs 是 支付宝 源码 且 编译目标不是 支付宝小程序 时执行该插件
const isAlipaySimilarTarget = isSimilarTarget(target)

// 微信 DSL 转 支付宝或者类似小程序平台时的兼容逻辑
if (sourceType === SourceTypes.wechat && isAlipaySimilarTarget) {
return this.transformCommonjsToESModule(runner)
}

// 仅当 sjs 是 支付宝 源码 且 编译目标不是 支付宝小程序 时执行后续逻辑
if (sourceType !== SourceTypes.alipay) return
if (sourceType === target) return
if (isSimilarTarget(target)) return
if (isAlipaySimilarTarget) return

runner.hooks.beforeBuildEntries.tap(this.name, (entryBuilder) => {
this.entryBuilder = entryBuilder
Expand All @@ -44,6 +69,57 @@ export default class AlipayCompilerSjsParserPlugin implements Plugin {
})
}

/**
* 微信转支付宝的场景下 ,wxs 内联文件中使用 module.exports.property 在支付宝中无法正常通过 name.var 调用,
* 需要将这种场景下的导出进行拓展,增加 export default { property }
* 这样在 webpack 打包时就可以正确导出 export default 文件
* NOTE: 该功能需要配合 cjsToEsmTransformer 使用,并放在 after 阶段
*/
transformCommonjsToESModule(runner: Runner) {
runner.hooks.sjsParser.tap(
this.name,
(transformers: ts.CustomTransformers, options) => {
if (
options.fileInfo.content.includes('module.exports.') &&
!/module.exports( |\t|\n)*=/.test(options.fileInfo.content)
) {
// 提取 module.exports.var 中的变量名
const names = getModuleExportsName(options.fileInfo.content)
if (names.length <= 0 || names.indexOf('default') > -1)
return transformers

transformers.after.push(
tsTransformerFactory((node, ctx) => {
const factory = ctx.factory

if (ts.isSourceFile(node)) {
return factory.updateSourceFile(node, [
...node.statements,
factory.createExportAssignment(
void 0,
void 0,
void 0,
factory.createObjectLiteralExpression(
names.map((name) =>
factory.createShorthandPropertyAssignment(
factory.createIdentifier(name)
)
)
)
)
])
}

return node
})
)
}

return transformers
}
)
}

transformESModuleToCommonjs(transformers: ts.CustomTransformers) {
transformers.before.push(
tsTransformerFactory((node, ctx) => {
Expand Down
24 changes: 21 additions & 3 deletions packages/plugin-compiler-alipay/src/templateProcessorToAlipay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ export const templateProcessorToAlipay = {
// 支付宝不支持 大写的标签名, 需要全部转换为小写
if (node.tag) node.tag = node.tag.toLowerCase()

/**
* 如果 sjs name 存在 this,会被转换成 thisSjs,此时如果 node content 中存在 this 调用,
* 将 this 调用转换成 thisSjs 调用
*/
if (
context.sharedContext &&
context.sharedContext.hasSjsModuleAttrNameAsThis
) {
const { content } = node
if (node.content) {
node.content = content.map((c) => {
if (typeof c === 'string') return c.replace(/this\./g, 'thisSjs.')
return c
})
}
}

// 处理双向绑定支持
processTwoWayBinding(node, context)
},
Expand Down Expand Up @@ -138,20 +155,21 @@ export const templateProcessorToAlipay = {
): void {
// 支付宝不支持将 sjs 模块的名称命名为 this
// 这里需要将其转换为 thisSjs
if (!context.sharedContext) context.sharedContext = {}
if (
node.tag === sjsTagName &&
attrName === sjsModuleAttrName &&
node.attrs[attrName] === 'this'
) {
node.attrs[attrName] = 'thisSjs'
// 标记当前页面模版中存在 sjs 模块名称为 this
context.hasSjsModuleAttrNameAsThis = true
context.sharedContext.hasSjsModuleAttrNameAsThis = true
}

// 如果当前页面有 this 作为 sjs 模块名称
//
// 则替换为 thisSjs
if (
context.hasSjsModuleAttrNameAsThis &&
context.sharedContext.hasSjsModuleAttrNameAsThis &&
node.tag !== sjsTagName &&
typeof node.attrs[attrName] === 'string' &&
node.attrs[attrName].includes('this.')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IXMLElement } from '../IXmlNode'
import { attributeNode, dataBindingNode, ElementAtrrbute } from '../types'

const AttributesParseer = [
const AttributesParser = [
require('./unsupport/index').default,
require('./event/index').default,
require('./class/index').default,
Expand All @@ -10,13 +10,16 @@ const AttributesParseer = [
require('./slot/index').default
]

export default function (xmlElement: IXMLElement): ElementAtrrbute[] {
function parserAttribute(
xmlElement: IXMLElement,
parser = AttributesParser
): ElementAtrrbute[] {
const attributes: ElementAtrrbute[] = []
if (xmlElement.attributes) {
for (const key of Object.keys(xmlElement.attributes)) {
const value = xmlElement.attributes[key]
let att: ElementAtrrbute
for (const func of AttributesParseer) {
for (const func of parser) {
att = func(key, value)
if (att) {
break
Expand All @@ -32,3 +35,18 @@ export default function (xmlElement: IXMLElement): ElementAtrrbute[] {
}
return attributes
}

export default function (xmlElement: IXMLElement): ElementAtrrbute[] {
return parserAttribute(xmlElement, AttributesParser)
}

const SlotAttributesParser = [
require('./unsupport/index').default,
require('./event/index').default,
require('./ref/index').default,
require('./slot/index').default
]

export function parseSlotAttribute(xmlElement: IXMLElement): ElementAtrrbute[] {
return parserAttribute(xmlElement, SlotAttributesParser)
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default class DataBinding implements DataBindingNode {
res = this.parseExp(code)
// 再次提取变量
if (!res.isOK) {
logger.error(`绑定表达式: ${code.trim()} 不符合JS表达式规范`)
logger.error(`绑定表达式: ${code.trim()} 不符合 JS 表达式规范`)
}
}
if (res.code) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import parseAttribute from '../../attribute/index'
import { parseSlotAttribute } from '../../attribute/index'
import { attributeValueAndRemove, IXMLElement } from '../../IXmlNode'
import { dataBindingNode, slotNode, SlotNode } from '../../types'
import { parseSubElements } from '../index'
Expand All @@ -10,7 +10,7 @@ export default function (xmlElement: IXMLElement): SlotNode {
node.name = dataBindingNode(
attributeValueAndRemove(xmlElement.attributes, 'name')
)
node.attributes = parseAttribute(xmlElement)
node.attributes = parseSlotAttribute(xmlElement)
}
node.children = parseSubElements(xmlElement)
return node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { DataBindingNode } from '../ast/types'

export function databindingForAttribute(databinding: DataBindingNode) {
if (databinding.hasBinding) {
return t.jsxExpressionContainer(databinding.getExpressionAst())
const ast = databinding.getExpressionAst()
if (ast) return t.jsxExpressionContainer(databinding.getExpressionAst())
return null
} else {
if (databinding.express === undefined) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ export default function (document: Document, options: AXMLOptions) {
const result = generate.default(ast)
if (options.isAtomicMode && !options.unitTest) {
return babel.transformSync(result.code, {
configFile: false,
babelrc: false,
presets: [require(resolveDependency('@babel/preset-react'))]
}).code
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export function getOptionalChainCode(code) {
})

const source = babel.transformSync(result, {
configFile: false,
babelrc: false,
plugins: [
{
visitor: {
Expand Down
9 changes: 8 additions & 1 deletion packages/plugin-compiler-web/src/compiler/core/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,26 @@ export default function (content, options: BuildOptions) {

function transformFromCode(code: string, plugins, options: BuildOptions) {
let ast = babel.parse(code, {
configFile: false,
babelrc: false
// plugins: [require(resolveDependency('@babel/plugin-transform-react-jsx')), require(resolveDependency('@babel/plugin-proposal-class-properties'))]
})
const newCode = skipConditionalCompilation(code, ast, options)
if (newCode.length !== code.length) {
code = newCode
// 重新生成ast
ast = babel.parse(code)
ast = babel.parse(code, {
configFile: false,
babelrc: false
})
}

const babelConfig = {
compact: false,
generatorOpts: { comments: false },
ast: true,
configFile: false,
babelrc: false,
plugins
}

Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-compiler-web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EmitIntermediateAssetsPlugin } from './plugins/emitIntermediateAssetsPl
import { ExtractOrInjectCssPlugin } from './plugins/extractOrInjectCssPlugin'
import { GenerateJSXEntryPlugin } from './plugins/generateJSXEntryPlugin'
import { HtmlSupportPlugin } from './plugins/htmlSupportPlugin'
import { ScriptCompatiblePlugin } from './plugins/scriptCompatiblePlugin'
import { SjsCompatiblePlugin } from './plugins/sjsCompatiblePlugin'
import { TemplateCompatiblePlugin } from './plugins/templateCompatiblePlugin'

Expand Down Expand Up @@ -92,6 +93,7 @@ class WebCompilerPlugin implements MorPlugin {
new SjsCompatiblePlugin().apply(runner)
new TemplateCompatiblePlugin(entryBuilder).apply(runner)
new ConfigCompatiblePlugin(entryBuilder).apply(runner)
new ScriptCompatiblePlugin().apply(runner)

// bundle 模式适配
if (compileMode === 'bundle') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,18 @@ export class CommonConfigPlugin implements Plugin {
const BABEL_LOADER = {
loader: resolveDependency('babel-loader'),
options: {
configFile: false,
babelrc: false,
compact: false,
sourceType: 'unambiguous',
// 配置 targets 以保证低版本浏览器的兼容性
presets: [
[require(resolveDependency('@babel/preset-env')), babelEnvOptions],
require(resolveDependency('@babel/preset-react'))
[
require(resolveDependency('@babel/preset-react')),
// 允许一些错误的 namespace 存在
{ throwIfNamespace: false }
]
],
plugins: [
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export class EmitIntermediateAssetsPlugin implements Plugin {
command
) {
if (command.options?.emitWebIntermediateAssets) {
if (userConfig.target === target) {
const currentTarget = command.options?.target || userConfig.target
if (currentTarget === target) {
userConfig.web = userConfig.web || {}
userConfig.web.emitIntermediateAssets =
command.options.emitWebIntermediateAssets
Expand Down
Loading

0 comments on commit 84103ef

Please sign in to comment.