Skip to content

Commit

Permalink
Merge pull request #3 from didi/master
Browse files Browse the repository at this point in the history
merge code.
  • Loading branch information
nianxiongdi committed Feb 7, 2022
2 parents 18e646b + 0cb7ce7 commit 8e26843
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 35 deletions.
25 changes: 14 additions & 11 deletions docs-vuepress/articles/2.7-release.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Mpx2.7版本正式发布,大幅提升编译构建速度

[Mpx](https://www.mpxjs.cn/)是一款开源的增强型跨端小程序框架,它具有良好的开发体验,极致的应用性能和一份源码同构输出所有小程序平台及web环境的跨端能力。近期,我们发布了框架最新的2.7版本,基于`webpack5`彻底重构了框架的核心编译构建流程,利用持久化缓存大幅提升了编译构建速度,最高提升可达10倍。除此之外,`Mpx@2.7`版本还带来了一系列重要的功能更新,包括分包输出能力增强,完善的单元测试支持和用户Rules应用等,下面会对这些新特性进行逐个展开介绍。
> 作者:[hiyuki](https://github.com/hiyuki)
[Mpx](https://www.mpxjs.cn/)是一款开源的增强型跨端小程序框架,它具有良好的开发体验,极致的应用性能和一份源码同构输出所有小程序平台及web环境的跨端能力。近期,我们发布了框架最新的2.7版本,基于`webpack5`彻底重构了框架的核心编译构建流程,利用持久化缓存大幅提升了编译构建速度,最高提升可达10倍。除此之外,`Mpx2.7`版本还带来了一系列重要的功能更新,包括分包输出能力增强,完善的单元测试支持和用户Rules应用等,下面会对这些新特性进行逐个展开介绍。

## 编译构建提速

Expand All @@ -10,20 +12,21 @@

直到2020年底,`webpack5`正式发布,基于文件系统持久化缓存能力的出现,让我们看到了该问题的解决之道。由于webpack5相较于webpack4有着非常多的升级改动,而Mpx的编译构建在过去版本中也存在着各种历史遗留问题,我们花了较长的时间吃透webpack5的源码以及重新思考Mpx中存在的不合理设计。经过3个多月的开发,我们彻底重构了Mpx的核心编译构建流程,使其能够**完整安全地**利用webpack5持久化缓存能力进行构建提速,同时也彻底解决了旧版Mpx中存在的历史遗留问题,在去年10月完成了beta版的开发与发布,并在业务中进行了初步的落地尝试。之后又经过2个多月的业务回归测试和框架功能补全,我们在beta版的基础上又迭代修复了近30个patch版本,在业务上完成了充分的回归测试,让我们确信目前的新版本已经达到了能够release的阶段。

> 为什么不是`Vite`
> 聊起编译耗时,可能大部分人首先想到的是Vite,诚然Vite是一个极富创造力的技术方案,但是在小程序场景下,却不一定是最合适的方案,这主要源于Vite最核心的设计利用了现代浏览器原生支持的ESM,而目前没有任何一家的小程序环境能够原生支持ESM,这就使得Vite最核心的按需编译能力无法得到发挥,而Vite使用esbuild带来的编译速度提升,在webpack环境中也可以选择esbuild-loader提供的能力来替换babel/terser,而且目前esbuild提供的编译能力成熟度还远不能和babel/terser相比,再加上Mpx的编译构建流程很大程度上依赖了webpack提供的能力,从成本和收益上考虑采用webpack5对于我们来说无疑是更好的方案。
> 为什么不是`Vite`
>
> 聊起编译提速,可能大部分人首先想到的是Vite,诚然Vite是一个极富创造力的技术方案,但是在小程序场景下,却不一定是最合适的方案,这主要源于Vite最核心的设计利用了现代浏览器原生支持的ESM,而目前没有任何一家的小程序环境能够原生支持ESM,这就使得Vite最核心的按需编译能力无法得到发挥,而Vite使用esbuild带来的编译速度提升,在webpack环境中也可以选择esbuild-loader提供的能力来替换babel/terser,而且目前esbuild提供的编译能力成熟度还远不能和babel/terser相比,再加上Mpx的编译构建流程很大程度上依赖了webpack提供的能力,从成本和收益上考虑采用webpack5对于我们来说无疑是更好的方案。
以团队负责的小程序项目为例,我们来看一下Mpx@2.7版本带来的编译提速成果:相较于旧版长达15分钟的构建耗时,在无缓存环境下,新版的构建耗时约为8分钟,速度提升了近1倍;而在有缓存环境下,新版构建耗时可以缩短至80s,速度提升了**10倍以上**!随着我们对CI流程的持久化缓存改造完成,可以确保在日常的大部分构建场景都会在有缓存的环境下进行。
以团队负责的小程序项目为例,我们来看一下Mpx2.7版本带来的编译提速效果:相较于旧版长达15分钟的构建耗时,在无缓存环境下,新版的构建耗时约为8分钟,速度提升了近1倍;而在有缓存环境下,新版构建耗时可以缩短至80s,速度提升了**10倍以上**!随着我们对CI流程的持久化缓存改造完成,可以确保在日常的大部分构建场景都会在有缓存的环境下进行。

## 分包输出能力增强

在Mpx@2.7版本中,我们对小程序分包能力的支持进行了进一步的完善增强。
在Mpx2.7版本中,我们对小程序分包能力的支持进行了进一步的完善增强。

### 独立分包初始化模块

在过去的版本中,我们对独立分包进行过专门的构建支持,以满足独立分包资源独占的要求。不过在使用独立分包进行业务开发时,往往会面临的一个棘手问题在于初始化逻辑无处安放。这是由于独立分包没有app.js,而在小程序中,组件的js逻辑会早于页面js执行,具体的执行顺序又和组件的嵌套关系有关,因此我们无法找到一个确定的代码位置来存放独立分包的初始化逻辑。

在Mpx@2.7版本中,我们针对独立分包新增了一个全新的增强特性,让用户能够声明独立分包的初始化模块,该模块将会在独立分包启动时全局最先执行,其实现思路大致如下:在构建时为独立分包中所有的组件和页面都添加模块引用,指向用户声明的初始化模块,这样在独立分包启动时,不管哪个组件/页面的js最先执行,都能保障这个初始化模块最先执行,同时由于模块缓存的存在,后续的组件/页面执行时,该模块也不会被重复执行。
在Mpx2.7版本中,我们针对独立分包新增了一个全新的增强特性,让用户能够声明独立分包的初始化模块,该模块将会在独立分包启动时全局最先执行,其实现思路大致如下:在构建时为独立分包中所有的组件和页面都添加模块引用,指向用户声明的初始化模块,这样在独立分包启动时,不管哪个组件/页面的js最先执行,都能保障这个初始化模块最先执行,同时由于模块缓存的存在,后续的组件/页面执行时,该模块也不会被重复执行。

该特性的详细使用方式可以参考我们的[文档说明](https://www.mpxjs.cn/guide/advance/subpackage.html#独立分包)

Expand All @@ -37,7 +40,7 @@

![分包异步化资源引用](https://dpubstatic.udache.com/static/dpubimg/c7ac0180-6914-46c6-aaed-c1d3cface8e1.jpeg)

我们在Mpx@2.7版本中对分包异步化中最常用的`跨分包自定义组件引用`进行了完整支持,与原生小程序不同,Mpx中资源的分包归属不由源码位置决定,而是由资源引用关系决定,因此在跨分包资源引用的场景下,用户需要声明引用的资源属于哪个分包,简单使用示例如下:
我们在Mpx2.7版本中对分包异步化中最常用的`跨分包自定义组件引用`进行了完整支持,与原生小程序不同,Mpx中资源的分包归属不由源码位置决定,而是由资源引用关系决定,因此在跨分包资源引用的场景下,用户需要声明引用的资源属于哪个分包,简单使用示例如下:

```json5
{
Expand All @@ -60,7 +63,7 @@

## 单元测试支持

Mpx自从20年开始就对单元测试有了初步的支持,但过去的单测方案在设计上存在一些缺陷,可用性不高,业务落地困难,在Mpx@2.7版本中,我们设计了一套全新的技术方案,克服了原有方案中存在的所有问题,在可用性上得到了质的飞跃,新旧方案的对比如下:
Mpx自从20年开始就对单元测试有了初步的支持,但过去的单测方案在设计上存在一些缺陷,可用性不高,业务落地困难,在Mpx2.7版本中,我们设计了一套全新的技术方案,克服了原有方案中存在的所有问题,在可用性上得到了质的飞跃,新旧方案的对比如下:

旧方案:通过Mpx编译构建预先将完整的项目源码构建输出为原生小程序格式,再通过jest + miniprogram-simulate加载构建产出的原生小程序组件来执行测试case。该方案的优点在于编译流程统一,方案实现成本较低,缺点在于执行任何case都需要执行完整的构建流程,耗时较长;而且由于构建本身不基于jest进行,也无法使用jest提供的模块mock功能。

Expand All @@ -78,13 +81,13 @@ Mpx自从20年开始就对单元测试有了初步的支持,但过去的单测

Mpx的单文件支持很大程度上参考了`vue-loader`的设计,在vue-loader@15版本前,对于单文件组件中各个区块(block)的loaders应用逻辑默认内置在loader当中,如需对某些区块进行自定义配置,需要向loader的options中传递额外的loader应用规则,无法复用webpack配置的`module.rules`中已经定义好的规则,这往往会导致我们需要在loader options和module.rules中维护重复的loader规则,同样的问题也存在于旧版的`mpx-loader`中。

在vue-loader@15版本发布之后,其通过克隆用户原始的rules的方式实现了module.rules的复用,用户不再需要往loader options中传递冗余的loaders规则,本次全新Mpx@2.7版本也支持了该特性,我们使用了webpack提供的`matchResource`能力实现了module.rules的复用,该方案相较于clone rules的方式实现起来更加简洁优雅。
在vue-loader@15版本发布之后,其通过克隆用户原始的rules的方式实现了module.rules的复用,用户不再需要往loader options中传递冗余的loaders规则,本次全新Mpx2.7版本也支持了该特性,我们使用了webpack提供的`matchResource`能力实现了module.rules的复用,该方案相较于clone rules的方式实现起来更加简洁优雅。

以上就是Mpx@2.7版本的核心更新内容,目前使用脚手架工具创建的项目默认都会使用2.7版本,过往项目如需迁移升级可以查看[这篇指南](https://www.mpxjs.cn/guide/migrate/2.7.html)
以上就是Mpx2.7版本的核心更新内容,目前使用脚手架工具创建的项目默认都会使用2.7版本,过往项目如需迁移升级可以查看[这篇指南](https://www.mpxjs.cn/guide/migrate/2.7.html)

## 即将到来的新特性

以上就是我们本次Mpx@2.7版本带来的全部特性,后续我们也有计划撰写一系列文章对其中的技术细节进行更加深入的分析与介绍,除了上面介绍到的特性外,我们还有一系列特性处于即将发布的状态:
以上就是我们本次Mpx2.7版本带来的全部特性,后续我们也有计划撰写一系列文章对其中的技术细节进行更加深入的分析与介绍,除了上面介绍到的特性外,我们还有一系列特性处于即将发布的状态:
* [组件维度运行时渲染方案](https://github.com/didi/mpx/pull/919)
* [页面局部构建能力](https://github.com/didi/mpx/pull/924)
* E2E自动化测试能力
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"packages": [
"packages/*"
],
"version": "2.7.2"
"version": "2.7.3"
}
3 changes: 2 additions & 1 deletion packages/webpack-plugin/lib/extractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ module.exports.pitch = async function (remainingRequest) {
skipEmit: true,
extractedInfo: {
content: `@import "${relativePath}";\n`,
index: -1
index,
pre: true
}
})
}
Expand Down
28 changes: 23 additions & 5 deletions packages/webpack-plugin/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,23 @@ class MpxWebpackPlugin {
mpx.assetsModulesMap.set(filename, modules)
})

const fillExtractedAssetsMap = (assetsMap, { index, content }, filename) => {
if (assetsMap.has(index)) {
if (assetsMap.get(index) !== content) {
compilation.errors.push(new Error(`The extracted file [${filename}] is filled with same index [${index}] and different content:
old content: ${assetsMap.get(index)}
new content: ${content}
please check!`))
}
} else {
assetsMap.set(index, content)
}
}

const sortExtractedAssetsMap = (assetsMap) => {
return [...assetsMap.entries()].sort((a, b) => a[0] - b[0]).map(item => item[1])
}

compilation.hooks.beforeModuleAssets.tap('MpxWebpackPlugin', () => {
const extractedAssetsMap = new Map()
for (const module of compilation.modules) {
Expand All @@ -846,19 +863,19 @@ class MpxWebpackPlugin {
if (extractedInfo) {
let extractedAssets = extractedAssetsMap.get(filename)
if (!extractedAssets) {
extractedAssets = []
extractedAssets = [new Map(), new Map()]
extractedAssetsMap.set(filename, extractedAssets)
}
extractedAssets.push(extractedInfo)
fillExtractedAssetsMap(extractedInfo.pre ? extractedAssets[0] : extractedAssets[1], extractedInfo, filename)
compilation.hooks.moduleAsset.call(module, filename)
}
}
}

for (const [filename, extractedAssets] of extractedAssetsMap) {
const sortedExtractedAssets = extractedAssets.sort((a, b) => a.index - b.index)
for (const [filename, [pre, normal]] of extractedAssetsMap) {
const sortedExtractedAssets = [...sortExtractedAssetsMap(pre), ...sortExtractedAssetsMap(normal)]
const source = new ConcatSource()
sortedExtractedAssets.forEach(({ content }) => {
sortedExtractedAssets.forEach((content) => {
if (content) {
// 处理replace path
if (/"mpx_replace_path_.*?"/.test(content)) {
Expand Down Expand Up @@ -1344,6 +1361,7 @@ try {
const fs = compiler.intermediateFileSystem
const cacheLocation = compiler.options.cache.cacheLocation
return new Promise((resolve) => {
if (!cacheLocation) return resolve()
if (typeof fs.rm === 'function') {
fs.rm(cacheLocation, {
recursive: true,
Expand Down
25 changes: 11 additions & 14 deletions packages/webpack-plugin/lib/wxs/WxsModuleIdsPlugin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const {
compareModulesByPreOrderIndexOrIdentifier
} = require('webpack/lib/util/comparators')
const { assignAscendingModuleIds } = require('webpack/lib/ids/IdHelpers')
const {
assignAscendingModuleIds,
getUsedModuleIdsAndModules
} = require('webpack/lib/ids/IdHelpers')

/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Module")} Module */
Expand All @@ -12,19 +15,13 @@ class WxsModuleIdsPlugin {
name: 'WxsModuleIdsPlugin',
// 放在最前面执行,确保生成的代码模块为数组形式,符合wxs规范
stage: -1000
}, modules => {
const chunkGraph = compilation.chunkGraph
const modulesInNaturalOrder = Array.from(modules)
.filter(
m =>
m.needId &&
chunkGraph.getNumberOfModuleChunks(m) > 0 &&
chunkGraph.getModuleId(m) === null
)
.sort(
compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph)
)
assignAscendingModuleIds(modulesInNaturalOrder, compilation)
}, () => {
const [usedIds, modulesInNaturalOrder] =
getUsedModuleIdsAndModules(compilation)
modulesInNaturalOrder.sort(
compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph)
)
assignAscendingModuleIds(usedIds, modulesInNaturalOrder, compilation)
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/webpack-plugin/lib/wxss/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module.exports = function (content, map) {
alreadyImported[imp.url] = true
}
return true
}).map((imp) => {
}).map((imp, i) => {
if (!isUrlRequest(imp.url, root, externals)) {
return 'exports.push([module.id, ' +
JSON.stringify('@import url(' + imp.url + ');') + ', ' +
Expand All @@ -75,7 +75,7 @@ module.exports = function (content, map) {
isStatic: true,
issuerFile: mpx.getExtractedFile(this.resource),
fromImport: true
})
}, i)
return 'exports.push([module.id, ' +
JSON.stringify('@import "') +
'+ require(' + requestString + ') +' +
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mpxjs/webpack-plugin",
"version": "2.7.2",
"version": "2.7.3",
"description": "mpx compile core",
"keywords": [
"mpx"
Expand Down

0 comments on commit 8e26843

Please sign in to comment.