我們使用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