【转】关于uni-build打包小程序的源码分析
关于uni-build打包小程序的源码分析
我们使用uni-app的脚手架开发微信小程序其实已经比较方便。但是uni-app框架是怎么实现将vue项目打包成微信小程序项目,很少有人关注。关于uni-build打包小程序的源码分析。底层还是使用的webpack打包框架,uni-app实现了自己的一套算法。针对小程序所要求的文件结构,将VUE项目转换成微信小程序可识别的项目结构。看了一下源码,不是很好读,我接下来主要把找到的编译入口方法展示给大家看下,有需要的朋友可以顺着入口往里解读。
前言
小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。
一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:

一个小程序页面由四个文件组成,分别是:

注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。
https://developers.weixin.qq.com/miniprogram/dev/framework/structure.html
一、打包实现在依赖包vue-cli-plugin-uni中,入口看build.js文件

uni-build打包的build源码
async function build (args, api, options) {
const fs = require('fs-extra')
const chalk = require('chalk')
const webpack = require('webpack')
const {
log,
done,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils')
const runByAliIde = process.env.BUILD_ENV === 'ali-ide'
log()
if (!runByHBuilderX && !runByAliIde) {
logWithSpinner(`开始编译当前项目至 ${process.env.UNI_SUB_PLATFORM || process.env.UNI_PLATFORM} 平台...`)
}
// 解析文件输出目录
const targetDir = api.resolve(options.outputDir)
// 获取打包配置文件
const webpackConfigs = getWebpackConfigs(api, args, options)
// 生成环境打包,会先清理掉 node_modules/.cache 下的缓存文件
if (process.env.NODE_ENV === 'production') {
try {
fs.emptyDir(path.resolve(process.env.UNI_CLI_CONTEXT, 'node_modules/.cache'))
} catch (e) {}
}
// 有clean参数,或者是app-plus打包,则清空输出目录
if (args.clean || process.env.UNI_PLATFORM === 'app-plus') {
await fs.emptyDir(targetDir)
}
if (process.env.UNI_USING_NATIVE || process.env.UNI_USING_V3_NATIVE) {
webpackConfigs.length = 0
}
if (
process.env.UNI_USING_NATIVE ||
process.env.UNI_USING_V3_NATIVE ||
(process.UNI_NVUE_ENTRY && Object.keys(process.UNI_NVUE_ENTRY).length)
) {
webpackConfigs.push(require('@dcloudio/vue-cli-plugin-hbuilderx/build/webpack.nvue.conf.js')(process.UNI_NVUE_ENTRY))
}
return new Promise((resolve, reject) => {
webpack(webpackConfigs, (err, stats) => {
if (!runByHBuilderX && !runByAliIde) {
stopSpinner(false)
}
if (err) {
return reject(err)
}
if (stats.hasErrors()) {
/* eslint-disable prefer-promise-reject-errors */
return reject('Build failed with errors.')
}
if (!args.silent && (process.env.UNI_PLATFORM !== 'app-plus' || process.env.UNI_AUTOMATOR_WS_ENDPOINT)) {
const targetDirShort = path.relative(
api.service.context,
process.env.UNI_OUTPUT_DIR
)
if (!args.watch) {
const dirMsg = runByHBuilderX ? ''
: `The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.`
done(`Build complete. ${dirMsg}`)
if (process.env.UNI_PLATFORM === 'h5' && !isInHBuilderX) {
console.log()
console.log('欢迎将H5站部署到uniCloud前端网页托管平台,高速、免费、安全、省心,详见:')
console.log('https://uniapp.dcloud.io/uniCloud/hosting')
}
} else {
const dirMsg = runByHBuilderX ? '' : `The ${chalk.cyan(targetDirShort)} directory is ready. `
done(`Build complete. ${dirMsg}Watching for changes...`)
}
}
resolve()
})
})
}
二、打包微信小程序的代码入口地址

mp的入口文件index.js中定义了微信小程序打包的相关配置
webpackConfig (webpackConfig, vueOptions, api) {
if (!webpackConfig.optimization) {
webpackConfig.optimization = {}
}
// disable noEmitOnErrors
webpackConfig.optimization.noEmitOnErrors = false
webpackConfig.optimization.runtimeChunk = {
name: 'common/runtime'
}
webpackConfig.optimization.splitChunks = require('../split-chunks')()
parseEntry()
const statCode = process.env.UNI_USING_STAT ? 'import \'@dcloudio/uni-stat\';' : ''
const beforeCode = 'import \'uni-pages\';'
return {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
entry () {
return process.UNI_ENTRY
},
output: {
filename: '[name].js',
chunkFilename: '[id].js',
globalObject: process.env.UNI_PLATFORM === 'mp-alipay' ? 'my' : 'global'
// sourceMapFilename: '../.sourcemap/' + process.env.UNI_PLATFORM + '/[name].js.map'
},
performance: {
hints: false
},
resolve: {
extensions: ['.nvue'],
alias: { // 仅 mp-weixin
'mpvue-page-factory': require.resolve(
'@dcloudio/vue-cli-plugin-uni/packages/mpvue-page-factory')
}
},
module: {
rules: [{
test: path.resolve(process.env.UNI_INPUT_DIR, getMainEntry()),
use: [{
loader: path.resolve(__dirname, '../../packages/wrap-loader'),
options: {
before: [
beforeCode + require('../util').getAutomatorCode() + statCode
]
}
}, {
loader: '@dcloudio/webpack-uni-mp-loader/lib/main'
}]
}, {
resourceQuery: /vue&type=script/,
use: [{
loader: '@dcloudio/webpack-uni-mp-loader/lib/script'
}]
}, {
resourceQuery: /vue&type=template/,
use: [{
loader: '@dcloudio/webpack-uni-mp-loader/lib/template'
}, {
loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-app-loader/page-meta'
}]
}, createTemplateCacheLoader(api), {
resourceQuery: [
/lang=wxs/,
/lang=filter/,
/lang=sjs/,
/blockType=wxs/,
/blockType=filter/,
/blockType=sjs/
],
use: [{
loader: require.resolve(
'@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-filter-loader')
}]
}]
},
plugins: [
new WebpackUniAppPlugin(),
createUniMPPlugin(),
new webpack.ProvidePlugin(getProvides())
]
}
},
chainWebpack (webpackConfig, vueOptions, api) {
if (process.env.UNI_PLATFORM === 'mp-baidu') {
webpackConfig.module
.rule('js')
.exclude
.add(/\.filter\.js$/)
}
const compilerOptions = process.env.UNI_USING_COMPONENTS ? {} : require('../mp-compiler-options')
modifyVueLoader(webpackConfig, {}, compilerOptions, api)
const styleExt = getPlatformExts().style
webpackConfig.plugin('extract-css')
.init((Plugin, args) => new Plugin({
filename: '[name]' + styleExt
}))
if (
process.env.NODE_ENV === 'production' &&
process.env.UNI_PLATFORM !== 'app-plus'
) {
const OptimizeCssnanoPlugin = require('../../packages/@intervolga/optimize-cssnano-plugin/index.js')
webpackConfig.plugin('optimize-css')
.init((Plugin, args) => new OptimizeCssnanoPlugin({
sourceMap: false,
filter (assetName) {
return path.extname(assetName) === styleExt
},
cssnanoOptions: {
preset: [
'default',
Object.assign({}, getPlatformCssnano(), {
discardComments: true
})
]
}
}))
}
webpackConfig.plugins.delete('hmr')
webpackConfig.plugins.delete('html')
webpackConfig.plugins.delete('copy')
webpackConfig.plugins.delete('preload')
webpackConfig.plugins.delete('prefetch')
}
三、运行打包前所捕获到的 webpackConfigs 相关参数
[ { mode: 'production',
context: 'D:\\02code\\app-wechat-supply',
devtool: false,
node:
{ setImmediate: false,
process: 'mock',
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty' },
output:
{ path: 'D:\\02code\\app-wechat-supply\\dist\\mit',
filename: '[name].js',
publicPath: '/',
chunkFilename: '[id].js',
globalObject: 'global' },
resolve:
{ alias: [Object],
extensions: [Array],
modules: [Array],
plugins: [Array] },
resolveLoader: { modules: [Array], plugins: [Array] },
module:
{ noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/,
rules: [Array] },
optimization:
{ splitChunks: [Object],
minimizer: [Array],
noEmitOnErrors: false,
runtimeChunk: [Object],
namedModules: false },
plugins:
[ VueLoaderPlugin {},
[DefinePlugin],
[CaseSensitivePathsPlugin],
[FriendlyErrorsWebpackPlugin],
[MiniCssExtractPlugin],
[OptimizeCssnanoPlugin],
[HashedModuleIdsPlugin],
[NamedChunksPlugin],
[DefinePlugin],
[CopyPlugin],
[CopyPlugin],
WebpackUniAppPlugin {},
WebpackUniMPPlugin {},
[ProvidePlugin],
[CopyPlugin] ],
entry: [Function: entry],
performance: { assetFilter: [Function: assetFilter], hints: false } } ]
四、小程序打包后的项目结构

参考资料:
https://developers.weixin.qq.com/ebook?action=get_post_info&docid=000668c6910b784b00860870a5ac0a
https://developers.weixin.qq.com/miniprogram/dev/framework/structure.html
每天都是崭新的开始 ——Mr.司满(214382122)[]~( ̄▽ ̄)~*


浙公网安备 33010602011771号