1. 「uniapp 如何支持微信小程序环境开发」初探uniapp为此做了哪些努力?

我心飞翔 分类:vue

uniapp 是什么?

uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台。

我们当前项目中使用unaipp进行微信小程序的开发,版本是2.0.1-32920211122003。好奇uniapp是如何做到这层转换的。因此从uniapp中抠出了核心逻辑(做了很多简化工作,为了你能更好的理解啊),并写了一个简易demo,来一探究竟。

mock的所有代码已上传到个人github,见mock-uniapp-for-wxmp

构建前后的目录结构对比

构建前

构建后

1. 看到除了App.vue 这个vue文件,其他的vue文件如 todo-item.vue,home/main.vue,sell/index.vue 都被转换成了小程序的页面结构(wxml、js、wxss、json)。
2. 其他的一些应用配置文件(app.json等)还是保留着在(为了简化整体构建过程而做的努力)。实际上在官方脚手架中是pages.json + manifest.json => app.json、project.config.json。
3. app.js和app.wxss来自哪❓,common/main.js,runime.js,vendor.js又是什么❓

uniapp底层是基于webpack进行构建的,我们只需要在调用webpack方法之前拿到配置,并分析每个配置中的作用,就基本能还原大致的过程了。构建的实际入口在下面文件中

// node_modules/@dcloudio/vue-cli-plugin-uni/commands/build.js
async function build (args, api, options) {
  //....
    
  return new Promise((resolve, reject) => {
    webpack(webpackConfigs, (err, stats) => { /*..*/ })
  })
}

我们项目中的配置如下:

下面分析下核心的配置

entry

{
    common/main: "/Users/songyu/tencent/doctor-uni/src/main.js",
    pages/home/main: "/Users/songyu/tencent/doctor-uni/src/main.js?{\"page\":\"pages%2Fhome%2Fmain\"}"
}

