diff --git a/docs-vuepress/.vuepress/config.js b/docs-vuepress/.vuepress/config.js index c61955049d..e971a27c39 100644 --- a/docs-vuepress/.vuepress/config.js +++ b/docs-vuepress/.vuepress/config.js @@ -135,6 +135,7 @@ module.exports = { description: '深度性能优化的增强型小程序开发框架' }, }, + shouldPrefetch: () => false, plugins: { '@vuepress/pwa': { serviceWorker: true, diff --git a/docs-vuepress/api/app-config.md b/docs-vuepress/api/app-config.md index 80ca27d206..b1c546592e 100644 --- a/docs-vuepress/api/app-config.md +++ b/docs-vuepress/api/app-config.md @@ -155,3 +155,27 @@ mpx.config.webRouteConfig = { mode: 'history' } ``` + +## errorHandler +- **类型**: `Function` + +- **默认值**:`null` + +- **用法**: + +```js +mpx.config.errorHandler = function (errmsg, location, error) { + // errmsg: 框架内部运行报错的报错归类信息,例如当执行一个watch方法报错时,会是 "Unhandled error occurs during execution of watch callback!" + // location: 具体报错的代码路径,可选项,不一定存在 + // error: 具体的错误堆栈,可选项,不一定存在 + // handle error +} +``` + +Mpx 框架运行时报错捕获感知处理函数。 + +* Mpx 框架生命周期执行错误; +* Mpx 中的 computed、watch 等内置方法执行报错; +* Mpx 框架的运行时的检测报错,例如存在目标平台不支持的属性,入参出参类型错误等; + +同时被捕获的错误会通过 console.error 输出。 diff --git a/docs-vuepress/api/compile.md b/docs-vuepress/api/compile.md index 794548da1c..422bba38e8 100644 --- a/docs-vuepress/api/compile.md +++ b/docs-vuepress/api/compile.md @@ -250,6 +250,7 @@ module.exports = defineConfig({ }) ``` ::: + ### externalClasses - **类型**:`Array` @@ -488,6 +489,7 @@ module.exports = defineConfig({ }) ``` ::: + ### autoSplit - **类型**:`boolean` @@ -1126,8 +1128,6 @@ module.exports = defineConfig({ ``` ::: - - ### i18n ```js @@ -1404,6 +1404,82 @@ module.exports = defineConfig({ 该特性只能用于**开发环境**,默认情况下会阻止所有页面(**入口 app.mpx 除外**)的打包。 ::: +### asyncSubpackageRules + +- **类型**: + ```ts +type Condition = string | Function | RegExp + +interface AsyncSubpackageRules { + include: Condition | Array + exclude?: Condition | Array + root: string + placeholder: string | { name: string, resource?: string} +} +``` + * include: 同 webpack include 规则 + * exclude: 同 webpack exclude 规则 + * root: 匹配规则的组件或js模块的输出分包名 + * placeholder: 匹配规则的组件所配置的componentPlaceholder,可支持配置原生组件和自定义组件,原生组件可直接以string类型配置,自定义组件需要配置对象,name 为该自定义组件名, resource 为自定义组件的路径,路径可为绝对路径和相对于项目目录的相对路径 + +- **详细**:异步分包场景下批量设置组件或 js 模块的异步分包,提升资源异步分包输出的灵活性。 + +- **示例**: + +```js +// include 可以是正则、字符串、函数、数组 +new MpxWebpackPlugin({ + asyncSubpackageRules: [ + { + include: '/project/pages', // 文件路径包含 '/project/pages' 的组件或者 require.async 异步引用的js 模块都会被打包至sub1分包 + root: 'sub1', + placeholder: 'view' + } + ] +}) +// 若配置自定义组件 +new MpxWebpackPlugin({ + asyncSubpackageRules: [ + { + include: '/project/pages', // 文件路径包含 '/project/pages' 的组件或者 require.async 异步引用的js 模块都会被打包至sub1分包 + root: 'sub1', + placeholder: { + name: 'other-placeholder', + resource: '/user/xxxx/other.mpx' // 自定义组件的绝对路径 + } + } + ] +}) +``` + +::: tip @mpxjs/cli@3.x 版本配置如下 +```js +// vue.config.js +module.exports = defineConfig({ + pluginOptions: { + mpx: { + plugin: { + // include 可以是正则、字符串、函数、数组 + asyncSubpackageRules: [ + { + include: '/project/pages', // 文件路径包含 '/project/pages' 的组件或者 require.async 异步引用的js 模块都会被打包至sub1分包 + root: 'sub1', + placeholder: 'view' + } + ] + } + } + } +}) +``` +::: + +:::warning +* 该配置匹配的组件,若使用方在引用路径已设置?root或componentPlaceholder,则以引用路径中的?root或componentPlaceholder为最高优先级 +* 若placeholder配置使用自定义组件,注意一定要配置 placeholder 中的 resource 字段 +* 本功能只会对使用require.async异步引用的js模块生效,若引用路径中已配置?root,则以路径中?root优先 +::: + ## MpxWebpackPlugin static methods `MpxWebpackPlugin` 通过静态方法暴露了以下五个内置 loader,详情如下: diff --git a/lerna.json b/lerna.json index 39b022c18f..ba3b04c822 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "2.8.40" + "version": "2.8.47" } diff --git a/packages/api-proxy/package.json b/packages/api-proxy/package.json index 990b17a55e..810a9b157b 100644 --- a/packages/api-proxy/package.json +++ b/packages/api-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@mpxjs/api-proxy", - "version": "2.8.40", + "version": "2.8.46", "description": "convert miniprogram API at each end", "module": "src/index.js", "types": "@types/index.d.ts", @@ -37,8 +37,6 @@ }, "homepage": "https://github.com/didi/mpx#readme", "dependencies": { - "@mpxjs/core": "^2.8.47", - "@mpxjs/utils": "^2.8.15", "axios": "^0.21.1" } } diff --git a/packages/api-proxy/src/platform/api/event-channel/index.web.js b/packages/api-proxy/src/platform/api/event-channel/index.web.js index c7e03549f9..23b8c4ad0d 100644 --- a/packages/api-proxy/src/platform/api/event-channel/index.web.js +++ b/packages/api-proxy/src/platform/api/event-channel/index.web.js @@ -19,13 +19,13 @@ class EventChannel { } } - off (eventName, EventCallback) { - if (EventCallback) { + off (eventName, listener) { + if (listener) { const cbs = this.listener[eventName] const copyCbs = [] if (cbs) { cbs.forEach((item) => { - if (item.fn !== EventCallback) { + if (item.fn !== listener) { copyCbs.push(item) } }) @@ -36,26 +36,25 @@ class EventChannel { } } - on (eventName, EventCallback) { - (this.listener[eventName] || (this.listener[eventName] = [])).push({ fn: EventCallback, type: 'on' }) + on (eventName, listener) { + this._addListener(eventName, listener, 'on') } - once (eventName, EventCallback) { - (this.listener[eventName] || (this.listener[eventName] = [])).push({ fn: EventCallback, type: 'once' }) + once (eventName, listener) { + this._addListener(eventName, listener, 'once') } - _addListener (eventName, EventCallback, type) { - (this.listener[eventName] || (this.listener[eventName] = [])).push({ fn: EventCallback, type }) + _addListener (eventName, listener, type) { + (this.listener[eventName] || (this.listener[eventName] = [])).push({ fn: listener, type }) } _addListeners (events) { - if (Object.prototype.toString.call(events) === '[object Object]') { - Object.keys(events).forEach((eventName) => { - (this.listener[eventName] || (this.listener[eventName] = [])).push({ fn: events[eventName], type: 'on' }) - }) - } + Object.keys(events).forEach((eventName) => { + this.on(eventName, events[eventName]) + }) } } + export { EventChannel } diff --git a/packages/api-proxy/src/platform/api/route/index.web.js b/packages/api-proxy/src/platform/api/route/index.web.js index cccea16f06..c2243fda66 100644 --- a/packages/api-proxy/src/platform/api/route/index.web.js +++ b/packages/api-proxy/src/platform/api/route/index.web.js @@ -1,6 +1,8 @@ import { webHandleSuccess, webHandleFail, isTabBarPage } from '../../../common/js' import { EventChannel } from '../event-channel' +let routeCount = 0 + function redirectTo (options = {}) { const router = global.__mpxRouter if (router) { @@ -10,9 +12,13 @@ function redirectTo (options = {}) { return Promise.reject(res) } router.__mpxAction = { type: 'redirect' } + if (routeCount === 0 && router.currentRoute.query.routeCount) routeCount = router.currentRoute.query.routeCount router.replace( { - path: options.url + path: options.url, + query: { + routeCount: ++routeCount + } }, () => { const res = { errMsg: 'redirectTo:ok' } @@ -42,9 +48,13 @@ function navigateTo (options = {}) { if (options.events) { eventChannel._addListeners(options.events) } + if (routeCount === 0 && router.currentRoute.query.routeCount) routeCount = router.currentRoute.query.routeCount router.push( { - path: options.url + path: options.url, + query: { + routeCount: ++routeCount + } }, () => { const res = { errMsg: 'navigateTo:ok', eventChannel } @@ -72,19 +82,17 @@ function navigateBack (options = {}) { } } -let reLaunchCount = 0 - function reLaunch (options = {}) { const router = global.__mpxRouter if (router) { - if (reLaunchCount === 0 && router.currentRoute.query.reLaunchCount) reLaunchCount = router.currentRoute.query.reLaunchCount - const delta = router.stack.length - 1 + if (routeCount === 0 && router.currentRoute.query.routeCount) routeCount = router.currentRoute.query.routeCount router.__mpxAction = { type: 'reLaunch', path: options.url, - reLaunchCount: ++reLaunchCount, + routeCount: ++routeCount, replaced: false } + const delta = router.stack.length - 1 // 在需要操作后退时,先操作后退,在beforeEach中基于当前action通过next()进行replace操作,避免部分浏览器的表现不一致 if (delta > 0) { router.go(-delta) @@ -94,7 +102,7 @@ function reLaunch (options = {}) { { path: options.url, query: { - reLaunchCount + routeCount } }, () => { @@ -123,12 +131,12 @@ function switchTab (options = {}) { webHandleFail(res, options.fail, options.complete) return Promise.reject(res) } - const delta = router.stack.length - 1 router.__mpxAction = { type: 'switch', path: options.url, replaced: false } + const delta = router.stack.length - 1 if (delta > 0) { router.go(-delta) } else { diff --git a/packages/core/@types/index.d.ts b/packages/core/@types/index.d.ts index b21e2d3a9f..f688652d79 100644 --- a/packages/core/@types/index.d.ts +++ b/packages/core/@types/index.d.ts @@ -31,18 +31,22 @@ type ArrayType = T extends Array ? R : never; // Mpx types type Data = object | (() => object) -type PropType = StringConstructor | NumberConstructor | BooleanConstructor | ObjectConstructor | ArrayConstructor | null +type PropConstructor = { + new (...args: any[]): T & {}; +} | { + (): T; +} -interface PropOpt { - type: PropType - optionalTypes?: Array - value?: any +export type PropType = PropConstructor - observer? (value: any, old: any, changedPath: string): void +type FullPropType = { + type: PropType; + value?: T; + optionalTypes?: PropType[]; } interface Properties { - [key: string]: WechatMiniprogram.Component.AllProperty + [key: string]: WechatMiniprogram.Component.AllProperty | PropType | FullPropType } interface Methods { @@ -78,8 +82,12 @@ type PropValueType = Def extends { } ? T : Def extends (...args: any[]) => infer T - ? T - : any; + ? T + : Def extends FullPropType + ? T + : Def extends PropType + ? T + : any; type GetPropsType = { readonly [K in keyof T]: PropValueType diff --git a/packages/core/package.json b/packages/core/package.json index 39a4fe13b9..fab2d08d11 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@mpxjs/core", - "version": "2.8.40", + "version": "2.8.47", "description": "mpx runtime core", "keywords": [ "miniprogram", diff --git a/packages/core/src/platform/builtInMixins/pageRouteMixin.js b/packages/core/src/platform/builtInMixins/pageRouteMixin.js index 39a0d4ba6a..1ca7318b21 100644 --- a/packages/core/src/platform/builtInMixins/pageRouteMixin.js +++ b/packages/core/src/platform/builtInMixins/pageRouteMixin.js @@ -4,6 +4,19 @@ export default function pageRouteMixin (mixinType) { return { beforeCreate () { this.route = this.$options.__mpxPageRoute || '' + }, + methods: { + getOpenerEventChannel () { + const router = global.__mpxRouter + const eventChannel = router && router.eventChannelMap[this.route] + return eventChannel || {} + } + } + } + } + return { + methods: { + getOpenerEventChannel () { } } } diff --git a/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js b/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js index 9e3d86e0c1..62cd045032 100644 --- a/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js +++ b/packages/core/src/platform/builtInMixins/proxyEventMixin.web.js @@ -18,11 +18,6 @@ export default function proxyEventMixin () { const originValue = valuePath.reduce((acc, cur) => acc[cur], $event.detail) const value = filterMethod ? (innerFilter[filterMethod] ? innerFilter[filterMethod](originValue) : typeof this[filterMethod] === 'function' && this[filterMethod]) : originValue setByPath(this, expr, value) - }, - getOpenerEventChannel () { - const router = global.__mpxRouter - const eventChannel = router && router.__mpxAction && router.__mpxAction.eventChannel - return eventChannel } } } diff --git a/packages/core/src/platform/builtInMixins/refsMixin.web.js b/packages/core/src/platform/builtInMixins/refsMixin.web.js index 2d1e5550d4..343b33df81 100644 --- a/packages/core/src/platform/builtInMixins/refsMixin.web.js +++ b/packages/core/src/platform/builtInMixins/refsMixin.web.js @@ -8,7 +8,7 @@ function getEl (ref) { function processRefs (refs) { Object.keys(refs).forEach((key) => { - const matched = /^__mpx_ref_([^_]+)__$/.exec(key) + const matched = /^__mpx_ref_(.+)__$/.exec(key) const rKey = matched && matched[1] if (rKey) { const ref = refs[key] diff --git a/packages/core/src/vuePlugin.js b/packages/core/src/vuePlugin.js index c68427ad6b..9fb59bdd73 100644 --- a/packages/core/src/vuePlugin.js +++ b/packages/core/src/vuePlugin.js @@ -26,6 +26,6 @@ export default function install (Vue) { return createSelectorQuery().in(this) } Vue.prototype.createIntersectionObserver = function (component, options) { - return createIntersectionObserver(component, options) + return createIntersectionObserver(this, options) } } diff --git a/packages/size-report/README.md b/packages/size-report/README.md index 4c43a15c8e..90fdcbf31e 100644 --- a/packages/size-report/README.md +++ b/packages/size-report/README.md @@ -43,6 +43,8 @@ const MpxSizeReportPlugin = require('@mpxjs/size-report') port: 0, // 本地服务端口,非必填,默认 0(随机端口) host: '127.0.0.1', // 本地服务host,非必填 }, + // group 阈值校验时忽略该分包体积,总包体积校验时也会忽略该分包体积 + ignoreSubpackages: ['new-homepage'], // 体积报告生成后输出的文件地址名,路径相对为 dist/wx 或者 dist/ali filename: '../report.json', // 配置阈值,此处代表总包体积阈值为 16MB,分包体积阈值为 2MB,超出将会触发编译报错提醒,该报错不阻断构建 @@ -62,8 +64,11 @@ const MpxSizeReportPlugin = require('@mpxjs/size-report') }, { name: 'pageGroup', - // 每个分组中可分别配置阈值,如果不配置则表示 - threshold: '500KB', + // 每个分组中可分别配置阈值,如果不配置则表示没任何限制 + threshold: { + size: '500KB', + preWarningSize: true// 可选项,开启分组体积阈值预警,会以warning的形式在每次构建后输出当前分组的剩余体积 + }, entryRules: { include: ['src/pages/index', 'src/pages/user'] } @@ -84,7 +89,7 @@ const MpxSizeReportPlugin = require('@mpxjs/size-report') include: [ 'src/components/tools.js' ] - } + }, // 是否收集页面维度体积详情,默认 false reportPages: true, // 是否收集资源维度体积详情,默认 false diff --git a/packages/size-report/package.json b/packages/size-report/package.json index 7df6e86bd4..77c6485096 100644 --- a/packages/size-report/package.json +++ b/packages/size-report/package.json @@ -1,6 +1,6 @@ { "name": "@mpxjs/size-report", - "version": "2.8.40", + "version": "2.8.47", "description": "mpx size report plugin", "main": "src/index.js", "scripts": { diff --git a/packages/size-report/src/SizeReportPlugin.js b/packages/size-report/src/SizeReportPlugin.js index f8782972da..31477aec9b 100644 --- a/packages/size-report/src/SizeReportPlugin.js +++ b/packages/size-report/src/SizeReportPlugin.js @@ -109,6 +109,8 @@ class SizeReportPlugin { const needEntryPathRules = this.options.needEntryPathRules || {} + const ignoreSubpackages = this.options.ignoreSubpackages + if (reportPages) { Object.entries(mpx.pagesMap).forEach(([resourcePath, name]) => { reportGroups.push({ @@ -130,7 +132,13 @@ class SizeReportPlugin { function addModuleEntryGraph (moduleId, relation) { if (typeof moduleId !== 'number') return - if (!moduleEntryGraphMap.has(moduleId)) moduleEntryGraphMap.set(moduleId, { target: !!(relation && relation.target), children: new Set(), parents: new Set() }) + if (!moduleEntryGraphMap.has(moduleId)) { + moduleEntryGraphMap.set(moduleId, { + target: !!(relation && relation.target), + children: new Set(), + parents: new Set() + }) + } const value = moduleEntryGraphMap.get(moduleId) if (Array.isArray(relation.children)) { @@ -289,6 +297,7 @@ class SizeReportPlugin { const entrySet = getEntrySet(reportGroup.entryModules, reportGroup.ignoreSubEntry) Object.assign(reportGroup, entrySet, { selfSize: 0, + ignoreSelfSize: 0, selfSizeInfo: {}, sharedSize: 0, sharedSizeInfo: {}, @@ -321,6 +330,9 @@ class SizeReportPlugin { }) })) { reportGroup.selfSize += fillInfo.size + if (ignoreSubpackages && ignoreSubpackages.includes(packageName)) { + reportGroup.ignoreSelfSize += fillInfo.size + } return fillSizeInfo(reportGroup.selfSizeInfo, packageName, fillType, fillInfo) } else if (has(noEntryModules, (noEntryModule) => { return reportGroup.noEntryModules.has(noEntryModule) @@ -335,6 +347,9 @@ class SizeReportPlugin { return reportGroup.selfEntryModules.has(entryModule) })) { reportGroup.selfSize += fillInfo.size + if (ignoreSubpackages && ignoreSubpackages.includes(packageName)) { + reportGroup.ignoreSelfSize += fillInfo.size + } return fillSizeInfo(reportGroup.selfSizeInfo, packageName, fillType, fillInfo) } else if (has(entryModules, (entryModule) => { return reportGroup.selfEntryModules.has(entryModule) || reportGroup.sharedEntryModules.has(entryModule) @@ -360,6 +375,7 @@ class SizeReportPlugin { } } } + divideEquallySize(sharedModulesGroupsSet, fillInfo.size) divideEquallySize(customGroupSharedModulesGroupsSet, fillInfo.size) } @@ -481,7 +497,8 @@ class SizeReportPlugin { totalSize: 0, staticSize: 0, chunkSize: 0, - copySize: 0 + copySize: 0, + webpackTemplateSize: 0 } function fillPackagesSizeInfo (packageName, size) { @@ -579,7 +596,6 @@ class SizeReportPlugin { packageName, size, modules: [] - // webpackTemplateSize: 0 } assetsSizeInfo.assets.push(chunkAssetInfo) fillPackagesSizeInfo(packageName, size) @@ -617,8 +633,7 @@ class SizeReportPlugin { chunkAssetInfo.modules.push(moduleData) size -= moduleSize } - - // chunkAssetInfo.webpackTemplateSize = size + sizeSummary.webpackTemplateSize += size // filter sourcemap } else if (!/\.m?js\.map$/i.test(name)) { // static copy assets such as project.config.json @@ -647,15 +662,15 @@ class SizeReportPlugin { function checkThreshold (threshold, size, sizeInfo, reportGroupName) { const sizeThreshold = normalizeThreshold(threshold.size || threshold) - const preWarningThreshold = normalizeThreshold(threshold.preWarningSize || threshold) + const preWarningSize = threshold.preWarningSize const packagesThreshold = threshold.packages const prefix = reportGroupName ? `${reportGroupName}体积分组` : '总包' if (sizeThreshold && size && size > sizeThreshold) { compilation.errors.push(`${prefix}的总体积(${size}B)超过设定阈值(${sizeThreshold}B),共${(size - sizeThreshold) / 1024}kb,请检查!`) } - if (preWarningThreshold && size && size > preWarningThreshold) { - compilation.warnings.push(`${prefix}的总体积(${size}B)超过设定预警阈值(${preWarningThreshold}B),共${(size - preWarningThreshold) / 1024}kb,请注意!`) + if (preWarningSize && size && size < sizeThreshold) { + compilation.warnings.push(`当前${prefix}的总体积 ${size / 1024}kb,${prefix}的体积阈值为${sizeThreshold / 1024}kb, 共剩余${(sizeThreshold - size) / 1024}kb,请注意!`) } if (packagesThreshold && sizeInfo) { @@ -671,12 +686,24 @@ class SizeReportPlugin { } if (this.options.threshold) { - checkThreshold(this.options.threshold, sizeSummary.totalSize, packagesSizeInfo) + let filterIgnoreTotalSize = sizeSummary.totalSize + if (ignoreSubpackages) { + ignoreSubpackages.forEach(ignoreName => { + if (packagesSizeInfo[ignoreName]) { + filterIgnoreTotalSize -= packagesSizeInfo[ignoreName] + } + }) + } + checkThreshold(this.options.threshold, filterIgnoreTotalSize, packagesSizeInfo) } reportGroups.forEach((reportGroup) => { if (reportGroup.threshold) { - checkThreshold(reportGroup.threshold, reportGroup.selfSize, reportGroup.selfSizeInfo, reportGroup.name || 'anonymous group') + let groupSelfSize = reportGroup.selfSize + if (reportGroup.ignoreSelfSize) { + groupSelfSize -= reportGroup.ignoreSelfSize + } + checkThreshold(reportGroup.threshold, groupSelfSize, reportGroup.selfSizeInfo, reportGroup.name || 'anonymous group') } }) @@ -744,7 +771,7 @@ class SizeReportPlugin { assetsSizeInfo.assets.forEach((asset) => { if (asset.modules) sortAndFormat(asset.modules) }) - 'totalSize|staticSize|chunkSize|copySize'.split('|').forEach((key) => { + 'totalSize|staticSize|chunkSize|copySize|webpackTemplateSize'.split('|').forEach((key) => { sizeSummary[key] = formatSize(sizeSummary[key]) }) diff --git a/packages/webpack-plugin/lib/dependencies/ResolveDependency.js b/packages/webpack-plugin/lib/dependencies/ResolveDependency.js index 9ae4d38ddc..b56cb636fc 100644 --- a/packages/webpack-plugin/lib/dependencies/ResolveDependency.js +++ b/packages/webpack-plugin/lib/dependencies/ResolveDependency.js @@ -1,6 +1,7 @@ const NullDependency = require('webpack/lib/dependencies/NullDependency') const parseRequest = require('../utils/parse-request') const makeSerializable = require('webpack/lib/util/makeSerializable') +const { matchCondition } = require('../utils/match-condition') class ResolveDependency extends NullDependency { constructor (resource, packageName, issuerResource, range) { @@ -22,28 +23,29 @@ class ResolveDependency extends NullDependency { } getResolved () { - const { resource, packageName, compilation } = this + const { resource, packageName, compilation, issuerResource } = this if (!compilation) return '' const mpx = compilation.__mpx__ if (!mpx) return '' - const { pagesMap, componentsMap, staticResourcesMap } = mpx + const { pagesMap, componentsMap, staticResourcesMap, partialCompile } = mpx const { resourcePath } = parseRequest(resource) const currentComponentsMap = componentsMap[packageName] const mainComponentsMap = componentsMap.main const currentStaticResourcesMap = staticResourcesMap[packageName] const mainStaticResourcesMap = staticResourcesMap.main - return pagesMap[resourcePath] || currentComponentsMap[resourcePath] || mainComponentsMap[resourcePath] || currentStaticResourcesMap[resourcePath] || mainStaticResourcesMap[resourcePath] || '' + const resolveResult = pagesMap[resourcePath] || currentComponentsMap[resourcePath] || mainComponentsMap[resourcePath] || currentStaticResourcesMap[resourcePath] || mainStaticResourcesMap[resourcePath] || '' + if (!resolveResult) { + if (!partialCompile || matchCondition(resourcePath, partialCompile)) { + compilation.errors.push(new Error(`Path ${resource} is not a page/component/static resource, which is resolved from ${issuerResource}!`)) + } + } + return resolveResult } // resolved可能会动态变更,需用此更新hash updateHash (hash, context) { this.resolved = this.getResolved() - const { resource, issuerResource, compilation } = this - if (this.resolved) { - hash.update(this.resolved) - } else { - compilation.errors.push(new Error(`Path ${resource} is not a page/component/static resource, which is resolved from ${issuerResource}!`)) - } + hash.update(this.resolved) super.updateHash(hash, context) } diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js index 0b80f43124..be39a137a1 100644 --- a/packages/webpack-plugin/lib/index.js +++ b/packages/webpack-plugin/lib/index.js @@ -38,7 +38,6 @@ const FlagPluginDependency = require('./dependencies/FlagPluginDependency') const RemoveEntryDependency = require('./dependencies/RemoveEntryDependency') const RecordVueContentDependency = require('./dependencies/RecordVueContentDependency') const SplitChunksPlugin = require('webpack/lib/optimize/SplitChunksPlugin') -const PartialCompilePlugin = require('./partial-compile/index') const fixRelative = require('./utils/fix-relative') const parseRequest = require('./utils/parse-request') const { matchCondition } = require('./utils/match-condition') @@ -55,6 +54,7 @@ const jsonThemeCompilerPath = normalize.lib('json-compiler/theme') const jsonPluginCompilerPath = normalize.lib('json-compiler/plugin') const extractorPath = normalize.lib('extractor') const async = require('async') +const { parseQuery } = require('loader-utils') const stringifyLoadersAndResource = require('./utils/stringify-loaders-resource') const emitFile = require('./utils/emit-file') const { MPX_PROCESSED_FLAG, MPX_DISABLE_EXTRACTOR_CACHE } = require('./utils/const') @@ -167,6 +167,7 @@ class MpxWebpackPlugin { }, options.nativeConfig) options.webConfig = options.webConfig || {} options.partialCompile = options.mode !== 'web' && options.partialCompile + options.asyncSubpackageRules = options.asyncSubpackageRules || null options.retryRequireAsync = options.retryRequireAsync || false options.enableAliRequireAsync = options.enableAliRequireAsync || false this.options = options @@ -381,7 +382,33 @@ class MpxWebpackPlugin { let mpx if (this.options.partialCompile) { - new PartialCompilePlugin(this.options.partialCompile).apply(compiler) + function isResolvingPage (obj) { + // valid query should start with '?' + const query = parseQuery(obj.query || '?') + return query.isPage && !query.type + } + // new PartialCompilePlugin(this.options.partialCompile).apply(compiler) + compiler.resolverFactory.hooks.resolver.intercept({ + factory: (type, hook) => { + hook.tap('MpxPartialCompilePlugin', (resolver) => { + resolver.hooks.result.tapAsync({ + name: 'MpxPartialCompilePlugin', + stage: -100 + }, (obj, resolverContext, callback) => { + if (obj.path.startsWith(require.resolve('./json-compiler/default-page.mpx'))) { + return callback(null, obj) + } + if (isResolvingPage(obj) && !matchCondition(obj.path, this.options.partialCompile)) { + const infix = obj.query ? '&' : '?' + obj.query += `${infix}resourcePath=${obj.path}` + obj.path = require.resolve('./json-compiler/default-page.mpx') + } + callback(null, obj) + }) + }) + return hook + } + }) } const getPackageCacheGroup = packageName => { @@ -595,6 +622,8 @@ class MpxWebpackPlugin { removedChunks: [], forceProxyEventRules: this.options.forceProxyEventRules, enableRequireAsync: this.options.mode === 'wx' || (this.options.mode === 'ali' && this.options.enableAliRequireAsync), + partialCompile: this.options.partialCompile, + asyncSubpackageRules: this.options.asyncSubpackageRules, pathHash: (resourcePath) => { if (this.options.pathHashMode === 'relative' && this.options.projectRoot) { return hash(path.relative(this.options.projectRoot, resourcePath)) @@ -999,13 +1028,22 @@ class MpxWebpackPlugin { let request = expr.arguments[0].value const range = expr.arguments[0].range const context = parser.state.module.context - const { queryObj } = parseRequest(request) - if (queryObj.root) { + const { queryObj, resourcePath } = parseRequest(request) + let tarRoot = queryObj.root + if (!tarRoot && mpx.asyncSubpackageRules) { + for (const item of mpx.asyncSubpackageRules) { + if (matchCondition(resourcePath, item)) { + tarRoot = item.root + break + } + } + } + if (tarRoot) { // 删除root query - request = addQuery(request, {}, false, ['root']) + if (queryObj.root) request = addQuery(request, {}, false, ['root']) // 目前仅wx和ali支持require.async,ali需要开启enableAliRequireAsync,其余平台使用CommonJsAsyncDependency进行模拟抹平 if (mpx.enableRequireAsync) { - const dep = new DynamicEntryDependency(request, 'export', '', queryObj.root, '', context, range, { + const dep = new DynamicEntryDependency(request, 'export', '', tarRoot, '', context, range, { isRequireAsync: true, retryRequireAsync: !!this.options.retryRequireAsync }) @@ -1415,8 +1453,8 @@ try { }) const typeLoaderProcessInfo = { - styles: ['css-loader', wxssLoaderPath, styleCompilerPath], - template: ['html-loader', wxmlLoaderPath, templateCompilerPath] + styles: ['node_modules/css-loader', wxssLoaderPath, styleCompilerPath], + template: ['node_modules/html-loader', wxmlLoaderPath, templateCompilerPath] } // 应用过rules后,注入mpx相关资源编译loader @@ -1479,15 +1517,15 @@ try { if (mpx.mode === 'web') { const mpxStyleOptions = queryObj.mpxStyleOptions const firstLoader = loaders[0] ? toPosix(loaders[0].loader) : '' - const isPitcherRequest = firstLoader.includes('vue-loader/lib/loaders/pitcher') + const isPitcherRequest = firstLoader.includes('node_modules/vue-loader/lib/loaders/pitcher') let cssLoaderIndex = -1 let vueStyleLoaderIndex = -1 let mpxStyleLoaderIndex = -1 loaders.forEach((loader, index) => { const currentLoader = toPosix(loader.loader) - if (currentLoader.includes('css-loader') && cssLoaderIndex === -1) { + if (currentLoader.includes('node_modules/css-loader') && cssLoaderIndex === -1) { cssLoaderIndex = index - } else if (currentLoader.includes('vue-loader/lib/loaders/stylePostLoader') && vueStyleLoaderIndex === -1) { + } else if (currentLoader.includes('node_modules/vue-loader/lib/loaders/stylePostLoader') && vueStyleLoaderIndex === -1) { vueStyleLoaderIndex = index } else if (currentLoader.includes(styleCompilerPath) && mpxStyleLoaderIndex === -1) { mpxStyleLoaderIndex = index diff --git a/packages/webpack-plugin/lib/json-compiler/default-page.mpx b/packages/webpack-plugin/lib/json-compiler/default-page.mpx new file mode 100644 index 0000000000..c3d015dc4d --- /dev/null +++ b/packages/webpack-plugin/lib/json-compiler/default-page.mpx @@ -0,0 +1,3 @@ + diff --git a/packages/webpack-plugin/lib/json-compiler/helper.js b/packages/webpack-plugin/lib/json-compiler/helper.js index 6a2d2b7ebe..de64a50d3d 100644 --- a/packages/webpack-plugin/lib/json-compiler/helper.js +++ b/packages/webpack-plugin/lib/json-compiler/helper.js @@ -6,6 +6,7 @@ const parseRequest = require('../utils/parse-request') const addQuery = require('../utils/add-query') const loaderUtils = require('loader-utils') const resolve = require('../utils/resolve') +const { matchCondition } = require('../utils/match-condition') module.exports = function createJSONHelper ({ loaderContext, emitWarning, customGetDynamicEntry }) { const mpx = loaderContext.getMpx() @@ -17,6 +18,7 @@ module.exports = function createJSONHelper ({ loaderContext, emitWarning, custom const getOutputPath = mpx.getOutputPath const mode = mpx.mode const enableRequireAsync = mpx.enableRequireAsync + const asyncSubpackageRules = mpx.asyncSubpackageRules const isUrlRequest = r => isUrlRequestRaw(r, root, externals) const urlToRequest = r => loaderUtils.urlToRequest(r) @@ -45,17 +47,27 @@ module.exports = function createJSONHelper ({ loaderContext, emitWarning, custom if (resolveMode === 'native') { component = urlToRequest(component) } - resolve(context, component, loaderContext, (err, resource, info) => { if (err) return callback(err) const { resourcePath, queryObj } = parseRequest(resource) - + let placeholder = null if (queryObj.root) { // 删除root query resource = addQuery(resource, {}, false, ['root']) // 目前只有微信支持分包异步化 - if (enableRequireAsync) tarRoot = queryObj.root + if (enableRequireAsync) { + tarRoot = queryObj.root + } + } else if (!queryObj.root && asyncSubpackageRules && enableRequireAsync) { + for (const item of asyncSubpackageRules) { + if (matchCondition(resourcePath, item)) { + tarRoot = item.root + placeholder = item.placeholder + break + } + } } + const parsed = path.parse(resourcePath) const ext = parsed.ext const resourceName = path.join(parsed.dir, parsed.name) @@ -84,7 +96,7 @@ module.exports = function createJSONHelper ({ loaderContext, emitWarning, custom } const entry = getDynamicEntry(resource, 'component', outputPath, tarRoot, relativePath) - callback(null, entry) + callback(null, entry, tarRoot, placeholder) }) } @@ -101,7 +113,9 @@ module.exports = function createJSONHelper ({ loaderContext, emitWarning, custom // 增加 page 标识 page = addQuery(page, { isPage: true }) resolve(context, page, loaderContext, (err, resource) => { - if (err) return callback(err) + if (err) { + return callback(err) + } const { resourcePath, queryObj: { isFirst } } = parseRequest(resource) const ext = path.extname(resourcePath) let outputPath @@ -124,7 +138,8 @@ module.exports = function createJSONHelper ({ loaderContext, emitWarning, custom const key = [resourcePath, outputPath, tarRoot].join('|') callback(null, entry, { isFirst, - key + key, + resource }) }) } diff --git a/packages/webpack-plugin/lib/json-compiler/index.js b/packages/webpack-plugin/lib/json-compiler/index.js index 445b955d92..5d96d1bb0c 100644 --- a/packages/webpack-plugin/lib/json-compiler/index.js +++ b/packages/webpack-plugin/lib/json-compiler/index.js @@ -38,6 +38,7 @@ 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 || '' @@ -55,6 +56,25 @@ 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') { + placeholder = { + name: placeholder + } + } + if (!placeholder.name) { + emitError('The asyncSubpackageRules configuration format of @mpxjs/webpack-plugin a is incorrect') + } + return placeholder + } + const { isUrlRequest, urlToRequest, @@ -142,22 +162,6 @@ module.exports = function (content) { } } - // 校验异步组件占位符 componentPlaceholder 不为空 - if (mpx.enableRequireAsync) { - const { usingComponents, componentPlaceholder = {} } = json - if (usingComponents) { - for (const compName in usingComponents) { - const compPath = usingComponents[compName] - if (!/\?root=/g.test(compPath)) continue - const compPlaceholder = componentPlaceholder[compName] - if (!compPlaceholder) { - const errMsg = `componentPlaceholder of "${compName}" doesn't exist! \n\r` - emitError(errMsg) - } - } - } - } - // 快应用补全json配置,必填项 if (mode === 'qa' && isApp) { const defaultConf = { @@ -177,14 +181,14 @@ module.exports = function (content) { type: 'json', waterfall: true, warn: emitWarning, - error: emitError + error: emitError, + data: { + // polyfill global usingComponents & record globalComponents + globalComponents: mpx.usingComponents + } } if (!isApp) { rulesRunnerOptions.mainKey = pagesMap[resourcePath] ? 'page' : 'component' - // polyfill global usingComponents - rulesRunnerOptions.data = { - globalComponents: mpx.usingComponents - } } const rulesRunner = getRulesRunner(rulesRunnerOptions) @@ -193,22 +197,47 @@ module.exports = function (content) { rulesRunner(json) } - if (isApp && json.usingComponents) { + if (isApp) { + Object.assign(mpx.usingComponents, json.usingComponents) // 在 rulesRunner 运行后保存全局注册组件 - this._module.addPresentationalDependency(new RecordGlobalComponentsDependency(json.usingComponents, this.context)) + // todo 其余地方在使用mpx.usingComponents时存在缓存问题,要规避该问题需要在所有使用mpx.usingComponents的loader中添加app resourcePath作为fileDependency,但对于缓存有效率影响巨大 + // todo 需要考虑一种精准控制缓存的方式,仅在全局组件发生变更时才使相关使用方的缓存失效,例如按需在相关模块上动态添加request query? + this._module.addPresentationalDependency(new RecordGlobalComponentsDependency(mpx.usingComponents, this.context)) } const processComponents = (components, context, callback) => { if (components) { async.eachOf(components, (component, name, callback) => { - processComponent(component, context, { relativePath }, (err, entry) => { + processComponent(component, context, { relativePath }, (err, entry, root, placeholder) => { if (err === RESOLVE_IGNORED_ERR) { delete components[name] return callback() } if (err) return callback(err) components[name] = entry - callback() + if (root) { + 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() + } }) }, callback) } else { @@ -221,14 +250,20 @@ module.exports = function (content) { const localPages = [] const subPackagesCfg = {} const pageKeySet = new Set() - + const defaultPagePath = require.resolve('./default-page.mpx') const processPages = (pages, context, tarRoot = '', callback) => { if (pages) { + const pagesCache = [] async.each(pages, (page, callback) => { - processPage(page, context, tarRoot, (err, entry, { isFirst, key } = {}) => { + processPage(page, context, tarRoot, (err, entry, { isFirst, key, resource } = {}) => { if (err) return callback(err === RESOLVE_IGNORED_ERR ? null : err) if (pageKeySet.has(key)) return callback() + if (resource.startsWith(defaultPagePath)) { + pagesCache.push(entry) + return callback() + } pageKeySet.add(key) + if (tarRoot && subPackagesCfg) { subPackagesCfg[tarRoot].pages.push(entry) } else { @@ -241,7 +276,18 @@ module.exports = function (content) { } callback() }) - }, callback) + }, () => { + if (tarRoot && subPackagesCfg) { + if (!subPackagesCfg[tarRoot].pages.length && pagesCache[0]) { + subPackagesCfg[tarRoot].pages.push(pagesCache[0]) + } + } else { + if (!localPages.length && pagesCache[0]) { + localPages.push(pagesCache[0]) + } + } + callback() + }) } else { callback() } diff --git a/packages/webpack-plugin/lib/loader.js b/packages/webpack-plugin/lib/loader.js index 7b5312fd94..9061f21083 100644 --- a/packages/webpack-plugin/lib/loader.js +++ b/packages/webpack-plugin/lib/loader.js @@ -120,7 +120,6 @@ module.exports = function (content) { let usingComponents = [].concat(Object.keys(mpx.usingComponents)) let componentPlaceholder = [] - let componentGenerics = {} if (parts.json && parts.json.content) { @@ -134,18 +133,12 @@ module.exports = function (content) { } if (!isApp) { rulesRunnerOptions.mainKey = pagesMap[resourcePath] ? 'page' : 'component' - // polyfill global usingComponents - // 预读json时无需注入polyfill全局组件 - // rulesRunnerOptions.data = { - // globalComponents: mpx.usingComponents - // } } - + const rulesRunner = getRulesRunner(rulesRunnerOptions) try { const ret = JSON5.parse(parts.json.content) + if (rulesRunner) rulesRunner(ret) if (ret.usingComponents) { - const rulesRunner = getRulesRunner(rulesRunnerOptions) - if (rulesRunner) rulesRunner(ret) usingComponents = usingComponents.concat(Object.keys(ret.usingComponents)) } if (ret.componentPlaceholder) { diff --git a/packages/webpack-plugin/lib/native-loader.js b/packages/webpack-plugin/lib/native-loader.js index 64c81893a6..e025cc428f 100644 --- a/packages/webpack-plugin/lib/native-loader.js +++ b/packages/webpack-plugin/lib/native-loader.js @@ -137,6 +137,7 @@ module.exports = function (content) { } catch (e) { return callback(e) } + let usingComponents = Object.keys(mpx.usingComponents) const rulesRunnerOptions = { mode, srcMode, @@ -147,16 +148,10 @@ module.exports = function (content) { } if (!isApp) { rulesRunnerOptions.mainKey = pagesMap[resourcePath] ? 'page' : 'component' - // polyfill global usingComponents - // 预读json时无需注入polyfill全局组件 - // rulesRunnerOptions.data = { - // globalComponents: mpx.usingComponents - // } } - let usingComponents = Object.keys(mpx.usingComponents) + const rulesRunner = getRulesRunner(rulesRunnerOptions) + if (rulesRunner) rulesRunner(json) if (json.usingComponents) { - const rulesRunner = getRulesRunner(rulesRunnerOptions) - if (rulesRunner) rulesRunner(json) usingComponents = usingComponents.concat(Object.keys(json.usingComponents)) } const { diff --git a/packages/webpack-plugin/lib/partial-compile/index.js b/packages/webpack-plugin/lib/partial-compile/index.js deleted file mode 100644 index 3896e74558..0000000000 --- a/packages/webpack-plugin/lib/partial-compile/index.js +++ /dev/null @@ -1,35 +0,0 @@ -const { matchCondition } = require('../utils/match-condition') -const { parseQuery } = require('loader-utils') - -class MpxPartialCompilePlugin { - constructor (condition) { - this.condition = condition - } - - isResolvingPage (obj) { - // valid query should start with '?' - const query = obj.query || '?' - return parseQuery(query).isPage - } - - apply (compiler) { - compiler.resolverFactory.hooks.resolver.intercept({ - factory: (type, hook) => { - hook.tap('MpxPartialCompilePlugin', (resolver) => { - resolver.hooks.result.tapAsync({ - name: 'MpxPartialCompilePlugin', - stage: -100 - }, (obj, resolverContext, callback) => { - if (this.isResolvingPage(obj) && !matchCondition(obj.path, this.condition)) { - obj.path = false - } - callback(null, obj) - }) - }) - return hook - } - }) - } -} - -module.exports = MpxPartialCompilePlugin diff --git a/packages/webpack-plugin/lib/platform/json/wx/index.js b/packages/webpack-plugin/lib/platform/json/wx/index.js index f0ec54bd1b..182e96a421 100644 --- a/packages/webpack-plugin/lib/platform/json/wx/index.js +++ b/packages/webpack-plugin/lib/platform/json/wx/index.js @@ -70,6 +70,13 @@ module.exports = function getSpec ({ warn, error }) { return input } + function fillGlobalComponents (input, { globalComponents }) { + if (globalComponents) { + Object.assign(globalComponents, input.usingComponents) + } + return input + } + // 处理 ali swan 的组件名大写字母转连字符:WordExample/wordExample -> word-example function componentNameCapitalToHyphen (type) { return function (input) { @@ -318,6 +325,20 @@ module.exports = function getSpec ({ warn, error }) { tt: deletePath(), jd: deletePath(true) }, + { + test: 'usingComponents', + ali: componentNameCapitalToHyphen('usingComponents'), + swan: componentNameCapitalToHyphen('usingComponents') + }, + { + test: 'usingComponents', + // todo ali 2.0已支持全局组件,待移除 + ali: fillGlobalComponents, + qq: fillGlobalComponents, + swan: fillGlobalComponents, + tt: fillGlobalComponents, + jd: fillGlobalComponents + }, { test: 'usingComponents', // todo ali 2.0已支持全局组件,待移除 diff --git a/packages/webpack-plugin/lib/platform/template/wx/component-config/hypen-tag-name.js b/packages/webpack-plugin/lib/platform/template/wx/component-config/hypen-tag-name.js index 61c3f701cb..a6f7dace79 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/component-config/hypen-tag-name.js +++ b/packages/webpack-plugin/lib/platform/template/wx/component-config/hypen-tag-name.js @@ -1,14 +1,10 @@ const { capitalToHyphen } = require('../../../../utils/string') module.exports = function () { - function convertTagName (name) { - return capitalToHyphen(name) - } - return { // tag name contains capital letters test: /[A-Z]/, - ali: convertTagName, - swan: convertTagName + ali: capitalToHyphen, + swan: capitalToHyphen } } diff --git a/packages/webpack-plugin/lib/platform/template/wx/component-config/index.js b/packages/webpack-plugin/lib/platform/template/wx/component-config/index.js index 5121a16e60..c19ceb4d1a 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/component-config/index.js +++ b/packages/webpack-plugin/lib/platform/template/wx/component-config/index.js @@ -8,7 +8,7 @@ const checkbox = require('./checkbox') const coverImage = require('./cover-image') const coverView = require('./cover-view') const form = require('./form') -const HyphenTagName = require('./hypen-tag-name') +const hyphenTagName = require('./hypen-tag-name') const icon = require('./icon') const image = require('./image') const input = require('./input') @@ -118,7 +118,7 @@ module.exports = function getComponentConfigs ({ warn, error }) { camera({ print }), livePlayer({ print }), livePusher({ print }), - HyphenTagName({ print }), + hyphenTagName({ print }), component() ] } diff --git a/packages/webpack-plugin/lib/platform/template/wx/index.js b/packages/webpack-plugin/lib/platform/template/wx/index.js index ac0ae2cfae..46281db0d8 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/index.js +++ b/packages/webpack-plugin/lib/platform/template/wx/index.js @@ -190,17 +190,10 @@ module.exports = function getSpec ({ warn, error }) { } const styleBinding = [] el.isStyleParsed = true - el.attrsList.forEach((item) => { + // 不过滤的话每一个属性都要 parse + el.attrsList.filter(item => this.test.test(item.name)).forEach((item) => { const parsed = parseMustache(item.value) - if (item.name === 'style') { - if (parsed.hasBinding || parsed.result.indexOf('rpx') > -1) { - styleBinding.push(parseMustache(item.value).result) - } else { - styleBinding.push(JSON.stringify(item.value)) - } - } else if (item.name === 'wx:style') { - styleBinding.push(parseMustache(item.value).result) - } + styleBinding.push(parsed.result) }) return { name: ':style', @@ -295,18 +288,39 @@ module.exports = function getSpec ({ warn, error }) { }, swan ({ name, value }, { eventRules }) { const match = this.test.exec(name) + const prefix = match[1] const eventName = match[2] - runRules(eventRules, eventName, { mode: 'swan' }) + const modifierStr = match[3] || '' + const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'swan' }) + const rEventName = runRules(eventRules, eventName, { mode: 'swan' }) + return { + name: rPrefix + rEventName + modifierStr, + value + } }, qq ({ name, value }, { eventRules }) { const match = this.test.exec(name) + const prefix = match[1] const eventName = match[2] - runRules(eventRules, eventName, { mode: 'qq' }) + const modifierStr = match[3] || '' + const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'qq' }) + const rEventName = runRules(eventRules, eventName, { mode: 'qq' }) + return { + name: rPrefix + rEventName + modifierStr, + value + } }, jd ({ name, value }, { eventRules }) { const match = this.test.exec(name) + const prefix = match[1] const eventName = match[2] - runRules(eventRules, eventName, { mode: 'jd' }) + const modifierStr = match[3] || '' + const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'jd' }) + const rEventName = runRules(eventRules, eventName, { mode: 'jd' }) + return { + name: rPrefix + rEventName + modifierStr, + value + } }, // tt ({ name, value }, { eventRules }) { // const match = this.test.exec(name) @@ -322,15 +336,33 @@ module.exports = function getSpec ({ warn, error }) { // }, tt ({ name, value }, { eventRules }) { const match = this.test.exec(name) + const prefix = match[1] const eventName = match[2] - runRules(eventRules, eventName, { mode: 'tt' }) + const modifierStr = match[3] || '' + const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'tt' }) + const rEventName = runRules(eventRules, eventName, { mode: 'tt' }) + return { + name: rPrefix + rEventName + modifierStr, + value + } }, dd ({ name, value }, { eventRules }) { const match = this.test.exec(name) + const prefix = match[1] const eventName = match[2] - runRules(eventRules, eventName, { mode: 'dd' }) + const modifierStr = match[3] || '' + const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'dd' }) + const rEventName = runRules(eventRules, eventName, { mode: 'dd' }) + return { + name: rPrefix + rEventName + modifierStr, + value + } }, web ({ name, value }, { eventRules, el }) { + if (parseMustache(value).hasBinding) { + error('Web environment does not support mustache binding in event props!') + return + } const match = this.test.exec(name) const prefix = match[1] const eventName = match[2] diff --git a/packages/webpack-plugin/lib/runtime/base.styl b/packages/webpack-plugin/lib/runtime/base.styl index 1796bab6db..38b6414eb7 100644 --- a/packages/webpack-plugin/lib/runtime/base.styl +++ b/packages/webpack-plugin/lib/runtime/base.styl @@ -120,8 +120,3 @@ html, body, .app { font-family "weui" src url('data:application/octet-stream;base64,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') format('truetype') } - -.mpx-root-view { - display: inline - line-height: normal -} \ No newline at end of file diff --git a/packages/webpack-plugin/lib/runtime/components/web/mpx-keep-alive.vue b/packages/webpack-plugin/lib/runtime/components/web/mpx-keep-alive.vue index fcd5a475d9..3fc40eceb5 100644 --- a/packages/webpack-plugin/lib/runtime/components/web/mpx-keep-alive.vue +++ b/packages/webpack-plugin/lib/runtime/components/web/mpx-keep-alive.vue @@ -30,9 +30,7 @@ } function getVnodeKey (vnode) { - if (vnode && vnode.componentOptions) { - return vnode.componentOptions.Ctor.cid + (vnode.componentOptions.tag ? ('::' + (vnode.componentOptions.tag)) : '') - } + return vnode.tag + (vnode.key ? `::${vnode.key}` : '') } export default { @@ -44,12 +42,13 @@ if (!isBrowser) { return vnode || (slot && slot[0]) } - const vnodeKey = getVnodeKey(vnode) const router = global.__mpxRouter - if (vnodeKey && router && vnode.data.routerView) { + if (router) { + // 存在routeCount的情况下修改vnode.key避免patch时复用旧节点实例 + if (router.currentRoute.query.routeCount) vnode.key = router.currentRoute.query.routeCount + const vnodeKey = getVnodeKey(vnode) if (router.needCache) { router.needCache.vnode = vnode - router.needCache.vnodeKey = vnodeKey router.needCache = null } @@ -69,25 +68,17 @@ const stack = router.stack if (stack.length) { - // 只要历史栈缓存中存在对应的页面存活实例,就进行复用 + // 只要历史栈缓存中存在对应的页面存活实例且vnodeKey相同,就进行复用 for (let i = stack.length; i > 0; i--) { const current = stack[i - 1] - if (current.vnode && current.vnodeKey === vnodeKey && current.vnode.componentInstance) { + if (current.vnode && getVnodeKey(current.vnode) === vnodeKey && current.vnode.componentInstance) { vnode.componentInstance = current.vnode.componentInstance - // 避免组件实例复用但是vnode.key不一致带来的bad case - vnode.key = current.vnode.key break } } } - if (router.__mpxAction) { - if (router.__mpxAction.type === 'reLaunch') { - // reLaunch时修改新vnode的key, 确保任何情况下都新创建组件实例 - vnode.key = (vnode.key || '') + router.__mpxAction.reLaunchCount - } - router.__mpxAction = null - } + if (router.__mpxAction) router.__mpxAction = null vnode.data.keepAlive = true } diff --git a/packages/webpack-plugin/lib/runtime/components/web/mpx-scroll-view.vue b/packages/webpack-plugin/lib/runtime/components/web/mpx-scroll-view.vue index 3a10e80e6c..4bdc171c9d 100644 --- a/packages/webpack-plugin/lib/runtime/components/web/mpx-scroll-view.vue +++ b/packages/webpack-plugin/lib/runtime/components/web/mpx-scroll-view.vue @@ -113,7 +113,7 @@ }, watch: { scrollIntoView (val) { - this.bs && this.bs.scrollToElement('#' + val, this.scrollWithAnimation ? 200 : 0) + this.scrollToView(val, this.scrollWithAnimation ? 200 : 0) }, _scrollTop (val) { this.bs && this.bs.scrollTo(this.bs.x, -val, this.scrollWithAnimation ? 200 : 0) @@ -201,9 +201,7 @@ leading: true, trailing: false })) - if (this.scrollIntoView) { - this.bs.scrollToElement('#' + this.scrollIntoView) - } + if (this.scrollIntoView) this.scrollToView(this.scrollIntoView) // 若开启自定义下拉刷新 或 开启 scroll-view 增强特性 if (this.refresherEnabled || this.enhanced) { const actionsHandlerHooks = this.bs.scroller.actionsHandler.hooks @@ -258,6 +256,12 @@ } } }, + scrollToView (id, duration = 0) { + if (!id) return + id = '#' + id + if (!document.querySelector(id)) return // 不存在元素时阻断,直接调用better-scroll的方法会报错 + this.bs?.scrollToElement(id, duration) + }, initLayerComputed () { const wrapper = this.$refs.wrapper const wrapperWidth = wrapper.offsetWidth diff --git a/packages/webpack-plugin/lib/runtime/optionProcessor.js b/packages/webpack-plugin/lib/runtime/optionProcessor.js index a20c7a67b5..725bc370c5 100644 --- a/packages/webpack-plugin/lib/runtime/optionProcessor.js +++ b/packages/webpack-plugin/lib/runtime/optionProcessor.js @@ -59,6 +59,7 @@ export default function processOption ( global.__mpxRouter.stack = [] global.__mpxRouter.needCache = null global.__mpxRouter.needRemove = [] + global.__mpxRouter.eventChannelMap = {} // 处理reLaunch中传递的url并非首页时的replace逻辑 global.__mpxRouter.beforeEach(function (to, from, next) { let action = global.__mpxRouter.__mpxAction @@ -97,24 +98,15 @@ export default function processOption ( }) } } else { - let methods = '' - switch (action.type) { - case 'to': - methods = 'navigateTo' - break - case 'redirect': - methods = 'redirectTo' - break - case 'back': - methods = 'navigateBack' - break - case 'reLaunch': - methods = 'reLaunch' - break - default: - methods = 'navigateTo' + const typeMethodMap = { + to: 'navigateTo', + redirect: 'redirectTo', + back: 'navigateBack', + switch: 'switchTab', + reLaunch: 'reLaunch' } - throw new Error(`${methods}:fail page "${to.path}" is not found`) + const method = typeMethodMap[action.type] + throw new Error(`${method}:fail page "${to.path}" is not found`) } } @@ -126,6 +118,7 @@ export default function processOption ( case 'to': stack.push(insertItem) global.__mpxRouter.needCache = insertItem + if (action.eventChannel) global.__mpxRouter.eventChannelMap[to.path.slice(1)] = action.eventChannel break case 'back': global.__mpxRouter.needRemove = stack.splice(stack.length - action.delta, action.delta) @@ -166,7 +159,7 @@ export default function processOption ( return next({ path: action.path, query: { - reLaunchCount: action.reLaunchCount + routeCount: action.routeCount }, replace: true }) diff --git a/packages/webpack-plugin/lib/template-compiler/compiler.js b/packages/webpack-plugin/lib/template-compiler/compiler.js index 33379f1490..eb24a0bac4 100644 --- a/packages/webpack-plugin/lib/template-compiler/compiler.js +++ b/packages/webpack-plugin/lib/template-compiler/compiler.js @@ -878,6 +878,7 @@ function moveBaseDirective (target, from, isDelete = true) { } function stringify (str) { + if (mode === 'web') str = str.replace(/'/g, '"') return JSON.stringify(str) } @@ -988,7 +989,7 @@ function parseFuncStr2 (str) { if (subIndex) { const index1 = ret.index + subIndex const index2 = index1 + 6 - args = args.substring(0, index1) + JSON.stringify(eventIdentifier) + args.substring(index2) + args = args.substring(0, index1) + stringify(eventIdentifier) + args.substring(index2) } } return { @@ -1016,7 +1017,7 @@ function stringifyWithResolveComputed (modelValue) { computedStack.push(char) if (computedStack.length === 1) { fragment += '.' - result.push(JSON.stringify(fragment)) + result.push(stringify(fragment)) fragment = '' continue } @@ -1033,7 +1034,7 @@ function stringifyWithResolveComputed (modelValue) { fragment += char } if (fragment !== '') { - result.push(JSON.stringify(fragment)) + result.push(stringify(fragment)) } return result.join('+') } @@ -1696,7 +1697,7 @@ function processWebExternalClassesHack (el, options) { options.externalClasses.forEach((className) => { const index = classNames.indexOf(className) if (index > -1) { - replacements.push(`$attrs[${JSON.stringify(className)}]`) + replacements.push(`$attrs[${stringify(className)}]`) classNames.splice(index, 1) } }) @@ -1730,13 +1731,13 @@ function processWebExternalClassesHack (el, options) { options.externalClasses.forEach((className) => { const index = classNames.indexOf(className) if (index > -1) { - replacements.push(`$attrs[${JSON.stringify(className)}]`) + replacements.push(`$attrs[${stringify(className)}]`) classNames.splice(index, 1) } }) if (classNames.length) { - replacements.unshift(JSON.stringify(classNames.join(' '))) + replacements.unshift(stringify(classNames.join(' '))) } addAttrs(el, [{ diff --git a/packages/webpack-plugin/lib/utils/ts-loader-watch-run-loader-filter.js b/packages/webpack-plugin/lib/utils/ts-loader-watch-run-loader-filter.js index 7da499a3b0..eb93027bc4 100644 --- a/packages/webpack-plugin/lib/utils/ts-loader-watch-run-loader-filter.js +++ b/packages/webpack-plugin/lib/utils/ts-loader-watch-run-loader-filter.js @@ -1,10 +1,9 @@ module.exports = (loaders, loaderIndex) => { - for (let len = loaders.length; len > 0; --len) { - const currentLoader = loaders[len - 1] - if (currentLoader.path.endsWith('ts-loader/dist/stringify-loader.js')) { - break + for (let i = loaderIndex; i >= 0; i--) { + const currentLoader = loaders[i] + if (currentLoader.path.endsWith('node_modules/ts-loader/dist/stringify-loader.js')) { + return i } - loaderIndex-- } return loaderIndex } diff --git a/packages/webpack-plugin/lib/wxss/compile-exports.js b/packages/webpack-plugin/lib/wxss/compile-exports.js deleted file mode 100644 index 749d229f3a..0000000000 --- a/packages/webpack-plugin/lib/wxss/compile-exports.js +++ /dev/null @@ -1,52 +0,0 @@ -const camelCase = require('lodash.camelcase') - -function dashesCamelCase (str) { - return str.replace(/-+(\w)/g, function (match, firstLetter) { - return firstLetter.toUpperCase() - }) -} - -module.exports = function compileExports (result, importItemMatcher, camelCaseKeys) { - if (!Object.keys(result.exports).length) { - return '' - } - - const exportJs = Object.keys(result.exports).reduce(function (res, key) { - let valueAsString = JSON.stringify(result.exports[key]) - valueAsString = valueAsString.replace(result.importItemRegExpG, importItemMatcher) - - function addEntry (k) { - res.push('\t' + JSON.stringify(k) + ': ' + valueAsString) - } - - let targetKey - switch (camelCaseKeys) { - case true: - addEntry(key) - targetKey = camelCase(key) - if (targetKey !== key) { - addEntry(targetKey) - } - break - case 'dashes': - addEntry(key) - targetKey = dashesCamelCase(key) - if (targetKey !== key) { - addEntry(targetKey) - } - break - case 'only': - addEntry(camelCase(key)) - break - case 'dashesOnly': - addEntry(dashesCamelCase(key)) - break - default: - addEntry(key) - break - } - return res - }, []).join(',\n') - - return '{\n' + exportJs + '\n}' -} diff --git a/packages/webpack-plugin/lib/wxss/createResolver.js b/packages/webpack-plugin/lib/wxss/createResolver.js deleted file mode 100644 index dbeb7fa052..0000000000 --- a/packages/webpack-plugin/lib/wxss/createResolver.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = function createResolver (alias) { - if (typeof alias !== 'object' || Array.isArray(alias)) { - return function (url) { - return url - } - } - - alias = Object.keys(alias).map(function (key) { - let onlyModule = false - let obj = alias[key] - if (/\$$/.test(key)) { - onlyModule = true - key = key.substr(0, key.length - 1) - } - if (typeof obj === 'string') { - obj = { - alias: obj - } - } - obj = Object.assign({ - name: key, - onlyModule: onlyModule - }, obj) - return obj - }) - - return function (url) { - alias.forEach(function (obj) { - const name = obj.name - if (url === name || (!obj.onlyModule && url.startsWith(name + '/'))) { - url = obj.alias + url.substr(name.length) - } - }) - return url - } -} diff --git a/packages/webpack-plugin/lib/wxss/css-base.js b/packages/webpack-plugin/lib/wxss/css-base.js deleted file mode 100644 index 1fe3e40c12..0000000000 --- a/packages/webpack-plugin/lib/wxss/css-base.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra - Modified by @hiyuki -*/ -// css base code, injected by the css-loader -module.exports = function (useSourceMap) { - const list = [] - - // return the list of modules as css string - list.toString = function toString () { - return this.map(function (item) { - const content = cssWithMappingToString(item, useSourceMap) - if (item[2]) { - return '@media ' + item[2] + '{' + content + '}' - } else { - return content - } - }).join('') - } - - // import a list of modules into the list - list.i = function (modules, mediaQuery) { - if (typeof modules === 'string') { - modules = [[null, modules, '']] - } - const alreadyImportedModules = {} - for (let i = 0; i < this.length; i++) { - const id = this[i][0] - if (typeof id === 'number') { - alreadyImportedModules[id] = true - } - } - for (let i = 0; i < modules.length; i++) { - const item = modules[i] - // skip already imported module - // this implementation is not 100% perfect for weird media query combinations - // when a module is imported multiple times with different media queries. - // I hope this will never occur (Hey this way we have smaller bundles) - if (typeof item[0] !== 'number' || !alreadyImportedModules[item[0]]) { - if (mediaQuery && !item[2]) { - item[2] = mediaQuery - } else if (mediaQuery) { - item[2] = '(' + item[2] + ') and (' + mediaQuery + ')' - } - list.push(item) - } - } - } - return list -} - -function cssWithMappingToString (item, useSourceMap) { - const content = item[1] || '' - const cssMapping = item[3] - if (!cssMapping) { - return content - } - - if (useSourceMap && typeof btoa === 'function') { - const sourceMapping = toComment(cssMapping) - const sourceURLs = cssMapping.sources.map(function (source) { - return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */' - }) - - return [content].concat(sourceURLs).concat([sourceMapping]).join('\n') - } - - return [content].join('\n') -} - -// Adapted from convert-source-map (MIT) -function toComment (sourceMap) { - // eslint-disable-next-line no-undef - const base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) - const data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64 - - return '/*# ' + data + ' */' -} diff --git a/packages/webpack-plugin/lib/wxss/getLocalIdent.js b/packages/webpack-plugin/lib/wxss/getLocalIdent.js deleted file mode 100644 index e3d07e7d6f..0000000000 --- a/packages/webpack-plugin/lib/wxss/getLocalIdent.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra - Modified by @hiyuki -*/ -const loaderUtils = require('loader-utils') -const path = require('path') - -module.exports = function getLocalIdent (loaderContext, localIdentName, localName, options) { - if (!options.context) { - if (loaderContext.rootContext) { - options.context = loaderContext.rootContext - } else if (loaderContext.options && typeof loaderContext.options.context === 'string') { - options.context = loaderContext.options.context - } else { - options.context = loaderContext.context - } - } - const request = path.relative(options.context, loaderContext.resourcePath) - options.content = options.hashPrefix + request + '+' + localName - localIdentName = localIdentName.replace(/\[local\]/gi, localName) - const hash = loaderUtils.interpolateName(loaderContext, localIdentName, options) - /* eslint-disable prefer-regex-literals */ - return hash.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-').replace(/^((-?[0-9])|--)/, '_$1') -} diff --git a/packages/webpack-plugin/lib/wxss/localsLoader.js b/packages/webpack-plugin/lib/wxss/localsLoader.js deleted file mode 100644 index 802199a198..0000000000 --- a/packages/webpack-plugin/lib/wxss/localsLoader.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra - Modified by @hiyuki -*/ -const loaderUtils = require('loader-utils') -const processCss = require('./processCss') -const compileExports = require('./compile-exports') -const createResolver = require('./createResolver') - -module.exports = function (content) { - if (this.cacheable) this.cacheable() - const callback = this.async() - const query = loaderUtils.getOptions(this) || {} - const moduleMode = query.modules || query.module - const camelCaseKeys = query.camelCase || query.camelcase - const resolve = createResolver(query.alias) - - processCss(content, null, { - mode: moduleMode ? 'local' : 'global', - query: query, - minimize: this.minimize, - loaderContext: this, - resolve: resolve - }, function (err, result) { - if (err) return callback(err) - - function importItemMatcher (item) { - const match = result.importItemRegExp.exec(item) - const idx = +match[1] - const importItem = result.importItems[idx] - const importUrl = importItem.url - return '" + require(' + loaderUtils.stringifyRequest(this, importUrl) + ')' + - '[' + JSON.stringify(importItem.export) + '] + "' - } - - let exportJs = compileExports(result, importItemMatcher.bind(this), camelCaseKeys) - if (exportJs) { - exportJs = 'module.exports = ' + exportJs + ';' - } - - callback(null, exportJs) - }.bind(this)) -} diff --git a/packages/webpack-plugin/lib/wxss/processCss.js b/packages/webpack-plugin/lib/wxss/processCss.js deleted file mode 100644 index 85d0257cd9..0000000000 --- a/packages/webpack-plugin/lib/wxss/processCss.js +++ /dev/null @@ -1,274 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra - Modified by @hiyuki -*/ -const formatCodeFrame = require('@babel/code-frame') -const Tokenizer = require('css-selector-tokenizer') -const postcss = require('postcss') -const loaderUtils = require('loader-utils') -const assign = require('object-assign') -const getLocalIdent = require('./getLocalIdent') - -const icssUtils = require('icss-utils') -const localByDefault = require('postcss-modules-local-by-default') -const extractImports = require('postcss-modules-extract-imports') -const modulesScope = require('postcss-modules-scope') -const modulesValues = require('postcss-modules-values') -const valueParser = require('postcss-value-parser') -const isUrlRequest = require('../utils/is-url-request') -// css-loader-parser - -const parserPlugin = function (options) { - return { - postcssPlugin: 'css-loader-parser', - Once (css) { - const imports = {} - let exports = {} - const importItems = [] - const urlItems = [] - - function replaceImportsInString (str) { - if (options.import) { - const tokens = valueParser(str) - tokens.walk(function (node) { - if (node.type !== 'word') { - return - } - const token = node.value - const importIndex = imports['$' + token] - if (typeof importIndex === 'number') { - node.value = '___CSS_LOADER_IMPORT___' + importIndex + '___' - } - }) - return tokens.toString() - } - return str - } - - if (options.import) { - css.walkAtRules(/^import$/i, function (rule) { - const values = Tokenizer.parseValues(rule.params) - let url = values.nodes[0].nodes[0] - if (url && url.type === 'url') { - url = url.url - } else if (url && url.type === 'string') { - url = url.value - } else throw rule.error('Unexpected format ' + rule.params) - if (!url.replace(/\s/g, '').length) { - return - } - values.nodes[0].nodes.shift() - const mediaQuery = Tokenizer.stringifyValues(values) - - if (isUrlRequest(url, options.root)) { - url = loaderUtils.urlToRequest(url, options.root) - } - - importItems.push({ - url: url, - mediaQuery: mediaQuery - }) - rule.remove() - }) - } - - const icss = icssUtils.extractICSS(css) - exports = icss.icssExports - Object.keys(icss.icssImports).forEach(function (key) { - const url = loaderUtils.parseString(key) - Object.keys(icss.icssImports[key]).forEach(function (prop) { - imports['$' + prop] = importItems.length - importItems.push({ - url: url, - export: icss.icssImports[key][prop] - }) - }) - }) - - Object.keys(exports).forEach(function (exportName) { - exports[exportName] = replaceImportsInString(exports[exportName]) - }) - - function isAlias (url) { - // Handle alias starting by / and root disabled - return url !== options.resolve(url) - } - - function processNode (item) { - switch (item.type) { - case 'value': - item.nodes.forEach(processNode) - break - case 'nested-item': - item.nodes.forEach(processNode) - break - case 'item': { - const importIndex = imports['$' + item.name] - if (typeof importIndex === 'number') { - item.name = '___CSS_LOADER_IMPORT___' + importIndex + '___' - } - break - } - case 'url': - if (options.url && item.url.replace(/\s/g, '').length && !/^#/.test(item.url) && (isAlias(item.url) || isUrlRequest(item.url, options.root))) { - // Strip quotes, they will be re-added if the module needs them - item.stringType = '' - delete item.innerSpacingBefore - delete item.innerSpacingAfter - const url = item.url - item.url = '___CSS_LOADER_URL___' + urlItems.length + '___' - urlItems.push({ - url: url - }) - } - break - } - } - - css.walkDecls(function (decl) { - const values = Tokenizer.parseValues(decl.value) - values.nodes.forEach(function (value) { - value.nodes.forEach(processNode) - }) - decl.value = Tokenizer.stringifyValues(values) - }) - css.walkAtRules(function (atrule) { - if (typeof atrule.params === 'string') { - atrule.params = replaceImportsInString(atrule.params) - } - }) - - options.importItems = importItems - options.urlItems = urlItems - options.exports = exports - } - } -} - -module.exports = function processCss (inputSource, inputMap, options, callback) { - const query = options.query - const root = query.root && query.root.length > 0 ? query.root.replace(/\/$/, '') : query.root - const context = query.context - const localIdentName = query.localIdentName || '[hash:base64]' - const localIdentRegExp = query.localIdentRegExp - const forceMinimize = query.minimize - const minimize = typeof forceMinimize !== 'undefined' ? !!forceMinimize : options.minimize - - const customGetLocalIdent = query.getLocalIdent || getLocalIdent - - const parserOptions = { - root: root, - mode: options.mode, - url: query.url !== false, - import: query.import !== false, - resolve: options.resolve - } - - const pipeline = postcss([ - modulesValues, - localByDefault({ - mode: options.mode, - rewriteUrl: function (global, url) { - if (parserOptions.url) { - url = url.trim() - - if (!url.replace(/\s/g, '').length || !isUrlRequest(url, root)) { - return url - } - if (global) { - return loaderUtils.urlToRequest(url, root) - } - } - return url - } - }), - extractImports(), - modulesScope({ - generateScopedName: function generateScopedName (exportName) { - return customGetLocalIdent(options.loaderContext, localIdentName, exportName, { - regExp: localIdentRegExp, - hashPrefix: query.hashPrefix || '', - context: context - }) - } - }), - parserPlugin(parserOptions) - ]) - - if (minimize) { - const cssnano = require('cssnano') - const minimizeOptions = assign({}, query.minimize); - ['zindex', 'normalizeUrl', 'discardUnused', 'mergeIdents', 'reduceIdents', 'autoprefixer', 'svgo'].forEach(function (name) { - if (typeof minimizeOptions[name] === 'undefined') { - minimizeOptions[name] = false - } - }) - pipeline.use(cssnano(minimizeOptions)) - } - - pipeline.process(inputSource, { - // we need a prefix to avoid path rewriting of PostCSS - from: '/css-loader!' + options.from, - to: options.to, - map: options.sourceMap - ? { - prev: inputMap, - sourcesContent: true, - inline: false, - annotation: false - } - : null - }).then(function (result) { - callback(null, { - source: result.css, - map: result.map && result.map.toJSON(), - exports: parserOptions.exports, - importItems: parserOptions.importItems, - importItemRegExpG: /___CSS_LOADER_IMPORT___([0-9]+)___/g, - importItemRegExp: /___CSS_LOADER_IMPORT___([0-9]+)___/, - urlItems: parserOptions.urlItems, - urlItemRegExpG: /___CSS_LOADER_URL___([0-9]+)___/g, - urlItemRegExp: /___CSS_LOADER_URL___([0-9]+)___/ - }) - }).catch(function (err) { - if (err.name === 'CssSyntaxError') { - const wrappedError = new CSSLoaderError( - 'Syntax Error', - err.reason, - err.line != null && err.column != null - ? { line: err.line, column: err.column } - : null, - err.input.source - ) - callback(wrappedError) - } else { - callback(err) - } - }) -} - -function formatMessage (message, loc, source) { - let formatted = message - if (loc) { - formatted = formatted + - ' (' + loc.line + ':' + loc.column + ')' - } - if (loc && source) { - formatted = formatted + - '\n\n' + formatCodeFrame(source, loc.line, loc.column) + '\n' - } - return formatted -} - -function CSSLoaderError (name, message, loc, source, error) { - Error.call(this) - Error.captureStackTrace(this, CSSLoaderError) - this.name = name - this.error = error - this.message = formatMessage(message, loc, source) - this.message = formatMessage(message, loc, source) -} - -CSSLoaderError.prototype = Object.create(Error.prototype) -CSSLoaderError.prototype.constructor = CSSLoaderError diff --git a/packages/webpack-plugin/package.json b/packages/webpack-plugin/package.json index 0e8c160b26..fe6dd130e8 100644 --- a/packages/webpack-plugin/package.json +++ b/packages/webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@mpxjs/webpack-plugin", - "version": "2.8.40", + "version": "2.8.47", "description": "mpx compile core", "keywords": [ "mpx"