diff --git a/packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js b/packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js index 7e2a941bb2..8947ac96bd 100644 --- a/packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js +++ b/packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js @@ -8,10 +8,6 @@ const rawDimensions = { } let width, height -// TODO 临时适配折叠屏场景适配 -// const isLargeFoldableLike = (__mpx_mode__ === 'android') && (height / width < 1.5) && (width > 600) -// if (isLargeFoldableLike) width = width / 2 - function customDimensions (dimensions) { if (typeof Mpx.config.rnConfig?.customDimensions === 'function') { dimensions = Mpx.config.rnConfig.customDimensions(dimensions) || dimensions diff --git a/packages/webpack-plugin/lib/json-compiler/helper.js b/packages/webpack-plugin/lib/json-compiler/helper.js index d8b29d87af..5a123aeb83 100644 --- a/packages/webpack-plugin/lib/json-compiler/helper.js +++ b/packages/webpack-plugin/lib/json-compiler/helper.js @@ -8,8 +8,10 @@ const loaderUtils = require('loader-utils') const resolve = require('../utils/resolve') const { matchCondition } = require('../utils/match-condition') const { isWeb, isReact } = require('../utils/env') +const getBuildInTagComponent = require('../utils/get-build-tag-component') +const { capitalToHyphen } = require('../utils/string') -module.exports = function createJSONHelper ({ loaderContext, emitWarning, customGetDynamicEntry }) { +module.exports = function createJSONHelper ({ loaderContext, emitWarning, customGetDynamicEntry, emitError }) { const mpx = loaderContext.getMpx() const resolveMode = mpx.resolveMode const externals = mpx.externals @@ -166,11 +168,79 @@ module.exports = function createJSONHelper ({ loaderContext, emitWarning, custom }) } + const fillInComponentPlaceholder = ({ jsonObj, name: componentName, placeholder, placeholderEntry, resolveResourcePathMap }, callback) => { + let placeholderComponentName = placeholder.name + const componentPlaceholder = jsonObj.componentPlaceholder || {} + if (componentPlaceholder[componentName]) { + callback() + return + } + jsonObj.componentPlaceholder = componentPlaceholder + if (placeholderEntry) { + if (resolveResourcePathMap.has(placeholderComponentName) && resolveResourcePathMap.get(placeholderComponentName) !== placeholder.resourcePath) { + // 如果存在placeholder与已有usingComponents冲突, 重新生成一个组件名,在当前组件后增加一个数字 + let i = 1 + let newPlaceholder = placeholderComponentName + i + while (jsonObj.usingComponents[newPlaceholder]) { + newPlaceholder = placeholderComponentName + ++i + } + placeholderComponentName = newPlaceholder + } + jsonObj.usingComponents[placeholderComponentName] = placeholderEntry + resolveResourcePathMap.set(placeholderComponentName, placeholder.resourcePath) + } + componentPlaceholder[componentName] = placeholderComponentName + callback(null, { + name: placeholderComponentName, + entry: placeholderEntry + }) + } + + const getNormalizePlaceholder = (placeholder) => { + if (typeof placeholder === 'string') { + placeholder = getBuildInTagComponent(mode, placeholder) || { name: placeholder } + } + if (!placeholder.name) { + emitError('The asyncSubpackageRules configuration format of @mpxjs/webpack-plugin a is incorrect') + } + // ali 下与 rulesRunner 规则一致,组件名驼峰转连字符 + if (mode === 'ali') { + placeholder.name = capitalToHyphen(placeholder.name) + } + return placeholder + } + + const processPlaceholder = ({ jsonObj, context, name, tarRoot, placeholder, relativePath, resolveResourcePathMap }, callback) => { + if (tarRoot) { + if (placeholder) { + placeholder = getNormalizePlaceholder(placeholder) + if (placeholder.resource) { + processComponent(placeholder.resource, context, { relativePath }, (err, entry, { resourcePath }) => { + if (err) return callback(err) + placeholder.resourcePath = resourcePath + fillInComponentPlaceholder({ jsonObj, name, placeholder, placeholderEntry: entry, resolveResourcePathMap }, callback) + }) + } else { + fillInComponentPlaceholder({ jsonObj, name, placeholder }, callback) + } + } else { + if (!jsonObj.componentPlaceholder || !jsonObj.componentPlaceholder[name]) { + const errMsg = `componentPlaceholder of "${name}" doesn't exist! \n\r` + emitError(errMsg) + } + callback() + } + } else { + callback() + } + } + return { processComponent, processDynamicEntry, processPage, processJsExport, + processPlaceholder, isUrlRequest, urlToRequest } diff --git a/packages/webpack-plugin/lib/json-compiler/index.js b/packages/webpack-plugin/lib/json-compiler/index.js index cb6dfc89a2..0b82ffdafb 100644 --- a/packages/webpack-plugin/lib/json-compiler/index.js +++ b/packages/webpack-plugin/lib/json-compiler/index.js @@ -15,9 +15,6 @@ const RecordRuntimeInfoDependency = require('../dependencies/RecordRuntimeInfoDe const { MPX_DISABLE_EXTRACTOR_CACHE, RESOLVE_IGNORED_ERR, JSON_JS_EXT } = require('../utils/const') const resolve = require('../utils/resolve') const resolveTabBarPath = require('../utils/resolve-tab-bar-path') -const normalize = require('../utils/normalize') -const mpxViewPath = normalize.lib('runtime/components/ali/mpx-view.mpx') -const mpxTextPath = normalize.lib('runtime/components/ali/mpx-text.mpx') const resolveMpxCustomElementPath = require('../utils/resolve-mpx-custom-element-path') module.exports = function (content) { @@ -43,7 +40,6 @@ module.exports = function (content) { const globalSrcMode = mpx.srcMode const localSrcMode = queryObj.mode const srcMode = localSrcMode || globalSrcMode - const projectRoot = mpx.projectRoot const isApp = !(pagesMap[resourcePath] || componentsMap[resourcePath]) const publicPath = this._compilation.outputOptions.publicPath || '' @@ -62,36 +58,14 @@ module.exports = function (content) { ) } - const fillInComponentPlaceholder = (name, placeholder, placeholderEntry) => { - const componentPlaceholder = json.componentPlaceholder || {} - if (componentPlaceholder[name]) return - componentPlaceholder[name] = placeholder - json.componentPlaceholder = componentPlaceholder - if (placeholderEntry && !json.usingComponents[placeholder]) json.usingComponents[placeholder] = placeholderEntry - } - const normalizePlaceholder = (placeholder) => { - if (typeof placeholder === 'string') { - const placeholderMap = mode === 'ali' - ? { - view: { name: 'mpx-view', resource: mpxViewPath }, - text: { name: 'mpx-text', resource: mpxTextPath } - } - : {} - placeholder = placeholderMap[placeholder] || { name: placeholder } - } - if (!placeholder.name) { - emitError('The asyncSubpackageRules configuration format of @mpxjs/webpack-plugin a is incorrect') - } - return placeholder - } - const { isUrlRequest, urlToRequest, processPage, processDynamicEntry, processComponent, - processJsExport + processJsExport, + processPlaceholder } = createJSONHelper({ loaderContext: this, emitWarning, @@ -223,6 +197,9 @@ module.exports = function (content) { const processComponents = (components, context, callback) => { if (components) { + // 存在所有命中asyncSubpackageRules的组件 + const asyncComponents = [] + const resolveResourcePathMap = new Map() async.eachOf(components, (component, name, callback) => { processComponent(component, context, { relativePath }, (err, entry, { tarRoot, placeholder, resourcePath, queryObj = {} } = {}) => { if (err === RESOLVE_IGNORED_ERR) { @@ -242,29 +219,9 @@ module.exports = function (content) { isDynamic: queryObj.isDynamic } } - if (tarRoot) { - if (placeholder) { - placeholder = normalizePlaceholder(placeholder) - if (placeholder.resource) { - processComponent(placeholder.resource, projectRoot, { relativePath }, (err, entry) => { - if (err) return callback(err) - fillInComponentPlaceholder(name, placeholder.name, entry) - callback() - }) - } else { - fillInComponentPlaceholder(name, placeholder.name) - callback() - } - } else { - if (!json.componentPlaceholder || !json.componentPlaceholder[name]) { - const errMsg = `componentPlaceholder of "${name}" doesn't exist! \n\r` - emitError(errMsg) - } - callback() - } - } else { - callback() - } + resolveResourcePathMap.set(name, resourcePath) + if (tarRoot) asyncComponents.push({ name, tarRoot, placeholder, relativePath }) + callback() }) }, (err) => { if (err) return callback(err) @@ -279,7 +236,10 @@ module.exports = function (content) { components.element = mpxCustomElementPath Object.assign(components, mpx.getPackageInjectedComponentsMap(packageName)) } - callback() + // 使用async处理所有asyncComponents完成后调用callback + async.each(asyncComponents, ({ name, tarRoot, placeholder, relativePath }, callback) => { + processPlaceholder({ jsonObj: json, context, name, tarRoot, placeholder, relativePath, resolveResourcePathMap }, callback) + }, callback) }) } else { callback() diff --git a/packages/webpack-plugin/lib/platform/json/wx/index.js b/packages/webpack-plugin/lib/platform/json/wx/index.js index eb10de012a..d740e85f50 100644 --- a/packages/webpack-plugin/lib/platform/json/wx/index.js +++ b/packages/webpack-plugin/lib/platform/json/wx/index.js @@ -1,12 +1,9 @@ const runRules = require('../../run-rules') const normalizeTest = require('../normalize-test') const changeKey = require('../change-key') -const normalize = require('../../../utils/normalize') const { capitalToHyphen } = require('../../../utils/string') const { isOriginTag, isBuildInWebTag, isBuildInReactTag } = require('../../../utils/dom-tag-config') - -const mpxViewPath = normalize.lib('runtime/components/ali/mpx-view.mpx') -const mpxTextPath = normalize.lib('runtime/components/ali/mpx-text.mpx') +const getBuildInTagComponent = require('../../../utils/get-build-tag-component') module.exports = function getSpec ({ warn, error }) { function print (mode, path, isError) { @@ -46,28 +43,26 @@ module.exports = function getSpec ({ warn, error }) { } // 处理支付宝 componentPlaceholder 不支持 view、text 原生标签 - function aliComponentPlaceholderFallback (input) { - // 处理 驼峰转连字符 - input = componentNameCapitalToHyphen('componentPlaceholder')(input) + // 将 placeholder 中使用的内建组件转化为 mpx-xxx, 并在 usingComponents 填充 + function fixComponentPlaceholder (input, { mode }) { + if (!input.componentPlaceholder) return input + if (mode === 'ali') { + // 处理 驼峰转连字符 + input = componentNameCapitalToHyphen('componentPlaceholder')(input) + } const componentPlaceholder = input.componentPlaceholder - const usingComponents = input.usingComponents || (input.usingComponents = {}) + const usingComponents = input.usingComponents || {} for (const cph in componentPlaceholder) { const cur = componentPlaceholder[cph] - const placeholderCompMatched = cur.match(/^(?:view|text)$/g) - if (!Array.isArray(placeholderCompMatched)) continue - let compName, compPath - switch (placeholderCompMatched[0]) { - case 'view': - compName = 'mpx-view' - compPath = mpxViewPath - break - case 'text': - compName = 'mpx-text' - compPath = mpxTextPath - } - usingComponents[compName] = compPath - componentPlaceholder[cph] = compName + const comp = getBuildInTagComponent(mode, cur) + if (!comp || usingComponents[cur]) continue + const { name, resource } = comp + usingComponents[name] = resource + componentPlaceholder[cph] = name } + + input.usingComponents = usingComponents + input.componentPlaceholder = componentPlaceholder return input } @@ -172,7 +167,6 @@ module.exports = function getSpec ({ warn, error }) { }, { test: 'componentPlaceholder', - ali: aliComponentPlaceholderFallback, swan: deletePath(), jd: deletePath() }, @@ -190,6 +184,13 @@ module.exports = function getSpec ({ warn, error }) { ios: fixComponentName, android: fixComponentName, harmony: fixComponentName + }, + { + ali: fixComponentPlaceholder, + web: fixComponentPlaceholder, + ios: fixComponentPlaceholder, + android: fixComponentPlaceholder, + harmony: fixComponentPlaceholder } ] @@ -454,12 +455,6 @@ module.exports = function getSpec ({ warn, error }) { swan: getWindowRule(), tt: getWindowRule(), jd: getWindowRule() - }, - { - web: fixComponentName, - ios: fixComponentName, - android: fixComponentName, - harmony: fixComponentName } ] } diff --git a/packages/webpack-plugin/lib/react/processJSON.js b/packages/webpack-plugin/lib/react/processJSON.js index ddb828337d..de44504a3a 100644 --- a/packages/webpack-plugin/lib/react/processJSON.js +++ b/packages/webpack-plugin/lib/react/processJSON.js @@ -12,11 +12,8 @@ const { transSubpackage } = require('../utils/trans-async-sub-rules') const createJSONHelper = require('../json-compiler/helper') const getRulesRunner = require('../platform/index') const { RESOLVE_IGNORED_ERR } = require('../utils/const') -const normalize = require('../utils/normalize') const RecordResourceMapDependency = require('../dependencies/RecordResourceMapDependency') const RecordPageConfigsMapDependency = require('../dependencies/RecordPageConfigsMapDependency') -const mpxViewPath = normalize.lib('runtime/components/react/dist/mpx-view.jsx') -const mpxTextPath = normalize.lib('runtime/components/react/dist/mpx-text.jsx') module.exports = function (jsonContent, { loaderContext, @@ -54,11 +51,26 @@ module.exports = function (jsonContent, { const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r) + function fillInComponentsMap (name, entry, tarRoot) { + const { resource, outputPath } = entry + const { resourcePath } = parseRequest(resource) + componentsMap[resourcePath] = outputPath + loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'component', outputPath)) + localComponentsMap[name] = { + resource: addQuery(resource, { + isComponent: true, + outputPath + }), + async: tarRoot + } + } + const { isUrlRequest, urlToRequest, processPage, - processComponent + processComponent, + processPlaceholder } = createJSONHelper({ loaderContext, emitWarning, @@ -137,45 +149,6 @@ module.exports = function (jsonContent, { isShow: true } - const fillInComponentPlaceholder = (name, placeholder, placeholderEntry) => { - const componentPlaceholder = jsonObj.componentPlaceholder || {} - if (componentPlaceholder[name]) return - componentPlaceholder[name] = placeholder - jsonObj.componentPlaceholder = componentPlaceholder - if (placeholderEntry && !jsonObj.usingComponents[placeholder]) jsonObj.usingComponents[placeholder] = placeholderEntry - } - - const fillInComponentsMap = (name, entry, tarRoot) => { - const { resource, outputPath } = entry - const { resourcePath } = parseRequest(resource) - tarRoot = transSubpackage(mpx.transSubpackageRules, tarRoot) - componentsMap[resourcePath] = outputPath - loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'component', outputPath)) - localComponentsMap[name] = { - resource: addQuery(resource, { - isComponent: true, - outputPath - }), - async: tarRoot - } - } - - const normalizePlaceholder = (placeholder) => { - if (typeof placeholder === 'string') { - const placeholderMap = mode === 'ali' - ? { - view: { name: 'mpx-view', resource: mpxViewPath }, - text: { name: 'mpx-text', resource: mpxTextPath } - } - : {} - placeholder = placeholderMap[placeholder] || { name: placeholder } - } - if (!placeholder.name) { - emitError('The asyncSubpackageRules configuration format of @mpxjs/webpack-plugin a is incorrect') - } - return placeholder - } - const processTabBar = (tabBar, callback) => { if (tabBar) { tabBar = Object.assign({}, defaultTabbar, tabBar) @@ -345,38 +318,34 @@ module.exports = function (jsonContent, { const processComponents = (components, context, callback) => { if (components) { + const asyncComponents = [] + const resolveResourcePathMap = new Map() async.eachOf(components, (component, name, callback) => { - processComponent(component, context, {}, (err, entry = {}, { tarRoot, placeholder } = {}) => { + processComponent(component, context, {}, (err, entry = {}, { tarRoot, placeholder, resourcePath } = {}) => { if (err) return callback(err === RESOLVE_IGNORED_ERR ? null : err) - fillInComponentsMap(name, entry, tarRoot) const { relativePath } = entry - if (tarRoot) { + tarRoot = transSubpackage(mpx.transSubpackageRules, tarRoot) + + resolveResourcePathMap.set(name, resourcePath) + if (tarRoot) asyncComponents.push({ name, tarRoot, placeholder, relativePath }) + + fillInComponentsMap(name, entry, tarRoot) + callback() + }) + }, (err) => { + if (err) return callback(err) + async.each(asyncComponents, ({ name, tarRoot, placeholder, relativePath }, callback) => { + processPlaceholder({ jsonObj, context, name, tarRoot, placeholder, relativePath, resolveResourcePathMap }, (err, placeholder) => { + if (err) return callback(err) if (placeholder) { - placeholder = normalizePlaceholder(placeholder) - if (placeholder.resource) { - processComponent(placeholder.resource, projectRoot, { relativePath }, (err, entry) => { - if (err) return callback(err) - fillInComponentPlaceholder(name, placeholder.name, entry) - fillInComponentsMap(placeholder.name, entry, '') - callback() - }) - } else { - fillInComponentPlaceholder(name, placeholder.name) - callback() - } - } else { - if (!jsonObj.componentPlaceholder || !jsonObj.componentPlaceholder[name]) { - const errMsg = `componentPlaceholder of "${name}" doesn't exist! \n\r` - emitError(errMsg) - } - callback() + const { name, entry } = placeholder + fillInComponentsMap(name, entry, '') } - } else { callback() - } - }) - }, callback) + }) + }, callback) + }) } else { callback() } @@ -394,7 +363,6 @@ module.exports = function (jsonContent, { callback() } } - async.parallel([ (callback) => { // 添加首页标识 diff --git a/packages/webpack-plugin/lib/react/script-helper.js b/packages/webpack-plugin/lib/react/script-helper.js index 0be9883954..31cf1a8a72 100644 --- a/packages/webpack-plugin/lib/react/script-helper.js +++ b/packages/webpack-plugin/lib/react/script-helper.js @@ -4,16 +4,11 @@ const parseRequest = require('../utils/parse-request') const shallowStringify = require('../utils/shallow-stringify') const normalize = require('../utils/normalize') const addQuery = require('../utils/add-query') -const { isBuildInReactTag } = require('../utils/dom-tag-config') function stringifyRequest (loaderContext, request) { return loaderUtils.stringifyRequest(loaderContext, request) } -function getBuiltInComponentRequest (component) { - return JSON.stringify(addQuery(`@mpxjs/webpack-plugin/lib/runtime/components/react/dist/${component}`, { isComponent: true })) -} - function getAsyncChunkName (chunkName) { if (chunkName && typeof chunkName !== 'boolean') { return `/* webpackChunkName: "${chunkName}/index" */` @@ -26,8 +21,8 @@ function getAsyncSuspense (type, moduleId, componentRequest, componentName, chun type: ${JSON.stringify(type)}, moduleId: ${JSON.stringify(moduleId)}, chunkName: ${JSON.stringify(chunkName)}, - getFallback: ${getFallback}, - getLoading: ${getLoading}, + ${getFallback ? `getFallback: ${getFallback},` : ''} + ${getLoading ? `getLoading: ${getLoading},` : ''} getChildren () { return import(${getAsyncChunkName(chunkName)}${componentRequest}).then(function (res) { return getComponent(res, {displayName: ${JSON.stringify(componentName)}}) @@ -95,22 +90,17 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC if (placeholder) { if (localComponentsMap[placeholder]) { const placeholderCfg = localComponentsMap[placeholder] - const placeholderRequest = stringifyRequest(loaderContext, placeholderCfg.resource) if (placeholderCfg.async) { loaderContext.emitWarning( new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`) ) } + const placeholderRequest = stringifyRequest(loaderContext, placeholderCfg.resource) getFallback = getComponentGetter(getComponent(placeholderRequest, placeholder)) } else { - const tag = `mpx-${placeholder}` - if (isBuildInReactTag(tag)) { - getFallback = getComponentGetter(getBuiltInComponent(getBuiltInComponentRequest(tag))) - } else { - loaderContext.emitError( - new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component, please check!`) - ) - } + loaderContext.emitError( + new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component or custom component, please check!`) + ) } } else { loaderContext.emitError( diff --git a/packages/webpack-plugin/lib/utils/get-build-tag-component.js b/packages/webpack-plugin/lib/utils/get-build-tag-component.js new file mode 100644 index 0000000000..b53ea9ac8b --- /dev/null +++ b/packages/webpack-plugin/lib/utils/get-build-tag-component.js @@ -0,0 +1,35 @@ +const normalize = require('./normalize') +const { isBuildInWebTag, isBuildInReactTag } = require('./dom-tag-config') + +module.exports = function getBuildInTagComponent (mode, tag) { + const aliBuildTag = ['view', 'text'].reduce((obj, name) => { + obj[name] = { + name: `mpx-${name}`, + resource: normalize.lib(`runtime/components/ali/mpx-${name}.mpx`) + } + return obj + }, {}) + + switch (mode) { + case 'ali': + return aliBuildTag[tag] + case 'web': + if (isBuildInWebTag(`mpx-${tag}`)) { + return { + name: `mpx-${tag}`, + resource: normalize.lib(`runtime/components/web/mpx-${tag}.vue`) + } + } + return undefined + case 'ios': + case 'android': + case 'harmony': + if (isBuildInReactTag(`mpx-${tag}`)) { + return { + name: `mpx-${tag}`, + resource: normalize.lib(`runtime/components/react/dist/mpx-${tag}.jsx`) + } + } + return undefined + } +} diff --git a/packages/webpack-plugin/lib/web/processJSON.js b/packages/webpack-plugin/lib/web/processJSON.js index 542a50cb40..eaad49dc9d 100644 --- a/packages/webpack-plugin/lib/web/processJSON.js +++ b/packages/webpack-plugin/lib/web/processJSON.js @@ -49,11 +49,26 @@ module.exports = function (jsonContent, { const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r) + function fillInComponentsMap (name, entry, tarRoot) { + const { resource, outputPath } = entry + const { resourcePath } = parseRequest(resource) + componentsMap[resourcePath] = outputPath + loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'component', outputPath)) + localComponentsMap[name] = { + resource: addQuery(resource, { + isComponent: true, + outputPath + }), + async: tarRoot + } + } + const { isUrlRequest, urlToRequest, processPage, - processComponent + processComponent, + processPlaceholder } = createJSONHelper({ loaderContext, emitWarning, @@ -287,22 +302,35 @@ module.exports = function (jsonContent, { const processComponents = (components, context, callback) => { if (components) { + const asyncComponents = [] + const resolveResourcePathMap = new Map() async.eachOf(components, (component, name, callback) => { - processComponent(component, context, {}, (err, { resource, outputPath } = {}, { tarRoot } = {}) => { + processComponent(component, context, {}, (err, entry = {}, { tarRoot, placeholder, resourcePath } = {}) => { if (err) return callback(err === RESOLVE_IGNORED_ERR ? null : err) - const { resourcePath, queryObj } = parseRequest(resource) - componentsMap[resourcePath] = outputPath - loaderContext._module && loaderContext._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, 'component', outputPath)) - localComponentsMap[name] = { - resource: addQuery(resource, { - isComponent: true, - outputPath - }), - async: queryObj.async || tarRoot - } + const { relativePath, resource } = entry + const { queryObj } = parseRequest(resource) + + tarRoot = queryObj.async || tarRoot + + resolveResourcePathMap.set(name, resourcePath) + if (tarRoot) asyncComponents.push({ name, tarRoot, placeholder, relativePath }) + + fillInComponentsMap(name, entry, tarRoot) callback() }) - }, callback) + }, (err) => { + if (err) return callback(err) + async.each(asyncComponents, ({ name, tarRoot, placeholder, relativePath }, callback) => { + processPlaceholder({ jsonObj, context, name, tarRoot, placeholder, relativePath, resolveResourcePathMap }, (err, placeholder) => { + if (err) return callback(err) + if (placeholder) { + const { name, entry } = placeholder + fillInComponentsMap(name, entry, '') + } + callback() + }) + }, callback) + }) } else { callback() } diff --git a/packages/webpack-plugin/lib/web/processScript.js b/packages/webpack-plugin/lib/web/processScript.js index 62e3312fc6..b01f220a09 100644 --- a/packages/webpack-plugin/lib/web/processScript.js +++ b/packages/webpack-plugin/lib/web/processScript.js @@ -78,12 +78,12 @@ module.exports = function (script, { } content += getRequireScript({ ctorType, script, loaderContext }) content += ` + // @ts-ignore export default processComponentOption({ option: global.__mpxOptionsMap[${JSON.stringify(moduleId)}], ctorType: ${JSON.stringify(ctorType)}, outputPath: ${JSON.stringify(outputPath)}, pageConfig: ${JSON.stringify(pageConfig)}, - // @ts-ignore componentsMap: ${shallowStringify(componentsMap)}, componentGenerics: ${JSON.stringify(componentGenerics)}, genericsInfo: ${JSON.stringify(genericsInfo)}, diff --git a/packages/webpack-plugin/lib/web/script-helper.js b/packages/webpack-plugin/lib/web/script-helper.js index 3c8bd4c9cb..1b8f9630cc 100644 --- a/packages/webpack-plugin/lib/web/script-helper.js +++ b/packages/webpack-plugin/lib/web/script-helper.js @@ -24,19 +24,29 @@ function buildComponentsMap ({ localComponentsMap, builtInComponentsMap, loaderC Object.keys(localComponentsMap).forEach((componentName) => { const componentCfg = localComponentsMap[componentName] const componentRequest = stringifyRequest(loaderContext, componentCfg.resource) + if (componentCfg.async) { - // todo 暂时只处理局部注册的组件作为componentPlaceholder,暂不支持全局组件和原生组件,如使用了支持范围外的组件将不进行placeholder渲染及替换 - if (jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName] && localComponentsMap[jsonConfig.componentPlaceholder[componentName]]) { - const placeholder = jsonConfig.componentPlaceholder[componentName] + const placeholder = jsonConfig.componentPlaceholder && jsonConfig.componentPlaceholder[componentName] + if (placeholder) { const placeholderCfg = localComponentsMap[placeholder] - const placeholderRequest = stringifyRequest(loaderContext, placeholderCfg.resource) - if (placeholderCfg.async) { - loaderContext.emitWarning( - new Error(`[Mpx json error][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`) + if (placeholderCfg) { + if (placeholderCfg.async) { + loaderContext.emitWarning( + new Error(`[Mpx json error][${loaderContext.resource}]: componentPlaceholder ${placeholder} should not be a async component, please check!`) + ) + } + const placeholderRequest = stringifyRequest(loaderContext, placeholderCfg.resource) + componentsMap[componentName] = `function(){return {component: import(${getAsyncChunkName(componentCfg.async)}${componentRequest}).then(function(res){return getComponent(res)}), loading: getComponent(require(${placeholderRequest}))}}` + } else { + loaderContext.emitError( + new Error(`[json processor][${loaderContext.resource}]: componentPlaceholder ${placeholder} is not built-in component or custom component, please check!`) ) + componentsMap[componentName] = `function(){return import(${getAsyncChunkName(componentCfg.async)}${componentRequest}).then(function(res){return getComponent(res)})}` } - componentsMap[componentName] = `function(){return {component: import(${getAsyncChunkName(componentCfg.async)}${componentRequest}).then(function(res){return getComponent(res)}), loading: getComponent(require(${placeholderRequest}))}}` } else { + loaderContext.emitError( + new Error(`[json processor][${loaderContext.resource}]: ${componentName} has no componentPlaceholder, please check!`) + ) componentsMap[componentName] = `function(){return import(${getAsyncChunkName(componentCfg.async)}${componentRequest}).then(function(res){return getComponent(res)})}` } } else { diff --git a/test/e2e/miniprogram-project/config/mpxPlugin.conf.js b/test/e2e/miniprogram-project/config/mpxPlugin.conf.js index f64c066800..4122aadf07 100644 --- a/test/e2e/miniprogram-project/config/mpxPlugin.conf.js +++ b/test/e2e/miniprogram-project/config/mpxPlugin.conf.js @@ -90,6 +90,16 @@ module.exports = { } // 输出示例: pages/testax34dde3/index.js return path.join(type + 's', name + hash, 'index' + ext) - } + }, + asyncSubpackageRules: [ + { + include: path.resolve('./src/subpackage/test/components/asyncComp.mpx'), + root: 'test', + placeholder: { + name: 'placeholder-view', + resource: path.resolve('./src/components/placeholder-view.mpx') + } + } + ] } diff --git a/test/e2e/miniprogram-project/src/components/placeholder-view.mpx b/test/e2e/miniprogram-project/src/components/placeholder-view.mpx new file mode 100644 index 0000000000..40f2e8f994 --- /dev/null +++ b/test/e2e/miniprogram-project/src/components/placeholder-view.mpx @@ -0,0 +1,18 @@ + + + + + + diff --git a/test/e2e/miniprogram-project/src/subpackage/test/components/asyncComp.mpx b/test/e2e/miniprogram-project/src/subpackage/test/components/asyncComp.mpx new file mode 100644 index 0000000000..758acb61c5 --- /dev/null +++ b/test/e2e/miniprogram-project/src/subpackage/test/components/asyncComp.mpx @@ -0,0 +1,18 @@ + + + + + + diff --git a/test/e2e/miniprogram-project/src/subpackage/test/pages/index.mpx b/test/e2e/miniprogram-project/src/subpackage/test/pages/index.mpx index 35ded6a6b9..d968592d73 100644 --- a/test/e2e/miniprogram-project/src/subpackage/test/pages/index.mpx +++ b/test/e2e/miniprogram-project/src/subpackage/test/pages/index.mpx @@ -20,6 +20,6 @@ createPage({ diff --git a/test/e2e/miniprogram-project/src/subpackage/test2/app.mpx b/test/e2e/miniprogram-project/src/subpackage/test2/app.mpx index 37a6b28ee5..a7e043bba6 100644 --- a/test/e2e/miniprogram-project/src/subpackage/test2/app.mpx +++ b/test/e2e/miniprogram-project/src/subpackage/test2/app.mpx @@ -1,7 +1,10 @@ diff --git a/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules1.mpx b/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules1.mpx new file mode 100644 index 0000000000..de4726855b --- /dev/null +++ b/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules1.mpx @@ -0,0 +1,19 @@ + + + + + diff --git a/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules2.mpx b/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules2.mpx new file mode 100644 index 0000000000..4ab0f54071 --- /dev/null +++ b/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules2.mpx @@ -0,0 +1,22 @@ + + + + + + diff --git a/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules3.mpx b/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules3.mpx new file mode 100644 index 0000000000..5902449bea --- /dev/null +++ b/test/e2e/miniprogram-project/src/subpackage/test2/pages/testAsyncSubpackageRules3.mpx @@ -0,0 +1,26 @@ + + + + + + diff --git a/test/e2e/miniprogram-project/test/app.spec.js b/test/e2e/miniprogram-project/test/app.spec.js index b696208fb5..90a9336f88 100644 --- a/test/e2e/miniprogram-project/test/app.spec.js +++ b/test/e2e/miniprogram-project/test/app.spec.js @@ -7,14 +7,27 @@ const readFileSyncInDist = (filePath, options) => { return fs.readFileSync(realPath, options) } +// 排序SubPackages数组,按root字段排序,每一项的pages按字符串排序 +const sortSubPackages = (subPackages) => { + return subPackages + .sort((a, b) => a.root.localeCompare(b.root)) + .map((item) => ({ + ...item, + pages: item.pages.sort() + })) +} + describe('test App instance', () => { let subpackage = [] beforeEach(() => { - subpackage = [ + subpackage = sortSubPackages([ { 'root': 'test2', 'pages': [ - 'pages/index' + 'pages/index', + 'pages/testAsyncSubpackageRules1', + 'pages/testAsyncSubpackageRules2', + 'pages/testAsyncSubpackageRules3' ] }, { @@ -23,17 +36,14 @@ describe('test App instance', () => { 'pages/index' ] } - ] + ]) }) it('should wx App instance json is correct', function () { const wxAppJsonStr = readFileSyncInDist('dist/wx/app.json', 'utf-8') const wxAppJsonObj = json5.parse(wxAppJsonStr) // const wxPages = wxAppJsonObj.pages - const wxSubPackages = wxAppJsonObj.subPackages - if (subpackage[0].root !== wxSubPackages[0].root) { - subpackage.reverse() - } + const wxSubPackages = sortSubPackages(wxAppJsonObj.subPackages) // expect(wxPages).toMatch(['pages/index', 'pages/mode', 'pages/alias', 'pages/someEnv']) expect(wxSubPackages).toEqual(subpackage) }) @@ -42,11 +52,8 @@ describe('test App instance', () => { const aliAppJsonStr = readFileSyncInDist('dist/ali/app.json', 'utf-8') const aliAppJsonObj = json5.parse(aliAppJsonStr) const aliPages = aliAppJsonObj.pages - const aliSubPackages = aliAppJsonObj.subPackages + const aliSubPackages = sortSubPackages(aliAppJsonObj.subPackages) expect(aliPages).toEqual(['pages/index', 'pages/mode', 'pages/alias']) - if (subpackage[0].root !== aliSubPackages[0].root) { - subpackage.reverse() - } expect(aliSubPackages).toEqual(subpackage) }) @@ -54,11 +61,8 @@ describe('test App instance', () => { const ttAppJsonStr = readFileSyncInDist('dist/tt/app.json', 'utf-8') const ttAppJsonObj = json5.parse(ttAppJsonStr) const ttPages = ttAppJsonObj.pages - const ttSubPackages = ttAppJsonObj.subPackages + const ttSubPackages = sortSubPackages(ttAppJsonObj.subPackages) expect(ttPages).toEqual(['pages/index', 'pages/mode', 'pages/alias']) - if (subpackage[0].root !== ttSubPackages[0].root) { - subpackage.reverse() - } expect(ttSubPackages).toEqual(subpackage) }) @@ -66,11 +70,8 @@ describe('test App instance', () => { const swanAppJsonStr = readFileSyncInDist('dist/swan/app.json', 'utf-8') const swanAppJsonObj = json5.parse(swanAppJsonStr) const swanPages = swanAppJsonObj.pages - const swanSubPackages = swanAppJsonObj.subPackages + const swanSubPackages = sortSubPackages(swanAppJsonObj.subPackages) expect(swanPages).toEqual(['pages/index', 'pages/mode', 'pages/alias']) - if (subpackage[0].root !== swanSubPackages[0].root) { - subpackage.reverse() - } expect(swanSubPackages).toEqual(subpackage) }) diff --git a/test/e2e/miniprogram-project/test/asyncSubpackagesRules.spec.js b/test/e2e/miniprogram-project/test/asyncSubpackagesRules.spec.js new file mode 100644 index 0000000000..8ed7cefe7a --- /dev/null +++ b/test/e2e/miniprogram-project/test/asyncSubpackagesRules.spec.js @@ -0,0 +1,55 @@ +const fs = require('fs') +const json5 = require('json5') +const path = require('path') + +const readFileSyncInDist = (filePath, options) => { + const realPath = path.join(path.resolve(), filePath) + return fs.readFileSync(realPath, options) +} + +describe('test webpackPlugin asyncSubpackageRules', () => { + it('should automatically supplement usingComponents and componentPlaceholder', function () { + const wxAppJsonStr = readFileSyncInDist('dist/wx/test2/pages/testAsyncSubpackageRules1.json') + const wxAppJsonObj = json5.parse(wxAppJsonStr) + expect(wxAppJsonObj).toMatchObject({ + usingComponents: { + asyncComp: expect.any(String), + 'placeholder-view': expect.any(String) + }, + componentPlaceholder: { + asyncComp: 'placeholder-view' + } + }) + }) + + it('should not add duplicate usingComponents', function () { + const wxAppJsonStr = readFileSyncInDist('dist/wx/test2/pages/testAsyncSubpackageRules2.json') + const wxAppJsonObj = json5.parse(wxAppJsonStr) + expect(wxAppJsonObj).toMatchObject({ + usingComponents: { + asyncComp: expect.any(String), + 'placeholder-view': expect.any(String) + }, + componentPlaceholder: { + asyncComp: 'placeholder-view' + } + }) + }) + + it('should not overwrite existing usingComponents and componentPlaceholder', function () { + const wxAppJsonStr = readFileSyncInDist('dist/wx/test2/pages/testAsyncSubpackageRules3.json') + const wxAppJsonObj = json5.parse(wxAppJsonStr) + expect(wxAppJsonObj).toMatchObject({ + usingComponents: { + asyncComp: expect.any(String), + asyncComp1: expect.any(String), + 'placeholder-view': expect.any(String), + 'placeholder-view1': expect.any(String) + }, + componentPlaceholder: { + asyncComp: 'placeholder-view1', + asyncComp1: 'button' + } + }) + }) +})