It has been more than half a year since the micro program was released. With the powerful influence of the micro channel platform, more and more enterprises have joined the development of small programs. Small programs have the following advantages over M pages:

1, small programs have more capabilities, including positioning, recording, files, media, all kinds of hardware capabilities, more imagination space

2. Run inside wechat, and experience is closer to APP

3. In the overly competitive Internet industry, the cost of acquiring an effective APP user is already very high. Compared with APP, small programs are more lightweight, ready-to-use and easier to acquire users

Development of contrast

From the perspective of development, small program official encapsulation of many commonly used components to bring a lot of convenience to development, but also bring a lot of inconvenience:

1, the small program redefine the DOM structure, no window, document, div, SPAN and so on, small program only view, text, image and other packaged components, page layout can only be realized by these basic components, for developers need a certain habit conversion cost

2. Direct DOM manipulation is not recommended for applets (DOM and some properties are only available from July 2017), and there is a high learning cost for developers unfamiliar with the MVVM pattern

3, small program no cookie, can only simulate the cookie operation through storage (including HTTP setCookie also need to process)

wepy

The team has recently developed multiple WeChat small programs, in order to make up for a lack of small programs of various VUE development habits and the continuation of developers, in the team development chose wepy framework, the framework is tencent internal development framework, based on the small program design basic reference VUE, development mode and coding, a more than 80% is close to the VUE, Developers can switch from VUE development to applets development at a fraction of the cost. The main advantages over applets are as follows:

Wepy is packaged again in the original small program development mode, which is closer to the existing MVVM framework development mode. The framework was developed with reference to some of the features of the current framework and incorporated into it. Below is a comparison of the code before and after using Wepy.

Official DEMO code:


     
  1. /index.js

  2. // Get the application instance

  3. var app = getApp()

  4. Page({

  5.    data: {

  6.        motto: 'Hello World',

  7.        userInfo: {}

  8.    },

  9. // Event handlers

  10.    bindViewTap: function() {

  11.        console.log('button clicked')

  12.    },

  13.    onLoad: function () {

  14.        console.log('onLoad')

  15.    }

  16. })

Copy the code

Implementation based on Wepy:


     
  1. import wepy from 'wepy';

  2. export default class Index extends wepy.page {

  3.    data = {

  4.        motto: 'Hello World',

  5.        userInfo: {}

  6.    };

  7.    methods = {

  8.        bindViewTap () {

  9.            console.log('button clicked');

  10.        }

  11.    };

  12.    onLoad() {

  13.        console.log('onLoad');

  14.    };

  15. }

Copy the code

2. The real component development applet has tags that can realize component reuse, but only at the level of template fragment reuse, business code and interaction events still need to be processed in the page. Componentized loose coupling and reuse cannot be achieved.

Wepy component example


     
  1. // index.wpy

  2. <template>

  3.    <view>

  4.        <panel>

  5.            <h1 slot="title"></h1>

  6.        </panel>

  7.        <counter1 :num="myNum"></counter1>

  8.        <counter2 :num.sync="syncNum"></counter2>

  9.        <list :item="items"></list>

  10.    </view>

  11. </template>

  12. <script>

  13. import wepy from 'wepy';

  14. import List from '.. /components/list';

  15. import Panel from '.. /components/panel';

  16. import Counter from '.. /components/counter';

  17. export default class Index extends wepy.page {

  18.    config = {

  19.        "navigationBarTitleText": "test"

  20.    };

  21.    components = {

  22.        panel: Panel,

  23.        counter1: Counter,

  24.        counter2: Counter,

  25.        list: List

  26.    };

  27.    data = {

  28.        myNum: 50,

  29.        syncNum: 100,

  30.        items: [1, 2, 3, 4]

  31.    }

  32. }

  33. </script>

Copy the code

