前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3. 「uniapp 如何支持微信小程序环境开发」配置项简化到可以让你一盔全貌之:loader + plugin

3. 「uniapp 如何支持微信小程序环境开发」配置项简化到可以让你一盔全貌之:loader + plugin

作者头像
tinyant
发布2023-03-10 14:24:32
1.8K0
发布2023-03-10 14:24:32
举报

本节我们看下module.rules + plugin相关的配置


.vue文件的编译

当然要配vue-loader啊,.vue文件解析全靠他了。vue-loader的整体流程的分析可以参考我之前的文章:「.vue文件的编译」1. vue-loader@15.8.3 的整体流程

下面是我们当前简易demo中的vue-loader的配置

代码语言:javascript
复制
{
    test: /\.vue$/,
    use: [
        {
            loader: 'vue-loader',
            options: {
                compiler: require('./uni-template-compiler'),
                compilerOptions: { // 这里你可以先忽略,后面会单独小节分析
                    mp: {
                        platform: "mp-weixin",
                        scopedSlotsCompiler: "auto",
                    },
                    filterModules: {},
                    filterTagName: 'wxs',
                },
            }
        },
    ]
},

const { VueLoaderPlugin } = require('vue-loader')
new VueLoaderPlugin(),

uniapp实际上也对其进行了改造,并放到@dcloudio/vue-cli-plugin-uni/packages路径下,当然模板编译的核心库vue-template-compiler。也做了部分更改。

对比后发现是针对部分特性如easycom(auto-components),renderjscustom-blocks<wxs>的支持。这些都不是关键的特性,属于优化层面的,这些特性实际上可以丢掉的。所以这里我是使用原始的vue-loader

<template>块拆分为 -> .wxml文件

上面的配置中有个关键信息,即提供了 options.compileroptions.compilerOptions

代码语言:javascript
复制
options: {
    compiler: require('./uni-template-compiler'),
    compilerOptions: {
        mp: {
            platform: "mp-weixin",
            scopedSlotsCompiler: "auto",
        },
        filterModules: {},
        filterTagName: 'wxs',
    },
}

.vue文件中有一个<template>块,这个块的最终编译实际上是由templateLoader转交给vue-template-compiler处理的,vue-template-compiler是用来将模板编译成render函数,render函数返回一个虚拟DOM树。vue-template-compiler虽然是一个单独的库,但实际上是由vue源码中的 src/compiler部分构成,每次发布vue版本时对应发布这个库,一对一的,用来生成当前vue版本需要的虚拟DOM树。实际上vue也提供runtime + compiler版本,即可以线上动态编译template

代码语言:javascript
复制
// node_modules/vue-loader/lib/loaders/templateLoader.js

module.exports = function (source) {
    //...
    const compiler = options.compiler || require('vue-template-compiler')
    
    const compilerOptions = Object.assign({...}, options.compilerOptions, {...})

  // for vue-component-compiler
  const finalOptions = {
        source,
        filename: this.resourcePath,
        compiler,
        compilerOptions, ...
    }
    //...
    const compiled = compileTemplate(finalOptions)
    //...
    const { code } = compiled

    // finish with ESM exports
    return code + `\nexport { render, staticRenderFns }`
}

看到了吗,上面的compiler的来源,如果有设置options.compiler则用配置提供的,如果没有则默认使用vue-template-compiler,并且也会将我们提供的compilerOptions合并到最终的选项中,传递给构建方法compileTemplte。 因为默认的vue-template-compiler只会讲我们的<template>编译成render函数,但是小程序要的确是真真切切的wxml文件,所以啊,提供了自己的模板编译,用来生成wxml文件。

比如我们demo中的src/components/global-com.vuetempalte如下

代码语言:javascript
复制
<template>
  <div class="global-compo">  
    全局组件: {{ name }} <br />
    属性值: {{ content }}
  </div>
</template>

构建后会生成单独的wxml文件

代码语言:javascript
复制
<view class="global-compo _div">
    {{'全局组件: '+name+''}}
    <view class="_br"></view>
    {{'属性值: '+content+''}}
