> For the complete documentation index, see [llms.txt](https://raozhanping.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://raozhanping.gitbook.io/notes/js/js-baidu-minipro.md).

# 百度小程序

## 整体框架

> 百度小程序的开发api，架构设计和微信小程序基本一致

为了提升整体性能，充分利用手机的多CPU性能：\
1\. 把逻辑层与渲染层分离，分别位于不同的运行容器 2. 异步请求都由native来执行

![baidu\_mini\_pro架构](https://handsomeliuyang.github.io/2019/02/25/百度小程序源码解读/整体框架2.png)

* 逻辑层
  1. 逻辑层就是对开发者所暴露的api，有APP， Page，布局文件，其中的App。Page都是两个函数
  2. App()函数的处理： 直接创建App对象，全局唯一对象
  3. Page()函数的处理： 保存到Map中，不会马上构建Page对象，当导航到页面时，才会真正创建Page对象
* 渲染层
  1. 使用MVVM框架san来渲染界面
  2. 在编译期间把小程序标签转化为san框架所支持的标签
  3. 为每个小程序页面，创建对应的san框架下Page组件，PageComponent的template就是swan.xml转译后的内容
* 渲染层与逻辑层交互
  1. 渲染层接收用户的交互事件，由统一的函数处理后，通过消息总线传递到逻辑层的Page对象，再调用对应的函数
  2. 逻辑层依据用户操作，执行业务操作，修改data数据，通过消息总线传递到渲染层的组件里，San.Page组件会自动更新界面

## 开发流程

### 编译

1. 目录结构

   ![目录结构-src](/files/-Lh9FDQF7qXVsVsBFv0L)
2. app.js的源码

   ```javascript
   App({
    onLaunch(event) {
        console.log('onLaunch');
    },
    onShow(event) {
        console.log('onShow');
    },
    globalData: {
        userInfo: 'user'
    }
   });
   ```
3. index.js的源码

   ```javascript
   var p = [];
   Page({
    data: {
        text: "这是一段文字."
    },
    add: function(e) {
        p.push("其他文字");
        this.setData({
            text: "这是一段文字." + p.join(",")
        })
    },
    remove: function(e) {
        if(p.length > 0){
            p.pop();
            this.setData({
                text: "这是一段文字." + p.join(",")
            });
        }
    }
   });
   ```
4. index.swan的源码

   ```javascript
   <view>
    <view class="text-px text-{{text}}">{{text}}</view>
    <button class="btn" type="primary" bind:tap="add">add text</button>
    <button class="btn" type="primary" bind:tap="remove">remove text</button>
   </view>
   ```

   **编译之后**
5. 目录结构 ![目录结构-dist](/files/-Lh9FDQHdjY50T5ktiEb)
6. app.js的源码

   ```javascript
   window.define("138",
    function(t, e, n, o, a, i, s, c, r, u, d, l, g, w, f, h) {
        var p = [];
        Page({
            data: {
                text: "这是一段文字."
            },
            add: function(t) {
                p.push("其他文字");
                this.setData({
                    text: "这是一段文字." + p.join(",")
                })
            },
            remove: function(t) {
                p.length > 0 && (p.pop(), this.setData({
                    text: "这是一段文字." + p.join(",")
                }))
            }
        })
   });
   window.define("193",
    function(t, e, n, o, a, i, s, c, r, u, d, l, g, w, f, h) {
        App({
            onLaunch: function(t) {
                console.log("Lifecycle App onLaunch")
            },
            onShow: function(t) {
                console.log("Lifecycle App onShow")
            },
            globalData: {
                userInfo: 'user'
            }
        })
   });
   window.__swanRoute = "app";
   window.usingComponents = [];
   require("193");
   window.__swanRoute = "pages/text/text";
   window.usingComponents = [];
   require("138");
   ```
7. index.swan.js的源码

   ```javascript
   // 注意，做了一些简化
   ((global)=>{
    global.errorMsg = [];
    var templateComponents = Object.assign({}, {});
    var param = {};
    var filterArr = JSON.parse("[]");
    try {
        filterArr && filterArr.forEach(function (item) {
            param[item.module] = eval(item.module)
        });
        var pageContent = `
            <view>
                <view>{{text}}</view>
                <button class=\"btn\" type=\"primary\" on-bindtap=\"eventHappen('tap', $event, 'add', '', 'bind')\">
                    add text
                </button>
                <button class=\"btn\" type=\"primary\" on-bindtap=\"eventHappen('tap', $event, 'remove', '', 'bind')\">
                    remove text
                </button>
            </view>`;
        var renderPage = function (filters, modules) {
            // 路径与该组件映射
            // var customAbsolutePathMap = (global.componentFactory.getAllComponents(), {});
            // 当前页面使用的自定义组件
            // const pageUsingComponentMap = JSON.parse("{}");
            // 生成该页面引用的自定义组件
            // const customComponents = Object.keys(pageUsingComponentMap).reduce((customComponents, customName) => {
            //     customComponents[customName] = customAbsolutePathMap[pageUsingComponentMap[customName]];
            //     return customComponents;
            // }, {});
            global.pageRender(pageContent, templateComponents)
        };
        renderPage(filterArr, param);
    } catch (e) {
        global.errorMsg['execError'] = e;
        throw e;
    }
   })(window);
   ```

编译总结：

1. 对template进行转换：
   * 标签转换：bind:tap ===> on-bindtap
   * 事件包装：eventHappen(‘tap’, $event, ‘add’, ‘’, ‘bind’)
2. 对App.js进行包装，提升效率，减少逐一加载流程
3. 通过渲染模板，生成index.swan.js文件，提升渲染效率

### 加载、启动、渲染

用户点击跳转到小程序后：

1. Native的任务：
   * 下载小程序.zip文件
   * 启动两个web运行容器：
     * 渲染层webview加载slaves.html
     * 逻辑层jscore加载master.html
   * 解析小程序app.json，发送’AppReady’事件
2. 逻辑层master.js
   * 监听’AppReady’事件，执行小程序的调起逻辑

     \`\`\`javascript

     /\*\*
3. 监听客户端的调起逻辑 \*/ listenAppReady() { this.swaninterface.bind('AppReady', event => { console.log('master listener AppReady ', event); swanEvents('masterActiveStart'); // 给三方用的，并非给框架用，请保留 this.context.appConfig = event.appConfig; // 初始化master的入口逻辑 this.initRender(event); // this.preLoadSubPackage(); }); }

   ````
   - 初始化master的入口逻辑，小程序的每个界面，对应一个Slave对象（与渲染层的slave.js不一样），依据用户打开多个页面，构建一个导航栈，保存在navigator对象里
   ```javascript
   /**
   * 初始化渲染
   *
   * @param {Object} initEvent - 客户端传递的初始化事件对象
   * @param {string} initEvent.appConfig - 客户端将app.json的内容（json字符串）给前端用于处理
   * @param {string} initEvent.appPath - app在手机上的磁盘位置
   * @param {string} initEvent.wvID - 第一个slave的id
   * @param {string} initEvent.pageUrl - 第一个slave的url
   */
   initRender(initEvent) {
     // 设置appConfig
     this.navigator.setAppConfig({
         ...JSON.parse(initEvent.appConfig),
         ...{
             appRootPath: initEvent.appPath
         }
     });
     swanEvents('masterActiveInitRender');
     // 压入initSlave
     this.navigator.pushInitSlave({
         pageUrl: initEvent.pageUrl,
         slaveId: +initEvent.wvID,
         root: initEvent.root,
         preventAppLoad: initEvent.preventAppLoad
     });
     this.appPath = initEvent.appPath;
     swanEvents('masterActivePushInitslave');
   }
   ````

   * 创建初始化页面的slave后，如果没有预加载，就加载小程序里的app.js文件（注意：是编译后的app.js文件），并发送’slaveLoaded’事件，通知渲染层开始渲染

     \`\`\`javascript

     /\*\*
   * 初始化第一个slave
   * @param {Object} \[initParams] - 初始化的参数

     \*/

     pushInitSlave(initParams) {

     ....

     // 创建初始化slave

     this.initSlave = this.createInitSlave(initParams.pageUrl, this.appConfig);

     // slave的init调用

     this.initSlave

     ```
     .init(initParams)
     .then(initRes => {
         swanEvents('masterActiveCreateInitslaveEnd');
         // 入栈
         this.history.pushHistory(this.initSlave);
         swanEvents('masterActivePushInitslaveEnd');
         // 调用slave的onEnqueue生命周期函数
         this.initSlave.onEnqueue();
         swanEvents('masterActiveOnqueueInitslave');
     });
     ```

     }

     /\*\*
   * 初始化为第一个页面

     \*
   * @param {Object} initParams 初始化的配置参数
   * @return {Promise} 返回初始化之后的Promise流 \*/ Slave.init(initParams) { this.isFirstPage = true; return Promise .resolve(initParams) .then(initParams => { swanEvents('masterActiveInitAction'); if (!!initParams.preventAppLoad) { return initParams; } // const loadCommonJs = this.appConfig.splitAppJs // && !this.appConfig.subPackages // ? 'common.js' : 'app.js'; const loadCommonJs = 'app.js'; return loader .loadjs(`${this.appRootPath}/${loadCommonJs}`, 'masterActiveAppJsLoaded') .then(() => { return this.loadJs.call(this, initParams); }); }) .then(initParams => { this.uri = initParams.pageUrl.split('?')\[0]; this.accessUri = initParams.pageUrl; this.slaveId = initParams.slaveId; // init的事件为客户端处理，确保是在slave加载完成之后，所以可以直接派发 this.swaninterface.communicator.fireMessage({ type: `slaveLoaded${this.slaveId}`, message: {slaveId: this.slaveId} }); return initParams; }); }

     \`\`\`
   * 执行slave入栈后的生命周期函数this.initSlave.onEnqueue(); 在此函数里，会真正Page Instance，同时监听到渲染层准备好后，发送’initData’事件

     \`\`\`javascript /\*\*
   * 入栈之后的生命周期方法

     \*
   * @return {Object} 入栈之后，创建的本slave的页面实例对象

     \*/

     onEnqueue() {

     return this.createPageInstance();

     }

     /\*\*
   * 创建页面实例，并且，当slave加载完成之后，向slave传递初始化data

     \*
   * @return {Promise} 创建完成的事件流

     \*/

     createPageInstance() {

     if (this.isCreated()) {

     ```
     return Promise.resolve();
     ```

     }

     swanEvents('masterActiveCreatePageFlowStart', {

     ```
     uri: this.uri
     ```

     });

     const userPageInstance = createPageInstance(this.accessUri, this.slaveId, this.appConfig);

     const query = userPageInstance.privateProperties.accessUri.split('?')\[1];

     this.setUserPageInstance(userPageInstance);

     try {

     ```
     swanEvents('masterPageOnLoadHookStart');
     userPageInstance._onLoad(getParams(query));
     swanEvents('masterPageOnLoadHookEnd');
     ```

     }

     catch (e) {

     ```
     // avoid empty state
     ```

     }

     this.status = STATUS\_MAP.CREATED;

     console.log(`Master 监听 slaveLoaded 事件，slaveId=${this.slaveId}`);

     return this.swaninterface.invoke('loadJs', {

     ```
     uri: this.uri,
     eventObj: {
         wvID: this.slaveId
     },
     success: params => {
         swanEvents('masterActiveCreatePageFlowEnd');
         swanEvents('masterActiveSendInitdataStart');
         userPageInstance.privateMethod
             .sendInitData.call(userPageInstance, this.appConfig);
         swanEvents('masterActiveSendInitdataEnd');
     }
     ```

     });

     }

     \`\`\`
4. 渲染层slave.js
   * 监听’PageReady’事件，加载对应页面的文件：app.css，index.css，index.swan.js文件

     \`\`\`javascript

     /\*\*
   * 监听pageReady，触发整个入口的调起
   * @param {Object} \[global] 全局对象

     \*/

     listenPageReady(global) {

     swanEvents('slavePreloadListened');

     // 控制是否开启预取initData的开关

     let advancedInitDataSwitch = false;

     this.swaninterface.bind('PageReady', event => {

     &#x20;  swanEvents('slaveActiveStart', {

     ```
        pageInitRenderStart: Date.now() + ''
     ```

     &#x20;  });

     &#x20;  ...

     &#x20;  const appPath = event.appPath;

     &#x20;  const pagePath = event.pagePath.split('?')\[0];

     &#x20;  const onReachBottomDistance = event.onReachBottomDistance;

     &#x20;  ...

     &#x20;  let loadUserRes = () => {

     ```
        // 设置页面的基础路径为当前页面本应所在的路径
        // 行内样式等使用相对路径变成此值
        // setPageBasePath(`${appPath}/${pagePath}`);
        swanEvents('slaveActivePageLoadStart');
        // 加载用户的资源
        Promise.all([
            loader.loadcss(`${appPath}/app.css`, 'slaveActiveAppCssLoaded'),
            loader.loadcss(`${appPath}/${pagePath}.css`, 'slaveActivePageCssLoaded')
        ])
            .catch(() => {
                console.warn('加载css资源出现问题，请检查css文件');
            })
            .then(() => {
                // todo: 兼容天幕，第一个等天幕同步后，干掉
                swanEvents('slaveActiveCssLoaded');
                swanEvents('slaveActiveSwanJsStart');
                loader.loadjs(`${appPath}/${pagePath}.swan.js`, 'slaveActiveSwanJsLoaded');
            });
     ```

     &#x20;  };

     &#x20;  // (event.devhook === 'true' ? loadHook().then(loadUserRes).catch(loadUserRes) : loadUserRes());

     &#x20;  loadUserRes();

     });

     }

     \`\`\`
   * 在每个页面编译后的xxx.swan.js文件里，会执行pageRender()函数，进行界面渲染，如此demo里的index.swan.js文件

     ```javascript
     ((global)=>{
     global.errorMsg = [];
     var templateComponents = Object.assign({}, {});
     var param = {};
     var filterArr = JSON.parse("[]");
     try {
        filterArr && filterArr.forEach(function (item) {
            param[item.module] = eval(item.module)
        });
        var pageContent = `
            <div class=\"wrap\">
                <div>{{text}}</div>
                <button class=\"btn\" type=\"primary\" v-on:click=\"eventHappen('tap', $event, 'add', '', 'bind')\">
                    add text
                </button>
                <button class=\"btn\" type=\"primary\" v-on:click=\"eventHappen('tap', $event, 'remove', '', 'bind')\">
                    remove text
                </button>
            </div>`;
        var renderPage = function (filters, modules) {
            ...
            global.pageRender(pageContent, templateComponents)
        };
        renderPage(filterArr, param);
     } catch (e) {
        global.errorMsg['execError'] = e;
        throw e;
     }
     })(window);
     ```
   * global.pageRender()函数是在slave.js文件里定义的方法，其内部的逻辑就是创建对应的san框架里的Page组件，等待初始化数据过来后，再绑定到界面上

     \`\`\`javascript

     /\*\*
   * 注册所有components(也包括顶层components -- page)

     \*/

     registerComponents() {

     ...

     global.pageRender = (pageTemplate, templateComponents, customComponents, filters, modules) => {

     &#x20;  ...

     &#x20;  // 定义当前页面的组件

     &#x20;  componentFactory.componentDefine(

     ```
        'page',
        {
            template: `<swan-page tabindex="-1">${pageTemplate}</swan-page>`,
            superComponent: 'super-page'
        },
        {
            classProperties: {
                components: {...componentFactory.getComponents(), ...templateComponents, ...customComponents},
                filters: {
                    ...filtersObj
                }
            }
        }
     ```

     &#x20;  );

     &#x20;  swanEvents('slaveActiveDefineComponentPage');

     &#x20;  // 获取page的组件类

     &#x20;  const Page = global.componentFactory.getComponents('page');

     &#x20;  // 初始化页面对象

     &#x20;  const page = new Page();

     &#x20;  swanEvents('slaveActiveConstructUserPage');

     &#x20;  // 调用页面对象的加载完成通知

     &#x20;  page.slaveLoaded();

     &#x20;  swanEvents('slaveActiveUserPageSlaveloaded');

     &#x20;  // 用于记录用户模板代码在开始执行到监听initData事件之前的耗时

     &#x20;  global.FeSlaveSwanJsInitEnd = Date.now();

     &#x20;  // 监听等待initData，进行渲染

     &#x20;  page.communicator.onMessage('initData', params => {

     ```
        swanEvents('slaveActiveReceiveInitData');
        try {
            // 根据master传递的data，设定初始数据，并进行渲染
            page.setInitData(params);
            swanEvents('slaveActiveRenderStart');
            // 真正的页面渲染，发生在initData之后
            // 此处让页面真正挂载处于自定义组件成功引用其他自定义组件之后,
            // 引用其它自定义组件是在同一时序promise.resolve().then里执行, 故此处attach时, 自定义组件已引用完成
            setTimeout(() => {
                page.attach(document.body);
                // 通知master加载首屏之后的逻辑
                page.communicator.sendMessage(
                    'master', {
                        type: 'slaveAttached',
                        slaveId: page.slaveId
                    }
                );
                swanEvents('slaveActivePageAttached');
            }, 0);
        }
        catch (e) {
            console.log(e);
            global.errorMsg['renderError'] = e;
        }
     ```

     &#x20;  }, {listenPreviousEvent: true});

     &#x20;  ...

     };

     ...

     }

     \`\`\`
   * 当界面渲染后，发送’slaveAttached’事件，逻辑层执行onShow()生命周期函数

## reference

* [百度小程序源码解读](https://handsomeliuyang.github.io/2019/02/25/百度小程序源码解读/)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://raozhanping.gitbook.io/notes/js/js-baidu-minipro.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