The biggest drawback of applets is that they don’t support NPM packages, so they can’t directly use a lot of excellent open source content. During compilation, Wepy will recursively traverse the require in the code and copy the corresponding dependency files from node_modules. And modify require to be a relative path to implement support for external NPM packages. The diagram below:

Json, app.js, and app.wxss. The page has four files: index.json, index.js, index.wxml, and index.wxss. And the text must have the same name. Therefore, the comparison of development directories before and after wepy development is as follows:

Official DEMO:


     
  1. project

  2. ├ ─ ─ pages

  3. | ├ ─ ─ the index

  4. | | ├ ─ ─ index. Json configuration index page

  5. | | ├ ─ ─ index. Js index page logic

  6. | | ├ ─ ─ index. WXML index page structure

  7. | | └ ─ ─ index. WXSS index page style sheets

  8. | └ ─ ─ the log

  9. | ├ ─ ─ the json log configuration page

  10. | ├ ─ ─ the WXML log page logic

  11. | ├ ─ ─ the js log page structure

  12. | └ ─ ─ the WXSS log page style sheets

  13. ├─ App.js Small program logic

  14. ├─ App.json Applet Public Settings

  15. └─ app.wxSS Public style sheet for small programs

Copy the code

Directory structure with the Wepy framework:


     
  1. project

  2. └ ─ ─ the SRC

  3. ├ ─ ─ pages

  4. | ├ ─ ─ index. Wpy index page configuration, structure, style, logic

  5. | └ ─ ─ the wpy log page configuration, structure, style, logic

  6. ├ ─ └─ app.wpy Small program config (Global style config, declare hooks, etc.)

Copy the code

5. Babel compilation is used by default and some new features of ES6/7 are supported.

6. Wepy supports less

Default enabled uses new features like Promise, async/await, etc

How to develop

A quick start

The installation


     
  1. npm install wepy-cli -g

Copy the code

The scaffold


     
  1. wepy new myproject

Copy the code

Switch to the project directory


     
  1. cd myproject

Copy the code

Real-time compilation


     
  1. wepy build --watch

Copy the code

The directory structure


     
  1. ├─ dist app Developer tools designated directory

  2. ├ ─ ─ node_modules

  3. ├─ SRC code for the directory

  4. | ├ ─ ─ components components folder (not full page)

  5. . | | ├ ─ ─ com_a wpy a reusable component

  6. . | | └ ─ ─ com_b wpy reusable component b

  7. | ├ ─ ─ pages page folder (full page)

  8. | | ├ ─ ─ index. The wpy index page

  9. | | └ ─ ─ page. Wpy page page

  10. | └ ─ ─ app. Wpy small application configuration items (global style configuration, statement hooks, etc.)

  11. └─ package.json package config

Copy the code

Wepy and VUE are very similar in their coding styles, and VUE developers can switch seamlessly, so here are the main differences:

1. Both support props, Data, computed, Components, methods, and Watch (Watcher in Wepy). However, methods in WEpy can only be used for page event binding. All methods in VUE are placed under methods

2. In wepy, the.sync modifier (similar to vu1.x) should be added to enable the props to be dynamically updated. After the parent component is passed to the child component, this

3. Wepy supports data bidirectional binding. When defining props, add twoway: True to enable the child component to modify the parent component data

4. Vue2. x recommends using eventBus for component communication, while wepy uses $broadcast, $emit, and $invoke to communicate


     
  1. · First, the event listener needs to be written under the Events property:

  2. ``` bash

  3. import wepy from 'wepy';

  4. export default class Com extends wepy.component {

  5.    components = {};

  6.    data = {};

  7.    methods = {};

  8.    events = {

  9.        'some-event': (p1, p2, p3, $event) => {

  10.              console.log(`${this.name} receive ${$event.name} from ${$event.source.name}`);

  11.        }

  12.    };

  13.    // Other properties

  14. }

  15. ` ` `

  16. · $broadcast: The parent component fires all child component events

  17. · $emit: Child component fires parent component event

  18. · $invoke: Child component fires child component events

