资讯专栏INFORMATION COLUMN

深入wepy源码:wpy文件编译过程

stdying / 604人阅读

摘要:深入源码文件编译过程是腾讯开源的一款小程序框架,主要通过预编译的手段,让开发者采用类风格开发。处理好的最终会写入文件中,文件存储路径会判断类型是否为。根据上面的流程图,可以看出所有的文件生成之前都会经过处理。

深入wepy源码:wpy文件编译过程

wepy 是腾讯开源的一款小程序框架,主要通过预编译的手段,让开发者采用类 Vue 风格开发。 让我们一起看看, wepy 是如何实现预编译的。先放上一张官网的流程图,后面的分析可以参考该图。

wepy-cli 主要负责 .wpy 文件的编译,目录结构如下:

编译的入口是 src/compile.js 中的 compile() 方法,该方法主要是根据文件类型,执行不同的 compiler ,比如 .wpy 文件会走 compile-wpy.js 下的 compile() 方法。

compile(opath) {
  ...
  switch(opath.ext) {  
    case ext:  
      cWpy.compile(opath);  
      break;  
    case ".less":  
      cStyle.compile("less", opath);  
      break;  
    case ".sass":  
      cStyle.compile("sass", opath);  
      break;  
    case ".scss":  
      cStyle.compile("scss", opath);  
      break;  
    case ".js":  
      cScript.compile("babel", null, "js", opath);  
      break;  
    case ".ts":  
      cScript.compile("typescript", null, "ts", opath);  
      break;  
    default:  
      util.output("拷贝", path.join(opath.dir, opath.base)); 
    ... 
  } 
}  
.wpy文件拆解

compile-wpy.js 下的 compile() 方法,核心调用了 resolveWpy() 方法。

resolveWpy() 方法,主要是将 .wpy 拆解成 rst 对象,并对其中的 template、script 做一些预处理,然后将 template、 script、 style 三部分移交给不同的 compiler 处理。

生成rst对象

通过 xmldom 获取 xml 对象,然后遍历节点,拆解为 rst对象。

import {DOMParser} from "xmldom";
export default {
  createParser (opath) {
    return new DOMParser({
      ...
    })
  },
  ...
  resolveWpy () {
    let xml = this.createParser(opath).parseFromString(content);
  }
}

rst对象结构如下:

let rst = {
  moduleId: moduleId,
  style: [],
  template: {
    code: "",
    src: "",
    type: ""
  },
  script: {
    code: "",
    src: "",
    type: ""
  }
}; 

此外,还对 template 做了如下一些预处理:

pug 预编译

获取文件中的 import ,放入 rst.template.components

获取 propsevents ,放入 rst.script.code

compile-template

compile-template.js 中的 compile() 方法,根据 template 的 lang 值,执行不同的 compiler ,比如 wepy-compile-typescript 。编译完成后,执行 compileXML 方法,做了如下的操作:

updateSlot 方法: 替换 slot 内容

updateBind 方法: 在 {{}} 和 attr 上加入组件的前缀,例如:{{width}} -> {{$ComponentName$width}}

把自定义的标签、指令转换为 wxml 语法,例如:



compile-style

依旧先是根据 lang 值,先执行不同的 compiler ,比如 wepy-compile-less 。编译完成后,执行 src/style-compiler/scope.js 中的 scopedHandler() 方法,处理 scoped

import postcss from "postcss";
import scopeId from "./scope-id";

export default function scopedHandler (id, content) {
  console.log("id is: ", id)
  console.log("css content is: ", content)
  return postcss([scopeId(id)])
    .process(content)
    .then(function (result) {
      console.log("css result is: ", result.css)
      return result.css
    }).catch((e) => {
      return Promise.reject(e)
    })
}

这里主要是利用 add-id 的 postcss 插件,插件源码可参考 src/style-compiler/scope-id.js。根据上面的代码,打印出来的log如下:

最后,会把 requires 由绝对路径替换为相对路径,并在 wxss 中引入,最终生成的 wxss 文件为:

@import "./../components/demo.wxss";

