国产亚洲精品资源在线26u

      
      
      <th id="ljgpb"><pre id="ljgpb"><sup id="ljgpb"></sup></pre></th>

      <rp id="ljgpb"></rp>

      您現在的位置: 微信小程序 > 微信小程序運營 > 經驗 >

      微信原生小程序轉其他平臺的小程序實踐

      來源:微信小程序 編輯:Yiyongtong.com 發布時間:2020-05-18 10:36熱度:

      如何將現有的微信原生小程序轉其他平臺的小程序?我想如果打算做這么一件事,或許大多數同學和我一樣可能沒什么思路。我第一次聽說是在一次小程序生態技術大會上,一公司的一位前端技術人員談到他們公司主要是將自己的微信小程序通過團隊開發的工具轉成其他平臺的小程序,所以當時我也很想了解這個工具是怎么做的,實現過程是什么?

      恰好近期有這么一次需求要將現有的微信小程序上開發別家的小程序,這個事要么通過現在比較好的方案uni-app來做,要么就用對應的原生小程序來寫。但是從零開始做,工作量實在太大了,周期好長呀,那么多頁面,得搞到啥時候。就在決定開始調研uni-app來做的時候,恰好有一天在微信上看見了一篇技術文章: 開源|wwto:小程序跨端遷移解決方案——微信轉其他小程序 最后放棄了使用uni-app來做,嘗試通過這個工具來轉換。

      下面也將圍繞 wwto 這個工具庫,通過將我們現有的微信小程序轉 支付寶小程序 來了解其轉換原理,同時呢會說下在轉換的過程中遇到的各種問題是如何解決的,希望對有需要的同學能有所幫助

      在了解 wwto 這個工具庫后,它大致的架構是這樣的,下面這張圖是使用作者的,更權威一點。

      通過了解 編譯 以及 運行時 這兩個模塊的實現過程就能夠明白小程序轉換的過程中做了那些事情以及怎么去做的了

      下面對這兩個階段所做的事簡單說下:

      1、在 編譯 階段主要對4個文件做了處理,分別是:*.js、 *.json、 *.wxml、 *wxss

      *.wxml 的處理部分代碼如下,可看源碼 wxml.js

      function convert(wxmlText, isWpy) {
        return wxmlText
          .replace(/wx:/g, 'a:')
          .replace(/a:for-items/g, 'a:for')
          .replace(/a:for-key/g, 'a:key')
          //  data-set 全部轉為小寫
          .replace(/data-[^=\s]=/g, (match) => match.toLocaleLowerCase())
          // // s:for-index="{{idx}}" -> s:for-index="idx"
          .replace(/a:for-index=['"]({{\w+}})['"]/ig, (match) => match.replace('{{', '').replace('}}', ''))
          // 自定義組件命名不能用駝峰
          .replace(/<[\w]+/ig, (match) => {
            return isWpy ? match : match.replace(/[A-Z]/g, (m) => ['-', m.toLowerCase()].join(''));
          })
          // 事件綁定名稱對齊
          .replace(/\s+catch[\w]+=['"]/ig, (match) => match.replace(/catchsubmit/ig, 'onSubmit')
            .replace(/catch(\w)/g, (m, p1) => ['catch', p1.toUpperCase()].join('')));
            
          ... 省略
      }
      
      module.exports = convert;
      復制代碼

      通過對文件的處理:例如

      <view bind:cellTap='onTabMyCrad' wx:if="{{hasJoin}}">...</view>  變成了 <view onCellTap='onTabMyCrad' a:if="{{hasJoin}}">...</view>
      也就是把微信的語法轉換為目標小程序的語法結構。
      復制代碼

      *.js 的處理部分代碼如下,源代碼 script.js

      function convert(jsText, isWpy) {
        return jsText
          .replace(/(require\(['"])(\w+)/g, '$1./$2')
          .replace(/(from\s+['"])(\w+)/g, (match, p1) => {
            // 相對路徑以./開頭
            return match.replace(p1, [p1, isWpy ? '' : './'].join(''));
          })
          .replace(/\.properties/g, (match) => {
            return match.replace('.properties', '.props');
          })
          .replace(/Component\([\s\S]+methods:[^{]*{/, (match) => {
            return [
              match,
              `,\r\ntriggerEvent: function(name, opt) {
                  this.props['on' + name[0].toUpperCase() + name.substring(1)]({detail:opt});
                },\r\n`
            ].join('');
          })
          .replace(/[\s\S]+/, (match) => {
            // 只處理組件
            if (!match.match(/Component\(/)) return match;
            
            ... 省略
          });
      }
      
      module.exports = convert;
      復制代碼

      通過對組件的處理如圖:

      這么轉換的目的也就是原文中 開源|wwto:小程序跨端遷移解決方案——微信轉其他小程序 提到的 : 支付寶小程序組件的生命周期函數與微信小程序完全不一樣,也沒有一一對應的關系。這種情況無法使用簡單的方法名正則替換,本方案是注入支付寶小程序組件的生命周期函數,在這些生命周期函數中在調用微信小程序的生命周期函數,這樣以來就避免了方法名替換無法一一對應的問題,也能更方便地書寫適配代碼。

      對 *.json 以及 *wxss 的處理就不列出代碼了,可看源碼: json.js 、 wxss.js

      2、在 運行時 階段又做了哪些事情呢?...

      主要在每個js文件頭部加入了適配代碼adaptor.js

      截取部分實現代碼如下: 源代碼可參考 converter.js

      function convert(opt = {}) {
        const src = opt.source || './src';
        const dest = opt.target || './alibaba';
        const assets = opt.assets || config.getAssets(src);
        
        ...省略
        // 注入適配器代碼
        gulp.src(sysPath.resolve(__dirname, '../../../node_modules/mp-adaptor/lib/alibaba.js'))
          .pipe(rename('adaptor.js'))
          .pipe(gulp.dest(dest)).on('end', () => {
            logger.info('復制 adaptor.js 完成!');
          });
      
        // 處理腳本文件
        gulp.src(`${src}/**/*.js`)
          .pipe(replace(/[\s\S]*/g, (match) => converter.script(match)))
          .pipe(through2.obj(function(file, enc, cb) {
            const path = file.history[0].replace(file.base, '');
            const spec = path.split(sysPath.sep);
            const adaptor = new Array(spec.length - 1).fill('..').concat('adaptor.js').join('/');
            const str = [
              `import wx from '${adaptor.replace(/^\.\./, '.')}';`,
              ab2str(file.contents)
            ].join('\r\n');
            file.contents = str2ab(str);
      
            this.push(file);
            cb();
          }))
          .pipe(gulp.dest(dest));
      }
      
      module.exports = convert;
      復制代碼

      加入的adapter.js 代碼是什么樣的呢? 參考源碼 alibaba.js

      function getInstance() {
        // eslint-disable-next-line no-undef
        const wx = my;
        wx.has_ali_hook_flag = true;
        
        const {
          getSystemInfo
        } = wx;
        wx.getSystemInfo = function(opt) {
          ...省略
          return getSystemInfo.call(this, opt);
        };
        ...省略
        return wx;
      }
      
      export default getInstance();
      
      上面的適配代碼:主要就是包裝抹平微信與支付寶兩個平臺間api的調用差異,既可以使用原微信wx.*的方式來調用,也可以使用支付寶小程序平臺my.*的方式來調用api,說白了就是對微信的api包裝了一層。
      復制代碼

      通過分析 wwto 這個工具庫的實現過程,也就學習到了如何基于現有的微信小程序轉其他平臺小程序的實現過程了。下面說下這次轉換的過程中遇到了那些問題以及怎么解決的。

      微信小程序代碼轉換階段-實踐

      轉換的時候遇見這么一些問題:

      首先,wwto工具做不到運行時 diff 的抹平,也做不到一個 API 從無到有的過程

      1、現階段我們的微信小程序依賴 vantUI 組件庫,使用wwto來轉換壓根就不支持

      2、微信小程序中常用的api:selectComponent 在支付寶里小程序里面不支持

      3、微信的分包加載是否支持?不支持又該如何處理等?

      對于第二個問題,需要修改 wwto 工具庫的代碼,使其支持這個api,我這邊的實現思路如下: 如Page A 頁面依賴 Component B組件,可以在B組件的ready生命周期階段把當前組件實例this掛載到全局對象getApp()中的某個屬性上,然后在Page A中實現selectComponent這個api,這個api就來獲取掛載到getApp()上對應的實例組件。

      修改處在 script.js 代碼中,可以打開文件比對 如下:

      對于第三個問題,通過了解支付寶的分包機制文檔,完全可以支持微信小程序的,但是,這里我在調試的時候支付寶開發者工具和到真機預覽的時,兩者差異完全不一樣,在開發者工具完全運行正常,可是在真機預覽的時候會遇見各種奇葩問題,大部分都是adaptor.js 重寫wx.* 的api導致的問題,通過調試了好長時間,終于找到了問題的根源所在,我已經在githup上 向wwto 開源者提issue了,可查看 adaptor.js 重復執行 了解,并且我已提交了PR進行了修正

      對于第二個大問題,做的事就相對比較多了,如果在不了解wwto這個工具庫代碼實現思路情況下,可能是無法解決的,當時能想到的解決辦法就是,仿照 vantUI 組件庫的代碼實現,重新采用微信自定義組件的形式重寫,但是這樣做工作量又上去了,比起使用uni-app來,這個不可行,工作量也好大呀!這個時候,我幾乎又要放棄使用這個工具來轉換了。那這里能不能換別的思路去解決呢?答案肯定是有的,前提就是了解wwto工具的代碼實現過程以及思路:wwto是在轉換的時候,通過修改原微信小程序的文件,那么我就能夠仿照其思想在小程序運行時添加兼容的代碼來讓vantUI微信小程序組件庫能夠跑在支付寶小程序中,聽起來是多么一件有趣的事

      如何去做呢?通過查看了vantUI組件庫的代碼實現,是可以按這種思路實現的,大致需要修改組件庫中兩處代碼

      1、源代碼 basic.js 修改如下,主要是解決微信小程序triggerEvent api的功能,獲取組件實例

      let Behavior = p => p
      export const basic = Behavior({
          methods: {
              $emit(...args) { 
                let name = args[0];
                let onPropsfn = this.props['on' + name[0].toUpperCase() + name.substring(1)];
                // 可以正則匹配 data-*值 ['datadata-mm', 'data-', 'data-1'].filter(v => v.match(/^data-\w+/))
                let index = this.data && this.data['data-index'];
                if (onPropsfn) {
                  if (args.length == 1) {
                    onPropsfn({detail: undefined})
                  } else if (args.length == 2) {
                    onPropsfn({detail: args[1], currentTarget:{dataset: {index: index}}})
                  } else if (args.length >= 3) {
                    onPropsfn.apply(this, args);
                  }
                }
                // this.triggerEvent(...args);
              },
            ... 省略
          }
      });
      添加的代碼實現:都是參考wwto實現的思路
      復制代碼

      2、源代碼 component.js 修改如下,主要是解決微信小程序中一系特性功能如:externalClasses、properties、behaviors => 模擬到支付寶小程序中,如果有興趣可以比對添加的代碼,如何抹平這些特性差異,其中微信的relations組件特性,沒法模擬,替代方案就只能用支付寶小程序相關組件了

      import { basic } from '../mixins/basic';
      import { observe } from '../mixins/observer/index';
      function mapKeys(source, target, map) {
          Object.keys(map).forEach(key => {
              if (source[key]) {
                  target[map[key]] = source[key];
              }
          });
      }
      function noop() {}
      function VantComponent(vantOptions = {}) {
          const options = {};
          mapKeys(vantOptions, options, {
              data: 'data',
              props: 'properties',
              mixins: 'behaviors',
              methods: 'methods',
              beforeCreate: 'created',
              created: 'attached',
              mounted: 'ready',
              relations: 'relations',
              destroyed: 'detached',
              classes: 'externalClasses'
          });
          options.properties = options.properties || {};
          // relations 微信組件特性,暫時沒法模擬到支付寶
          const { relation } = vantOptions;
          if (relation) {
              options.relations = Object.assign(options.relations || {}, {
                  [`../${relation.name}/index`]: relation
              });
          }
          // add default externalClasses
          options.externalClasses = options.externalClasses || [];
          options.externalClasses.push('custom-class');
          // add default behaviors
          options.behaviors = options.behaviors || [];
          options.behaviors.push(basic);
          // map field to form-field behavior
          if (vantOptions.field) {
              options.behaviors.push('wx://form-field');
          }
          // add default options
          options.options = {
              multipleSlots: true,
              addGlobalClass: true
          };
          observe(vantOptions, options);
      
          /**
           *  參照wwto => 運行時調整options
           */
           
          /**
          *  mixins
          */
          options.mixins = options.mixins || [];
          options.mixins = options.mixins.concat(options.behaviors);
      
          /**
           *  const lifeCircleNames = ['created', 'attached', 'ready', 'detached'];
           */
          options.methods = options.methods || {};
          const lifeCircleNames = ['created', 'attached', 'ready', 'detached'];
          lifeCircleNames.forEach(name => {
            let methods = options.methods[name] = options.methods[name] || options[name] || noop;
            // fix selectComponents api
            if (name == 'ready') {
              options.methods[name] = function() {
                if(this.data.id){
                  var app = getApp();
                  app.globalData.insComp = app.globalData.insComp || {};
                  app.globalData.insComp[this.data.id] = this;
                };
                methods();
              }
            }
          })
      
          /**
           *  處理this.__observers
           */
          let has = Object.prototype.hasOwnProperty;
          let propMap = {};
          let observerMap = null;
          let walkProps = obj => {
            Object.keys(obj).forEach((key) => {
              if (!has.call(obj, key)) return
              let item = obj[key];
              // String Number Boolean 設定默認值
              if (item === String) {
                propMap[key] = '';
              } else if (item === Boolean) {
                propMap[key] = false;
              } else if (item === Number) {
                propMap[key] = 0;
              } else if (item && typeof item == 'object') {
                let type = item.type;
                if (!('value' in item)) {
                  if (type === String) {
                    propMap[key] = '';
                  } else if (type === Boolean) {
                    propMap[key] = false;
                  } else if (type === Number) {
                    propMap[key] = 0;
                  } else {
                    propMap[key] = ''; // 暫時默認值
                  }
                } else {
                  propMap[key] = item.value;
                }
      
                if (item.observer) {
                  // debugger
                  observerMap = observerMap || {};
      
                  if (typeof item.observer === 'function') {
                    observerMap[key] = item.observer;
                  } else { // 微信小程序中observer也可以使用對象的形式
                    observerMap[key] = function() {
                      this[item.observer] && this[item.observer].apply(this, arguments);
                    };
                  }
                }
              } else {
                propMap[key] = item;
              }
            });
          }
      
          // 處理properties => props
          let properties = options.properties;
          walkProps(properties);
      
          let mininsProperties = options.mixins.filter(item => item.properties);
          mininsProperties.forEach(item => {
            walkProps(item.properties);
          })
      
          /**
           *  處理 externalClasses 同時手動拷貝class
           */
          let externalClasses = options.externalClasses;
          externalClasses.forEach(clas => {
            propMap[clas.replace(/-(\w)/g, (match, p) => p.toUpperCase())] = '';
          })
      
          options.props = propMap;
          options.props.__observers = observerMap
      
          /**
           *  my生命周期函數
           */
          options.didMount = function(){
            this.data = Object.assign({}, this.data, this.props);
      
            this.created && this.created.apply(this, arguments);
            this.attached && this.attached.apply(this, arguments);
            this.ready && this.ready.apply(this, arguments);
      
            /**
             *  解決初始化observer component 組件
             */
            if (this.props.__observers) {
              Object.keys(this.props.__observers).forEach(key => {
                this.props.__observers[key].call(this, this.props[key])
              })
            }
          }
      
          options.didUnmount = function(){
            this.detached && this.detached.apply(this, arguments);
          }
      
          options.didUpdate = function(prevProps, preData) {
            for (let key in this.props) {
              if (this.props.__observers && typeof(this.props.__observers[key]) === 'function') {
                if (JSON.stringify(prevProps[key]) !== JSON.stringify(this.props[key]) &&
                JSON.stringify(preData[key]) !== JSON.stringify(this.props[key])) {
                  this.setData(Object.assign({}, this.data, {[key]: this.props[key]}));
                  this.props.__observers[key].apply(this, [this.props[key], prevProps[key]]);
                }
              } else if (this.props[key] !== prevProps[key]) {
                this.data[key] = this.props[key];
                this.setData(this.data);
              }
            }
          }
      
          Component(options);
      }
      export { VantComponent };
      
      復制代碼

      到這里主要的問題解決了,其他一些微信小程序到支付寶小程序的差異就不都列出來了,可以靈活的修改wwto 的代碼來實現轉換時的差異, 如果后期有同樣需求的同學嘗試轉換時有遇見問題,也可留言交流。

      最后

      最初在決定到底要不要使用wwto這個工具來轉換微信小程序的時候,心里面也是沒底的,畢竟剛開源,我估計是第一個在剛開源的時候來做轉換的。而且本身也從未開發過支付寶小程序,也不知道支付寶小程序和微信小程序有哪些大致的差異,再加上調研技術上也沒有給充裕的時間來決策到底用什么方案來實現其他平臺的小程序。最后決定使用wwto來做這件事,主要是不想做重復的工作,對僅僅使用新的技術框架uni-app來重寫,估計對我來說短期也不會有太多的技術積累收益,當然同時呢我也想快速的了解微信和支付寶的一些差異,重要的一點就是wwto開源的,每個部分的源代碼都能debug。綜合以上幾點于是決定來趟一下這個渾水,總的結果來說,項目周期縮短了好多,大致花了兩周的時間來完成了,也了解了支付寶小程序和微信小程序之間的一些差異,解決了好多問題,解決問題的過程中也很頭疼...

      最后轉換后的效果圖就不給出了,歡迎在微信和支付寶中搜索: “ 咔咔找車 ” 小程序看兩者差異。

      国产亚洲精品资源在线26u

          
          
          <th id="ljgpb"><pre id="ljgpb"><sup id="ljgpb"></sup></pre></th>

          <rp id="ljgpb"></rp>