1.0.0 • Published 2 years ago

uniapp-wmp-clone-with-data-patch-webpack-plugin v1.0.0

Weekly downloads
-
License
ISC
Repository
-
Last release
2 years ago

使用方法

  1. 安装插件 npm i -D uniapp-wmp-clone-with-data-patch-webpack-plugin
  2. 在项目根目录中添加 vue.config.js 文件并且配置插件
  3. 根据需要配置参数
// vue.config.js
const CloneWithDataPatchWebpackPlugin = require("uniapp-wmp-clone-with-data-patch-webpack-plugin");
module.exports = {
  chainWebpack: (config) => {
    config
        .plugin("cloneWithDataPatch")
        .use(CloneWithDataPatchWebpackPlugin, [
          // 【****注意****】
          // 下边这些参数都 不是必须的!! 这里写上也只是为了说明参数,并不是实际需要这样配置才行
          // 实际上,没有下列参数说明的那些情况最好不要进行任何配置
          {
            /*
              待注入方法的数组 【一般不需要修改】
                1. 方法必须包含方法名,不能是匿名方法
                2. 必须包含一个方法名叫copy的方法
                3. 这些方法在同一个平级作用域
              	4. utils.copy, utils.typeOf, utils.each 是内置的 copy 以及其依赖的方法。不满足自身需求的可以自行配置
            */
            injectMethods: [utils.copy, utils.typeOf, utils.each],
            /*
              获取说明文件输出路径的方法 【一般不需要修改】
                1. 会用此文件的存在与否来检测是否有添加过补丁
                2. 默认和 mp.runtime.esm.js 文件同级
                3. 最好是放在HBuilderX的软件目录下,这样版本更新的时候会被删除,从而通过检测重新添加补丁
                4. 可以通过 this.cwd 获取HBuilderX的Node运行时的路径
            */
            getFlagFilePath() {
              return PATH.join(this.wmpRuntimePath, "cloneWithDataPatch.txt");
            },
            /*
              获取待修复文件的路径的方法 【一般不需要修改】
                1. 应该只有在版本更新获取不到 mp.runtime.esm.js 这个文件的时候去修改为正确的路径
                2. 可以通过 this.cwd 获取HBuilderX的Node运行时的路径
            */
            getWmpRuntimeFilePath() {
              return PATH.join(this.wmpRuntimePath, "mp.runtime.esm.js");
            },
            /*
              获取待修复文件备份路径的方法 【一般不需要修改】
                1. mp.runtime.esm.js 修改前的原始文件输出路径
                2. 默认在 mp.runtime.esm.js 同级目录下
                3. 可以通过 this.cwd 获取HBuilderX的Node运行时的路径
            */
            getWmpRuntimeFileCopyPath() {
              return PATH.join(this.wmpRuntimePath, "mp.runtime.esm.js.copy.js");
            },
          },
        ]);
  },
};

相关说明

这个插件主要用于修复 uni-app 使用 HBuilderX 构建项目的时候在微信小程序端对组件 prop 采用JSON.parse(JSON.stringify(ret))暴力序列化处理,导致的prop传递的对象或者数组中包含的方法等数据被过滤的问题。参下:

// 父组件
<template>
	<v-child :data="data"></v-child>
</template>
<script>
export default {
    data(){
        return {
            // 传递 data 并且其中包含一个方法 fn
            data: {
                id: 1,
                fn(){}
            }
        }
    }
}
</script>

// 子组件
<script>
export default {
    props: {
        data: Object
    },
    mounted(){
        // 接收 data
        // 在 H5 中正常获取 fn
        // 但是在微信小程序中 fn 为 undfined
        console.log(this.data.fn); // undefined
    }
}
</script>

问题原因在于编译到微信小程序端的时候其中HBuilderX中的一个依赖项 mp.runtime.esm.js 中的 cloneWithData 方法末尾采用了 JSON.parse(JSON.stringify(ret)) 的方式去深度复制数据。参下:

// mp.runtime.esm.js
function cloneWithData(vm) {
  var ret = Object.create(null);
  var dataKeys = [].concat(
    Object.keys(vm._data || {}),
    Object.keys(vm._computedWatchers || {}));

  dataKeys.reduce(function(ret, key) {
    ret[key] = vm[key];
    return ret
  }, ret);
  var compositionApiState = vm.__composition_api_state__ || vm.__secret_vfa_state__;
  var rawBindings = compositionApiState && compositionApiState.rawBindings;
  if (rawBindings) {
    Object.keys(rawBindings).forEach(function (key) {
      ret[key] = vm[key];
    });
  }
  Object.assign(ret, vm.$mp.data || {});
  if (
    Array.isArray(vm.$options.behaviors) &&
    vm.$options.behaviors.indexOf('uni://form-field') !== -1
  ) { //form-field
    ret['name'] = vm.name;
    ret['value'] = vm.value;
  }
  // ↓↓↓↓↓↓↓↓↓↓↓↓↓也就是这句↓↓↓↓↓↓↓↓↓↓↓↓↓↓
  return JSON.parse(JSON.stringify(ret))
}

介于自己在项目中多次遇到这个问题产生的bug,而且也没有很优雅的替代方式。

这个问题19年就有人提出过,但不知道什么原因官方一直没改,所以只能自己动手了。

原理很简单,利用 webpack 插件修改这个依赖文件的这部分代码为标准的深度复制就可以了。

每次运行和打包的时候会去检测这部分逻辑然后修改,这样就避免了更新了HBuilderX之后失效的问题。

修改后的文件参下:

// mp.runtime.esm.js
// 注入深度复制方法
function cloneWithDataPatch_copy(obj){...}
// ...
function cloneWithData(vm) {
    // ....
    // 修改 JSON.parse(JSON.stringify(ret)) 这段代码为深度复制
    return cloneWithDataPatch_copy(ret);
}
1.0.0

2 years ago