Page{background:#F4F5F7} ...  
compile-script

依旧先是根据 lang 值,执行不同的 compiler。compiler 执行完之后,判断是否是 npm 包,如果不是,依据不同的 type 类型,加入 wepy 初始化的代码。

if (type !== "npm") {
  if (type === "page" || type === "app") {
    code = code.replace(/exports.defaults*=s*(w+);/ig, function (m, defaultExport) {
      if (defaultExport === "undefined") {
        return "";
      }
      if (type === "page") {
        let pagePath = path.join(path.relative(appPath.dir, opath.dir), opath.name).replace(//ig, "/");
        return `
Page(require("wepy").default.$createPage(${defaultExport} , "${pagePath}"));
`;
      } else {
        appPath = opath;
        let appConfig = JSON.stringify(config.appConfig || {});
        let appCode = `
App(require("wepy").default.$createApp(${defaultExport}, ${appConfig}));
`;
        if (config.cliLogs) {
          appCode += "require("./_wepylogs.js")
";
        }
        return appCode;
      }
    });
  }
}

接下来会执行 resolveDeps() 方法,主要是处理 requires。根据 require 文件的类型,拷贝至对应的目录,再把 code 中的 require 代码替换为 相对路径。

处理好的 code 最终会写入 js 文件中,文件存储路径会判断类型是否为 npm。

let target;
if (type !== "npm") {
  target = util.getDistPath(opath, "js");
} else {
  code = this.npmHack(opath, code);
  target = path.join(npmPath, path.relative(opath.npm.modulePath, path.join(opath.dir, opath.base)));
}
plugin

根据上面的流程图,可以看出所有的文件生成之前都会经过 Plugin 处理。先来看一下,compiler 中是如何载入 Plugin 的。

let plg = new loader.PluginHelper(config.plugins, {
  type: "css",
  code: allContent,
  file: target,
  output (p) {
    util.output(p.action, p.file);
  },
  done (rst) {
    util.output("写入", rst.file);
    util.writeFile(target, rst.code);
  }
});

其中,config.plugins 就是在 wepy.config.js 中定义的 plugins。让我们来看一下 PluginHelper 类是如何定义的。

class PluginHelper {
  constructor (plugins, op) {
    this.applyPlugin(0, op);
    return true;
  }
  applyPlugin (index, op) {
    let plg = loadedPlugins[index];

    if (!plg) {
      op.done && op.done(op);
    } else {
      op.next = () => {
        this.applyPlugin(index + 1, op);
      };
      op.catch = () => {
        op.error && op.error(op);
      };
      if (plg)
        plg.apply(op);
    }
  }
}

在有多个插件的时候,不断的调用 next(),最后执行 done()

编写plugin

wxss 与 css 相比,拓展了尺寸单位,即引入了 rpx 单位。但是设计童鞋给到的设计稿单位一般为 px,那现在我们就一起来编写一个可以将 px 转换为 rpx 的 wepy plugin。

从 PluginHelper 类的定义可以看出,是调用了 plugin 中的 apply() 方法。另外,只有 .wxss 中的 rpx 才需要转换,所以会加一层判断,如果不是 wxss 文件,接着执行下一个 plugin。rpx 转换为 px 的核心是,使用了 postcss-px2units plugin。下面就是设计好的 wepy-plugin-px2units,更多源码可参考 github 地址。

import postcss from "postcss";
import px2units from "postcss-px2units";

export default class {

  constructor(c = {}) {
    const def = {
      filter: new RegExp(".(wxss)$"),
      config: {}
    };

    this.setting = Object.assign({}, def, c);
  }
  apply (op) {

    let setting = this.setting;

    if (!setting.filter.test(op.file)) {
      op.next();
    } else {
      op.output && op.output({
        action: "变更",
        file: op.file
      });

      let prefixer = postcss([ px2units(this.setting.config) ]);

      prefixer.process(op.code, { from: op.file }).then((result) => {
        op.code = result.css;
        op.next();
      }).catch(e => {
        op.err = e;
        op.catch();
      });
    }
  }
}
最后

本文分析的源码以 wepy-cli@1.7.1 版本为准,更多信息可参考 wepy github (即 github 1.7.x 分支)。另外,文中有任何表述不清或不当的地方,欢迎大家批评指正。

本文首发于:https://github.com/yingye/Blo...

欢迎各位关注我的Blog,正文以issue形式呈现,喜欢请点star,订阅请点watch~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94894.html

相关文章

  • wepy - 一个小程序的组件化开发框架

    摘要:主要解决问题开发模式转换在原有的小程序的开发模式下进行再次封装,更贴近于现有框架开发模式。官方代码获取应用实例事件处理函数基于的实现支持组件化开发。根组件,一般都是页面父组件小程序对象子组件列表方法参数返回值说明组件初始化。 小程序框架wepy文档 Github地址 快速入门 项目创建与使用 安装wepy 以下安装都通过npm安装 安装 wepy 命令行工具。 npm install ...

    I_Am 评论0 收藏0
  • 【babel+小程序】记“编写babel插件”与“通过语法解析替换小程序路由表”的经历

    摘要:而扫描各个模块并合并路由表的脚本非常简单,读写文件就了。编写插件之前先要理解抽象语法树这个概念。的解析器,的配置。编写脚本识别字段思路首先获取到源代码是类单文件的语法。获取内的字段,并替换成已生成的路由表。 话不多说先上图,简要说明一下干了些什么事。图可能太模糊,可以点svg看看showImg(https://segmentfault.com/img/bV3fs4?w=771&h=63...

    李昌杰 评论0 收藏0
  • wepy框架开发小程序文档

    摘要:目录项目构建文件使用优化之处组件通信的使用注意事项报错记录踩坑记录项目构建官方文档地址链接项目源码地址链接项目资料地址链接简单介绍是一个微信小程序框架,支持模块化开发,开发风格类似。使用的方式请求小程序原生都将化。 目录 wepy项目构建 wepy文件使用 wepy优化之处 wepy组件通信 wepy的API使用 wepy注意事项 wepy报错记录 wepy踩坑记录 1. wep...

    Luosunce 评论0 收藏0
  • 微信小程序wepy框架学习和使用心得

    摘要:四最后一点点感悟本文总结的比较浅显很多地方说的也不是太详细欢迎大家留言一起交流探讨坚持学习不断探索总结路漫漫其修远兮吾将上下而求索参考资料官方文档微信小程序官网开发文档官方开发文档 一、微信小程序wepy框架简介: 微信小程序WePY框架是腾讯官方推出来的框架,类似的框架还有美团的mpvue,京东的Taro等; 目前公司开发小程序主要用到的是微信原生方法和官方的wepy框架; wepy...

    Baaaan 评论0 收藏0

发表评论

0条评论

stdying

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<