优化前
主包将近2M了,我还用了一些插件,上传的时候也会打进主包导致上传超过2M不成功.本次优化主要是优化主包中的wxml页面,mpvue打包时会把所有vue单文件模式下的vue模板打包到主包中,如果你的分包页面或者vue组件有很多主包就会变得很大.
下图可以看到pageA分包中的页面有313Kb,这些都应该打包到分包中.不能占用主包的空间.commponents下是全局组件打包到主包中也没什么问题
开始优化
先来看看打包出来的目录结构,我的项目有1个主包pages和2个分包pagesA,pagesCharts. 优化后应该像下图这样vue.wxml打包到自己的包里去.
1.修改页面引入
需要在打包过程中修改这里的引入路径,修改成/pagesA/valueAddService/bill-list.vue.wxml,引入路径到各个自己的包里.
经过一番查找最后在mpvue-loader中找到了编译代码.做如下修改
之后的修改都是在这一个文件中完成.
function createPageMPML (emitFile, resourcePath, rootComponent, context, fileExt) {
const { src } = getFileInfo(resourcePath) || {}
const { name, filePath } = getCompInfo(context, rootComponent, fileExt)
// 分包优化1 修改page.wxml中引入的路径 修改为从各个主包或分包中引入
let newPath = filePath.replace('/xcx-xhr-co.ibanbu.com/src', '') // 这里根据你的项目打包出来的结果自行修改
const MPMLContent = genPageML(name, newPath, fileExt)
emitFile(`${src}.${fileExt.template}`, MPMLContent)
}
2.修改为优化1中的文件位置 编译到分包中减少主包的大小
function createComponentMPML ({ emitWarning, emitError, emitFile, resourcePath, context, compiled, fileExt }) {
cacheCreateMPMLFns[resourcePath] = arguments
const { pageType, moduleId, components } = getFileInfo(resourcePath) || {}
const { name, filePath } = getCompInfo(context, resourcePath, fileExt)
const options = { components, pageType, name, moduleId }
const MPMLContent = genComponentMPML(compiled, options, emitFile, emitError, emitWarning, fileExt)
// 分包优化2 修改为优化1中的文件位置 编译到分包中减少主包的大小
let newPath = filePath
if (filePath.startsWith('/xcx-xhr-co.ibanbu.com/src/pages')) { // 因为的我分包都是这个前缀,所以这么写,你们的情况可能不同就要多加判断了
newPath = filePath.replace('/xcx-xhr-co.ibanbu.com/src', '')
}
emitFile(newPath, MPMLContent)
}
这样修改后基本完成差不多了.但如果你的vue页面里引入其他vue组件的话还需要下一步修改
3.修改单vue组件中引入分包中的组件路径
还是以我的这个页面为例,页面中引入了1个components下的全局vue组件和3个pagesA下的分包组件,全局组件我们不动,只需要修改分包组件的引入路径就可以了
对优化2的createComponentMPML函数做如下添加
function createComponentMPML ({ emitWarning, emitError, emitFile, resourcePath, context, compiled, fileExt }) {
cacheCreateMPMLFns[resourcePath] = arguments
const { pageType, moduleId, components } = getFileInfo(resourcePath) || {}
// 分包优化3 修改单vue组建中引入分包中的组件路径
for (let key in components) {
if (typeof components[key] === 'object' && components[key].src) {
if (components[key].src.startsWith('/xcx-xhr-co.ibanbu.com/src/pages')) {
components[key].src = components[key].src.replace('/xcx-xhr-co.ibanbu.com/src', '')
}
}
}
const { name, filePath } = getCompInfo(context, resourcePath, fileExt)
const options = { components, pageType, name, moduleId }
const MPMLContent = genComponentMPML(compiled, options, emitFile, emitError, emitWarning, fileExt)
// 分包优化2 修改为优化1中的文件位置 编译到分包中减少主包的大小
let newPath = filePath
if (filePath.startsWith('/xcx-xhr-co.ibanbu.com/src/pages')) {
newPath = filePath.replace('/xcx-xhr-co.ibanbu.com/src', '')
}
emitFile(newPath, MPMLContent)
}
4.修改完成, 重新打包!
打包后目录结构,看到只有全局组件打包到这个文件夹下
其他的vue组件都到了自己的相应包中了
打包后代码包结构,从1.8M下降到了1.4M,之后再怎么增加页面都不会影响主包大小了.我的项目也能完美运行了~
完整修改文件
mpvue-loader@2.0.0 /node_modules/mpvue-loader/lib/mp-compiler/index.js
const babel = require('babel-core')
const path = require('path')
const fs = require('fs')
const deepEqual = require('deep-equal')
const compiler = require('mpvue-template-compiler')
const { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents } = require('./parse')
const { parseComponentsDeps: parseComponentsDepsTs } = require('./parse-ts')
const { genPageML } = require('./templates')
const {
cacheFileInfo,
getFileInfo,
getCompInfo,
resolveTarget,
covertCCVar,
cacheSlots,
getSlots,
htmlBeautify,
getBabelrc
} = require('./util')
function genComponentMPML (compiled, options, emitFile, emitError, emitWarning, fileExt) {
options.components['slots'] = { src: '/components/slots', name: 'slots' }
const { code: mpmlContent, compiled: compiledResult, slots, importCode } = compiler.compileToMPML(compiled, options, fileExt)
const { mpErrors, mpTips } = compiledResult
// 缓存 slots,延迟编译
cacheSlots(slots, importCode)
if (mpErrors && mpErrors.length) {
emitError('\n Error compiling template:\n' + mpErrors.map(e => ` - ${e}`).join('\n') + '\n')
}
if (mpTips && mpTips.length) {
emitWarning(mpTips.map(e => ` - ${e}`).join('\n') + '\n')
}
return htmlBeautify(mpmlContent)
}
function createPageMPML (emitFile, resourcePath, rootComponent, context, fileExt) {
const { src } = getFileInfo(resourcePath) || {}
const { name, filePath } = getCompInfo(context, rootComponent, fileExt)
// 分包优化1 修改page.wxml中引入的路径 修改为从各个主包或分包中引入
let newPath = filePath.replace('/xcx-xhr-co.ibanbu.com/src', '')
const MPMLContent = genPageML(name, newPath, fileExt)
emitFile(`${src}.${fileExt.template}`, MPMLContent)
}
// 更新全局组件时,需要重新生成 mpml,用这个字段保存所有需要更新的页面及其参数
const cacheCreateMPMLFns = {}
function createComponentMPML ({ emitWarning, emitError, emitFile, resourcePath, context, compiled, fileExt }) {
cacheCreateMPMLFns[resourcePath] = arguments
const { pageType, moduleId, components } = getFileInfo(resourcePath) || {}
// 分包优化3 修改单vue组建中引入分包中的组件路径
for (let key in components) {
if (typeof components[key] === 'object' && components[key].src) {
if (components[key].src.startsWith('/xcx-xhr-co.ibanbu.com/src/pages')) {
components[key].src = components[key].src.replace('/xcx-xhr-co.ibanbu.com/src', '')
}
}
}
const { name, filePath } = getCompInfo(context, resourcePath, fileExt)
const options = { components, pageType, name, moduleId }
const MPMLContent = genComponentMPML(compiled, options, emitFile, emitError, emitWarning, fileExt)
// 分包优化2 修改为优化1中的文件位置 编译到分包中减少主包的大小
let newPath = filePath
if (filePath.startsWith('/xcx-xhr-co.ibanbu.com/src/pages')) {
newPath = filePath.replace('/xcx-xhr-co.ibanbu.com/src', '')
}
emitFile(newPath, MPMLContent)
}
let slotsHookAdded = false
function compileMPML (compiled, html, options) {
const fileExt = options.fileExt
if (!slotsHookAdded) {
// avoid add hook several times during compilation
slotsHookAdded = true
// TODO: support webpack4
this._compilation.plugin('seal', () => {
const content = getSlots()
if (content.trim()) {
this.emitFile(`components/slots.${fileExt.template}`, htmlBeautify(content))
}
slotsHookAdded = false
})
}
return new Promise(resolve => {
const pollComponentsStatus = () => {
const { pageType, components } = getFileInfo(this.resourcePath) || {}
if (!pageType || (components && !components.isCompleted)) {
setTimeout(pollComponentsStatus, 20)
} else {
resolve()
}
}
pollComponentsStatus()
}).then(() => {
createComponentMPML({
emitWarning: this.emitWarning,
emitError: this.emitError,
emitFile: this.emitFile,
resourcePath: this.resourcePath,
context: this.options.context,
rootComponent: null,
compiled, html,
fileExt
})
})
}
// 针对 .vue 单文件的脚本逻辑的处理
// 处理出当前单文件组件的子组件依赖
function compileMPScript (script, mpOptioins, moduleId) {
const { resourcePath, options, resolve, context } = this
const babelrc = getBabelrc(mpOptioins.globalBabelrc)
let scriptContent = script.content
const babelOptions = { extends: babelrc, plugins: [parseComponentsDeps] }
if (script.src) {
const scriptpath = path.join(path.dirname(resourcePath), script.src)
scriptContent = fs.readFileSync(scriptpath).toString()
}
let metadata
if (script.lang === 'ts') {
metadata = parseComponentsDepsTs(scriptContent)
} else {
const result = babel.transform(scriptContent, babelOptions)
metadata = result.metadata
}
// metadata: importsMap, components
const { importsMap, components: originComponents } = metadata
// 处理子组件的信息
const components = {}
const fileInfo = resolveTarget(resourcePath, options.entry)
const callback = () => resolveComponent(resourcePath, fileInfo, importsMap, components, moduleId)
if (originComponents) {
resolveSrc(originComponents, components, resolve, context, options.context, mpOptioins.fileExt)
.then(() => callback())
.catch(err => {
console.error(err)
callback()
})
} else {
callback()
}
return script
}
// checkMPEntry 针对 entry main.js 的入口处理: 编译出 app, page 的入口js、mpml、json
let globalComponents
function compileMP (content, mpOptioins) {
const { resourcePath, emitFile, resolve, context, options } = this
const fileInfo = resolveTarget(resourcePath, options.entry)
cacheFileInfo(resourcePath, fileInfo)
const { isApp, isPage } = fileInfo
if (isApp) {
// 解析前将可能存在的全局组件清空
clearGlobalComponents()
}
const babelrc = getBabelrc(mpOptioins.globalBabelrc)
// app入口进行全局component解析
const { metadata } = babel.transform(content, { extends: babelrc, plugins: isApp ? [parseConfig, parseGlobalComponents] : [parseConfig] })
// metadata: config
const { rootComponent, globalComponents: globalComps } = metadata
if (isApp) {
// 保存旧数据,用于对比
const oldGlobalComponents = globalComponents
// 开始解析组件路径时把全局组件清空,解析完成后再进行赋值,标志全局组件解析完成
globalComponents = null
// 解析全局组件的路径
const components = {}
resolveSrc(globalComps, components, resolve, context, options.context, mpOptioins.fileExt).then(() => {
handleResult(components)
}).catch(err => {
console.error(err)
handleResult(components)
})
const handleResult = components => {
globalComponents = components
// 热更时,如果全局组件更新,需要重新生成所有的 mpml
if (oldGlobalComponents && !deepEqual(oldGlobalComponents, globalComponents)) {
// 更新所有页面的组件
Object.keys(cacheResolveComponents).forEach(k => {
resolveComponent(...cacheResolveComponents[k])
})
// 重新生成所有 mpml
Object.keys(cacheCreateMPMLFns).forEach(k => {
createComponentMPML(...cacheCreateMPMLFns[k])
})
}
}
}
if (isApp || isPage) {
// 这儿应该异步在所有的模块都清晰后再生成
// 生成入口 mpml
if (isPage && rootComponent) {
resolve(context, rootComponent, (err, rootComponentSrc) => {
if (err) return
// 这儿需要搞定 根组件的 路径
// resourcePath = C:\ibanbu.com\xcx-xhr-co.ibanbu.com\node_modules\mpvue-entry\dist\pagesIndex.js
// rootComponentSrc = C:\ibanbu.com\xcx-xhr-co.ibanbu.com\src\pages\index.vue
createPageMPML(emitFile, resourcePath, rootComponentSrc, this.options.context, mpOptioins.fileExt)
})
}
}
return content
}
function resolveSrc (originComponents, components, resolveFn, context, projectRoot, fileExt) {
return Promise.all(Object.keys(originComponents).map(k => {
return new Promise((resolve, reject) => {
resolveFn(context, originComponents[k], (err, realSrc) => {
if (err) return reject(err)
const com = covertCCVar(k)
const { filePath, name } = getCompInfo(projectRoot, realSrc, fileExt)
components[com] = { src: filePath, name }
resolve()
})
})
}))
}
const cacheResolveComponents = {}
function resolveComponent (resourcePath, fileInfo, importsMap, localComponents, moduleId) {
// 需要等待全局组件解析完成
if (!globalComponents) {
setTimeout(resolveComponent, 20, ...arguments)
} else {
// 保存当前所有参数,在热更时如果全局组件发生变化,需要进行组件更新
cacheResolveComponents[resourcePath] = arguments
const components = Object.assign({}, globalComponents, localComponents)
components.isCompleted = true
cacheFileInfo(resourcePath, fileInfo, { importsMap, components, moduleId })
}
}
module.exports = {
compileMP,
compileMPML,
compileMPScript
}