</view>

关于模板编译这里实际上我感觉可能是整个uniapp最难的地方(在我看来是这样),这部分我后面会单独小节分析,uniapp是如何做的,以及我自己的实现思路。

先简单说下:uniapp在这里实际上是将vue-template-compiler返回后的render函数转换成AST,而后根据每个节点的情况去处理成小程序wxml要求的规范。然后通过loaderContext.emitFile(..)来生成文件的

<style>块拆分为 -> .wxss文件

.wxss是小程序组件的样式文件。

没什么好说的:css-loader + mini-css-extract-plugin插件

代码语言:javascript
复制
// loader配置
{
    test: /\.css$/i,
    use: [MiniCssExtractPlugin.loader, "css-loader"],
},

// plugin
new MiniCssExtractPlugin({filename: "[name].wxss"}),

当然如果你想支持less/sass等,需要额外增加less-loadersass-loader等。

<script>拆分为 -> .js文件

webpack中对于模块的拆分,首先想到的就是SplitChunksPlugin,否则默认情况下,同步引用模块是不会被拆分出去的。

我想使用该插件应该也能达到目的,只要你把cacheGroups规则配置好。

但是有更好的方式使用 require.ensureimport() 方式来引用模块,这两种方式都是动态加载模块的方式,webpack会自动将相应的模块拆分出去作为单独的chunk,原理见我之前的系列文章「webpack源码分析」从dependency graph 到 chunk graph

那具体该如何做呢,毕竟开发的时候是import xxxComponent from 'xxxComponent'

看下具体的例子吧

代码语言:javascript
复制
// 我们demo中的 src/main.js 文件 注册的全局组件为例
import globalCompo from './components/global-compo.vue'
Vue.component('global-compo', globalCompo);

转换成下面

代码语言:javascript
复制
import('./components/todo-item.vue' /* webpackChunkName: "components/todo-item" */)

注意:注释里面的webpackChunkName非常重要,webpack称为magic comments,猜得出会将拆分出的新chunk命名为此或者放到这个路径下?自己感受吧

或者转化成下面

代码语言:javascript
复制
require.ensure([], () => resolve(require('./components/todo-item.vue')), 'components/todo-item')

显然我们需要在loader阶段做,解析依赖之前(parser.parse)。原因见之前的系列文章webpack源码分析」模块构建之解析_source获取dependencies

所以就有了这样的一个loader,来将.vue文件中引用的组件形式进行替换。两个大的步骤

先是要从.vue文件中解析出依赖的组件,需要注意的是解析全局组件和局部组件是两种不同的解析方式,这是由使用方式决定的。当前简化后版本实现中,两种类型组件的注册限定为下面方式:

代码语言:javascript
复制
// 全局组件注册方式如下:
// src/main.js
Vue.component()
// src/App.vue
export default {
    components: {}
}

// 局部组件注册方式如下
// .vue文件
export default {
    components: {}
}

然后替换组件的引用方式。

loader实现见; 全局组件解析局部组件解析loader

配置如下:

代码语言:javascript
复制
{
    test: `${context}/main.js`,
    use: [
        {
            loader: path.resolve(__dirname, 'mp/main.js'),
            options: {
                bridge: path.resolve(__dirname, '../../runtime/uni-mp-weixin/index')
            },
        },
    ]
},
{
    resourceQuery: /vue&amp;type=script/,
    use: [
        {
            loader: path.resolve(__dirname, 'mp/script.js')
        }
    ]
},

看到上面第一个loader提供了一个选项,这个会在本节后面会分析到,不要着急哈。


细节:如果你只是做上面的转化会发现构建的代码运行会报错: 因为你使用了document.create('script'),这是由于动态加载js就是这么做的。而实际上,小程序组件的加载是框架加载的,因为我们将上述动态加载的逻辑放在一个不会执行的函数中,就好了。不如

代码语言:javascript
复制
var todoItem = function(){
    import(.....)
}

显然这里应该 -> .json文件