入口是main.js和所有的${page}.vue(所有的页面,而不是组件,当然也不包括App.vue

optimization

{
  "splitChunks": {
    "cacheGroups": {
      "default": false,
      "vendors": false,
      "commons": {
        "minChunks": 1,
        "name": "common/vendor",
        "chunks": "all",
        "test": function (module) { ... }
      }
    }
  },
  "runtimeChunk": {
    "name": "common/runtime"
  }
}

这个配置实际上解释了产物中的common/runtime.jscommon/vendor.js的来源了。

module

module.noParse

/^(vue|vue-router|vuex|vuex-router-sync)$/

Prevent webpack from parsing any files matching the given regular expression(s). Ignored files should not have calls to importrequiredefine or any other importing mechanism. This can boost build performance when ignoring large libraries.

module.rules

test、resourceQuery

name

remark(关键?)

[/.vue$/, /.nvue$/]

@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/index.js:vue-loader @15.8.3

uniapp改造了vue-loader,并通过options.compiler来自定义模板编译部分。✅

/.m?jsx?$/

babel-loader: @8.2.2 @dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader

what is babel: convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments。✅ what is webpack-preprocess-loader : Bring the awesome "Conditional Compilation" to the Webpack, and more.

.../src/pages.json

babel-loader @dcloudio/webpack-uni-pages-loader

后者:生成小程序需要的app.json、project.config.json。✅

.../src/main.js

wrap-loader @dcloudio/webpack-uni-mp-loader/lib/main

前者:Adds custom content before and after the loaded source。✅ 后者:1. 从main.js中提取全局组件。2. 向${page}.vue和main.js注入代码(运行时需要)。✅

/vue&type=script/

@dcloudio/webpack-uni-mp-loader/lib/script

收集来自App.vue和${page}.vue中的组件并缓存起来到xxx.json(全局或者局部组件,usingComponents)✅

/vue&type=template/

@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader

条件编译

/vue&type=template/

@dcloudio/webpack-uni-mp-loader/lib/template @dcloudio/vue-cli-plugin-uni/packages/webpack-uni-app-loader/page-meta

前者:收集全局组件包括main.js和pages.json中声明的✅,而后传递给vue-loader.options,目的是在模板解析过程中需要判断用到了哪些全局组件(为了支持其它不支持全局组件的场景,微信小程序不需要) 后者:支持微信小程序<page-meta>特性

/type=uni-cache-loader-template/

ident: "uni-cache-loader-template-options",loader: cache-loader

The cache-loader allow to Caches the result of following loaders on disk (default) or in the database.

[/lang=wxs/, /lang=filter/, /lang=sjs/, /blockType=wxs/, /blockType=filter/, /blockType=sjs/]

@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-filter-loader/index.js

支持微信小程序WXS特性

还有很多和静态资源以及样式相关的rules,显然对于整体的构建流程没什么用,必须忽略啊。

plugins

plugin

remark

VueLoaderPlugin

和上面说到的vue-loader是配合使用的✅

MiniCssExtractPlugin

This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.

内置插件: WebpackUniAppPlugin

easycom 特性相关

内置插件: WebpackUniMPPlugin

app.js、app.wxss、app.json、${component/page}.json输出;添加小程序组件的自动注册逻辑。✅

webpack.DefinePlugin

replaces variables in your code with other values or expressions at compile time.

webpack.ProvidePlugin

Automatically load modules instead of having to import or require them everywhere.

CopyWebpackPlugin

Copies individual files or entire directories, which already exist, to the build directory.

FriendlyErrorsWebpackPlugin

recognizes certain classes of webpack errors and cleans, aggregates and prioritizes them to provide a better Developer Experience.

CaseSensitivePathsPlugin

This Webpack plugin enforces the entire path of all required modules match the exact case of the actual path on disk. Using this plugin helps alleviate cases where developers working on OSX, which does not follow strict path case sensitivity, will cause conflicts with other developers or build boxes running other operating systems which require correctly cased paths.

webpack.SourceMapDevToolPlugin

This plugin enables more fine grained control of source map generation. It is also enabled automatically by certain settings of the devtool configuration option.

resolve & resolveLoader

resolveresolveLoader webpack构建过程所有加载的资源都需要转化为本地路况,有一个专门的模块enhanced-reolve(可以参考enhanced-reolve源码分析),来解析资源路径,资源会区分普通的模块和loader场景,resolve和resoveLoader就用来辅助解析的。

// resolve
{
    alias: {
        //...
        vue$: "@dcloudio/vue-cli-plugin-uni/packages/mp-vue",
        vuex: "@dcloudio/vue-cli-plugin-uni/packages/vuex3/dist/vuex.common.js"
    }
}

// resolveLoader
{
    alias: {
        //...
        vue-loader: "@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/index.js"
    }
}

由于小程序环境和web环境差异比较大,不能直接使用web环境的运行时,小程序中的vue时是被改造过的(其实主要是阉割虚拟DOM部分,修改patch函数,patch函数从dom-diff变为了data-diff),因此通过resolve.alias来指向修改后的文件。比如这样就会将import Vue from 'vue'从默认查找node_modules/vue变更为查找@dcloudio/vue-cli-plugin-uni/packages/vuex3/dist/vuex.common.js

uniapp也对很多第三方库进行了修改,在node_modules/@dcloudio/vue-cli-plugin-uni/packages路径下面,包括vue-loader@vue/component-compiler-utilsvuex等等。

但是在我们在配置loader时如vue-loader时,是如下写法:

{
    loader: "vue-loader",
}

但是此时希望被查找的是被改后的vue-loader,因此通过resolveLoader.alias配置指向修改后的vue-loader@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/index.js

另外还有一个有趣的点,如被修改后vue-loader也引用了被修改后的第三方库如vue-template-compiler等,此时的这些模块的查找并不会走webpack,而后走node自己的模块查找机制,显然默认情况下肯定是找的node_modules下安装的模块,因此uniapp在初始化的时候会通过module-alias这个库来修改node的默认查找规则。

// node_modules/@dcloudio/vue-cli-plugin-uni/lib/env.js
const moduleAlias = require('module-alias')
//...
moduleAlias.addAlias('vue-template-compiler', '@dcloudio/vue-cli-plugin-uni/packages/vue-template-compiler')
//...

module-alias:Create aliases of directories and register custom module paths in NodeJS like a boss!

总结

实际上想了解或者实现uniapp如何做到支持构建到小程序环境的,涉及到对小程序webpackvue多个方向的熟悉,包含了运行和构建两个大的方向。

当前小节只是通过webpack的关键配置项如entrymodule.rulesplugins来初步了解uniapp为了做了哪些事情。下一小节重点分析我简化后的配置,以及做了哪些变更。

回复

我来回复
  • 暂无回复内容