Copy the code

5. The life cycle of VUE includes Created and Mounted, while wepy only supports the life cycle of onLoad and onReady

6. Wepy does not support VUE features such as filters, keep-alive, ref, transition, global plug-ins, route management, server rendering, etc

Wepy principle research

Although wepy improves the experience of developing small programs, ultimately wepy needs to be compiled into the format that small programs need to run in, so the core of Wepy is code parsing and compilation.

Wepy project files mainly have two: wepy-cli: used to extract, analyze and compile. Wpy files into WXML, WXSS, JS and JSON formats required by small programs. Wepy: JS framework in compiled JS files

Wepy compilation process

Break down the process core code


     
  1. // Wepy custom attributes are replaced by applet standard attributes

  2. return content.replace(/<([\w-]+)\s*[\s\S]*? (\/|<\/[\w-]+)>/ig, (tag, tagName) => {

  3.    tagName = tagName.toLowerCase();

  4.    return tag.replace(/\s+:([\w-_]*)([\.\w]*)\s*=/ig, (attr, name, type) => { // replace :param.sync => v-bind:param.sync

  5.        if (type === '.once' || type === '.sync') {

  6.        }

  7.        else

  8.            type = '.once';

  9.        return ` v-bind:${name}${type}=`;

  10.    }).replace(/\s+\@([\w-_]*)([\.\w]*)\s*=/ig, (attr, name, type) => { // replace @change => v-on:change

  11. const prefix = type ! == '.user' ? (type === '.stop' ? 'catch' : 'bind') : 'v-on:';

  12.        return ` ${prefix}${name}=`;

  13.    });

  14. });

  15. .

  16. // Parse the wepy file as XML

  17. xml = this.createParser().parseFromString(content);

  18. const moduleId = util.genId(filepath);

  19. // The extracted format

  20. let rst = {

  21.    moduleId: moduleId,

  22.    style: [],

  23.    template: {

  24.        code: '',

  25.        src: '',

  26.        type: ''

  27.    },

  28.    script: {

  29.        code: '',

  30.        src: '',

  31.        type: ''

  32.    }

  33. };

  34. // Recycle the extraction process

  35. [].slice.call(xml.childNodes || []).forEach((child) => {

  36.    const nodeName = child.nodeName;

  37.    if (nodeName === 'style' || nodeName === 'template' || nodeName === 'script') {

  38.        let rstTypeObj;

  39.        if (nodeName === 'style') {

  40.            rstTypeObj = {code: ''};

  41.            rst[nodeName].push(rstTypeObj);

  42.        } else {

  43.            rstTypeObj = rst[nodeName];

  44.        }

  45.        rstTypeObj.src = child.getAttribute('src');

  46.        rstTypeObj.type = child.getAttribute('lang') || child.getAttribute('type');

  47.        if (nodeName === 'style') {

  48. // Add the scoped attribute for style

  49.            rstTypeObj.scoped = child.getAttribute('scoped') ? true : false;

  50.        }

  51.        if (rstTypeObj.src) {

  52.            rstTypeObj.src = path.resolve(opath.dir, rstTypeObj.src);

  53.        }

  54.        if (rstTypeObj.src && util.isFile(rstTypeObj.src)) {

  55.            const fileCode = util.readFile(rstTypeObj.src, 'utf-8');

  56.            if (fileCode === null) {

  57. Throw 'failed to open file:' + rsttypeobj.src;

  58.            } else {

  59.                rstTypeObj.code += fileCode;

  60.            }

  61.        } else {

  62.            [].slice.call(child.childNodes || []).forEach((c) => {

  63.                rstTypeObj.code += util.decode(c.toString());

  64.            });

  65.        }

  66. if (! rstTypeObj.src)

  67.            rstTypeObj.src = path.join(opath.dir, opath.name + opath.ext);

  68.    }

  69. });

  70. .

  71. // Unpack the WXML extraction process

  72. (() = > {

  73. if (rst.template.type ! == 'wxml' && rst.template.type ! == 'xml') {

  74.        let compiler = loader.loadCompiler(rst.template.type);

  75.        if (compiler && compiler.sync) {

  76.            if (rst.template.type === 'pug') { // fix indent for pug, https://github.com/wepyjs/wepy/issues/211

  77.                let indent = util.getIndent(rst.template.code);

  78.                if (indent.firstLineIndent) {

  79.                    rst.template.code = util.fixIndent(rst.template.code, indent.firstLineIndent * -1, indent.char);

  80.                }

  81.            }

  82. // Call the WXML parsing module

  83.            let compilerConfig = config.compilers[rst.template.type];

  84.            // xmldom replaceNode have some issues when parsing pug minify html, so if it's not set, then default to un-minify html.

  85.            if (compilerConfig.pretty === undefined) {

  86.                compilerConfig.pretty = true;

  87.            }

  88.            rst.template.code = compiler.sync(rst.template.code, config.compilers[rst.template.type] || {});

  89.            rst.template.type = 'wxml';

  90.        }

  91.    }

  92.    if (rst.template.code)

  93.        rst.template.node = this.createParser().parseFromString(util.attrReplace(rst.template.code));

  94. }) ();

  95. // Process of extracting import resource files

  96. (() = > {

  97.    let coms = {};

  98.    rst.script.code.replace(/import\s*([\w\-\_]*)\s*from\s*['"]([\w\-\_\.\/]*)['"]/ig, (match, com, path) => {

  99.        coms[com] = path;

  100.    });

  101.    let match = rst.script.code.match(/[\s\r\n]components\s*=[\s\r\n]*/);

  102.    match = match ? match[0] : undefined;

  103.    let components = match ? this.grabConfigFromScript(rst.script.code, rst.script.code.indexOf(match) + match.length) : false;

  104. let vars = Object.keys(coms).map((com, i) => `var ${com} = "${coms[com]}"; `).join('\r\n');

  105.    try {

  106.        if (components) {

  107.            rst.template.components = new Function(`${vars}\r\nreturn ${components}`)();

  108.        } else {

  109.            rst.template.components = {};

  110.        }

  111.    } catch (e) {

  112. Util. Output (' error ', path.join(opath. Dir, opath. Base));

  113. Util. Error (${e}\r\n${vars}\r\nreturn ${components} ');

  114.    }

  115. }) ();

  116. .

Copy the code

Wepy has special script, style, template, and config parsing modules.


     
  1. //compile-template.js

  2. .

  3. // Write the disassembled WXML structure to the file

  4. getTemplate (content) {

  5.    content = `<template>${content}</template>`;

  6.    let doc = new DOMImplementation().createDocument();

  7.    let node = new DOMParser().parseFromString(content);

  8.    let template = [].slice.call(node.childNodes || []).filter((n) => n.nodeName === 'template');

  9.    [].slice.call(template[0].childNodes || []).forEach((n) => {

  10.        doc.appendChild(n);

  11.    });

  12. .

  13.    return doc;

  14. },

  15. // Process to the WXML format required by wechat applet

  16. compileXML (node, template, prefix, childNodes, comAppendAttribute = {}, propsMapping = {}) {

  17. / / handle slot

  18.    this.updateSlot(node, childNodes);

  19. // Handle the data binding bind method

  20.    this.updateBind(node, prefix, {}, propsMapping);

  21. / / handle the className

  22.    if (node && node.documentElement) {

  23.        Object.keys(comAppendAttribute).forEach((key) => {

  24.            if (key === 'class') {

  25.                let classNames = node.documentElement.getAttribute('class').split(' ').concat(comAppendAttribute[key].split(' ')).join(' ');

  26.                node.documentElement.setAttribute('class', classNames);

  27.            } else {

  28.                node.documentElement.setAttribute(key, comAppendAttribute[key]);

  29.            }

  30.        });

  31.    }

  32. // Handle the repeat tag

  33.    let repeats = util.elemToArray(node.getElementsByTagName('repeat'));

  34. .

  35. // Process components

  36.    let componentElements = util.elemToArray(node.getElementsByTagName('component'));

  37. .

  38.    return node;

  39. },

  40. // The template file compiles the module

  41. compile (wpy){

  42. .

  43. // Write the compiled content to the file

  44.    let plg = new loader.PluginHelper(config.plugins, {

  45.        type: 'wxml',

  46.        code: util.decode(node.toString()),

  47.        file: target,

  48.        output (p) {

  49.            util.output(p.action, p.file);

  50.        },

  51.        done (rst) {

  52. // Write operation

  53. Util. output(' write ', rst.file); util.output(' write ', rst.file);

  54.            rst.code = self.replaceBooleanAttr(rst.code);

  55.            util.writeFile(target, rst.code);

  56.        }

  57.    });

  58. }

Copy the code

Compare files before and after compilation

Wepy files before compilation:


     
  1. <scroll-view scroll-y="true" class="list-page" scroll-top="{{scrollTop}}" bindscrolltolower="loadMore">

  2. <! -- Item List component -->

  3.    <view class="goods-list">

  4.      <GoodsList :goodsList.sync="goodsList" :clickItemHandler="clickHandler" :redirect="redirect" :pageUrl="pageUrl"></GoodsList>

  5.    </view>

  6. </scroll-view>

Copy the code

Wepy compiled files:


     
  1. <scroll-view scroll-y="true" class="list-page" scroll-top="{{scrollTop}}" bindscrolltolower="loadMore">

  2.  <view class="goods-list">

  3.    <view  wx:for="{{$GoodsList$goodsList}}" wx:for-item="item" wx:for-index="index" wx:key="{{item.infoId}}" bindtap="$GoodsList$clickHandler" data-index="{{index}}" class="item-list-container{{index%2==0 ? ' left-item' : ''}}">

  4.      <view class="item-img-list"><image src="{{item.pic}}" class="item-img" mode="aspectFill"/></view>

  5.      <view class="item-desc">

  6.        <view class="item-list-title">

  7.          <text class="item-title">{{item.title}}</text>

  8.        </view>

  9.        <view class="item-list-price">

  10. < view wx: if = "{{item. The price & item. The price > 0}}" class = "item - nowPrice" > < I > $< / I > {{item. Price}} < / view >

  11. <view wx:if="{{item.originalPrice &&item.originalPrice >0}}" class=" item-oriprice ">¥{{item.originalprice}}</view>

  12.        </view>

  13. <view class="item-list-local"><view>{{item.cityName}}{{item.cityName&&item.businessName? ' | ':''}}{{item.businessName}} </view>

  14.      </view>

  15.      </view>

  16.        <form class="form" bindsubmit="$GoodsList$sendFromId" report-submit="true" data-index="{{index}}">

  17.          <button class="submit-button" form-type="submit"/>

  18.        </form>

  19.      </view>

  20.    </view>

  21.  </view>

  22. </scroll-view>

Copy the code

It can be seen that wepy directly writes all the introduced components into the page, and outputs them in the format of wechat applet. Of course, it can also be seen from one side that using wepy framework, the code style is more concise and elegant than the original

The brief analysis of the above is wepy implementation principle, interested friends can go to read the source code (https://github.com/wepyjs/wepy). Generally speaking, the core of Wepy lies in the compilation process, which can compile elegant and concise codes similar to VUE style into complex codes required by wechat applets.

Wepy, as an excellent micro channel small program framework, can help us greatly improve the development efficiency and stand out among the few small program frameworks. I hope more teams will choose Wepy.

PS: Wepy is also in the process of implementing applets and VUE code isomorphism, but it is still in the development stage. It would be very cool if we could implement a development in the future and produce applets and M pages at the same time.