我这里把小程序里面的.json文件暂时分为三类

  1. app.json
  2. 页面或者组件的json
  3. project.config.json、sitemap.json 等配置文件

上面第三种在当前实现中没有做特殊处理,就是直接复制,因此引入了copy-webpack-plugin插件

当前的设计是(借鉴uniapp):src/下有一个app.json(uniapp是pages.json),然后和小程序官方配置的区别是,页面路径的配置,官方是字符串数组,我们这里改为对象数组,目的是为了设置各页面的style等配置。如下:

app.json和各页面组件的json文件基于该文件生成,在构建过程中的唯一变化是会修改usingComponents 这个是在上一部分解析组件引用的情况时会保存下来。 最后实现了一个插件来输出这些.json文件

逻辑比较简单,不深入分析了,代码在这mp-plugin

全局文件 app.js、app.wxss

这两个文件是手动生成的,调用compilation.assets就行

插件代码在这中的generateApp方法

逻辑比较简单,不分析了。

运行时

到目前为止一直在说构建的问题,如何把.vue文件构建成小程序组件的形式,但是显然最终的运行怎么能离开运行时代码呢?

运行时实际上包括两个

  1. vue运行时,响应式,自定义事件等逻辑,还是来自vue提供的特性。
  2. 桥:连接 vue运行时和小程序框架

运行时代码的分析还是会单独小节分析,这里只是说如何将运行时模块注入到最终的产物中。

实际上处理也很简单,利用中间代码来加入额外的模块依赖,见

代码语言:javascript
复制
const loaderUtils = require('loader-utils')
const processComponents = require('./babel/index');

// 暂时只解析 Vue.component('', todoItem) 写法
const traverse = require('./babel/global-component-traverse');

module.exports = async function (content) {
    this.cacheable &amp;&amp; this.cacheable();
    if (this.resourceQuery) {
       //... 分析组件依赖,替换组件为动态导入,更新json.usingComponents
    } else {
        const options = loaderUtils.getOptions(this);
        // 注意通过?main让 src/main.js下次被解析是走上面的if分支
        return [`import Vue from 'vue'`, `import '${options.bridge}'`, `import '${this.resourcePath}?main'`].join('\n');
    }
}

options.bridge在上面提到过,文件的本地连接。import Vue from 'vue'结合resolve.alias

代码语言:javascript
复制
resolve: {
    alias: {
        "vue$": path.resolve(__dirname, "../runtime/mp-vue/mp.runtime.esm.js")
    }
},

因为bridge这个运行时暴露了将createAppcreatePagecreateComponent直接挂载到了wx这个全局对象上,并且我对他们也是直接这么使用的即 wx.createXxx。需要知道运行时是整个框架最底层的依赖,必须是最前置的,当然webpack生成的runtime实际上是模块化机制,当然更加底层了,所以app.js得第一行就是require('./common/runtime.js')

但是bridge运行时还暴露很多uni上的api,如uni.showToast等,这些是模块内的。因此做法是通过ProvidePlugin来自动导入依赖的变量

代码语言:javascript
复制
new webpack.ProvidePlugin({
    createApp:       [mpRuntime, 'createApp'],
    createComponent: [mpRuntime, 'createComponent'],
    createPage:      [mpRuntime, 'createPage'],
    uni:             [mpRuntime, 'default']
})

总结

  1. 重点是 vue 文件如何转换为小程序组件结构
  1. 配置文件 app.json主要来自开发者自己的配置,其中的usingComponents会在解析src/main.jssrc/App.vue文件时收集到全局组件并最终更新到app.json中。
  2. 运行时相关:【vue】运行时和【桥】运行时,我的做法是直接作为底层依赖,首先被加载。(uniapp不是这么做的)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • .vue文件的编译
    • <template>块拆分为 -> .wxml文件
      • <style>块拆分为 -> .wxss文件
        • <script>拆分为 -> .js文件
          • 显然这里应该 -> .json文件
          • 全局文件 app.js、app.wxss
          • 运行时
          • 总结
          相关产品与服务
          云开发 CloudBase
          云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档