chore: add cloudbase

This commit is contained in:
dntzhang 2020-03-17 16:27:00 +08:00
parent 0046f14740
commit 123bb319aa
161 changed files with 33751 additions and 0 deletions

View File

@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -0,0 +1,8 @@
{
"extends": "eslint:recommended",
"rules": {
"no-unused-vars": ["error", { "varsIgnorePattern": "Taro" }],
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".tsx"] }]
},
"parser": "babel-eslint"
}

6
packages/omi-cloudbase/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
dist/miniprogram
dist/cloudfunctions
.temp/
.rn_temp/
node_modules/
.DS_Store

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Omi Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,451 @@
# 简介
云开发CloudBase[https://www.cloudbase.net/](https://www.cloudbase.net/) 是基于Serverless架构构建的一站式后端云服务涵盖函数、数据库、存储、CDN等服务免后端运维支持小程序、Web和APP开发。
其中,小程序·云开发是微信和腾讯云联合推出的云端一体化解决方案,基于云开发可以免鉴权调用微信所有开放能力,在微信开发者工具中即可开通使用。
# Omi Cloud 实战小程序 markdown 内容发布系统
开发小程序,但是:没有后端!没有运维!没有 DBA没有域名没有证书没有钱没有时间没有精力
**没有关系,会 javascript 就可以,小程序•云开发带你起飞!**
开发者可以使用云开发开发微信小程序、小游戏,无需搭建服务器,即可使用云端能力。云开发为开发者提供完整的云端支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。
目前提供三大基础能力支持:
- 云函数:在云端运行的代码,微信私有协议天然鉴权,开发者只需编写自身业务逻辑代码
- 数据库:一个既可在小程序前端操作,也能在云函数中读写的 JSON 数据库
- 存储:在小程序前端直接上传/下载云端文件,在云开发控制台可视化管理
## 一步一步搭建
本文将一步一步教你使用 小程序•云开发 + Omip + Comi 搭建一个支持 markdown 和代码高亮的小程序内容展示和发布系统。
预览:
![](https://github.com/Tencent/omi/raw/master/assets/omi-cloud.jpg)
### 1.建表
<img src="https://github.com/Tencent/omi/raw/master/assets/oc.jpg" width="800">
操作路径: 微信开发者工具→云开发→数据库→添加集合
article 集合字段说明:
| **字段** | **说明** |
| ------------------------------- | ----------------------------------- |
| _id | 数据的唯一 id用户写入时系统自动生产 |
| _openid | 用户的唯一标识,用户写入时系统自动生产 |
| createTime | 文章创建时间 |
| md | 文章内容 |
| order | 文章的顺序 |
| title | 文章的标题 |
很明显,这个表用来存储所有的文章。然后设置表的读写权限:
<img src="https://github.com/Tencent/omi/raw/master/assets/db-config.jpg" width="300">
因为后续可能支持用户发表文章,所有设置成第一个。
### 2.初始化项目目录
```bash
$ npm i omi-cli -g
$ omi init-cloud my-app
$ cd my-app
$ npm start
```
<img src="https://github.com/Tencent/omi/raw/master/assets/dir.jpg" width="400">
这里是使用 omip 作为脚手架,也支持 Omi mps-cloud 创建原生小程序的云开发的脚手架:
```bash
$ npm i omi-cli -g
$ omi init-mps-cloud my-app
$ cd my-app/miniprogram
$ npm install
$ npm start
```
### 3.项目初始化 app.js
```jsx
import './app.css'
import './pages/list/index'
import { render, WeElement, define } from 'omi'
define('my-app', class extends WeElement {
config = {
pages: [
'pages/list/index',
'pages/detail/index',
'pages/import/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'Omi Cloud',
navigationBarTextStyle: 'black'
}
}
install() {
if (!wx.cloud) {
console.error('请使用 2.2.3 或以上的基础库以使用云能力')
} else {
wx.cloud.init({
traceUser: true,
})
this.globalData.db = wx.cloud.database({
env: 'test-06eb2e'
})
}
}
render() {
return (
<page-list />
)
}
})
render(<my-app />, '#app')
```
<img src="https://github.com/Tencent/omi/raw/master/assets/env.jpg" width="600">
`wx.cloud.database` 代码参数里的 env 可以从上面获取到,一般创建两个环境,一个用户测试环境,一个用于生产环境。
- pages/list/index 文章列表首页
- pages/detail/index 文章详情夜
- pages/import/index 文章导入页(先简单通过代码导入 markdown没提供 UI)
### 导入 markdown 数据
```js
import { WeElement, define } from 'omi'
import data from './test.md'
const app = getApp()
define('page-import', class extends WeElement {
installed() {
wx.cloud.callFunction({
name: 'login',
data: {},
success: res => {
app.globalData.openid = res.result.openid
app.globalData.db.collection('article').add({
data: {
md: data.md,
title: 'test',
createTime: app.globalData.db.serverDate()
},
success: (res) => {
console.log(res)
},
fail: err => {
console.error('[云函数] [login] 调用失败', err)
}
})
},
fail: err => {
console.error('[云函数] [login] 调用失败', err)
}
})
}
...
...
})
```
注意三点:
- 通过 `wx.cloud.callFunction` 调用云函数进行登陆,且获取 openid接着导入数据会自动带上提交该 openid。
- 通过 `app.globalData.db.serverDate()` 获取服务端时间,客户端时间不可靠
- 文章导入只由管理员负责
注意 `import data from './test.md'`,这里通过修改 omip 里的 scripts 逻辑实现。
这里解释下 import markdown 原理:
```js
let code = fs.readFileSync(item).toString()
if (path.extname(item) === '.md') {
code = `export default { md: \`${code.replace(/`/g, '\\`').replace(/\$/g, '\\$')}\` }`
}
```
检测到 md 后缀的文件,把文件里的 markdown 字符串对关键字进行转义然后变成一个 js 模块。
这也算是使用中间编译的好处之一吧,如果原生的小程序目前没办法 import markdown 文件,当然原生小程序 API 和周边生态在不断进化,腾讯 Omi 团队开发的 [mps 框架](https://github.com/Tencent/omi/tree/master/packages/mps) 就是让你在原生小程序中使用 jsx 和 less。
上面的详细代码可以[点击这里](https://github.com/Tencent/omi/blob/master/packages/omi-cloud/scripts/taro-cli/src/weapp.js#L1968-L1971)查看到。
### 列表页
<img src="https://github.com/Tencent/omi/raw/master/assets/omi-cloud-list.jpg" width="420">
请求 list 数据
```js
//先展示 loading
wx.showLoading({
title: '加载中'
})
//调用云函数获取 openid
wx.cloud.callFunction({
name: 'login',
data: {},
success: res => {
app.globalData.openid = res.result.openid
app.globalData.db.collection('article').field({
title: true,
_id: true,
order: true
}).get().then(res => {
this.data.list = res.data.sort(function (a, b) {
return a.order - b.order
})
this.update()
wx.hideLoading()
})
},
fail: err => {
console.error('[云函数] [login] 调用失败', err)
}
})
```
- 请求 list通过 field 方法筛选字段,毕竟 list 不需要 md 字段,这样可以减少数据传输,节约带宽
- 通过 order 字段对 list 进行排序(这样管理员不需要发版本就可以手动调整 order 给 list 排序)
完整的代码:
```jsx
import { WeElement, define } from 'omi'
import './index.css'
import arrowPng from './arrow.png'
//获取应用实例
const app = getApp()
define('page-about', class extends WeElement {
config = {
navigationBarBackgroundColor: '#24292e',
navigationBarTextStyle: 'white',
navigationBarTitleText: 'Omi',
backgroundColor: '#ccc',
backgroundTextStyle: 'light'
}
data = {
list: []
}
installed() {
wx.showLoading({
title: '加载中'
})
wx.cloud.callFunction({
name: 'login',
data: {},
success: res => {
console.log('[云函数] [login] user openid: ', res.result.openid)
app.globalData.openid = res.result.openid
app.globalData.db.collection('article').field({
title: true,
_id: true,
order: true
}).get().then(res => {
this.data.list = res.data.sort(function (a, b) {
return a.order - b.order
})
this.update()
wx.hideLoading()
})
},
fail: err => {
console.error('[云函数] [login] 调用失败', err)
}
})
}
gotoDetail = (evt) => {
wx.navigateTo({
url: '../detail/index?id=' + evt.currentTarget.dataset.id
})
}
render() {
return (
<view class='ctn'>
{list.map(item => (
<view class='item' data-id={item._id} bindtap={this.gotoDetail}>
<text>{item.title}</text>
<image src={arrowPng}></image>
</view>
))}
</view>
)
}
})
```
Omip 可以直接让你使用 jsx 书写 wxml 结构。编译出的 wxml 如下:
```html
<block>
<view class="ctn">
<view class="item" data-id="{{item._id}}" bindtap="gotoDetail" wx:for="{{list}}" wx:for-item="item"><text>{{item.title}}</text>
<image src="{{arrowPng}}"></image>
</view>
</view>
</block>
```
这里需要注意,点击每一项跳转详情也一定要使用 `evt.currentTarget.dataset.id`,而不能使用 `evt.target.dataset.id`。这样点击到文字或者image
上获取不到 id。
<!--
### 依赖的数据表
<img src="https://github.com/Tencent/omi/raw/master/assets/omi-cloud-db.jpg" width="600">
<img src="https://github.com/Tencent/omi/raw/master/assets/omi-cloud-db-todo.jpg" width="600">
-->
### 文章详情展示
这里使用 Comi 进行 markdown 渲染! Comi 读 ['kəʊmɪ],类似中文 科米,是腾讯 Omi 团队开发的小程序代码高亮和 markdown 渲染组件。Comi 是基于下面几个优秀的社区组件进行二次开发而成。
* wxParse
* remarkable
* html2json
* htmlparser
* prism
效果预览:
<img src="https://github.com/Tencent/omi/raw/master/assets/comi.jpg" width="320">
```js
import { WeElement, define } from 'omi'
import './index.css'
import comi from '../../components/comi/comi'
//获取应用实例
const app = getApp()
define('page-about', class extends WeElement {
config = {
navigationBarBackgroundColor: '#24292e',
navigationBarTextStyle: 'white',
navigationBarTitleText: ' ',
backgroundColor: '#eeeeee',
backgroundTextStyle: 'light'
}
install(options) {
wx.showLoading({
title: '加载中'
})
app.globalData.db.collection('article').doc(options.id).get().then(res=>{
comi(res.data.md, this.$scope)
wx.hideLoading()
}).catch(err => {
console.error(err)
})
}
render() {
return (
<view>
<include src="../../components/comi/comi.wxml" />
</view>
)
}
})
```
除了在 omip 中使用,原生小程序也可以使用 Comi:
先拷贝 [此目录](https://github.com/Tencent/omi/tree/master/packages/comi/mp/comi) 到你的项目。
js:
```js
const comi = require('../../comi/comi.js');
Page({
onLoad: function () {
comi(`你要渲染的 md`, this)
}
})
```
wxml:
```html
<include src="../../comi/comi.wxml" />
```
wxss:
```css
@import "../../comi/comi.wxss";
```
大功告成,简单把!
### 云函数与调试
云函数即在云端(服务器端)运行的函数。在物理设计上,一个云函数可由多个文件组成,占用一定量的 CPU 内存等计算资源;各云函数完全独立;可分别部署在不同的地区。开发者无需购买、搭建服务器,只需编写函数代码并部署到云端即可在小程序端调用,同时云函数之间也可互相调用。
一个云函数的写法与一个在本地定义的 JavaScript 方法无异,代码运行在云端 Node.js 中。当云函数被小程序端调用时,定义的代码会被放在 Node.js 运行环境中执行。我们可以如在 Node.js 环境中使用 JavaScript 一样在云函数中进行网络请求等操作,而且我们还可以通过云函数后端 SDK 搭配使用多种服务,比如使用云函数 SDK 中提供的数据库和存储 API 进行数据库和存储的操作,这部分可参考数据库和存储后端 API 文档。
云开发的云函数的独特优势在于与微信登录鉴权的无缝整合。当小程序端调用云函数时,云函数的传入参数中会被注入小程序端用户的 openid开发者无需校验 openid 的正确性因为微信已经完成了这部分鉴权,开发者可以直接使用该 openid。
在本文的小程序里有个 todo 的案例,里面的 remove 使用了云函数,用于清空所有已完成的任务。
<img src="https://github.com/Tencent/omi/raw/master/assets/todomvc.jpg" width="420">
```js
const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
try {
return await db.collection('todo').where({
done: true
}).remove()
} catch (e) {
console.error(e)
}
}
```
<img src="https://github.com/Tencent/omi/raw/master/assets/debug-cloud-fn.png" width="820">
不过最新的IED云函数支持了本地调试功能感兴趣的可以[点击这里](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/local-debug.html)了解下。
## 相关链接
- [源码地址](https://github.com/Tencent/omi/tree/master/packages/omi-cloud)
- [官方教程](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html)
- [小程序端 API 文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-client-api/)
- [服务端 API 文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-server-api/)

View File

@ -0,0 +1,9 @@
module.exports = {
env: {
NODE_ENV: '"development"'
},
defineConstants: {
},
weapp: {},
h5: {}
}

View File

@ -0,0 +1,107 @@
const config = {
projectName: 'my-app',
date: '2019-2-21',
designWidth: 750,
deviceRatio: {
'640': 2.34 / 2,
'750': 1,
'828': 1.81 / 2
},
alias: {
'omi': 'src/libs/omip-h5/omi.esm.js',
'@tarojs': 'src/libs'
},
sourceRoot: 'src',
outputRoot: 'dist',
plugins: {
babel: {
sourceMap: true,
presets: [
['env', {
modules: false
}]
],
plugins: [
'transform-decorators-legacy',
'transform-class-properties',
'transform-object-rest-spread',
// ['transform-react-jsx', { pragma: 'global.Omi.h' }]
]
}
},
defineConstants: {
},
copy: {
patterns: [
],
options: {
}
},
weapp: {
module: {
postcss: {
autoprefixer: {
enable: true,
config: {
browsers: [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]
}
},
pxtransform: {
enable: false,
config: {
}
},
url: {
enable: true,
config: {
limit: 10240 // 设定转换尺寸上限
}
},
cssModules: {
enable: false, // 默认为 false如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
},
h5: {
publicPath: '/',
staticDirectory: 'static',
module: {
postcss: {
autoprefixer: {
enable: true,
config: {
browsers: [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]
}
},
cssModules: {
enable: false, // 默认为 false如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
}
}
module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}

View File

@ -0,0 +1,107 @@
const config = {
projectName: 'my-app',
date: '2019-2-21',
designWidth: 750,
deviceRatio: {
'640': 2.34 / 2,
'750': 1,
'828': 1.81 / 2
},
alias: {
'omi': 'src/libs/omip/index.js',
'@tarojs': 'src/libs'
},
sourceRoot: 'src',
outputRoot: 'dist/miniprogram',
plugins: {
babel: {
sourceMap: true,
presets: [
['env', {
modules: false
}]
],
plugins: [
'transform-decorators-legacy',
'transform-class-properties',
'transform-object-rest-spread',
['transform-react-jsx', { pragma: 'global.Omi.h' }]
]
}
},
defineConstants: {
},
copy: {
patterns: [
],
options: {
}
},
weapp: {
module: {
postcss: {
autoprefixer: {
enable: true,
config: {
browsers: [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]
}
},
pxtransform: {
enable: false,
config: {
}
},
url: {
enable: true,
config: {
limit: 10240 // 设定转换尺寸上限
}
},
cssModules: {
enable: false, // 默认为 false如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
},
h5: {
publicPath: '/',
staticDirectory: 'static',
module: {
postcss: {
autoprefixer: {
enable: true,
config: {
browsers: [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]
}
},
cssModules: {
enable: false, // 默认为 false如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
}
}
module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}

View File

@ -0,0 +1,11 @@
module.exports = {
env: {
NODE_ENV: '"production"'
},
defineConstants: {
},
weapp: {},
h5: {
publicPath: './',
}
}

2
packages/omi-cloudbase/dist/README.md vendored Normal file
View File

@ -0,0 +1,2 @@
* cloudfunctions 目录存放云函数
* miniprogram 目录存放小程序

View File

@ -0,0 +1,56 @@
{
"miniprogramRoot": "miniprogram/",
"cloudfunctionRoot": "cloudfunctions/",
"setting": {
"urlCheck": true,
"es6": true,
"postcss": true,
"minified": true,
"newFeature": true
},
"appid": "wxdf970566455966b0",
"projectname": "omi-cloud",
"libVersion": "2.6.2",
"condition": {
"search": {
"current": -1,
"list": []
},
"conversation": {
"current": -1,
"list": []
},
"plugin": {
"current": -1,
"list": []
},
"game": {
"list": []
},
"miniprogram": {
"current": 2,
"list": [
{
"id": -1,
"name": "导入数据",
"pathName": "pages/import/index",
"query": "",
"scene": null
},
{
"id": -1,
"name": "代码高亮",
"pathName": "pages/code/index",
"query": "",
"scene": null
},
{
"id": -1,
"name": "入口",
"pathName": "pages/list/index",
"scene": null
}
]
}
}
}

View File

@ -0,0 +1,123 @@
{
"name": "my-app",
"version": "1.0.0",
"private": true,
"description": "",
"scripts": {
"start": "npm run build:weapp -- --watch",
"build:weapp": "node ./scripts/taro-cli/bin/taro build --type weapp",
"build:swan": "node ./scripts/taro-cli/bin/taro build --type swan",
"build:alipay": "node ./scripts/taro-cli/bin/taro build --type alipay",
"build:tt": "node ./scripts/taro-cli/bin/taro build --type tt",
"build:h5": "node ./scripts/taro-cli/bin/taro build --type h5",
"build:rn": "node ./scripts/taro-cli/bin/taro build --type rn",
"dev:swan": "npm run build:swan -- --watch",
"dev:alipay": "npm run build:alipay -- --watch",
"dev:tt": "npm run build:tt -- --watch",
"dev:h5": "npm run build:h5 -- --watch",
"dev:rn": "npm run build:rn -- --watch"
},
"author": "",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0-beta.44",
"@fdaciuk/ajax": "^3.0.4",
"@tarojs/components": "1.2.13",
"@tarojs/router": "1.2.13",
"@tarojs/taro": "1.2.13",
"@tarojs/taro-alipay": "1.2.13",
"@tarojs/taro-h5": "1.2.13",
"@tarojs/taro-swan": "1.2.13",
"@tarojs/taro-tt": "1.2.13",
"@tarojs/taro-weapp": "1.2.13",
"@tarojs/taroize": "1.2.13",
"autoprefixer": "^8.4.1",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-generator": "^6.26.1",
"babel-helper-evaluate-path": "^0.5.0",
"babel-helper-mark-eval-scopes": "^0.4.3",
"babel-helper-remove-or-void": "^0.4.3",
"babel-plugin-danger-remove-unused-import": "^1.1.1",
"babel-plugin-minify-dead-code": "^0.5.2",
"babel-plugin-remove-dead-code": "^1.3.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-define": "^1.3.0",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-plugin-transform-jsx-to-stylesheet": "1.2.13",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-template": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"better-babel-generator": "^6.26.1",
"chalk": "^2.3.2",
"chokidar": "^2.0.3",
"commander": "^2.15.0",
"cross-spawn": "^6.0.5",
"css": "^2.2.4",
"css-to-react-native-transform": "^1.4.0",
"css-what": "^2.1.3",
"ejs": "^2.6.1",
"envinfo": "^6.0.1",
"eslint": "^4.15.0",
"eslint-plugin-taro": "1.2.13",
"fs-extra": "^5.0.0",
"generic-names": "^2.0.1",
"glob": "^7.1.2",
"html": "^1.0.0",
"inquirer": "^5.2.0",
"klaw": "^2.1.1",
"latest-version": "^4.0.0",
"lodash": "^4.17.5",
"mem-fs": "^1.1.3",
"mem-fs-editor": "^4.0.0",
"minimatch": "^3.0.4",
"nerv-devtools": "^1.3.9",
"nervjs": "^1.3.9",
"omi-router": "^2.0.8",
"ora": "^2.0.0",
"postcss": "^6.0.22",
"postcss-modules-extract-imports": "^1.1.0",
"postcss-modules-local-by-default": "^1.2.0",
"postcss-modules-resolve-imports": "^1.3.0",
"postcss-modules-scope": "^1.1.0",
"postcss-modules-values": "^1.3.0",
"postcss-pxtransform": "1.2.13",
"postcss-taro-unit-transform": "1.2.13",
"postcss-url": "^7.3.2",
"prettier": "^1.14.3",
"prop-types": "^15.6.2",
"remarkable": "^1.7.1",
"resolve": "^1.6.0",
"semver": "^5.5.0",
"shelljs": "^0.8.1",
"through2": "^2.0.3",
"typescript": "^3.2.2",
"vinyl": "^2.1.0",
"vinyl-fs": "^3.0.2"
},
"devDependencies": {
"@types/react": "^16.4.6",
"@types/webpack-env": "^1.13.6",
"@tarojs/plugin-babel": "1.2.13",
"@tarojs/plugin-csso": "1.2.13",
"@tarojs/plugin-uglifyjs": "1.2.13",
"@tarojs/webpack-runner": "1.2.13",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-jsx-stylesheet": "^0.6.5",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.6.1",
"babel-eslint": "^8.2.3",
"eslint": "^4.19.1",
"eslint-config-taro": "1.2.13",
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-taro": "1.2.13"
}
}

View File

@ -0,0 +1,56 @@
{
"miniprogramRoot": "miniprogram/",
"cloudfunctionRoot": "cloudfunctions/",
"setting": {
"urlCheck": true,
"es6": true,
"postcss": true,
"minified": true,
"newFeature": true
},
"appid": "wxdf970566455966b0",
"projectname": "omi-cloud",
"libVersion": "2.6.2",
"condition": {
"search": {
"current": -1,
"list": []
},
"conversation": {
"current": -1,
"list": []
},
"plugin": {
"current": -1,
"list": []
},
"game": {
"list": []
},
"miniprogram": {
"current": 2,
"list": [
{
"id": -1,
"name": "导入数据",
"pathName": "pages/import/index",
"query": "",
"scene": null
},
{
"id": -1,
"name": "代码高亮",
"pathName": "pages/code/index",
"query": "",
"scene": null
},
{
"id": -1,
"name": "入口",
"pathName": "pages/list/index",
"scene": null
}
]
}
}
}

View File

@ -0,0 +1,24 @@
#! /usr/bin/env node
const program = require('commander')
const { getPkgVersion, printPkgVersion } = require('../src/util')
printPkgVersion()
const startTime = new Date('2019-1-1 00:00').getTime()
const endTime = new Date('2019-1-2 00:00').getTime()
const nowTime = Date.now()
if (nowTime >= startTime && nowTime <= endTime) {
const yearTxt = String(require('fs-extra').readFileSync(require('path').resolve(__dirname, 'year.txt')))
console.log(require('chalk').rgb(255, 87, 34)(yearTxt))
}
program
.version(getPkgVersion())
.usage('<command> [options]')
.command('init [projectName]', 'Init a project with default templete')
.command('build', 'Build a project with options')
.command('update', 'Update packages of taro')
.command('convert', 'Convert weapp to taro')
.command('info', 'Diagnostics Taro env info')
.parse(process.argv)

View File

@ -0,0 +1,55 @@
#!/usr/bin/env node
const path = require('path')
const fs = require('fs-extra')
const program = require('commander')
const chalk = require('chalk')
const _ = require('lodash')
const build = require('../src/build')
const { PROJECT_CONFIG } = require('../src/util')
const projectConfPath = path.join(process.cwd(), PROJECT_CONFIG)
program
.option('--type [typeName]', 'Build type, weapp/h5/rn/swan/alipay/tt')
.option('--watch', 'Watch mode')
.option('--env [env]', 'Env type')
.option('--ui', 'Build Taro UI library')
.parse(process.argv)
const args = program.args
const { type, watch, ui } = program
let { env } = program
env = process.env.NODE_ENV || env
if (ui) {
console.log(chalk.green(`开始编译 UI 库`))
build(args, {
type: 'ui',
watch
})
return
}
if (!fs.existsSync(projectConfPath)) {
console.log(chalk.red(`找不到项目配置文件${PROJECT_CONFIG}请确定当前目录是Taro项目根目录!`))
process.exit(1)
}
if (env) {
process.env.NODE_ENV = env
} else {
if (watch) {
process.env.NODE_ENV = 'development'
} else {
process.env.NODE_ENV = 'production'
}
}
const projectConf = require(projectConfPath)(_.merge)
console.log(chalk.green(`开始编译项目 ${chalk.bold(projectConf.projectName)}`))
build(args, {
type,
watch
})

View File

@ -0,0 +1,12 @@
#!/usr/bin/env node
const program = require('commander')
const Convertor = require('../src/convertor')
program
.parse(process.argv)
const convertor = new Convertor()
convertor.run()

View File

@ -0,0 +1,53 @@
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const envinfo = require('envinfo')
const {getPkgVersion, UPDATE_PACKAGE_LIST} = require('../src/util')
const process = require('process')
const program = require('commander')
const npmPackages = UPDATE_PACKAGE_LIST.concat(['react', 'react-native', 'nervjs', 'expo'])
program.parse(process.argv)
const args = program.args
if (args.length === 1) {
switch (args[0]) {
case 'rn': {
rnInfo({
SDKs: ['iOS SDK', 'Android SDK']
})
break
}
default:
info()
}
} else {
info()
}
function rnInfo (options) {
const appPath = process.cwd()
const tempPath = path.join(appPath, '.rn_temp')
if (fs.lstatSync(tempPath).isDirectory()) {
process.chdir('.rn_temp')
info(options)
}
}
async function info (options) {
let info = await envinfo.run(
{
System: ['OS', 'Shell'],
Binaries: ['Node', 'Yarn', 'npm'],
npmPackages,
npmGlobalPackages: ['typescript'],
...options
},
{
title: `Taro CLI ${getPkgVersion()} environment info`
}
)
console.log(info)
}

View File

@ -0,0 +1,40 @@
#!/usr/bin/env node
const program = require('commander')
const Project = require('../src/project')
program
.option('--name [name]', '项目名称')
.option('--description [description]', '项目介绍')
.option('--typescript', '使用TypeScript')
.option('--no-typescript', '不使用TypeScript')
.option('--template [template]', '项目模板(default/redux/mobx)')
.option('--css [css]', 'CSS预处理器(sass/less/stylus/none)')
.parse(process.argv)
const args = program.args
const { template, description, name, css } = program
let typescript = ''
/**
* 非标准做法
* 为了兼容不指定typescript参数时在inquirer中询问是否使用typescript的情况
*/
if (program.rawArgs.indexOf('--typescript') !== -1) {
typescript = true
} else if (program.rawArgs.indexOf('--no-typescript') !== -1) {
typescript = false
}
const projectName = args[0] || name
const project = new Project({
projectName,
template,
description,
typescript,
css
})
project.create()

View File

@ -0,0 +1,118 @@
#!/usr/bin/env node
const path = require('path')
const fs = require('fs-extra')
const program = require('commander')
const chalk = require('chalk')
const { getPkgItemByKey } = require('../src/util')
const ora = require('ora')
const exec = require('child_process').exec
const getLatestVersion = require('latest-version')
const { PROJECT_CONFIG, UPDATE_PACKAGE_LIST} = require('../src/util')
const projectConfPath = path.join(process.cwd(), PROJECT_CONFIG)
const pkgPath = path.join(process.cwd(), 'package.json')
const { shouldUseYarn, shouldUseCnpm } = require('../src/util')
const pkgName = getPkgItemByKey('name')
// 这里没有使用 command 的形式taro-update-self
program.parse(process.argv)
const args = program.args
if (args.length === 1) {
switch (args[0]) {
case 'self': {
updateSelf()
break
}
case 'project': {
updateProject()
break
}
default:
info()
}
} else {
info()
}
function info () {
console.log(chalk.red('命令错误:'))
console.log(`${chalk.green('taro update self')} 更新 Taro 开发工具 taro-cli 到最新版本`)
console.log(`${chalk.green('taro update project')} 更新项目所有 Taro 相关依赖到最新版本...`)
}
function updateSelf () {
let command
if (shouldUseCnpm()) {
command = 'cnpm i -g @tarojs/cli@latest'
} else {
command = 'npm i -g @tarojs/cli@latest'
}
let child = exec(command)
const spinner = ora('即将将 Taro 开发工具 taro-cli 更新到最新版本...').start()
child.stdout.on('data', function (data) {
console.log(data)
spinner.stop()
})
child.stderr.on('data', function (data) {
console.log(data)
spinner.stop()
})
}
async function updateProject () {
if (!fs.existsSync(projectConfPath)) {
console.log(chalk.red(`找不到项目配置文件${PROJECT_CONFIG}请确定当前目录是Taro项目根目录!`))
process.exit(1)
}
const packageMap = require(pkgPath)
const version = await getLatestVersion(pkgName)
// 更新 @tarojs/* 版本
Object.keys(packageMap.dependencies).forEach((key) => {
if (UPDATE_PACKAGE_LIST.indexOf(key) !== -1) {
packageMap.dependencies[key] = version
}
})
Object.keys(packageMap.devDependencies).forEach((key) => {
if (UPDATE_PACKAGE_LIST.indexOf(key) !== -1) {
packageMap.devDependencies[key] = version
}
})
// 写入package.json
try {
await fs.writeJson(pkgPath, packageMap, {spaces: '\t'})
console.log(chalk.green('更新项目 package.json 成功!'))
console.log()
} catch (err) {
console.error(err)
}
let command
if (shouldUseYarn()) {
command = 'yarn'
} else if (shouldUseCnpm()) {
command = 'cnpm install'
} else {
command = 'npm install'
}
let child = exec(command)
const spinner = ora('即将将项目所有 Taro 相关依赖更新到最新版本...').start()
child.stdout.on('data', function (data) {
spinner.stop()
console.log(data)
})
child.stderr.on('data', function (data) {
spinner.stop()
console.log(data)
})
}

View File

@ -0,0 +1,9 @@
🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮
🎉 _ _ _ _ _____ _____ __ _____ _ 🎉
🏮 | | | | | | | / __ | _ / || _ | | | 🏮
🎉 | |_| | ___| | | ___ `' / /| |/' `| || |_| | | | 🎉
🏮 | _ |/ _ | | |/ _ \ / / | /| || |\____ | | | 🏮
🎉 | | | | __| | | (_) | ./ /__\ |_/ _| |.___/ / |_| 🎉
🏮 \_| |_/\___|_|_|\___/ \_____/\___/\___\____/ (_) 🏮
🎉 🎉
🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮 🎉 🏮

View File

@ -0,0 +1,87 @@
{
"name": "@tarojs/cli",
"version": "1.2.13",
"description": "cli tool for taro",
"main": "index.js",
"scripts": {
"test": "jest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/NervJS/taro.git"
},
"bin": {
"taro": "bin/taro"
},
"keywords": [
"taro",
"weapp"
],
"engines": {
"node": ">=8"
},
"author": "O2Team",
"license": "MIT",
"dependencies": {
"@tarojs/taroize": "1.2.13",
"autoprefixer": "^8.4.1",
"babel-core": "^6.26.3",
"babel-generator": "^6.26.1",
"babel-plugin-danger-remove-unused-import": "^1.1.1",
"babel-plugin-remove-dead-code": "^1.3.2",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-define": "^1.3.0",
"babel-plugin-transform-jsx-to-stylesheet": "1.2.13",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-template": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"better-babel-generator": "^6.26.1",
"chalk": "^2.3.2",
"chokidar": "^2.0.3",
"commander": "^2.15.0",
"cross-spawn": "^6.0.5",
"css-to-react-native-transform": "^1.4.0",
"ejs": "^2.6.1",
"envinfo": "^6.0.1",
"fs-extra": "^5.0.0",
"generic-names": "^2.0.1",
"glob": "^7.1.2",
"inquirer": "^5.2.0",
"klaw": "^2.1.1",
"latest-version": "^4.0.0",
"lodash": "^4.17.5",
"mem-fs": "^1.1.3",
"mem-fs-editor": "^4.0.0",
"minimatch": "^3.0.4",
"ora": "^2.0.0",
"postcss": "^6.0.22",
"postcss-modules-extract-imports": "^1.1.0",
"postcss-modules-local-by-default": "^1.2.0",
"postcss-modules-resolve-imports": "^1.3.0",
"postcss-modules-scope": "^1.1.0",
"postcss-modules-values": "^1.3.0",
"postcss-pxtransform": "1.2.13",
"postcss-taro-unit-transform": "1.2.13",
"postcss-url": "^7.3.2",
"prettier": "^1.14.3",
"prop-types": "^15.6.2",
"resolve": "^1.6.0",
"semver": "^5.5.0",
"shelljs": "^0.8.1",
"through2": "^2.0.3",
"vinyl": "^2.1.0",
"vinyl-fs": "^3.0.2"
},
"devDependencies": {
"babel-jest": "^23.6.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"jest": "^23.6.0",
"jest-react-native": "^18.0.0",
"react-native": "^0.55.4"
}
}

View File

@ -0,0 +1,90 @@
const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk')
const _ = require('lodash')
const Util = require('./util')
const CONFIG = require('./config')
const appPath = process.cwd()
function build (args, buildConfig) {
const { type, watch } = buildConfig
const configDir = require(path.join(appPath, Util.PROJECT_CONFIG))(_.merge)
const outputPath = path.join(appPath, configDir.outputRoot || CONFIG.OUTPUT_DIR)
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath)
} else {
if (type !== Util.BUILD_TYPES.H5) {
Util.emptyDirectory(outputPath)
}
}
switch (type) {
case Util.BUILD_TYPES.H5:
buildForH5({ watch })
break
case Util.BUILD_TYPES.WEAPP:
buildForWeapp({ watch })
break
case Util.BUILD_TYPES.SWAN:
buildForSwan({ watch })
break
case Util.BUILD_TYPES.ALIPAY:
buildForAlipay({ watch })
break
case Util.BUILD_TYPES.TT:
buildForTt({ watch })
break
case Util.BUILD_TYPES.RN:
buildForRN({ watch })
break
case Util.BUILD_TYPES.UI:
buildForUILibrary({ watch })
break
default:
console.log(chalk.red('输入类型错误,目前只支持 weapp/h5/rn/swan/alipay/tt 六端类型'))
}
}
function buildForWeapp ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.WEAPP
})
}
function buildForSwan ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.SWAN
})
}
function buildForAlipay ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.ALIPAY
})
}
function buildForTt ({ watch }) {
require('./weapp').build({
watch,
adapter: Util.BUILD_TYPES.TT
})
}
function buildForH5 (buildConfig) {
require('./h5').build(buildConfig)
}
function buildForRN ({ watch }) {
require('./rn').build({ watch })
}
function buildForUILibrary ({ watch }) {
require('./ui').build({ watch })
}
module.exports = build

View File

@ -0,0 +1,12 @@
module.exports = {
sourceMap: true,
presets: [
'env'
],
plugins: [
require('babel-plugin-transform-react-jsx'),
'transform-decorators-legacy',
'transform-class-properties',
'transform-object-rest-spread'
]
}

View File

@ -0,0 +1,15 @@
module.exports = {
sourceType: 'module',
plugins: [
'typescript',
'classProperties',
'jsx',
'trailingFunctionCommas',
'asyncFunctions',
'exponentiationOperator',
'asyncGenerators',
'objectRestSpread',
'decorators',
'dynamicImport'
]
}

View File

@ -0,0 +1,5 @@
module.exports = [
'last 3 versions',
'Android >= 4.1',
'ios >= 8'
]

View File

@ -0,0 +1,7 @@
module.exports = {
OUTPUT_DIR: 'dist',
SOURCE_DIR: 'src',
TEMP_DIR: '.temp',
NPM_DIR: 'npm',
ENTRY: 'app'
}

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"declaration": false,
"experimentalDecorators": true,
"jsx": "react",
"jsxFactory": "Nerv.createElement",
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false,
"noUnusedLocals": true,
"outDir": "./dist/",
"preserveConstEnums": true,
"removeComments": false,
"rootDir": ".",
"sourceMap": true,
"strictNullChecks": true,
"target": "es6"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
],
"compileOnSave": false
}

View File

@ -0,0 +1,3 @@
module.exports = {
}

View File

@ -0,0 +1,720 @@
const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk')
const prettier = require('prettier')
const traverse = require('babel-traverse').default
const t = require('babel-types')
const template = require('babel-template')
const taroize = require('@tarojs/taroize')
const wxTransformer = require('../../taro-transformer-wx/lib/src/index.js').default
const postcss = require('postcss')
const unitTransform = require('postcss-taro-unit-transform')
const {
BUILD_TYPES,
MINI_APP_FILES,
printLog,
pocessTypeEnum,
promoteRelativePath,
resolveScriptPath,
REG_SCRIPT,
REG_TYPESCRIPT,
processStyleImports,
getPkgVersion,
pascalCase,
emptyDirectory,
REG_URL,
REG_IMAGE
} = require('./util')
const { generateMinimalEscapeCode } = require('./util/ast_convert')
const Creator = require('./creator')
const babylonConfig = require('./config/babylon')
const prettierJSConfig = {
semi: false,
singleQuote: true,
parser: 'babel'
}
const OUTPUT_STYLE_EXTNAME = '.scss'
const WX_GLOBAL_FN = ['getApp', 'getCurrentPages', 'requirePlugin']
function analyzeImportUrl (sourceFilePath, scriptFiles, source, value) {
const valueExtname = path.extname(value)
if (path.isAbsolute(value)) {
printLog(pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 是绝对路径!`)
return
}
if (value.indexOf('.') === 0) {
if (REG_SCRIPT.test(valueExtname) || REG_TYPESCRIPT.test(valueExtname)) {
const vpath = path.resolve(sourceFilePath, '..', value)
let fPath = value
if (fs.existsSync(vpath)) {
fPath = vpath
} else {
printLog(pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 不存在!`)
}
scriptFiles.add(fPath)
} else {
let vpath = resolveScriptPath(path.resolve(sourceFilePath, '..', value))
if (vpath) {
if (!fs.existsSync(vpath)) {
printLog(pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 不存在!`)
} else {
if (fs.lstatSync(vpath).isDirectory()) {
if (fs.existsSync(path.join(vpath, 'index.js'))) {
vpath = path.join(vpath, 'index.js')
} else {
printLog(pocessTypeEnum.ERROR, '引用目录', `文件 ${sourceFilePath} 中引用了目录 ${value}`)
return
}
}
let relativePath = path.relative(sourceFilePath, vpath)
const relativePathExtname = path.extname(relativePath)
scriptFiles.add(vpath)
relativePath = promoteRelativePath(relativePath)
if (/\.wxs/.test(relativePathExtname)) {
relativePath += '.js'
} else {
relativePath = relativePath.replace(relativePathExtname, '.js')
}
source.value = relativePath
}
}
}
}
}
class Convertor {
constructor () {
this.root = process.cwd()
this.convertRoot = path.join(this.root, 'taroConvert')
this.convertDir = path.join(this.convertRoot, 'src')
this.importsDir = path.join(this.convertDir, 'imports')
this.fileTypes = MINI_APP_FILES[BUILD_TYPES.WEAPP]
this.pages = new Set()
this.components = new Set()
this.hadBeenCopyedFiles = new Set()
this.hadBeenBuiltComponents = new Set()
this.hadBeenBuiltImports = new Set()
this.init()
}
init () {
console.log(chalk.green('开始代码转换...'))
this.initConvert()
this.getApp()
this.getPages()
this.getSubPackages()
}
initConvert () {
if (fs.existsSync(this.convertRoot)) {
emptyDirectory(this.convertRoot, { excludes: ['node_modules'] })
} else {
fs.mkdirpSync(this.convertRoot)
}
}
parseAst ({ ast, sourceFilePath, outputFilePath, importStylePath, depComponents, imports = [], isApp = false }) {
const scriptFiles = new Set()
const self = this
let componentClassName = null
let needInsertImportTaro = false
traverse(ast, {
Program: {
enter (astPath) {
astPath.traverse({
ClassDeclaration (astPath) {
const node = astPath.node
let isTaroComponent = false
if (node.superClass) {
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
if (isTaroComponent) {
componentClassName = node.id.name
}
}
},
ClassExpression (astPath) {
const node = astPath.node
if (node.superClass) {
let isTaroComponent = false
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
if (isTaroComponent) {
if (node.id === null) {
const parentNode = astPath.parentPath.node
if (t.isVariableDeclarator(astPath.parentPath)) {
componentClassName = parentNode.id.name
}
} else {
componentClassName = node.id.name
}
}
}
},
ExportDefaultDeclaration (astPath) {
const node = astPath.node
const declaration = node.declaration
if (
declaration &&
(declaration.type === 'ClassDeclaration' || declaration.type === 'ClassExpression')
) {
const superClass = declaration.superClass
if (superClass) {
let isTaroComponent = false
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
if (isTaroComponent) {
componentClassName = declaration.id.name
}
}
}
},
ImportDeclaration (astPath) {
const node = astPath.node
const source = node.source
const value = source.value
analyzeImportUrl(sourceFilePath, scriptFiles, source, value)
},
CallExpression (astPath) {
const node = astPath.node
const calleePath = astPath.get('callee')
const callee = calleePath.node
if (callee.type === 'Identifier') {
if (callee.name === 'require') {
const args = node.arguments
const value = args[0].value
analyzeImportUrl(sourceFilePath, scriptFiles, args[0], value)
} else if (WX_GLOBAL_FN.includes(callee.name)) {
calleePath.replaceWith(
t.memberExpression(t.identifier('Taro'), callee)
)
needInsertImportTaro = true
}
} else if (callee.type === 'MemberExpression') {
const object = callee.object
if (object.name === 'wx') {
calleePath.get('object').replaceWith(t.identifier('Taro'))
needInsertImportTaro = true
}
}
}
})
},
exit (astPath) {
const lastImport = astPath.get('body').filter(p => p.isImportDeclaration()).pop()
const hasTaroImport = astPath.get('body').some(p => p.isImportDeclaration() && p.node.source.value === '@tarojs/taro')
if (needInsertImportTaro && !hasTaroImport) {
astPath.node.body.unshift(
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier('Taro'))],
t.stringLiteral('@tarojs/taro')
)
)
}
astPath.traverse({
StringLiteral (astPath) {
const value = astPath.node.value
const extname = path.extname(value)
if (extname && REG_IMAGE.test(extname) && !REG_URL.test(value)) {
let imageRelativePath = null
let sourceImagePath = null
let outputImagePath = null
if (path.isAbsolute(value)) {
sourceImagePath = path.join(self.root, value)
} else {
sourceImagePath = path.resolve(sourceFilePath, '..', value)
}
imageRelativePath = promoteRelativePath(path.relative(sourceFilePath, sourceImagePath))
outputImagePath = self.getDistFilePath(sourceImagePath)
if (fs.existsSync(sourceImagePath)) {
self.copyFileToTaro(sourceImagePath, outputImagePath)
printLog(pocessTypeEnum.COPY, '图片', self.generateShowPath(outputImagePath))
} else {
printLog(pocessTypeEnum.ERROR, '图片不存在', self.generateShowPath(sourceImagePath))
}
if (astPath.parentPath.isVariableDeclarator()) {
astPath.replaceWith(t.callExpression(t.identifier('require'), [t.stringLiteral(imageRelativePath)]))
} else if (astPath.parentPath.isJSXAttribute()) {
astPath.replaceWith(t.jSXExpressionContainer(t.callExpression(t.identifier('require'), [t.stringLiteral(imageRelativePath)])))
}
}
}
})
if (lastImport) {
if (importStylePath) {
lastImport.insertAfter(t.importDeclaration([], t.stringLiteral(promoteRelativePath(path.relative(sourceFilePath, importStylePath)))))
}
if (imports && imports.length) {
imports.forEach(({ name, ast }) => {
const importName = pascalCase(name)
if (componentClassName === importName) {
return
}
const importPath = path.join(self.importsDir, importName + '.js')
if (!self.hadBeenBuiltImports.has(importPath)) {
self.hadBeenBuiltImports.add(importPath)
self.writeFileToTaro(importPath, prettier.format(generateMinimalEscapeCode(ast), prettierJSConfig))
}
lastImport.insertAfter(template(`import ${importName} from '${promoteRelativePath(path.relative(outputFilePath, importPath))}'`, babylonConfig)())
})
}
if (depComponents && depComponents.size) {
depComponents.forEach(componentObj => {
const name = pascalCase(componentObj.name)
const component = componentObj.path
lastImport.insertAfter(template(`import ${name} from '${promoteRelativePath(path.relative(sourceFilePath, component))}'`, babylonConfig)())
})
}
if (isApp) {
astPath.node.body.push(template(`Taro.render(<App />, document.getElementById('app'))`, babylonConfig)())
}
}
}
}
})
return {
ast,
scriptFiles
}
}
getApp () {
this.entryJSPath = path.join(this.root, `app${this.fileTypes.SCRIPT}`)
this.entryJSONPath = path.join(this.root, `app${this.fileTypes.CONFIG}`)
this.entryStylePath = path.join(this.root, `app${this.fileTypes.STYLE}`)
try {
this.entryJSON = JSON.parse(String(fs.readFileSync(this.entryJSONPath)))
printLog(pocessTypeEnum.CONVERT, '入口文件', this.generateShowPath(this.entryJSPath))
printLog(pocessTypeEnum.CONVERT, '入口配置', this.generateShowPath(this.entryJSONPath))
if (fs.existsSync(this.entryStylePath)) {
this.entryStyle = String(fs.readFileSync(this.entryStylePath))
printLog(pocessTypeEnum.CONVERT, '入口样式', this.generateShowPath(this.entryStylePath))
}
} catch (err) {
this.entryJSON = {}
console.log(chalk.red(`app${this.fileTypes.CONFIG} 读取失败,请检查!`))
process.exit(1)
}
}
getPages () {
const pages = this.entryJSON['pages']
if (!pages || !pages.length) {
console.log(chalk.red(`app${this.fileTypes.CONFIG} 配置有误,缺少页面相关配置`))
return
}
this.pages = new Set(pages)
}
getSubPackages () {
const subPackages = this.entryJSON['subpackages'] || this.entryJSON['subPackages']
if (!subPackages || !subPackages.length) {
return
}
subPackages.forEach(item => {
if (item.pages && item.pages.length) {
const root = item.root
item.pages.forEach(page => {
let pagePath = `${root}/${page}`
pagePath = pagePath.replace(/\/{2,}/g, '/')
this.pages.add(pagePath)
})
}
})
}
generateScriptFiles (files) {
if (!files) {
return
}
if (files.size) {
files.forEach(file => {
if (!fs.existsSync(file) || this.hadBeenCopyedFiles.has(file)) {
return
}
const code = fs.readFileSync(file).toString()
let outputFilePath = file.replace(this.root, this.convertDir)
const extname = path.extname(outputFilePath)
if (/\.wxs/.test(extname)) {
outputFilePath += '.js'
}
const transformResult = wxTransformer({
code,
sourcePath: file,
outputPath: outputFilePath,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(file)
})
const { ast, scriptFiles } = this.parseAst({
ast: transformResult.ast,
outputFilePath,
sourceFilePath: file
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(outputFilePath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.COPY, 'JS 文件', this.generateShowPath(outputFilePath))
this.hadBeenCopyedFiles.add(file)
this.generateScriptFiles(scriptFiles)
})
}
}
writeFileToTaro (dist, code) {
fs.ensureDirSync(path.dirname(dist))
fs.writeFileSync(dist, code)
}
copyFileToTaro (from, to, options) {
const filename = path.basename(from)
if (fs.statSync(from).isFile() && !path.extname(to)) {
fs.ensureDir(to)
return fs.copySync(from, path.join(to, filename), options)
}
fs.ensureDir(path.dirname(to))
return fs.copySync(from, to, options)
}
getDistFilePath (src, extname) {
if (!extname) return src.replace(this.root, this.convertDir)
return src.replace(this.root, this.convertDir).replace(path.extname(src), extname)
}
generateShowPath (filePath) {
return filePath.replace(path.join(this.root, '/'), '').split(path.sep).join('/')
}
generateEntry () {
try {
const entryJS = String(fs.readFileSync(this.entryJSPath))
const entryJSON = JSON.stringify(this.entryJSON)
const entryDistJSPath = this.getDistFilePath(this.entryJSPath)
const taroizeResult = taroize({
json: entryJSON,
script: entryJS,
path: path.dirname(entryJS)
})
const { ast, scriptFiles } = this.parseAst({
ast: taroizeResult.ast,
sourceFilePath: this.entryJSPath,
outputFilePath: entryDistJSPath,
importStylePath: this.entryStyle ? this.entryStylePath.replace(path.extname(this.entryStylePath), OUTPUT_STYLE_EXTNAME) : null,
isApp: true
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(entryDistJSPath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.GENERATE, '入口文件', this.generateShowPath(entryDistJSPath))
if (this.entryStyle) {
this.traverseStyle(this.entryStylePath, this.entryStyle)
}
this.generateScriptFiles(scriptFiles)
if (this.entryJSON.tabBar) {
this.generateTabBarIcon(this.entryJSON.tabBar)
}
} catch (err) {
console.log(err)
}
}
generateTabBarIcon (tabBar) {
const { list = [] } = tabBar
const icons = new Set()
if (Array.isArray(list) && list.length) {
list.forEach(item => {
if (typeof item.iconPath === 'string') icons.add(item.iconPath)
if (typeof item.selectedIconPath === 'string') icons.add(item.selectedIconPath)
})
if (icons.size > 0) {
Array.from(icons)
.map(icon => path.join(this.root, icon))
.forEach(iconPath => {
const iconDistPath = this.getDistFilePath(iconPath)
this.copyFileToTaro(iconPath, iconDistPath)
printLog(pocessTypeEnum.COPY, 'TabBar 图标', this.generateShowPath(iconDistPath))
})
}
}
}
traversePages () {
this.pages.forEach(page => {
const pagePath = path.join(this.root, page)
const pageJSPath = pagePath + this.fileTypes.SCRIPT
const pageDistJSPath = this.getDistFilePath(pageJSPath)
const pageConfigPath = pagePath + this.fileTypes.CONFIG
const pageStylePath = pagePath + this.fileTypes.STYLE
const pageTemplPath = pagePath + this.fileTypes.TEMPL
try {
const param = {}
const depComponents = new Set()
if (!fs.existsSync(pageJSPath)) {
throw new Error(`页面 ${page} 没有 JS 文件!`)
}
printLog(pocessTypeEnum.CONVERT, '页面文件', this.generateShowPath(pageJSPath))
if (fs.existsSync(pageConfigPath)) {
printLog(pocessTypeEnum.CONVERT, '页面配置', this.generateShowPath(pageConfigPath))
const pageConfigStr = String(fs.readFileSync(pageConfigPath))
const pageConfig = JSON.parse(pageConfigStr)
const pageUsingComponnets = pageConfig.usingComponents
if (pageUsingComponnets) {
// 页面依赖组件
Object.keys(pageUsingComponnets).forEach(component => {
let componentPath = path.resolve(pageConfigPath, '..', pageUsingComponnets[component])
if (!fs.existsSync(resolveScriptPath(componentPath))) {
componentPath = path.join(this.root, pageUsingComponnets[component])
}
depComponents.add({
name: component,
path: componentPath
})
})
delete pageConfig.usingComponents
}
param.json = JSON.stringify(pageConfig)
}
param.script = String(fs.readFileSync(pageJSPath))
if (fs.existsSync(pageTemplPath)) {
printLog(pocessTypeEnum.CONVERT, '页面模板', this.generateShowPath(pageTemplPath))
param.wxml = String(fs.readFileSync(pageTemplPath))
}
let pageStyle = null
if (fs.existsSync(pageStylePath)) {
printLog(pocessTypeEnum.CONVERT, '页面样式', this.generateShowPath(pageStylePath))
pageStyle = String(fs.readFileSync(pageStylePath))
}
param.path = path.dirname(pageJSPath)
const taroizeResult = taroize(param)
const { ast, scriptFiles } = this.parseAst({
ast: taroizeResult.ast,
sourceFilePath: pageJSPath,
outputFilePath: pageDistJSPath,
importStylePath: pageStyle ? pageStylePath.replace(path.extname(pageStylePath), OUTPUT_STYLE_EXTNAME) : null,
depComponents,
imports: taroizeResult.imports
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(pageDistJSPath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.GENERATE, '页面文件', this.generateShowPath(pageDistJSPath))
if (pageStyle) {
this.traverseStyle(pageStylePath, pageStyle)
}
this.generateScriptFiles(scriptFiles)
this.traverseComponents(depComponents)
} catch (err) {
printLog(pocessTypeEnum.ERROR, '页面转换', this.generateShowPath(pageJSPath))
console.log(err)
}
})
}
traverseComponents (components) {
if (!components || !components.size) {
return
}
components.forEach(componentObj => {
const component = componentObj.path
if (this.hadBeenBuiltComponents.has(component)) return
this.hadBeenBuiltComponents.add(component)
const componentJSPath = component + this.fileTypes.SCRIPT
const componentDistJSPath = this.getDistFilePath(componentJSPath)
const componentConfigPath = component + this.fileTypes.CONFIG
const componentStylePath = component + this.fileTypes.STYLE
const componentTemplPath = component + this.fileTypes.TEMPL
try {
const param = {}
const depComponents = new Set()
if (!fs.existsSync(componentJSPath)) {
throw new Error(`组件 ${component} 没有 JS 文件!`)
}
printLog(pocessTypeEnum.CONVERT, '组件文件', this.generateShowPath(componentJSPath))
if (fs.existsSync(componentConfigPath)) {
printLog(pocessTypeEnum.CONVERT, '组件配置', this.generateShowPath(componentConfigPath))
const componentConfigStr = String(fs.readFileSync(componentConfigPath))
const componentConfig = JSON.parse(componentConfigStr)
const componentUsingComponnets = componentConfig.usingComponents
if (componentUsingComponnets) {
// 页面依赖组件
Object.keys(componentUsingComponnets).forEach(component => {
let componentPath = path.resolve(componentConfigPath, '..', componentUsingComponnets[component])
if (!fs.existsSync(resolveScriptPath(componentPath))) {
componentPath = path.join(this.root, componentUsingComponnets[component])
}
depComponents.add({
name: component,
path: componentPath
})
})
delete componentConfig.usingComponents
}
param.json = JSON.stringify(componentConfig)
}
param.script = String(fs.readFileSync(componentJSPath))
if (fs.existsSync(componentTemplPath)) {
printLog(pocessTypeEnum.CONVERT, '组件模板', this.generateShowPath(componentTemplPath))
param.wxml = String(fs.readFileSync(componentTemplPath))
}
let componentStyle = null
if (fs.existsSync(componentStylePath)) {
printLog(pocessTypeEnum.CONVERT, '组件样式', this.generateShowPath(componentStylePath))
componentStyle = String(fs.readFileSync(componentStylePath))
}
param.path = path.dirname(componentJSPath)
const taroizeResult = taroize(param)
const { ast, scriptFiles } = this.parseAst({
ast: taroizeResult.ast,
sourceFilePath: componentJSPath,
outputFilePath: componentDistJSPath,
importStylePath: componentStyle ? componentStylePath.replace(path.extname(componentStylePath), OUTPUT_STYLE_EXTNAME) : null,
depComponents,
imports: taroizeResult.imports
})
const jsCode = generateMinimalEscapeCode(ast)
this.writeFileToTaro(componentDistJSPath, prettier.format(jsCode, prettierJSConfig))
printLog(pocessTypeEnum.GENERATE, '组件文件', this.generateShowPath(componentDistJSPath))
if (componentStyle) {
this.traverseStyle(componentStylePath, componentStyle)
}
this.generateScriptFiles(scriptFiles)
this.traverseComponents(depComponents)
} catch (err) {
printLog(pocessTypeEnum.ERROR, '组件转换', this.generateShowPath(componentJSPath))
console.log(err)
}
})
}
async styleUnitTransform (filePath, content) {
const postcssResult = await postcss([
unitTransform()
]).process(content, {
from: filePath
})
return postcssResult
}
async traverseStyle (filePath, style) {
const { imports, content } = processStyleImports(style, BUILD_TYPES.WEAPP, (str, stylePath) => {
let relativePath = stylePath
if (path.isAbsolute(relativePath)) {
relativePath = promoteRelativePath(path.relative(filePath, path.join(this.root, stylePath)))
}
return str.replace(stylePath, relativePath)
.replace(MINI_APP_FILES[BUILD_TYPES.WEAPP].STYLE, OUTPUT_STYLE_EXTNAME)
})
const styleDist = this.getDistFilePath(filePath, OUTPUT_STYLE_EXTNAME)
const { css } = await this.styleUnitTransform(filePath, content)
this.writeFileToTaro(styleDist, css)
printLog(pocessTypeEnum.GENERATE, '样式文件', this.generateShowPath(styleDist))
if (imports && imports.length) {
imports.forEach(importItem => {
const importPath = path.isAbsolute(importItem)
? path.join(this.root, importItem)
: path.resolve(path.dirname(filePath), importItem)
if (fs.existsSync(importPath)) {
const styleText = fs.readFileSync(importPath).toString()
this.traverseStyle(importPath, styleText)
}
})
}
}
generateConfigFiles () {
const creator = new Creator()
const templateName = 'default'
const configDir = path.join(this.convertRoot, 'config')
const pkgPath = path.join(this.convertRoot, 'package.json')
const projectName = 'taroConvert'
const description = ''
const version = getPkgVersion()
const dateObj = new Date()
const date = `${dateObj.getFullYear()}-${(dateObj.getMonth() + 1)}-${dateObj.getDate()}`
creator.template(templateName, 'pkg', pkgPath, {
description,
projectName,
version,
css: 'sass',
typescript: false
})
creator.template(templateName, path.join('config', 'index'), path.join(configDir, 'index.js'), {
date,
projectName
})
creator.template(templateName, path.join('config', 'dev'), path.join(configDir, 'dev.js'))
creator.template(templateName, path.join('config', 'prod'), path.join(configDir, 'prod.js'))
creator.template(templateName, 'project', path.join(this.convertRoot, 'project.config.json'), {
description,
projectName
})
creator.template(templateName, 'gitignore', path.join(this.convertRoot, '.gitignore'))
creator.template(templateName, 'editorconfig', path.join(this.convertRoot, '.editorconfig'))
creator.template(templateName, 'eslintrc', path.join(this.convertRoot, '.eslintrc'), {
typescript: false
})
creator.template(templateName, 'indexhtml', path.join(this.convertDir, 'index.html'))
creator.fs.commit(() => {
const pkgObj = JSON.parse(fs.readFileSync(pkgPath).toString())
pkgObj.dependencies['@tarojs/with-weapp'] = `^${version}`
fs.writeJSONSync(pkgPath, pkgObj, {
spaces: 2,
EOL: '\n'
})
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'index.js')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'dev.js')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(configDir, 'prod.js')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(pkgPath))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, 'project.config.json')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.gitignore')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.editorconfig')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertRoot, '.eslintrc')))
printLog(pocessTypeEnum.GENERATE, '文件', this.generateShowPath(path.join(this.convertDir, 'index.html')))
})
}
run () {
this.generateEntry()
this.traversePages()
this.generateConfigFiles()
}
}
module.exports = Convertor

View File

@ -0,0 +1,86 @@
const path = require('path')
const fs = require('fs-extra')
const memFs = require('mem-fs')
const editor = require('mem-fs-editor')
const {
getRootPath
} = require('./util')
class Creator {
constructor () {
const store = memFs.create()
this.fs = editor.create(store)
this.sourceRoot(path.join(getRootPath()))
this.init()
}
init () {}
sourceRoot (rootPath) {
if (typeof rootPath === 'string') {
this._rootPath = path.resolve(rootPath)
}
if (!fs.existsSync(this._rootPath)) {
fs.ensureDirSync(this._rootPath)
}
return this._rootPath
}
templatePath () {
let filepath = path.join.apply(path, arguments)
if (!path.isAbsolute(filepath)) {
filepath = path.join(this._rootPath, 'templates', filepath)
}
return filepath
}
destinationRoot (rootPath) {
if (typeof rootPath === 'string') {
this._destinationRoot = path.resolve(rootPath)
if (!fs.existsSync(rootPath)) {
fs.ensureDirSync(rootPath)
}
process.chdir(rootPath)
}
return this._destinationRoot || process.cwd()
}
destinationPath () {
let filepath = path.join.apply(path, arguments)
if (!path.isAbsolute(filepath)) {
filepath = path.join(this.destinationRoot(), filepath)
}
return filepath
}
template (template, source, dest, data, options) {
if (typeof dest !== 'string') {
options = data
data = dest
dest = source
}
this.fs.copyTpl(
this.templatePath(template, source),
this.destinationPath(dest),
Object.assign({}, this, data),
options
)
return this
}
copy (template, type, source, dest) {
dest = dest || source
this.template(template, type, source, dest)
return this
}
writeGitKeepFile (dirname) {
dirname = path.resolve(dirname)
fs.writeFileSync(path.join(dirname, '.gitkeep'), 'Place hold file', 'utf8')
}
write () {}
}
module.exports = Creator

View File

@ -0,0 +1,58 @@
var actionTypes = {
"equals": "",
"element": "~",
"start": "^",
"end": "$",
"any": "*",
"not": "!",
"hyphen": "|"
};
var simpleSelectors = {
__proto__: null,
child: " > ",
parent: " < ",
sibling: " ~ ",
adjacent: " + ",
descendant: " ",
universal: "*"
};
function stringify(token){
return token.map(stringifySubselector).join(", ");
}
function stringifySubselector(token){
return token.map(stringifyToken).join("");
}
function stringifyToken(token){
if(token.type in simpleSelectors) return simpleSelectors[token.type];
if(token.type === "tag") return escapeName(token.name);
if(token.type === "attribute"){
if(token.action === "exists") return "[" + escapeName(token.name) + "]";
if(token.name === "id" && token.action === "equals" && !token.ignoreCase) return "#" + escapeName(token.value);
if(token.name === "class" && token.action === "element" && !token.ignoreCase) return "." + escapeName(token.value);
return "[" +
escapeName(token.name) + actionTypes[token.action] + "='" +
escapeName(token.value) + "'" + (token.ignoreCase ? "i" : "") + "]";
}
if(token.type === "pseudo"){
if(token.data === null) return ":" + escapeName(token.name);
if(typeof token.data === "string") return ":" + escapeName(token.name) + "(" + token.data + ")";
return ":" + escapeName(token.name) + "(" + stringify(token.data) + ")";
}
}
function escapeName(str){
//TODO
return str;
}
module.exports = stringify

View File

@ -0,0 +1,28 @@
let map = require('./tag-mapping')
let css = require('css')
let cssWhat = require('css-what')
let cssStringify = require('./css-stringify')
function compileWxss(str) {
let obj = css.parse(str)
obj.stylesheet.rules.forEach(rule => {
rule.selectors && rule.selectors.forEach((selector, index) => {
let sltObjs = cssWhat(selector)
sltObjs.forEach(sltObj => {
sltObj.forEach(item => {
if (item.type == 'tag') {
item.name = map(item.name)
}
})
})
rule.selectors[index] = cssStringify(sltObjs)
})
})
return css.stringify(obj)
}
module.exports = compileWxss

View File

@ -0,0 +1,19 @@
let tagMapping = {
view: 'div',
text: 'span',
image: 'img',
picker: 'select',
navigator: 'a',
}
function map(key) {
if (key.indexOf('-') !== -1) {
return key
}
if(tagMapping[key]){
return tagMapping[key]
}
return key
}
module.exports = map

View File

@ -0,0 +1,11 @@
var assign = function (s, d) {
if (typeof s !== 'object') {
return d
}
s = s || {}
d = d || {}
return JSON.parse((JSON.stringify(s) + JSON.stringify(d)).replace('}{', ','))
}
module.exports = {
assign: assign
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,173 @@
const path = require('path')
const fs = require('fs-extra')
const chalk = require('chalk')
const inquirer = require('inquirer')
const semver = require('semver')
const Creator = require('./creator')
const {
shouldUseYarn,
shouldUseCnpm,
getPkgVersion
} = require('./util')
const { SOURCE_DIR } = require('./config')
class Project extends Creator {
constructor (options) {
super()
const unSupportedVer = semver.lt(process.version, 'v7.6.0')
if (unSupportedVer) {
throw new Error('Node.js 版本过低,推荐升级 Node.js 至 v8.0.0+')
}
this.rootPath = this._rootPath
this.conf = Object.assign({
projectName: null,
template: null,
description: ''
}, options)
}
init () {
console.log(chalk.green(`Taro即将创建一个新项目!`))
console.log('Need help? Go and open issue: https://github.com/NervJS/taro/issues/new')
console.log()
}
create () {
this.ask()
.then(answers => {
const date = new Date()
this.conf = Object.assign(this.conf, answers)
this.conf.date = `${date.getFullYear()}-${(date.getMonth() + 1)}-${date.getDate()}`
this.write()
})
}
ask () {
const prompts = []
const conf = this.conf
if (typeof conf.projectName !== 'string') {
prompts.push({
type: 'input',
name: 'projectName',
message: '请输入项目名称!',
validate (input) {
if (!input) {
return '项目名不能为空!'
}
if (fs.existsSync(input)) {
return '当前目录已经存在同名项目,请换一个项目名!'
}
return true
}
})
} else if (fs.existsSync(conf.projectName)) {
prompts.push({
type: 'input',
name: 'projectName',
message: '当前目录已经存在同名项目,请换一个项目名!',
validate (input) {
if (!input) {
return '项目名不能为空!'
}
if (fs.existsSync(input)) {
return '项目名依然重复!'
}
return true
}
})
}
if (typeof conf.description !== 'string') {
prompts.push({
type: 'input',
name: 'description',
message: '请输入项目介绍!'
})
}
if (typeof conf.typescript !== 'boolean') {
prompts.push({
type: 'confirm',
name: 'typescript',
message: '是否需要使用 TypeScript '
})
}
const cssChoices = [{
name: 'Sass',
value: 'sass'
}, {
name: 'Less',
value: 'less'
}, {
name: 'Stylus',
value: 'stylus'
}, {
name: '无',
value: 'none'
}]
if (typeof conf.css !== 'string') {
prompts.push({
type: 'list',
name: 'css',
message: '请选择 CSS 预处理器Sass/Less/Stylus',
choices: cssChoices
})
}
const templateChoices = [{
name: '默认模板',
value: 'default'
}, {
name: 'Redux 模板',
value: 'redux'
}, {
name: 'Mobx 模板',
value: 'mobx'
}]
if (typeof conf.template !== 'string') {
prompts.push({
type: 'list',
name: 'template',
message: '请选择模板',
choices: templateChoices
})
} else {
let isTemplateExist = false
templateChoices.forEach(item => {
if (item.value === conf.template) {
isTemplateExist = true
}
})
if (!isTemplateExist) {
console.log(chalk.red('你选择的模板不存在!'))
console.log(chalk.red('目前提供了以下模板以供使用:'))
console.log()
templateChoices.forEach(item => {
console.log(chalk.green(`- ${item.name}`))
})
process.exit(1)
}
}
return inquirer.prompt(prompts)
}
write (cb) {
const { template } = this.conf
this.conf.src = SOURCE_DIR
const templateCreate = require(path.join(this.templatePath(), template, 'index.js'))
templateCreate(this, this.conf, {
shouldUseYarn,
shouldUseCnpm,
getPkgVersion
}, cb)
}
}
module.exports = Project

View File

@ -0,0 +1,249 @@
const fs = require('fs-extra')
const path = require('path')
const {performance} = require('perf_hooks')
const chokidar = require('chokidar')
const chalk = require('chalk')
const ejs = require('ejs')
const _ = require('lodash')
const shelljs = require('shelljs')
const klaw = require('klaw')
const Util = require('./util')
const npmProcess = require('./util/npm')
const CONFIG = require('./config')
const {getPkgVersion} = require('./util')
const StyleProcess = require('./rn/styleProcess')
const {transformJSCode} = require('./rn/transformJS')
const appPath = process.cwd()
const projectConfig = require(path.join(appPath, Util.PROJECT_CONFIG))(_.merge)
const sourceDirName = projectConfig.sourceRoot || CONFIG.SOURCE_DIR
const sourceDir = path.join(appPath, sourceDirName)
const tempDir = '.rn_temp'
const tempPath = path.join(appPath, tempDir)
const entryFilePath = Util.resolveScriptPath(path.join(sourceDir, CONFIG.ENTRY))
const entryFileName = path.basename(entryFilePath)
const pluginsConfig = projectConfig.plugins || {}
const pkgPath = path.join(__dirname, './rn/pkg')
let depTree = {}
let isBuildingStyles = {}
const styleDenpendencyTree = {}
function isEntryFile (filePath) {
return path.basename(filePath) === entryFileName
}
function compileDepStyles (filePath, styleFiles) {
if (isBuildingStyles[filePath] || styleFiles.length === 0) {
return Promise.resolve({})
}
isBuildingStyles[filePath] = true
return Promise.all(styleFiles.map(async p => { // to css string
const filePath = path.join(p)
const fileExt = path.extname(filePath)
Util.printLog(Util.pocessTypeEnum.COMPILE, _.camelCase(fileExt).toUpperCase(), filePath)
return StyleProcess.loadStyle({filePath, pluginsConfig})
})).then(resList => { // postcss
return Promise.all(resList.map(item => {
return StyleProcess.postCSS({...item, projectConfig})
}))
}).then(resList => {
let styleObjectEntire = {}
resList.forEach(item => {
let styleObject = StyleProcess.getStyleObject({css: item.css, filePath: item.filePath})
// validate styleObject
StyleProcess.validateStyle({styleObject, filePath: item.filePath})
Object.assign(styleObjectEntire, styleObject)
if (filePath !== entryFilePath) { // 非入口文件,合并全局样式
Object.assign(styleObjectEntire, _.get(styleDenpendencyTree, [entryFilePath, 'styleObjectEntire'], {}))
}
styleDenpendencyTree[filePath] = {
styleFiles,
styleObjectEntire
}
})
return JSON.stringify(styleObjectEntire, null, 2)
}).then(css => {
let tempFilePath = filePath.replace(sourceDir, tempPath)
const basename = path.basename(tempFilePath, path.extname(tempFilePath))
tempFilePath = path.join(path.dirname(tempFilePath), `${basename}_styles.js`)
StyleProcess.writeStyleFile({css, tempFilePath})
}).catch((e) => {
throw new Error(e)
})
}
function initProjectFile () {
// generator app.json
const appJsonObject = Object.assign({}, {
expo: {
sdkVersion: '27.0.0'
}
}, projectConfig.rn && projectConfig.rn.appJson)
// generator .${tempPath}/package.json TODO JSON.parse 这种写法可能会有隐患
const pkgTempObj = JSON.parse(
ejs.render(
fs.readFileSync(pkgPath, 'utf-8'), {
projectName: projectConfig.projectName,
version: getPkgVersion()
}
).replace(/(\r\n|\n|\r|\s+)/gm, '')
)
const dependencies = require(path.join(process.cwd(), 'package.json')).dependencies
pkgTempObj.dependencies = Object.assign({}, pkgTempObj.dependencies, dependencies)
// Copy bin/crna-entry.js ?
const crnaEntryPath = path.join(path.dirname(npmProcess.resolveNpmSync('@tarojs/rn-runner')), 'src/bin/crna-entry.js')
fs.writeFileSync(path.join(tempDir, 'app.json'), JSON.stringify(appJsonObject, null, 2))
Util.printLog(Util.pocessTypeEnum.GENERATE, 'app.json', path.join(tempPath, 'app.json'))
fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(pkgTempObj, null, 2))
Util.printLog(Util.pocessTypeEnum.GENERATE, 'package.json', path.join(tempPath, 'package.json'))
fs.copySync(crnaEntryPath, path.join(tempDir, 'bin/crna-entry.js'))
Util.printLog(Util.pocessTypeEnum.COPY, 'crna-entry.js', path.join(tempPath, 'bin/crna-entry.js'))
}
async function processFile (filePath) {
if (!fs.existsSync(filePath)) {
return
}
const dirname = path.dirname(filePath)
const distDirname = dirname.replace(sourceDir, tempDir)
let distPath = path.format({dir: distDirname, base: path.basename(filePath)})
const code = fs.readFileSync(filePath, 'utf-8')
if (Util.REG_STYLE.test(filePath)) {
// do something
} else if (Util.REG_SCRIPTS.test(filePath)) {
if (Util.REG_TYPESCRIPT.test(filePath)) {
distPath = distPath.replace(/\.(tsx|ts)(\?.*)?$/, '.js')
}
Util.printLog(Util.pocessTypeEnum.COMPILE, _.camelCase(path.extname(filePath)).toUpperCase(), filePath)
// transformJSCode
let transformResult = transformJSCode({code, filePath, isEntryFile: isEntryFile(filePath), projectConfig})
const jsCode = transformResult.code
fs.ensureDirSync(distDirname)
fs.writeFileSync(distPath, Buffer.from(jsCode))
// compileDepStyles
const styleFiles = transformResult.styleFiles
depTree[filePath] = styleFiles
await compileDepStyles(filePath, styleFiles)
} else {
fs.ensureDirSync(distDirname)
fs.copySync(filePath, distPath)
Util.printLog(Util.pocessTypeEnum.COPY, _.camelCase(path.extname(filePath)).toUpperCase(), filePath)
}
}
function buildTemp () {
fs.ensureDirSync(path.join(tempPath, 'bin'))
return new Promise((resolve, reject) => {
klaw(sourceDir)
.on('data', file => {
if (!file.stats.isDirectory()) {
processFile(file.path)
}
})
.on('end', () => {
initProjectFile()
if (!fs.existsSync(path.join(tempPath, 'node_modules'))) {
console.log()
console.log(chalk.yellow('开始安装依赖~'))
process.chdir(tempPath)
let command
if (Util.shouldUseYarn()) {
command = 'yarn'
} else if (Util.shouldUseCnpm()) {
command = 'cnpm install'
} else {
command = 'npm install'
}
shelljs.exec(command, {silent: false})
}
resolve()
})
})
}
async function buildDist ({watch}) {
const entry = {
app: path.join(tempPath, entryFileName)
}
const rnConfig = projectConfig.rn || {}
rnConfig.env = projectConfig.env
rnConfig.defineConstants = projectConfig.defineConstants
rnConfig.designWidth = projectConfig.designWidth
rnConfig.entry = entry
if (watch) {
rnConfig.isWatch = true
}
rnConfig.projectDir = tempPath
const rnRunner = await npmProcess.getNpmPkg('@tarojs/rn-runner')
rnRunner(rnConfig)
}
async function perfWrap (callback, args) {
isBuildingStyles = {} // 清空
// 后期可以优化,不编译全部
let t0 = performance.now()
await callback(args)
let t1 = performance.now()
Util.printLog(Util.pocessTypeEnum.COMPILE, `编译完成,花费${Math.round(t1 - t0)} ms`)
}
function watchFiles () {
const watcher = chokidar.watch(path.join(sourceDir), {
ignored: /(^|[/\\])\../,
persistent: true,
ignoreInitial: true
})
watcher
.on('ready', () => {
console.log()
console.log(chalk.gray('初始化完毕,监听文件修改中...'))
console.log()
})
.on('add', filePath => {
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.CREATE, '添加文件', relativePath)
perfWrap(buildTemp)
})
.on('change', filePath => {
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.MODIFY, '文件变动', relativePath)
if (Util.REG_SCRIPTS.test(filePath)) {
perfWrap(processFile, filePath)
}
if (Util.REG_STYLE.test(filePath)) {
_.forIn(depTree, (styleFiles, jsFilePath) => {
if (styleFiles.indexOf(filePath) > -1) {
perfWrap(processFile, jsFilePath)
}
})
}
})
.on('unlink', filePath => {
const relativePath = path.relative(appPath, filePath)
Util.printLog(Util.pocessTypeEnum.UNLINK, '删除文件', relativePath)
perfWrap(buildTemp)
})
.on('error', error => console.log(`Watcher error: ${error}`))
}
async function build ({watch}) {
fs.ensureDirSync(tempPath)
let t0 = performance.now()
await buildTemp()
let t1 = performance.now()
Util.printLog(Util.pocessTypeEnum.COMPILE, `编译完成,花费${Math.round(t1 - t0)} ms`)
await buildDist({watch})
if (watch) {
watchFiles()
}
}
module.exports = {build}

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict'
const normalizeColor = require('./normalizeColor')
const colorPropType = function (
isRequired,
props,
propName,
componentName,
location,
propFullName
) {
const color = props[propName]
if (color === undefined || color === null) {
if (isRequired) {
return new Error(
'Required ' +
location +
' `' +
(propFullName || propName) +
'` was not specified in `' +
componentName +
'`.'
)
}
return
}
if (typeof color === 'number') {
// Developers should not use a number, but we are using the prop type
// both for user provided colors and for transformed ones. This isn't ideal
// and should be fixed but will do for now...
return
}
if (normalizeColor(color) === null) {
return new Error(
'Invalid ' +
location +
' `' +
(propFullName || propName) +
'` supplied to `' +
componentName +
'`: ' +
color +
'\n' +
`Valid color formats are
- '#f0f' (#rgb)
- '#f0fc' (#rgba)
- '#ff00ff' (#rrggbb)
- '#ff00ff00' (#rrggbbaa)
- 'rgb(255, 255, 255)'
- 'rgba(255, 255, 255, 1.0)'
- 'hsl(360, 100%, 100%)'
- 'hsla(360, 100%, 100%, 1.0)'
- 'transparent'
- 'red'
- 0xff00ff00 (0xrrggbbaa)
`
)
}
}
const ColorPropType = colorPropType.bind(null, false /* isRequired */)
ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */)
module.exports = ColorPropType

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict'
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
* found when Flow v0.54 was deployed. To see the error delete this comment and
* run Flow. */
const keyMirror = require('fbjs/lib/keyMirror')
/**
* ImageResizeMode - Enum for different image resizing modes, set via
* `resizeMode` style property on `<Image>` components.
*/
const ImageResizeMode = keyMirror({
/**
* contain - The image will be resized such that it will be completely
* visible, contained within the frame of the View.
*/
contain: null,
/**
* cover - The image will be resized such that the entire area of the view
* is covered by the image, potentially clipping parts of the image.
*/
cover: null,
/**
* stretch - The image will be stretched to fill the entire frame of the
* view without clipping. This may change the aspect ratio of the image,
* distorting it.
*/
stretch: null,
/**
* center - The image will be scaled down such that it is completely visible,
* if bigger than the area of the view.
* The image will not be scaled up.
*/
center: null,
/**
* repeat - The image will be repeated to cover the frame of the View. The
* image will keep it's size and aspect ratio.
*/
repeat: null
})
module.exports = ImageResizeMode

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const ImageResizeMode = require('./ImageResizeMode')
const LayoutPropTypes = require('./LayoutPropTypes')
const ReactPropTypes = require('prop-types')
const ShadowPropTypesIOS = require('./ShadowPropTypesIOS')
const TransformPropTypes = require('./TransformPropTypes')
const ImageStylePropTypes = {
...LayoutPropTypes,
...ShadowPropTypesIOS,
...TransformPropTypes,
resizeMode: ReactPropTypes.oneOf(Object.keys(ImageResizeMode)),
backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
backgroundColor: ColorPropType,
borderColor: ColorPropType,
borderWidth: ReactPropTypes.number,
borderRadius: ReactPropTypes.number,
overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
/**
* Changes the color of all the non-transparent pixels to the tintColor.
*/
tintColor: ColorPropType,
opacity: ReactPropTypes.number,
/**
* When the image has rounded corners, specifying an overlayColor will
* cause the remaining space in the corners to be filled with a solid color.
* This is useful in cases which are not supported by the Android
* implementation of rounded corners:
* - Certain resize modes, such as 'contain'
* - Animated GIFs
*
* A typical way to use this prop is with images displayed on a solid
* background and setting the `overlayColor` to the same color
* as the background.
*
* For details of how this works under the hood, see
* http://frescolib.org/docs/rounded-corners-and-circles.html
*
* @platform android
*/
overlayColor: ReactPropTypes.string,
// Android-Specific styles
borderTopLeftRadius: ReactPropTypes.number,
borderTopRightRadius: ReactPropTypes.number,
borderBottomLeftRadius: ReactPropTypes.number,
borderBottomRightRadius: ReactPropTypes.number
}
module.exports = ImageStylePropTypes

View File

@ -0,0 +1,561 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* strict
*/
'use strict'
const ReactPropTypes = require('prop-types')
/**
* React Native's layout system is based on Flexbox and is powered both
* on iOS and Android by an open source project called `Yoga`:
* https://github.com/facebook/yoga
*
* The implementation in Yoga is slightly different from what the
* Flexbox spec defines - for example, we chose more sensible default
* values. Since our layout docs are generated from the comments in this
* file, please keep a brief comment describing each prop type.
*
* These properties are a subset of our styles that are consumed by the layout
* algorithm and affect the positioning and sizing of views.
*/
const LayoutPropTypes = {
/** `display` sets the display type of this component.
*
* It works similarly to `display` in CSS, but only support 'flex' and 'none'.
* 'flex' is the default.
*/
display: ReactPropTypes.oneOf(['none', 'flex']),
/** `width` sets the width of this component.
*
* It works similarly to `width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/width for more details.
*/
width: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `height` sets the height of this component.
*
* It works similarly to `height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/height for more details.
*/
height: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When the direction is `ltr`, `start` is equivalent to `left`.
* When the direction is `rtl`, `start` is equivalent to `right`.
*
* This style takes precedence over the `left`, `right`, and `end` styles.
*/
start: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When the direction is `ltr`, `end` is equivalent to `right`.
* When the direction is `rtl`, `end` is equivalent to `left`.
*
* This style takes precedence over the `left` and `right` styles.
*/
end: ReactPropTypes.oneOfType([ReactPropTypes.number, ReactPropTypes.string]),
/** `top` is the number of logical pixels to offset the top edge of
* this component.
*
* It works similarly to `top` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/top
* for more details of how `top` affects layout.
*/
top: ReactPropTypes.oneOfType([ReactPropTypes.number, ReactPropTypes.string]),
/** `left` is the number of logical pixels to offset the left edge of
* this component.
*
* It works similarly to `left` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/left
* for more details of how `left` affects layout.
*/
left: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `right` is the number of logical pixels to offset the right edge of
* this component.
*
* It works similarly to `right` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/right
* for more details of how `right` affects layout.
*/
right: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `bottom` is the number of logical pixels to offset the bottom edge of
* this component.
*
* It works similarly to `bottom` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/bottom
* for more details of how `bottom` affects layout.
*/
bottom: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `minWidth` is the minimum width for this component, in logical pixels.
*
* It works similarly to `min-width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/min-width
* for more details.
*/
minWidth: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `maxWidth` is the maximum width for this component, in logical pixels.
*
* It works similarly to `max-width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/max-width
* for more details.
*/
maxWidth: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `minHeight` is the minimum height for this component, in logical pixels.
*
* It works similarly to `min-height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/min-height
* for more details.
*/
minHeight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `maxHeight` is the maximum height for this component, in logical pixels.
*
* It works similarly to `max-height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/max-height
* for more details.
*/
maxHeight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `margin` has the same effect as setting each of
* `marginTop`, `marginLeft`, `marginBottom`, and `marginRight`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin
* for more details.
*/
margin: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `marginVertical` has the same effect as setting both
* `marginTop` and `marginBottom`.
*/
marginVertical: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `marginHorizontal` has the same effect as setting
* both `marginLeft` and `marginRight`.
*/
marginHorizontal: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginTop` works like `margin-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top
* for more details.
*/
marginTop: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginBottom` works like `margin-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom
* for more details.
*/
marginBottom: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginLeft` works like `margin-left` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left
* for more details.
*/
marginLeft: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `marginRight` works like `margin-right` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right
* for more details.
*/
marginRight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `marginStart` is equivalent to `marginLeft`.
* When direction is `rtl`, `marginStart` is equivalent to `marginRight`.
*/
marginStart: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `marginEnd` is equivalent to `marginRight`.
* When direction is `rtl`, `marginEnd` is equivalent to `marginLeft`.
*/
marginEnd: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `padding` has the same effect as setting each of
* `paddingTop`, `paddingBottom`, `paddingLeft`, and `paddingRight`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding
* for more details.
*/
padding: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `paddingVertical` is like setting both of
* `paddingTop` and `paddingBottom`.
*/
paddingVertical: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** Setting `paddingHorizontal` is like setting both of
* `paddingLeft` and `paddingRight`.
*/
paddingHorizontal: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingTop` works like `padding-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top
* for more details.
*/
paddingTop: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingBottom` works like `padding-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom
* for more details.
*/
paddingBottom: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingLeft` works like `padding-left` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left
* for more details.
*/
paddingLeft: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `paddingRight` works like `padding-right` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-right
* for more details.
*/
paddingRight: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `paddingStart` is equivalent to `paddingLeft`.
* When direction is `rtl`, `paddingStart` is equivalent to `paddingRight`.
*/
paddingStart: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* When direction is `ltr`, `paddingEnd` is equivalent to `paddingRight`.
* When direction is `rtl`, `paddingEnd` is equivalent to `paddingLeft`.
*/
paddingEnd: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/** `borderWidth` works like `border-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-width
* for more details.
*/
borderWidth: ReactPropTypes.number,
/** `borderTopWidth` works like `border-top-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-width
* for more details.
*/
borderTopWidth: ReactPropTypes.number,
/**
* When direction is `ltr`, `borderStartWidth` is equivalent to `borderLeftWidth`.
* When direction is `rtl`, `borderStartWidth` is equivalent to `borderRightWidth`.
*/
borderStartWidth: ReactPropTypes.number,
/**
* When direction is `ltr`, `borderEndWidth` is equivalent to `borderRightWidth`.
* When direction is `rtl`, `borderEndWidth` is equivalent to `borderLeftWidth`.
*/
borderEndWidth: ReactPropTypes.number,
/** `borderRightWidth` works like `border-right-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-width
* for more details.
*/
borderRightWidth: ReactPropTypes.number,
/** `borderBottomWidth` works like `border-bottom-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-width
* for more details.
*/
borderBottomWidth: ReactPropTypes.number,
/** `borderLeftWidth` works like `border-left-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-width
* for more details.
*/
borderLeftWidth: ReactPropTypes.number,
/** `position` in React Native is similar to regular CSS, but
* everything is set to `relative` by default, so `absolute`
* positioning is always just relative to the parent.
*
* If you want to position a child using specific numbers of logical
* pixels relative to its parent, set the child to have `absolute`
* position.
*
* If you want to position a child relative to something
* that is not its parent, just don't use styles for that. Use the
* component tree.
*
* See https://github.com/facebook/yoga
* for more details on how `position` differs between React Native
* and CSS.
*/
position: ReactPropTypes.oneOf(['absolute', 'relative']),
/** `flexDirection` controls which directions children of a container go.
* `row` goes left to right, `column` goes top to bottom, and you may
* be able to guess what the other two do. It works like `flex-direction`
* in CSS, except the default is `column`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction
* for more details.
*/
flexDirection: ReactPropTypes.oneOf([
'row',
'row-reverse',
'column',
'column-reverse'
]),
/** `flexWrap` controls whether children can wrap around after they
* hit the end of a flex container.
* It works like `flex-wrap` in CSS (default: nowrap).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap
* for more details.
*/
flexWrap: ReactPropTypes.oneOf(['wrap', 'nowrap', 'wrap-reverse']),
/** `justifyContent` aligns children in the main direction.
* For example, if children are flowing vertically, `justifyContent`
* controls how they align vertically.
* It works like `justify-content` in CSS (default: flex-start).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content
* for more details.
*/
justifyContent: ReactPropTypes.oneOf([
'flex-start',
'flex-end',
'center',
'space-between',
'space-around',
'space-evenly'
]),
/** `alignItems` aligns children in the cross direction.
* For example, if children are flowing vertically, `alignItems`
* controls how they align horizontally.
* It works like `align-items` in CSS (default: stretch).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-items
* for more details.
*/
alignItems: ReactPropTypes.oneOf([
'flex-start',
'flex-end',
'center',
'stretch',
'baseline'
]),
/** `alignSelf` controls how a child aligns in the cross direction,
* overriding the `alignItems` of the parent. It works like `align-self`
* in CSS (default: auto).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-self
* for more details.
*/
alignSelf: ReactPropTypes.oneOf([
'auto',
'flex-start',
'flex-end',
'center',
'stretch',
'baseline'
]),
/** `alignContent` controls how rows align in the cross direction,
* overriding the `alignContent` of the parent.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-content
* for more details.
*/
alignContent: ReactPropTypes.oneOf([
'flex-start',
'flex-end',
'center',
'stretch',
'space-between',
'space-around'
]),
/** `overflow` controls how children are measured and displayed.
* `overflow: hidden` causes views to be clipped while `overflow: scroll`
* causes views to be measured independently of their parents main axis.
* It works like `overflow` in CSS (default: visible).
* See https://developer.mozilla.org/en/docs/Web/CSS/overflow
* for more details.
* `overflow: visible` only works on iOS. On Android, all views will clip
* their children.
*/
overflow: ReactPropTypes.oneOf(['visible', 'hidden', 'scroll']),
/** In React Native `flex` does not work the same way that it does in CSS.
* `flex` is a number rather than a string, and it works
* according to the `Yoga` library
* at https://github.com/facebook/yoga
*
* When `flex` is a positive number, it makes the component flexible
* and it will be sized proportional to its flex value. So a
* component with `flex` set to 2 will take twice the space as a
* component with `flex` set to 1.
*
* When `flex` is 0, the component is sized according to `width`
* and `height` and it is inflexible.
*
* When `flex` is -1, the component is normally sized according
* `width` and `height`. However, if there's not enough space,
* the component will shrink to its `minWidth` and `minHeight`.
*
* flexGrow, flexShrink, and flexBasis work the same as in CSS.
*/
flex: ReactPropTypes.number,
flexGrow: ReactPropTypes.number,
flexShrink: ReactPropTypes.number,
flexBasis: ReactPropTypes.oneOfType([
ReactPropTypes.number,
ReactPropTypes.string
]),
/**
* Aspect ratio control the size of the undefined dimension of a node. Aspect ratio is a
* non-standard property only available in react native and not CSS.
*
* - On a node with a set width/height aspect ratio control the size of the unset dimension
* - On a node with a set flex basis aspect ratio controls the size of the node in the cross axis
* if unset
* - On a node with a measure function aspect ratio works as though the measure function measures
* the flex basis
* - On a node with flex grow/shrink aspect ratio controls the size of the node in the cross axis
* if unset
* - Aspect ratio takes min/max dimensions into account
*/
aspectRatio: ReactPropTypes.number,
/** `zIndex` controls which components display on top of others.
* Normally, you don't use `zIndex`. Components render according to
* their order in the document tree, so later components draw over
* earlier ones. `zIndex` may be useful if you have animations or custom
* modal interfaces where you don't want this behavior.
*
* It works like the CSS `z-index` property - components with a larger
* `zIndex` will render on top. Think of the z-direction like it's
* pointing from the phone into your eyeball.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/z-index for
* more details.
*/
zIndex: ReactPropTypes.number,
/** `direction` specifies the directional flow of the user interface.
* The default is `inherit`, except for root node which will have
* value based on the current locale.
* See https://facebook.github.io/yoga/docs/rtl/
* for more details.
* @platform ios
*/
direction: ReactPropTypes.oneOf(['inherit', 'ltr', 'rtl'])
}
module.exports = LayoutPropTypes

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const ReactPropTypes = require('prop-types')
/**
* These props can be used to dynamically generate shadows on views, images, text, etc.
*
* Because they are dynamically generated, they may cause performance regressions. Static
* shadow image asset may be a better way to go for optimal performance.
*
* These properties are iOS only - for similar functionality on Android, use the [`elevation`
* property](docs/viewstyleproptypes.html#elevation).
*/
const ShadowPropTypesIOS = {
/**
* Sets the drop shadow color
* @platform ios
*/
shadowColor: ColorPropType,
/**
* Sets the drop shadow offset
* @platform ios
*/
shadowOffset: ReactPropTypes.shape({
width: ReactPropTypes.number,
height: ReactPropTypes.number
}),
/**
* Sets the drop shadow opacity (multiplied by the color's alpha component)
* @platform ios
*/
shadowOpacity: ReactPropTypes.number,
/**
* Sets the drop shadow blur radius
* @platform ios
*/
shadowRadius: ReactPropTypes.number
}
module.exports = ShadowPropTypesIOS

View File

@ -0,0 +1,82 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ImageStylePropTypes = require('./ImageStylePropTypes')
const TextStylePropTypes = require('./TextStylePropTypes')
const ViewStylePropTypes = require('./ViewStylePropTypes')
const invariant = require('fbjs/lib/invariant')
// Hardcoded because this is a legit case but we don't want to load it from
// a private API. We might likely want to unify style sheet creation with how it
// is done in the DOM so this might move into React. I know what I'm doing so
// plz don't fire me.
const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'
class StyleSheetValidation {
static validateStyleProp (prop, style, caller) {
if (allStylePropTypes[prop] === undefined) {
const message1 = '"' + prop + '" is not a valid style property.'
const message2 =
'\nValid style props: ' +
JSON.stringify(Object.keys(allStylePropTypes).sort(), null, ' ')
styleError(message1, style, caller, message2)
}
const error = allStylePropTypes[prop](
style,
prop,
caller,
'prop',
null,
ReactPropTypesSecret
)
if (error) {
styleError(error.message, style, caller)
}
}
static validateStyle (name, styles) {
for (const prop in styles[name]) {
StyleSheetValidation.validateStyleProp(
prop,
styles[name],
'StyleSheet ' + name
)
}
}
static addValidStylePropTypes (stylePropTypes) {
for (const key in stylePropTypes) {
allStylePropTypes[key] = stylePropTypes[key]
}
}
}
const styleError = function (message1, style, caller, message2) {
invariant(
false,
message1 +
'\n' +
(caller || '<<unknown>>') +
': ' +
JSON.stringify(style, null, ' ') +
(message2 || '')
)
}
const allStylePropTypes = {}
StyleSheetValidation.addValidStylePropTypes(ImageStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(TextStylePropTypes)
StyleSheetValidation.addValidStylePropTypes(ViewStylePropTypes)
module.exports = StyleSheetValidation

View File

@ -0,0 +1,123 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const ReactPropTypes = require('prop-types')
const ViewStylePropTypes = require('./ViewStylePropTypes')
const TextStylePropTypes = {
...ViewStylePropTypes,
color: ColorPropType,
fontFamily: ReactPropTypes.string,
fontSize: ReactPropTypes.number,
fontStyle: ReactPropTypes.oneOf(['normal', 'italic']),
/**
* Specifies font weight. The values 'normal' and 'bold' are supported for
* most fonts. Not all fonts have a variant for each of the numeric values,
* in that case the closest one is chosen.
*/
fontWeight: ReactPropTypes.oneOf([
'normal' /* default */,
'bold',
'100',
'200',
'300',
'400',
'500',
'600',
'700',
'800',
'900'
]),
/**
* @platform ios
*/
fontVariant: ReactPropTypes.arrayOf(
ReactPropTypes.oneOf([
'small-caps',
'oldstyle-nums',
'lining-nums',
'tabular-nums',
'proportional-nums'
])
),
textShadowOffset: ReactPropTypes.shape({
width: ReactPropTypes.number,
height: ReactPropTypes.number
}),
textShadowRadius: ReactPropTypes.number,
textShadowColor: ColorPropType,
/**
* @platform ios
*/
letterSpacing: ReactPropTypes.number,
lineHeight: ReactPropTypes.number,
/**
* Specifies text alignment. The value 'justify' is only supported on iOS and
* fallbacks to `left` on Android.
*/
textAlign: ReactPropTypes.oneOf([
'auto' /* default */,
'left',
'right',
'center',
'justify'
]),
/**
* @platform android
*/
textAlignVertical: ReactPropTypes.oneOf([
'auto' /* default */,
'top',
'bottom',
'center'
]),
/**
* Set to `false` to remove extra font padding intended to make space for certain ascenders / descenders.
* With some fonts, this padding can make text look slightly misaligned when centered vertically.
* For best results also set `textAlignVertical` to `center`. Default is true.
* @platform android
*/
includeFontPadding: ReactPropTypes.bool,
textDecorationLine: ReactPropTypes.oneOf([
'none' /* default */,
'underline',
'line-through',
'underline line-through'
]),
/**
* @platform ios
*/
textDecorationStyle: ReactPropTypes.oneOf([
'solid' /* default */,
'double',
'dotted',
'dashed'
]),
/**
* @platform ios
*/
textDecorationColor: ColorPropType,
textTransform: ReactPropTypes.oneOf([
'none' /* default */,
'capitalize',
'uppercase',
'lowercase'
]),
/**
* @platform ios
*/
writingDirection: ReactPropTypes.oneOf(['auto' /* default */, 'ltr', 'rtl'])
}
module.exports = TextStylePropTypes

View File

@ -0,0 +1,109 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ReactPropTypes = require('prop-types')
const deprecatedPropType = require('./deprecatedPropType')
const TransformMatrixPropType = function (
props,
propName,
componentName
) {
if (props[propName]) {
return new Error(
'The transformMatrix style property is deprecated. ' +
'Use `transform: [{ matrix: ... }]` instead.'
)
}
}
const DecomposedMatrixPropType = function (
props,
propName,
componentName
) {
if (props[propName]) {
return new Error(
'The decomposedMatrix style property is deprecated. ' +
'Use `transform: [...]` instead.'
)
}
}
const TransformPropTypes = {
/**
* `transform` accepts an array of transformation objects. Each object specifies
* the property that will be transformed as the key, and the value to use in the
* transformation. Objects should not be combined. Use a single key/value pair
* per object.
*
* The rotate transformations require a string so that the transform may be
* expressed in degrees (deg) or radians (rad). For example:
*
* `transform([{ rotateX: '45deg' }, { rotateZ: '0.785398rad' }])`
*
* The skew transformations require a string so that the transform may be
* expressed in degrees (deg). For example:
*
* `transform([{ skewX: '45deg' }])`
*/
transform: ReactPropTypes.arrayOf(
ReactPropTypes.oneOfType([
ReactPropTypes.shape({perspective: ReactPropTypes.number}),
ReactPropTypes.shape({rotate: ReactPropTypes.string}),
ReactPropTypes.shape({rotateX: ReactPropTypes.string}),
ReactPropTypes.shape({rotateY: ReactPropTypes.string}),
ReactPropTypes.shape({rotateZ: ReactPropTypes.string}),
ReactPropTypes.shape({scale: ReactPropTypes.number}),
ReactPropTypes.shape({scaleX: ReactPropTypes.number}),
ReactPropTypes.shape({scaleY: ReactPropTypes.number}),
ReactPropTypes.shape({translateX: ReactPropTypes.number}),
ReactPropTypes.shape({translateY: ReactPropTypes.number}),
ReactPropTypes.shape({skewX: ReactPropTypes.string}),
ReactPropTypes.shape({skewY: ReactPropTypes.string})
])
),
/**
* Deprecated. Use the transform prop instead.
*/
transformMatrix: TransformMatrixPropType,
/**
* Deprecated. Use the transform prop instead.
*/
decomposedMatrix: DecomposedMatrixPropType,
/* Deprecated transform props used on Android only */
scaleX: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
scaleY: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
rotation: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
translateX: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
),
translateY: deprecatedPropType(
ReactPropTypes.number,
'Use the transform prop instead.'
)
}
module.exports = TransformPropTypes

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
'use strict'
const ColorPropType = require('./ColorPropType')
const LayoutPropTypes = require('./LayoutPropTypes')
const ReactPropTypes = require('prop-types')
const ShadowPropTypesIOS = require('./ShadowPropTypesIOS')
const TransformPropTypes = require('./TransformPropTypes')
/**
* Warning: Some of these properties may not be supported in all releases.
*/
const ViewStylePropTypes = {
...LayoutPropTypes,
...ShadowPropTypesIOS,
...TransformPropTypes,
backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
backgroundColor: ColorPropType,
borderColor: ColorPropType,
borderTopColor: ColorPropType,
borderRightColor: ColorPropType,
borderBottomColor: ColorPropType,
borderLeftColor: ColorPropType,
borderStartColor: ColorPropType,
borderEndColor: ColorPropType,
borderRadius: ReactPropTypes.number,
borderTopLeftRadius: ReactPropTypes.number,
borderTopRightRadius: ReactPropTypes.number,
borderTopStartRadius: ReactPropTypes.number,
borderTopEndRadius: ReactPropTypes.number,
borderBottomLeftRadius: ReactPropTypes.number,
borderBottomRightRadius: ReactPropTypes.number,
borderBottomStartRadius: ReactPropTypes.number,
borderBottomEndRadius: ReactPropTypes.number,
borderStyle: ReactPropTypes.oneOf(['solid', 'dotted', 'dashed']),
borderWidth: ReactPropTypes.number,
borderTopWidth: ReactPropTypes.number,
borderRightWidth: ReactPropTypes.number,
borderBottomWidth: ReactPropTypes.number,
borderLeftWidth: ReactPropTypes.number,
opacity: ReactPropTypes.number,
/**
* (Android-only) Sets the elevation of a view, using Android's underlying
* [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation).
* This adds a drop shadow to the item and affects z-order for overlapping views.
* Only supported on Android 5.0+, has no effect on earlier versions.
* @platform android
*/
elevation: ReactPropTypes.number
}
module.exports = ViewStylePropTypes

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* strict-local
*/
'use strict'
// const UIManager = require('UIManager')
/**
* Adds a deprecation warning when the prop is used.
*/
function deprecatedPropType (
propType,
explanation
) {
return function validate (props, propName, componentName, ...rest) {
// Don't warn for native components.
// if (!UIManager[componentName] && props[propName] !== undefined) {
if (props[propName] !== undefined) {
console.warn(
`\`${propName}\` supplied to \`${componentName}\` has been deprecated. ${explanation}`
)
}
return propType(props, propName, componentName, ...rest)
}
}
module.exports = deprecatedPropType

View File

@ -0,0 +1,3 @@
const StyleSheetValidation = require('./StyleSheetValidation')
module.exports = {StyleSheetValidation}

View File

@ -0,0 +1,372 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*
*/
/* eslint no-bitwise: 0 */
'use strict'
function normalizeColor (color) {
const matchers = getMatchers()
let match
if (typeof color === 'number') {
if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
return color
}
return null
}
// Ordered based on occurrences on Facebook codebase
if ((match = matchers.hex6.exec(color))) {
return parseInt(match[1] + 'ff', 16) >>> 0
}
if (names.hasOwnProperty(color)) {
return names[color]
}
if ((match = matchers.rgb.exec(color))) {
return (
// b
((parse255(match[1]) << 24) | // r
(parse255(match[2]) << 16) | // g
(parse255(match[3]) << 8) |
0x000000ff) >>> // a
0
)
}
if ((match = matchers.rgba.exec(color))) {
return (
// b
((parse255(match[1]) << 24) | // r
(parse255(match[2]) << 16) | // g
(parse255(match[3]) << 8) |
parse1(match[4])) >>> // a
0
)
}
if ((match = matchers.hex3.exec(color))) {
return (
parseInt(
match[1] +
match[1] + // r
match[2] +
match[2] + // g
match[3] +
match[3] + // b
'ff', // a
16
) >>> 0
)
}
// https://drafts.csswg.org/css-color-4/#hex-notation
if ((match = matchers.hex8.exec(color))) {
return parseInt(match[1], 16) >>> 0
}
if ((match = matchers.hex4.exec(color))) {
return (
parseInt(
match[1] +
match[1] + // r
match[2] +
match[2] + // g
match[3] +
match[3] + // b
match[4] +
match[4], // a
16
) >>> 0
)
}
if ((match = matchers.hsl.exec(color))) {
return (
(hslToRgb(
parse360(match[1]), // h
parsePercentage(match[2]), // s
parsePercentage(match[3]) // l
) |
0x000000ff) >>> // a
0
)
}
if ((match = matchers.hsla.exec(color))) {
return (
(hslToRgb(
parse360(match[1]), // h
parsePercentage(match[2]), // s
parsePercentage(match[3]) // l
) |
parse1(match[4])) >>> // a
0
)
}
return null
}
function hue2rgb (p, q, t) {
if (t < 0) {
t += 1
}
if (t > 1) {
t -= 1
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t
}
if (t < 1 / 2) {
return q
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6
}
return p
}
function hslToRgb (h, s, l) {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
const r = hue2rgb(p, q, h + 1 / 3)
const g = hue2rgb(p, q, h)
const b = hue2rgb(p, q, h - 1 / 3)
return (
(Math.round(r * 255) << 24) |
(Math.round(g * 255) << 16) |
(Math.round(b * 255) << 8)
)
}
// var INTEGER = '[-+]?\\d+';
const NUMBER = '[-+]?\\d*\\.?\\d+'
const PERCENTAGE = NUMBER + '%'
function call (...args) {
return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)'
}
let cachedMatchers
function getMatchers () {
if (cachedMatchers === undefined) {
cachedMatchers = {
rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)),
rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)),
hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)),
hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex6: /^#([0-9a-fA-F]{6})$/,
hex8: /^#([0-9a-fA-F]{8})$/
}
}
return cachedMatchers
}
function parse255 (str) {
const int = parseInt(str, 10)
if (int < 0) {
return 0
}
if (int > 255) {
return 255
}
return int
}
function parse360 (str) {
const int = parseFloat(str)
return (((int % 360) + 360) % 360) / 360
}
function parse1 (str) {
const num = parseFloat(str)
if (num < 0) {
return 0
}
if (num > 1) {
return 255
}
return Math.round(num * 255)
}
function parsePercentage (str) {
// parseFloat conveniently ignores the final %
const int = parseFloat(str)
if (int < 0) {
return 0
}
if (int > 100) {
return 1
}
return int / 100
}
const names = {
transparent: 0x00000000,
// http://www.w3.org/TR/css3-color/#svg-color
aliceblue: 0xf0f8ffff,
antiquewhite: 0xfaebd7ff,
aqua: 0x00ffffff,
aquamarine: 0x7fffd4ff,
azure: 0xf0ffffff,
beige: 0xf5f5dcff,
bisque: 0xffe4c4ff,
black: 0x000000ff,
blanchedalmond: 0xffebcdff,
blue: 0x0000ffff,
blueviolet: 0x8a2be2ff,
brown: 0xa52a2aff,
burlywood: 0xdeb887ff,
burntsienna: 0xea7e5dff,
cadetblue: 0x5f9ea0ff,
chartreuse: 0x7fff00ff,
chocolate: 0xd2691eff,
coral: 0xff7f50ff,
cornflowerblue: 0x6495edff,
cornsilk: 0xfff8dcff,
crimson: 0xdc143cff,
cyan: 0x00ffffff,
darkblue: 0x00008bff,
darkcyan: 0x008b8bff,
darkgoldenrod: 0xb8860bff,
darkgray: 0xa9a9a9ff,
darkgreen: 0x006400ff,
darkgrey: 0xa9a9a9ff,
darkkhaki: 0xbdb76bff,
darkmagenta: 0x8b008bff,
darkolivegreen: 0x556b2fff,
darkorange: 0xff8c00ff,
darkorchid: 0x9932ccff,
darkred: 0x8b0000ff,
darksalmon: 0xe9967aff,
darkseagreen: 0x8fbc8fff,
darkslateblue: 0x483d8bff,
darkslategray: 0x2f4f4fff,
darkslategrey: 0x2f4f4fff,
darkturquoise: 0x00ced1ff,
darkviolet: 0x9400d3ff,
deeppink: 0xff1493ff,
deepskyblue: 0x00bfffff,
dimgray: 0x696969ff,
dimgrey: 0x696969ff,
dodgerblue: 0x1e90ffff,
firebrick: 0xb22222ff,
floralwhite: 0xfffaf0ff,
forestgreen: 0x228b22ff,
fuchsia: 0xff00ffff,
gainsboro: 0xdcdcdcff,
ghostwhite: 0xf8f8ffff,
gold: 0xffd700ff,
goldenrod: 0xdaa520ff,
gray: 0x808080ff,
green: 0x008000ff,
greenyellow: 0xadff2fff,
grey: 0x808080ff,
honeydew: 0xf0fff0ff,
hotpink: 0xff69b4ff,
indianred: 0xcd5c5cff,
indigo: 0x4b0082ff,
ivory: 0xfffff0ff,
khaki: 0xf0e68cff,
lavender: 0xe6e6faff,
lavenderblush: 0xfff0f5ff,
lawngreen: 0x7cfc00ff,
lemonchiffon: 0xfffacdff,
lightblue: 0xadd8e6ff,
lightcoral: 0xf08080ff,
lightcyan: 0xe0ffffff,
lightgoldenrodyellow: 0xfafad2ff,
lightgray: 0xd3d3d3ff,
lightgreen: 0x90ee90ff,
lightgrey: 0xd3d3d3ff,
lightpink: 0xffb6c1ff,
lightsalmon: 0xffa07aff,
lightseagreen: 0x20b2aaff,
lightskyblue: 0x87cefaff,
lightslategray: 0x778899ff,
lightslategrey: 0x778899ff,
lightsteelblue: 0xb0c4deff,
lightyellow: 0xffffe0ff,
lime: 0x00ff00ff,
limegreen: 0x32cd32ff,
linen: 0xfaf0e6ff,
magenta: 0xff00ffff,
maroon: 0x800000ff,
mediumaquamarine: 0x66cdaaff,
mediumblue: 0x0000cdff,
mediumorchid: 0xba55d3ff,
mediumpurple: 0x9370dbff,
mediumseagreen: 0x3cb371ff,
mediumslateblue: 0x7b68eeff,
mediumspringgreen: 0x00fa9aff,
mediumturquoise: 0x48d1ccff,
mediumvioletred: 0xc71585ff,
midnightblue: 0x191970ff,
mintcream: 0xf5fffaff,
mistyrose: 0xffe4e1ff,
moccasin: 0xffe4b5ff,
navajowhite: 0xffdeadff,
navy: 0x000080ff,
oldlace: 0xfdf5e6ff,
olive: 0x808000ff,
olivedrab: 0x6b8e23ff,
orange: 0xffa500ff,
orangered: 0xff4500ff,
orchid: 0xda70d6ff,
palegoldenrod: 0xeee8aaff,
palegreen: 0x98fb98ff,
paleturquoise: 0xafeeeeff,
palevioletred: 0xdb7093ff,
papayawhip: 0xffefd5ff,
peachpuff: 0xffdab9ff,
peru: 0xcd853fff,
pink: 0xffc0cbff,
plum: 0xdda0ddff,
powderblue: 0xb0e0e6ff,
purple: 0x800080ff,
rebeccapurple: 0x663399ff,
red: 0xff0000ff,
rosybrown: 0xbc8f8fff,
royalblue: 0x4169e1ff,
saddlebrown: 0x8b4513ff,
salmon: 0xfa8072ff,
sandybrown: 0xf4a460ff,
seagreen: 0x2e8b57ff,
seashell: 0xfff5eeff,
sienna: 0xa0522dff,
silver: 0xc0c0c0ff,
skyblue: 0x87ceebff,
slateblue: 0x6a5acdff,
slategray: 0x708090ff,
slategrey: 0x708090ff,
snow: 0xfffafaff,
springgreen: 0x00ff7fff,
steelblue: 0x4682b4ff,
tan: 0xd2b48cff,
teal: 0x008080ff,
thistle: 0xd8bfd8ff,
tomato: 0xff6347ff,
turquoise: 0x40e0d0ff,
violet: 0xee82eeff,
wheat: 0xf5deb3ff,
white: 0xffffffff,
whitesmoke: 0xf5f5f5ff,
yellow: 0xffff00ff,
yellowgreen: 0x9acd32ff
}
module.exports = normalizeColor

View File

@ -0,0 +1,15 @@
{
"name":"<%= projectName %>",
"main": "./bin/crna-entry.js",
"dependencies": {
"@tarojs/components-rn": "^<%= version %>",
"@tarojs/taro-rn": "^<%= version %>",
"@tarojs/taro-router-rn": "^<%= version %>",
"@tarojs/taro-redux-rn": "^<%= version %>",
"expo": "27.0.1",
"react": "16.3.1",
"react-native": "0.55.2",
"redux": "^4.0.0",
"tslib": "^1.8.0"
}
}

View File

@ -0,0 +1,109 @@
const path = require('path')
const fs = require('fs-extra')
const postcss = require('postcss')
const chalk = require('chalk')
const pxtransform = require('postcss-pxtransform')
const transformCSS = require('css-to-react-native-transform').default
const {StyleSheetValidation} = require('./StyleSheet/index')
const Util = require('../util')
const npmProcess = require('../util/npm')
const DEVICE_RATIO = 'deviceRatio'
/**
* @description 读取 css/scss/less 文件预处理后返回 css string
* @param {string}filePath
* @param {object} pluginsConfig
* @returns {*}
*/
function loadStyle ({filePath, pluginsConfig}) {
const fileExt = path.extname(filePath)
const pluginName = Util.FILE_PROCESSOR_MAP[fileExt]
if (pluginName) {
return npmProcess.callPlugin(pluginName, null, filePath, pluginsConfig[pluginName] || {})
.then((item) => {
return {
css: item.css.toString(),
filePath
}
}).catch((e) => {
Util.printLog(Util.pocessTypeEnum.ERROR, '样式预处理', filePath)
console.log(e.stack)
})
}
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (err, content) => {
if (err) {
return reject(err)
}
resolve({
css: content,
filePath
})
})
})
}
/**
* @description 传入 css string 返回 postCSS 处理后的 css string
* @param {string} css
* @param {string} filePath
* @param {object} projectConfig
* @returns {Function | any}
*/
function postCSS ({css, filePath, projectConfig}) {
let pxTransformConfig = {
designWidth: projectConfig.designWidth || 750
}
if (projectConfig.hasOwnProperty(DEVICE_RATIO)) {
pxTransformConfig[DEVICE_RATIO] = projectConfig.deviceRatio
}
return postcss(pxtransform({
platform: 'rn',
...pxTransformConfig
}))
.process(css, {from: filePath})
.then((result) => {
return {
css: result.css,
filePath
}
})
}
function getStyleObject ({css, filePath}) {
var styleObject = {}
try {
styleObject = transformCSS(css)
} catch (err) {
Util.printLog(Util.pocessTypeEnum.WARNING, 'css-to-react-native 报错', filePath)
console.log(chalk.red(err.stack))
}
return styleObject
}
function validateStyle ({styleObject, filePath}) {
for (let name in styleObject) {
try {
StyleSheetValidation.validateStyle(name, styleObject)
} catch (err) {
Util.printLog(Util.pocessTypeEnum.WARNING, '样式不支持', filePath)
console.log(chalk.red(err.message))
}
}
}
function writeStyleFile ({css, tempFilePath}) {
const fileContent = `import { StyleSheet } from 'react-native'\n\nexport default StyleSheet.create(${css})`
fs.ensureDirSync(path.dirname(tempFilePath))
fs.writeFileSync(tempFilePath, fileContent)
Util.printLog(Util.pocessTypeEnum.GENERATE, '生成文件', tempFilePath)
}
module.exports = {
loadStyle,
postCSS,
getStyleObject,
validateStyle,
writeStyleFile
}

View File

@ -0,0 +1,489 @@
const path = require('path')
const babel = require('babel-core')
const traverse = require('babel-traverse').default
const t = require('babel-types')
const _ = require('lodash')
const generate = require('babel-generator').default
const template = require('babel-template')
const wxTransformer = require('../../../taro-transformer-wx/lib/src/index.js').default
const Util = require('../util')
const babylonConfig = require('../config/babylon')
const {source: toAst} = require('../util/ast_convert')
const reactImportDefaultName = 'React'
let taroImportDefaultName // import default from @tarojs/taro
let componentClassName // get app.js class name
const providerComponentName = 'Provider'
const setStoreFuncName = 'setStore'
const routerImportDefaultName = 'TaroRouter'
const DEVICE_RATIO = 'deviceRatio'
const taroApis = [
'getEnv',
'ENV_TYPE',
'eventCenter',
'Events',
'internal_safe_get',
'internal_dynamic_recursive'
]
const PACKAGES = {
'@tarojs/taro': '@tarojs/taro',
'@tarojs/taro-rn': '@tarojs/taro-rn',
'@tarojs/taro-router-rn': '@tarojs/taro-router-rn',
'@tarojs/redux': '@tarojs/redux',
'@tarojs/components': '@tarojs/components',
'@tarojs/components-rn': '@tarojs/components-rn',
'react': 'react',
'react-native': 'react-native',
'react-redux-rn': '@tarojs/taro-redux-rn',
'@tarojs/mobx': '@tarojs/mobx',
'@tarojs/mobx-rn': '@tarojs/mobx-rn'
}
function getInitPxTransformNode (projectConfig) {
let pxTransformConfig = {designWidth: projectConfig.designWidth || 750}
if (projectConfig.hasOwnProperty(DEVICE_RATIO)) {
pxTransformConfig[DEVICE_RATIO] = projectConfig.deviceRatio
}
const initPxTransformNode = toAst(`Taro.initPxTransform(${JSON.stringify(pxTransformConfig)})`)
return initPxTransformNode
}
function getClassPropertyVisitor ({filePath, pages, iconPaths, isEntryFile}) {
return (astPath) => {
const node = astPath.node
const key = node.key
const value = node.value
if (key.name !== 'config' || !t.isObjectExpression(value)) return
// 入口文件的 config ,与页面的分开处理
if (isEntryFile) {
// 读取 config 配置
astPath.traverse({
ObjectProperty (astPath) {
const node = astPath.node
const key = node.key
const value = node.value
// if (key.name !== 'pages' || !t.isArrayExpression(value)) return
if (key.name === 'pages' && t.isArrayExpression(value)) {
// 分包
let root = ''
const rootNode = astPath.parent.properties.find(v => {
return v.key.name === 'root'
})
root = rootNode ? rootNode.value.value : ''
value.elements.forEach(v => {
const pagePath = `${root}/${v.value}`.replace(/\/{2,}/g, '/')
pages.push(pagePath.replace(/^\//, ''))
})
astPath.remove()
}
// window
if (key.name === 'window' && t.isObjectExpression(value)) {
return
}
if (key.name === 'tabBar' && t.isObjectExpression(value)) {
astPath.traverse({
ObjectProperty (astPath) {
let node = astPath.node
let value = node.value.value
if (node.key.name === 'iconPath' ||
node.key.value === 'iconPath' ||
node.key.name === 'selectedIconPath' ||
node.key.value === 'selectedIconPath'
) {
if (typeof value !== 'string') return
let iconName = _.camelCase(value.split('/'))
if (iconPaths.indexOf(value) === -1) {
iconPaths.push(value)
}
astPath.insertAfter(t.objectProperty(
t.identifier(node.key.name || node.key.value),
t.identifier(iconName)
))
astPath.remove()
}
}
})
}
}
})
}
astPath.node.static = 'true'
}
}
function getJSAst (code, filePath) {
return wxTransformer({
code,
sourcePath: filePath,
isNormal: true,
isTyped: Util.REG_TYPESCRIPT.test(filePath),
adapter: 'rn'
}).ast
}
/**
* TS 编译器会把 class property 移到构造器
* 而小程序要求 `config` 和所有函数在初始化(after new Class)之后就收集到所有的函数和 config 信息
* 所以当如构造器里有 this.func = () => {...} 的形式就给他转换成普通的 classProperty function
* 如果有 config 就给他还原
*/
function resetTSClassProperty (body) {
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
for (const statement of _.cloneDeep(method.body.body)) {
if (t.isExpressionStatement(statement) && t.isAssignmentExpression(statement.expression)) {
const expr = statement.expression
const {left, right} = expr
if (
t.isMemberExpression(left) &&
t.isThisExpression(left.object) &&
t.isIdentifier(left.property)
) {
if (
(t.isArrowFunctionExpression(right) || t.isFunctionExpression(right)) ||
(left.property.name === 'config' && t.isObjectExpression(right))
) {
body.push(
t.classProperty(left.property, right)
)
_.remove(method.body.body, statement)
}
}
}
}
}
}
}
const ClassDeclarationOrExpression = {
enter (astPath) {
const node = astPath.node
if (!node.superClass) return
if (
node.superClass.type === 'MemberExpression' &&
node.superClass.object.name === taroImportDefaultName
) {
node.superClass.object.name = taroImportDefaultName
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
componentClassName = renameComponentClassName
astPath.replaceWith(
t.classDeclaration(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
} else {
componentClassName = node.id.name
}
} else if (node.superClass.name === 'Component') {
resetTSClassProperty(node.body.body)
if (node.id === null) {
const renameComponentClassName = '_TaroComponentClass'
componentClassName = renameComponentClassName
astPath.replaceWith(
t.classDeclaration(
t.identifier(renameComponentClassName),
node.superClass,
node.body,
node.decorators || []
)
)
} else {
componentClassName = node.id.name
}
}
}
}
function parseJSCode ({code, filePath, isEntryFile, projectConfig}) {
let ast
try {
ast = getJSAst(code, filePath)
} catch (e) {
throw e
}
const styleFiles = []
let pages = [] // app.js 里面的config 配置里面的 pages
let iconPaths = [] // app.js 里面的config 配置里面的需要引入的 iconPath
let hasAddReactImportDefaultName = false
let providorImportName
let storeName
let hasAppExportDefault
let classRenderReturnJSX
traverse(ast, {
ClassExpression: ClassDeclarationOrExpression,
ClassDeclaration: ClassDeclarationOrExpression,
ImportDeclaration (astPath) {
const node = astPath.node
const source = node.source
let value = source.value
const valueExtname = path.extname(value)
const specifiers = node.specifiers
const pathAlias = projectConfig.alias || {}
if (Util.isAliasPath(value, pathAlias)) {
source.value = value = Util.replaceAliasPath(filePath, value, pathAlias)
}
// 引入的包为 npm 包
if (!Util.isNpmPkg(value)) {
// import 样式处理
if (Util.REG_STYLE.test(valueExtname)) {
const stylePath = path.resolve(path.dirname(filePath), value)
if (styleFiles.indexOf(stylePath) < 0) {
styleFiles.push(stylePath)
}
}
return
}
if (value === PACKAGES['@tarojs/taro']) {
let specifier = specifiers.find(item => item.type === 'ImportDefaultSpecifier')
if (specifier) {
hasAddReactImportDefaultName = true
taroImportDefaultName = specifier.local.name
specifier.local.name = reactImportDefaultName
} else if (!hasAddReactImportDefaultName) {
hasAddReactImportDefaultName = true
node.specifiers.unshift(
t.importDefaultSpecifier(t.identifier(reactImportDefaultName))
)
}
// 删除从@tarojs/taro引入的 React
specifiers.forEach((item, index) => {
if (item.type === 'ImportDefaultSpecifier') {
specifiers.splice(index, 1)
}
})
const taroApisSpecifiers = []
specifiers.forEach((item, index) => {
if (item.imported && taroApis.indexOf(item.imported.name) >= 0) {
taroApisSpecifiers.push(t.importSpecifier(t.identifier(item.local.name), t.identifier(item.imported.name)))
specifiers.splice(index, 1)
}
})
source.value = PACKAGES['@tarojs/taro-rn']
// insert React
astPath.insertBefore(template(`import React from 'react'`, babylonConfig)())
if (taroApisSpecifiers.length) {
astPath.insertBefore(t.importDeclaration(taroApisSpecifiers, t.stringLiteral(PACKAGES['@tarojs/taro-rn'])))
}
if (!specifiers.length) {
astPath.remove()
}
} else if (value === PACKAGES['@tarojs/redux']) {
const specifier = specifiers.find(item => {
return t.isImportSpecifier(item) && item.imported.name === providerComponentName
})
if (specifier) {
providorImportName = specifier.local.name
} else {
providorImportName = providerComponentName
specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
}
source.value = PACKAGES['react-redux-rn']
} else if (value === PACKAGES['@tarojs/mobx']) {
const specifier = specifiers.find(item => {
return t.isImportSpecifier(item) && item.imported.name === providerComponentName
})
if (specifier) {
providorImportName = specifier.local.name
} else {
providorImportName = providerComponentName
specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
}
source.value = PACKAGES['@tarojs/mobx-rn']
} else if (value === PACKAGES['@tarojs/components']) {
source.value = PACKAGES['@tarojs/components-rn']
}
},
ClassProperty: getClassPropertyVisitor({filePath, pages, iconPaths, isEntryFile}),
// 获取 classRenderReturnJSX
ClassMethod (astPath) {
let node = astPath.node
const key = node.key
if (key.name !== 'render' || !isEntryFile) return
astPath.traverse({
BlockStatement (astPath) {
if (astPath.parent === node) {
node = astPath.node
astPath.traverse({
ReturnStatement (astPath) {
if (astPath.parent === node) {
astPath.traverse({
JSXElement (astPath) {
classRenderReturnJSX = generate(astPath.node).code
}
})
}
}
})
}
}
})
},
ExportDefaultDeclaration () {
if (isEntryFile) {
hasAppExportDefault = true
}
},
JSXOpeningElement: {
enter (astPath) {
if (astPath.node.name.name === 'Provider') {
for (let v of astPath.node.attributes) {
if (v.name.name !== 'store') continue
storeName = v.value.expression.name
break
}
}
}
},
Program: {
exit (astPath) {
const node = astPath.node
astPath.traverse({
ClassMethod (astPath) {
const node = astPath.node
const key = node.key
if (key.name !== 'render' || !isEntryFile) return
let funcBody = classRenderReturnJSX
if (pages.length > 0) {
funcBody = `<RootStack/>`
}
if (providerComponentName && storeName) {
// 使用redux 或 mobx
funcBody = `
<${providorImportName} store={${storeName}}>
${funcBody}
</${providorImportName}>`
}
node.body = template(`{return (${funcBody});}`, babylonConfig)()
},
CallExpression (astPath) {
const node = astPath.node
const callee = node.callee
const calleeName = callee.name
const parentPath = astPath.parentPath
if (t.isMemberExpression(callee)) {
if (callee.object.name === taroImportDefaultName && callee.property.name === 'render') {
astPath.remove()
}
} else {
if (calleeName === setStoreFuncName) {
if (parentPath.isAssignmentExpression() ||
parentPath.isExpressionStatement() ||
parentPath.isVariableDeclarator()) {
parentPath.remove()
}
}
}
}
})
// import Taro from @tarojs/taro-rn
if (taroImportDefaultName) {
const importTaro = template(
`import ${taroImportDefaultName} from '${PACKAGES['@tarojs/taro-rn']}'`,
babylonConfig
)()
node.body.unshift(importTaro)
}
if (isEntryFile) {
// 注入 import page from 'XXX'
pages.forEach(item => {
const pagePath = item.startsWith('/') ? item : `/${item}`
const screenName = _.camelCase(pagePath.split('/'), {pascalCase: true})
const importScreen = template(
`import ${screenName} from '.${pagePath}'`,
babylonConfig
)()
node.body.unshift(importScreen)
})
iconPaths.forEach(item => {
const iconPath = item.startsWith('/') ? item : `/${item}`
const iconName = _.camelCase(iconPath.split('/'))
const importIcon = template(
`import ${iconName} from '.${iconPath}'`,
babylonConfig
)()
node.body.unshift(importIcon)
})
// Taro.initRouter 生成 RootStack
const routerPages = pages
.map(item => {
const pagePath = item.startsWith('/') ? item : `/${item}`
const screenName = _.camelCase(pagePath.split('/'), {pascalCase: true})
return `['${item}',${screenName}]`
})
.join(',')
node.body.push(template(
`const RootStack = ${routerImportDefaultName}.initRouter(
[${routerPages}],
${taroImportDefaultName},
App.config
)`,
babylonConfig
)())
// initNativeApi
const initNativeApi = template(
`${taroImportDefaultName}.initNativeApi(${taroImportDefaultName})`,
babylonConfig
)()
node.body.push(initNativeApi)
// import @tarojs/taro-router-rn
const importTaroRouter = template(
`import TaroRouter from '${PACKAGES['@tarojs/taro-router-rn']}'`,
babylonConfig
)()
node.body.unshift(importTaroRouter)
// Taro.initPxTransform
node.body.push(getInitPxTransformNode(projectConfig))
// export default App
if (!hasAppExportDefault) {
const appExportDefault = template(
`export default ${componentClassName}`,
babylonConfig
)()
node.body.push(appExportDefault)
}
}
}
}
})
try {
const constantsReplaceList = Object.assign({
'process.env.TARO_ENV': Util.BUILD_TYPES.RN
}, Util.generateEnvList(projectConfig.env || {}), Util.generateConstantsList(projectConfig.defineConstants || {}))
// TODO 使用 babel-plugin-transform-jsx-to-stylesheet 处理 JSX 里面样式的处理,删除无效的样式引入待优化
ast = babel.transformFromAst(ast, code, {
plugins: [
[require('babel-plugin-transform-jsx-to-stylesheet'), {filePath}],
require('babel-plugin-transform-decorators-legacy').default,
require('babel-plugin-transform-class-properties'),
[require('babel-plugin-danger-remove-unused-import'), {ignore: ['@tarojs/taro', 'react', 'react-native', 'nervjs']}],
[require('babel-plugin-transform-define').default, constantsReplaceList]
]
}).ast
} catch (e) {
throw e
}
return {
code: unescape(generate(ast).code.replace(/\\u/g, '%u')),
styleFiles
}
}
module.exports = {transformJSCode: parseJSCode}

View File

@ -0,0 +1,431 @@
const fs = require('fs-extra')
const path = require('path')
const chokidar = require('chokidar')
const chalk = require('chalk')
const wxTransformer = require('../../taro-transformer-wx/lib/src/index.js').default
const traverse = require('babel-traverse').default
const t = require('babel-types')
const generate = require('babel-generator').default
const _ = require('lodash')
const { processFiles } = require('./h5')
const npmProcess = require('./util/npm')
const CONFIG = require('./config')
const {
resolveScriptPath,
resolveStylePath,
printLog,
pocessTypeEnum,
PROJECT_CONFIG,
BUILD_TYPES,
REG_STYLE,
REG_TYPESCRIPT,
cssImports
} = require('./util')
const appPath = process.cwd()
const configDir = path.join(appPath, PROJECT_CONFIG)
const projectConfig = require(configDir)(_.merge)
const sourceDirName = projectConfig.sourceRoot || CONFIG.SOURCE_DIR
let outputDirName = projectConfig.outputRoot || CONFIG.OUTPUT_DIR
const sourceDir = path.join(appPath, sourceDirName)
const entryFilePath = resolveScriptPath(path.join(sourceDir, 'index'))
const entryFileName = path.basename(entryFilePath)
const tempDir = '.temp'
const tempPath = path.join(appPath, tempDir)
const weappOutputName = 'weapp'
const h5OutputName = 'h5'
async function buildH5Script () {
const h5Config = projectConfig.h5 || {}
const entryFile = path.basename(entryFileName, path.extname(entryFileName)) + '.js'
outputDirName = `${outputDirName}/${h5OutputName}`
h5Config.env = projectConfig.env
h5Config.defineConstants = projectConfig.defineConstants
h5Config.plugins = projectConfig.plugins
h5Config.designWidth = projectConfig.designWidth
if (projectConfig.deviceRatio) {
h5Config.deviceRatio = projectConfig.deviceRatio
}
h5Config.sourceRoot = sourceDirName
h5Config.outputRoot = outputDirName
h5Config.entry = Object.assign({
app: [path.join(tempPath, entryFile)]
}, h5Config.entry)
h5Config.isWatch = false
const webpackRunner = await npmProcess.getNpmPkg('@tarojs/webpack-runner')
webpackRunner(h5Config)
}
async function buildH5Lib () {
try {
const outputDir = path.join(appPath, outputDirName, h5OutputName)
const tempEntryFilePath = resolveScriptPath(path.join(tempPath, 'index'))
const outputEntryFilePath = path.join(outputDir, path.basename(tempEntryFilePath))
const code = fs.readFileSync(tempEntryFilePath).toString()
const transformResult = wxTransformer({
code,
sourcePath: tempEntryFilePath,
outputPath: outputEntryFilePath,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(tempEntryFilePath)
})
const { styleFiles, components, code: generateCode } = parseEntryAst(transformResult.ast, tempEntryFilePath)
const relativePath = path.relative(appPath, tempEntryFilePath)
printLog(pocessTypeEnum.COPY, '发现文件', relativePath)
fs.ensureDirSync(path.dirname(outputEntryFilePath))
fs.writeFileSync(outputEntryFilePath, generateCode)
if (components.length) {
components.forEach(item => {
copyFileToDist(item.path, tempPath, outputDir)
})
analyzeFiles(components.map(item => item.path), tempPath, outputDir)
}
if (styleFiles.length) {
styleFiles.forEach(item => {
copyFileToDist(item, tempPath, path.join(appPath, outputDirName))
})
analyzeStyleFilesImport(styleFiles, tempPath, path.join(appPath, outputDirName))
}
} catch (err) {
console.log(err)
}
}
function copyFileToDist (filePath, sourceDir, outputDir) {
if (!path.isAbsolute(filePath)) {
return
}
const dirname = path.dirname(filePath)
const distDirname = dirname.replace(sourceDir, outputDir)
const relativePath = path.relative(appPath, filePath)
printLog(pocessTypeEnum.COPY, '发现文件', relativePath)
fs.ensureDirSync(distDirname)
fs.copyFileSync(filePath, path.format({
dir: distDirname,
base: path.basename(filePath)
}))
}
function parseEntryAst (ast, relativeFile) {
const styleFiles = []
const components = []
const importExportName = []
let exportDefaultName = null
traverse(ast, {
ExportNamedDeclaration (astPath) {
const node = astPath.node
const specifiers = node.specifiers
const source = node.source
if (source && source.type === 'StringLiteral') {
specifiers.forEach(specifier => {
const exported = specifier.exported
components.push({
name: exported.name,
path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
})
})
} else {
specifiers.forEach(specifier => {
const exported = specifier.exported
importExportName.push(exported.name)
})
}
},
ExportDefaultDeclaration (astPath) {
const node = astPath.node
const declaration = node.declaration
if (t.isIdentifier(declaration)) {
exportDefaultName = declaration.name
}
},
Program: {
exit (astPath) {
astPath.traverse({
ImportDeclaration (astPath) {
const node = astPath.node
const specifiers = node.specifiers
const source = node.source
const value = source.value
const valueExtname = path.extname(value)
if (REG_STYLE.test(valueExtname)) {
const stylePath = path.resolve(path.dirname(relativeFile), value)
if (styleFiles.indexOf(stylePath) < 0) {
styleFiles.push(stylePath)
}
astPath.remove()
} else {
if (importExportName.length) {
importExportName.forEach(nameItem => {
specifiers.forEach(specifier => {
const local = specifier.local
if (local.name === nameItem) {
components.push({
name: local.name,
path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
})
}
})
})
}
if (exportDefaultName != null) {
specifiers.forEach(specifier => {
const local = specifier.local
if (local.name === exportDefaultName) {
components.push({
name: local.name,
path: resolveScriptPath(path.resolve(path.dirname(relativeFile), source.value))
})
}
})
}
}
}
})
}
}
})
const code = generate(ast).code
return {
code,
styleFiles,
components
}
}
function analyzeFiles (files, sourceDir, outputDir) {
const { parseAst } = require('./weapp')
files.forEach(file => {
if (fs.existsSync(file)) {
const code = fs.readFileSync(file).toString()
const transformResult = wxTransformer({
code,
sourcePath: file,
outputPath: file,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(file)
})
const {
styleFiles,
scriptFiles,
jsonFiles,
mediaFiles
} = parseAst('NORMAL', transformResult.ast, [], file, file, true)
const resFiles = styleFiles.concat(scriptFiles, jsonFiles, mediaFiles)
if (resFiles.length) {
resFiles.forEach(item => {
copyFileToDist(item, sourceDir, outputDir)
})
}
if (scriptFiles.length) {
analyzeFiles(scriptFiles, sourceDir, outputDir)
}
if (styleFiles.length) {
analyzeStyleFilesImport(styleFiles, sourceDir, outputDir)
}
}
})
}
function analyzeStyleFilesImport (styleFiles, sourceDir, outputDir) {
styleFiles.forEach(item => {
if (!fs.existsSync(item)) {
return
}
let content = fs.readFileSync(item).toString()
content = content.replace(/(?:@import\s+)?\burl\s*\(\s*("(?:[^\\"\r\n\f]|\\[\s\S])*"|'(?:[^\\'\n\r\f]|\\[\s\S])*'|[^)}\s]+)\s*\)(\s*;?)/g, (m, $1) => {
if ($1) {
let filePath = $1.replace(/'?"?/g, '')
if (filePath.indexOf('.') === 0) {
filePath = path.resolve(path.dirname(item), filePath)
copyFileToDist(filePath, sourceDir, outputDir)
}
}
return m
})
let imports = cssImports(content)
if (imports.length > 0) {
imports = imports.map(importItem => {
const filePath = resolveStylePath(path.resolve(path.dirname(item), importItem))
copyFileToDist(filePath, sourceDir, outputDir)
return filePath
})
analyzeStyleFilesImport(imports, sourceDir, outputDir)
}
})
}
async function buildForWeapp () {
console.log()
console.log(chalk.green('开始编译小程序端组件库!'))
if (!fs.existsSync(entryFilePath)) {
console.log(chalk.red('入口文件不存在,请检查!'))
return
}
try {
const { compileDepStyles } = require('./weapp')
const outputDir = path.join(appPath, outputDirName, weappOutputName)
const outputEntryFilePath = path.join(outputDir, entryFileName)
const code = fs.readFileSync(entryFilePath).toString()
const transformResult = wxTransformer({
code,
sourcePath: entryFilePath,
outputPath: outputEntryFilePath,
isNormal: true,
isTyped: REG_TYPESCRIPT.test(entryFilePath)
})
const { styleFiles, components } = parseEntryAst(transformResult.ast, entryFilePath)
if (styleFiles.length) {
const outputStylePath = path.join(outputDir, 'css', 'index.css')
await compileDepStyles(outputStylePath, styleFiles, false)
}
const relativePath = path.relative(appPath, entryFilePath)
printLog(pocessTypeEnum.COPY, '发现文件', relativePath)
fs.ensureDirSync(path.dirname(outputEntryFilePath))
fs.copyFileSync(entryFilePath, path.format({
dir: path.dirname(outputEntryFilePath),
base: path.basename(outputEntryFilePath)
}))
if (components.length) {
components.forEach(item => {
copyFileToDist(item.path, sourceDir, outputDir)
})
analyzeFiles(components.map(item => item.path), sourceDir, outputDir)
}
} catch (err) {
console.log(err)
}
}
async function buildForH5 (buildConfig) {
const { buildTemp } = require('./h5')
console.log()
console.log(chalk.green('开始编译 H5 端组件库!'))
await buildTemp(buildConfig)
if (process.env.TARO_BUILD_TYPE === 'script') {
await buildH5Script()
} else {
await buildH5Lib()
}
}
function buildEntry () {
const content = `if (process.env.TARO_ENV === '${BUILD_TYPES.H5}') {
module.exports = require('./${h5OutputName}/index')
module.exports.default = module.exports
} else {
module.exports = require('./${weappOutputName}/index')
module.exports.default = module.exports
}`
const outputDir = path.join(appPath, outputDirName)
fs.writeFileSync(path.join(outputDir, 'index.js'), content)
}
function watchFiles () {
console.log('\n', chalk.gray('监听文件修改中...'), '\n')
const watchList = [sourceDir]
const uiConfig = projectConfig.ui || {}
const { extraWatchFiles = [] } = uiConfig
extraWatchFiles.forEach(item => {
watchList.push(path.join(appPath, item.path))
if (typeof item.handler === 'function') item.callback = item.handler({ buildH5Script })
})
const watcher = chokidar.watch(watchList, {
ignored: /(^|[/\\])\../,
ignoreInitial: true
})
function syncWeappFile (filePath) {
const outputDir = path.join(appPath, outputDirName, weappOutputName)
copyFileToDist(filePath, sourceDir, outputDir)
// 依赖分析
const extname = path.extname(filePath)
if (REG_STYLE.test(extname)) {
analyzeStyleFilesImport([filePath], sourceDir, outputDir)
} else {
analyzeFiles([filePath], sourceDir, outputDir)
}
}
function syncH5File (filePath) {
const outputDir = path.join(appPath, outputDirName, h5OutputName)
const fileTempPath = filePath.replace(sourceDir, tempPath)
processFiles(filePath)
if (process.env.TARO_BUILD_TYPE === 'script') {
buildH5Script()
} else {
copyFileToDist(fileTempPath, tempPath, outputDir)
// 依赖分析
const extname = path.extname(filePath)
if (REG_STYLE.test(extname)) {
analyzeStyleFilesImport([fileTempPath], tempPath, outputDir)
} else {
analyzeFiles([fileTempPath], tempPath, outputDir)
}
}
}
function handleChange (filePath, type, tips) {
const relativePath = path.relative(appPath, filePath)
printLog(type, tips, relativePath)
let processed = false
extraWatchFiles.forEach(item => {
if (filePath.indexOf(item.path.substr(2)) < 0) return
if (typeof item.callback === 'function') {
item.callback()
processed = true
}
})
if (processed) return
try {
syncWeappFile(filePath)
syncH5File(filePath)
} catch (err) {
console.log(err)
}
}
watcher
.on('add', filePath => handleChange(filePath, pocessTypeEnum.CREATE, '添加文件'))
.on('change', filePath => handleChange(filePath, pocessTypeEnum.MODIFY, '文件变动'))
.on('unlink', filePath => {
for (const path in extraWatchFiles) {
if (filePath.indexOf(path.substr(2)) > -1) return
}
const relativePath = path.relative(appPath, filePath)
printLog(pocessTypeEnum.UNLINK, '删除文件', relativePath)
const weappOutputPath = path.join(appPath, outputDirName, weappOutputName)
const h5OutputPath = path.join(appPath, outputDirName, h5OutputName)
const fileTempPath = filePath.replace(sourceDir, tempPath)
const fileWeappPath = filePath.replace(sourceDir, weappOutputPath)
const fileH5Path = filePath.replace(sourceDir, h5OutputPath)
fs.existsSync(fileTempPath) && fs.unlinkSync(fileTempPath)
fs.existsSync(fileWeappPath) && fs.unlinkSync(fileWeappPath)
fs.existsSync(fileH5Path) && fs.unlinkSync(fileH5Path)
})
}
async function build ({ watch }) {
buildEntry()
await buildForWeapp()
await buildForH5()
if (watch) {
watchFiles()
}
}
module.exports = {
build
}

View File

@ -0,0 +1,79 @@
const t = require('babel-types')
const babylonConfig = require('../config/babylon')
const template = require('babel-template')
const generate = require('better-babel-generator').default
function convertObjectToAstExpression (obj) {
const objArr = Object.keys(obj).map(key => {
const value = obj[key]
if (typeof value === 'string') {
return t.objectProperty(t.stringLiteral(key), t.stringLiteral(value))
}
if (typeof value === 'number') {
return t.objectProperty(t.stringLiteral(key), t.numericLiteral(value))
}
if (typeof value === 'boolean') {
return t.objectProperty(t.stringLiteral(key), t.booleanLiteral(value))
}
if (Array.isArray(value)) {
return t.objectProperty(t.stringLiteral(key), t.arrayExpression(convertArrayToAstExpression(value)))
}
if (value == null) {
return t.objectProperty(t.stringLiteral(key), t.nullLiteral())
}
if (typeof value === 'object') {
return t.objectProperty(t.stringLiteral(key), t.objectExpression(convertObjectToAstExpression(value)))
}
})
return objArr
}
// 最低限度的转义: https://github.com/mathiasbynens/jsesc#minimal
function generateMinimalEscapeCode (ast) {
return generate(ast, {
jsescOption: {
minimal: true
}
}).code
}
function convertArrayToAstExpression (arr) {
return arr.map(value => {
if (typeof value === 'string') {
return t.stringLiteral(value)
}
if (typeof value === 'number') {
return t.numericLiteral(value)
}
if (typeof value === 'boolean') {
return t.booleanLiteral(value)
}
if (Array.isArray(value)) {
return convertArrayToAstExpression(value)
}
if (value == null) {
return t.nullLiteral()
}
if (typeof value === 'object') {
return t.objectExpression(convertObjectToAstExpression(value))
}
})
}
function convertSourceStringToAstExpression (str, opts = {}) {
return template(str, Object.assign({}, babylonConfig, opts))()
}
const getObjKey = (node) => {
if (t.isIdentifier(node)) {
return node.name
} else {
return node.value
}
}
exports.obj = convertObjectToAstExpression
exports.array = convertArrayToAstExpression
exports.source = convertSourceStringToAstExpression
exports.getObjKey = getObjKey
exports.generateMinimalEscapeCode = generateMinimalEscapeCode

View File

@ -0,0 +1,604 @@
const path = require('path')
const crypto = require('crypto')
const os = require('os')
const fs = require('fs-extra')
const execSync = require('child_process').execSync
const chalk = require('chalk')
const _ = require('lodash')
const pocessTypeEnum = {
CREATE: 'create',
COMPILE: 'compile',
CONVERT: 'convert',
COPY: 'copy',
GENERATE: 'generate',
MODIFY: 'modify',
ERROR: 'error',
WARNING: 'warning',
UNLINK: 'unlink',
REFERENCE: 'reference'
}
const processTypeMap = {
[pocessTypeEnum.CREATE]: {
name: '创建',
color: 'cyan'
},
[pocessTypeEnum.COMPILE]: {
name: '编译',
color: 'green'
},
[pocessTypeEnum.CONVERT]: {
name: '转换',
color: chalk.rgb(255, 136, 0)
},
[pocessTypeEnum.COPY]: {
name: '拷贝',
color: 'magenta'
},
[pocessTypeEnum.GENERATE]: {
name: '生成',
color: 'blue'
},
[pocessTypeEnum.MODIFY]: {
name: '修改',
color: 'yellow'
},
[pocessTypeEnum.ERROR]: {
name: '错误',
color: 'red'
},
[pocessTypeEnum.WARNING]: {
name: '警告',
color: 'yellow'
},
[pocessTypeEnum.UNLINK]: {
name: '删除',
color: 'magenta'
},
[pocessTypeEnum.START]: {
name: '启动',
color: 'green'
},
[pocessTypeEnum.REFERENCE]: {
name: '引用',
color: 'blue'
}
}
exports.pocessTypeEnum = pocessTypeEnum
exports.CSS_EXT = ['.css', '.scss', '.sass', '.less', '.styl', '.wxss', '.acss']
exports.SCSS_EXT = ['.scss']
exports.JS_EXT = ['.js', '.jsx']
exports.TS_EXT = ['.ts', '.tsx']
exports.REG_JS = /\.js(\?.*)?$/
exports.REG_SCRIPT = /\.(js|jsx)(\?.*)?$/
exports.REG_TYPESCRIPT = /\.(tsx|ts)(\?.*)?$/
exports.REG_SCRIPTS = /\.[tj]sx?$/i
exports.REG_STYLE = /\.(css|scss|sass|less|styl|wxss)(\?.*)?$/
exports.REG_MEDIA = /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/
exports.REG_IMAGE = /\.(png|jpe?g|gif|bpm|svg|webp)(\?.*)?$/
exports.REG_FONT = /\.(woff2?|eot|ttf|otf)(\?.*)?$/
exports.REG_JSON = /\.json(\?.*)?$/
exports.REG_WXML_IMPORT = /<[import|include](.*)?src=(?:(?:'([^']*)')|(?:"([^"]*)"))/gi
exports.REG_URL = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
exports.CSS_IMPORT_REG = /@import (["'])(.+?)\1;/g
exports.BUILD_TYPES = {
WEAPP: 'weapp',
H5: 'h5',
RN: 'rn',
SWAN: 'swan',
ALIPAY: 'alipay',
TT: 'tt',
UI: 'ui'
}
exports.MINI_APP_FILES = {
[exports.BUILD_TYPES.WEAPP]: {
TEMPL: '.wxml',
STYLE: '.wxss',
SCRIPT: '.js',
CONFIG: '.json'
},
[exports.BUILD_TYPES.SWAN]: {
TEMPL: '.swan',
STYLE: '.css',
SCRIPT: '.js',
CONFIG: '.json'
},
[exports.BUILD_TYPES.ALIPAY]: {
TEMPL: '.axml',
STYLE: '.acss',
SCRIPT: '.js',
CONFIG: '.json'
},
[exports.BUILD_TYPES.TT]: {
TEMPL: '.ttml',
STYLE: '.ttss',
SCRIPT: '.js',
CONFIG: '.json'
}
}
exports.CONFIG_MAP = {
[exports.BUILD_TYPES.WEAPP]: {
navigationBarTitleText: 'navigationBarTitleText',
navigationBarBackgroundColor: 'navigationBarBackgroundColor',
enablePullDownRefresh: 'enablePullDownRefresh',
list: 'list',
text: 'text',
iconPath: 'iconPath',
selectedIconPath: 'selectedIconPath'
},
[exports.BUILD_TYPES.SWAN]: {
navigationBarTitleText: 'navigationBarTitleText',
navigationBarBackgroundColor: 'navigationBarBackgroundColor',
enablePullDownRefresh: 'enablePullDownRefresh',
list: 'list',
text: 'text',
iconPath: 'iconPath',
selectedIconPath: 'selectedIconPath'
},
[exports.BUILD_TYPES.TT]: {
navigationBarTitleText: 'navigationBarTitleText',
navigationBarBackgroundColor: 'navigationBarBackgroundColor',
enablePullDownRefresh: 'enablePullDownRefresh',
list: 'list',
text: 'text',
iconPath: 'iconPath',
selectedIconPath: 'selectedIconPath'
},
[exports.BUILD_TYPES.ALIPAY]: {
navigationBarTitleText: 'defaultTitle',
navigationBarBackgroundColor: 'titleBarColor',
enablePullDownRefresh: 'pullRefresh',
list: 'items',
text: 'name',
iconPath: 'icon',
selectedIconPath: 'activeIcon'
}
}
exports.PROJECT_CONFIG = 'config/index.js'
//@fix
exports.PROJECT_CONFIG_H5 = 'config/h5.js'
exports.DEVICE_RATIO = {
'640': 2.34 / 2,
'750': 1,
'828': 1.81 / 2
}
exports.FILE_PROCESSOR_MAP = {
'.js': 'babel',
'.scss': 'sass',
'.sass': 'sass',
'.less': 'less',
'.styl': 'stylus'
}
exports.isNpmPkg = function (name) {
if (/^(\.|\/)/.test(name)) {
return false
}
return true
}
exports.isAliasPath = function (name, pathAlias = {}) {
const prefixs = Object.keys(pathAlias)
if (prefixs.length === 0) {
return false
}
return prefixs.includes(name) || (new RegExp(`^(${prefixs.join('|')})/`).test(name))
}
exports.replaceAliasPath = function (filePath, name, pathAlias = {}) {
// 后续的 path.join 在遇到符号链接时将会解析为真实路径,如果
// 这里的 filePath 没有做同样的处理,可能会导致 import 指向
// 源代码文件,导致文件被意外修改
filePath = fs.realpathSync(filePath)
const prefixs = Object.keys(pathAlias)
if (prefixs.includes(name)) {
return exports.promoteRelativePath(path.relative(filePath, fs.realpathSync(pathAlias[name])))
}
const reg = new RegExp(`^(${prefixs.join('|')})/(.*)`)
name = name.replace(reg, function (m, $1, $2) {
return exports.promoteRelativePath(path.relative(filePath, path.join(pathAlias[$1], $2)))
})
return name
}
exports.promoteRelativePath = function (fPath) {
const fPathArr = fPath.split(path.sep)
let dotCount = 0
fPathArr.forEach(item => {
if (item.indexOf('..') >= 0) {
dotCount++
}
})
if (dotCount === 1) {
fPathArr.splice(0, 1, '.')
return fPathArr.join('/')
}
if (dotCount > 1) {
fPathArr.splice(0, 1)
return fPathArr.join('/')
}
return fPath.replace(/\\/g, '/')
}
exports.replaceAsync = async function (str, regex, asyncFn) {
const promises = []
str.replace(regex, (match, ...args) => {
const promise = asyncFn(match, ...args)
promises.push(promise)
})
const data = await Promise.all(promises)
return str.replace(regex, () => data.shift())
}
exports.homedir = (function () {
let homedir = null
const env = process.env
const home = env.HOME
const user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME
if (process.platform === 'win32') {
homedir = env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null
} else if (process.platform === 'darwin') {
homedir = home || (user ? `/Users/${user}` : null)
} else if (process.platform === 'linux') {
homedir = home || (process.getuid() === 0 ? '/root' : (user ? `/home/${user}` : null))
}
return typeof os.homedir === 'function' ? os.homedir : function () {
return homedir
}
})()
exports.getRootPath = function () {
return path.resolve(__dirname, '../../')
}
exports.getTaroPath = function () {
const taroPath = path.join(exports.homedir(), '.taro')
if (!fs.existsSync(taroPath)) {
fs.mkdirSync(taroPath)
}
return taroPath
}
exports.setConfig = function (config) {
const taroPath = exports.getTaroPath()
if (typeof config === 'object') {
const oldConfig = exports.getConfig()
config = Object.assign({}, oldConfig, config)
fs.writeFileSync(path.join(taroPath, 'config.json'), JSON.stringify(config, null, 2))
}
}
exports.getConfig = function () {
const configPath = path.join(exports.getTaroPath(), 'config.json')
if (fs.existsSync(configPath)) {
return require(configPath)
}
return {}
}
exports.getSystemUsername = function () {
const userHome = exports.homedir()
const systemUsername = process.env.USER || path.basename(userHome)
return systemUsername
}
exports.getPkgVersion = function () {
return require(path.join(exports.getRootPath(), 'package.json')).version
}
exports.getPkgItemByKey = function (key) {
const packageMap = require(path.join(exports.getRootPath(), 'package.json'))
if (Object.keys(packageMap).indexOf(key) === -1) {
return {}
} else {
return packageMap[key]
}
}
exports.printPkgVersion = function () {
const taroVersion = exports.getPkgVersion()
console.log(`👽 Omi-Cloud v${taroVersion}`)
console.log()
}
exports.shouldUseYarn = function () {
try {
execSync('yarn --version', { stdio: 'ignore' })
return true
} catch (e) {
return false
}
}
exports.shouldUseCnpm = function () {
try {
execSync('cnpm --version', { stdio: 'ignore' })
return true
} catch (e) {
return false
}
}
exports.isPublic = function isPublic (addr) {
return !exports.isPrivate(addr)
}
exports.isEmptyObject = function (obj) {
if (obj == null) {
return true
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
return false
}
}
return true
}
exports.urlJoin = function () {
function normalize (str) {
return str
.replace(/([/]+)/g, '/')
.replace(/\/\?(?!\?)/g, '?')
.replace(/\/#/g, '#')
.replace(/:\//g, '://')
}
const joined = [].slice.call(arguments, 0).join('/')
return normalize(joined)
}
exports.resolveScriptPath = function (p) {
let realPath = p
const SCRIPT_EXT = exports.JS_EXT.concat(exports.TS_EXT)
for (let i = 0; i < SCRIPT_EXT.length; i++) {
const item = SCRIPT_EXT[i]
if (fs.existsSync(`${p}${item}`)) {
return `${p}${item}`
}
if (fs.existsSync(`${p}${path.sep}index${item}`)) {
return `${p}${path.sep}index${item}`
}
}
return realPath
}
exports.resolveStylePath = function (p) {
let realPath = p
const CSS_EXT = exports.CSS_EXT
for (let i = 0; i < CSS_EXT.length; i++) {
const item = CSS_EXT[i]
if (fs.existsSync(`${p}${item}`)) {
return `${p}${item}`
}
}
return realPath
}
exports.isDifferentArray = function (a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) {
return true
}
if (a.length !== b.length) {
return true
}
a = a.sort()
b = b.sort()
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return true
}
}
return false
}
exports.checksum = function (buf, length) {
if (!Buffer.isBuffer(buf)) {
buf = Buffer.from(buf)
}
return crypto.createHash('md5').update(buf).digest('hex').slice(0, length || 8)
}
exports.printLog = function (type, tag, filePath) {
const typeShow = processTypeMap[type]
const tagLen = tag.replace(/[\u0391-\uFFE5]/g, 'aa').length
const tagFormatLen = 8
if (tagLen < tagFormatLen) {
const rightPadding = new Array(tagFormatLen - tagLen + 1).join(' ')
tag += rightPadding
}
const padding = ''
filePath = filePath || ''
if (typeof typeShow.color === 'string') {
console.log(chalk[typeShow.color](typeShow.name), padding, tag, padding, filePath)
} else {
console.log(typeShow.color(typeShow.name), padding, tag, padding, filePath)
}
}
exports.replaceContentEnv = function (content, env) {
if (env && !exports.isEmptyObject(env)) {
for (const key in env) {
const reg = new RegExp(`process.env.${key}`, 'g')
content = content.replace(reg, env[key])
}
return content
}
return content
}
exports.generateEnvList = function (env) {
const res = { }
if (env && !exports.isEmptyObject(env)) {
for (const key in env) {
try {
res[`process.env.${key}`] = JSON.parse(env[key])
} catch (err) {
res[`process.env.${key}`] = env[key]
}
}
}
return res
}
exports.replaceContentConstants = function (content, constants) {
if (constants && !exports.isEmptyObject(constants)) {
for (const key in constants) {
const reg = new RegExp(key, 'g')
content = content.replace(reg, constants[key])
}
return content
}
return content
}
exports.generateConstantsList = function (constants) {
const res = { }
if (constants && !exports.isEmptyObject(constants)) {
for (const key in constants) {
try {
res[key] = JSON.parse(constants[key])
} catch (err) {
res[key] = constants[key]
}
}
}
return res
}
exports.cssImports = function (content) {
let match = {}
const results = []
content = String(content).replace(/\/\*.+?\*\/|\/\/.*(?=[\n\r])/g, '')
while ((match = exports.CSS_IMPORT_REG.exec(content))) {
results.push(match[2])
}
return results
}
exports.processStyleImports = function (content, adapter, process) {
const style = []
const imports = []
const styleReg = new RegExp(`\\${exports.MINI_APP_FILES[adapter].STYLE}`)
content = content.replace(exports.CSS_IMPORT_REG, (m, $1, $2) => {
if (styleReg.test($2)) {
style.push(m)
imports.push($2)
if (process && typeof process === 'function') {
return process(m, $2)
}
return ''
}
if (process && typeof process === 'function') {
return process(m, $2)
}
return m
})
return {
content,
style,
imports
}
}
/*eslint-disable*/
const retries = (process.platform === 'win32') ? 100 : 1
exports.emptyDirectory = function (dirPath, opts = { excludes: [] }) {
if (fs.existsSync(dirPath)) {
fs.readdirSync(dirPath).forEach(file => {
const curPath = path.join(dirPath, file)
if (fs.lstatSync(curPath).isDirectory()) {
let removed = false
let i = 0 // retry counter
do {
try {
if (!opts.excludes.length || !opts.excludes.some(item => curPath.indexOf(item) >= 0)) {
exports.emptyDirectory(curPath)
fs.rmdirSync(curPath)
}
removed = true
} catch (e) {
} finally {
if (++i < retries) {
continue
}
}
} while (!removed)
} else {
fs.unlinkSync(curPath)
}
})
}
}
/* eslint-enable */
exports.recursiveFindNodeModules = function (filePath) {
const dirname = path.dirname(filePath)
const nodeModules = path.join(dirname, 'node_modules')
if (fs.existsSync(nodeModules)) {
return nodeModules
}
return exports.recursiveFindNodeModules(dirname)
}
exports.UPDATE_PACKAGE_LIST = [
'@tarojs/taro',
'@tarojs/async-await',
'@tarojs/cli',
'@tarojs/components',
'@tarojs/components-rn',
'@tarojs/taro-h5',
'@tarojs/taro-swan',
'@tarojs/taro-alipay',
'@tarojs/taro-tt',
'@tarojs/plugin-babel',
'@tarojs/plugin-csso',
'@tarojs/plugin-sass',
'@tarojs/plugin-less',
'@tarojs/plugin-stylus',
'@tarojs/plugin-uglifyjs',
'@tarojs/redux',
'@tarojs/redux-h5',
'@tarojs/taro-redux-rn',
'@tarojs/taro-router-rn',
'@tarojs/taro-rn',
'@tarojs/rn-runner',
'@tarojs/router',
'@tarojs/taro-weapp',
'@tarojs/webpack-runner',
'postcss-plugin-constparse',
'eslint-config-taro',
'eslint-plugin-taro',
'taro-transformer-wx',
'postcss-pxtransform',
'babel-plugin-transform-jsx-to-stylesheet',
'@tarojs/mobx',
'@tarojs/mobx-h5',
'@tarojs/mobx-rn',
'@tarojs/mobx-common',
'@tarojs/mobx-prop-types'
]
exports.pascalCase = (str) => str.charAt(0).toUpperCase() + _.camelCase(str.substr(1))
exports.getInstalledNpmPkgVersion = function (pkgName, basedir) {
const resolvePath = require('resolve')
try {
const pkg = resolvePath.sync(`${pkgName}/package.json`, { basedir })
const pkgJson = fs.readJSONSync(pkg)
return pkgJson.version
} catch (err) {
return null
}
}

View File

@ -0,0 +1,164 @@
const resolvePath = require('resolve')
const spawn = require('cross-spawn')
const chalk = require('chalk')
const Util = require('./')
const basedir = process.cwd()
const taroPluginPrefix = '@tarojs/plugin-'
const PEERS = /UNMET PEER DEPENDENCY ([a-z\-0-9.]+)@(.+)/gm
const npmCached = {}
const erroneous = []
const defaultInstallOptions = {
dev: false,
peerDependencies: true
}
function resolveNpm (pluginName) {
if (!npmCached[pluginName]) {
return new Promise((resolve, reject) => {
resolvePath(`${pluginName}`, {basedir}, (err, res) => {
if (err) {
return reject(err)
}
npmCached[pluginName] = res
resolve(res)
})
})
}
return Promise.resolve(npmCached[pluginName])
}
function resolveNpmSync (pluginName) {
try {
if (!npmCached[pluginName]) {
const res = resolvePath.sync(pluginName, {basedir})
return res
}
return npmCached[pluginName]
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.log(chalk.cyan(`缺少npm包${pluginName},开始安装...`))
const installOptions = {}
if (pluginName.indexOf(taroPluginPrefix) >= 0) {
installOptions.dev = true
}
installNpmPkg(pluginName, installOptions)
return resolveNpmSync(pluginName)
}
}
}
function installNpmPkg (pkgList, options) {
if (!pkgList) {
return
}
if (!Array.isArray(pkgList)) {
pkgList = [pkgList]
}
pkgList = pkgList.filter(dep => {
return erroneous.indexOf(dep) === -1
})
if (!pkgList.length) {
return
}
options = Object.assign({}, defaultInstallOptions, options)
let installer = ''
let args = []
if (Util.shouldUseYarn()) {
installer = 'yarn'
} else if (Util.shouldUseCnpm()) {
installer = 'cnpm'
} else {
installer = 'npm'
}
if (Util.shouldUseYarn()) {
args = ['add'].concat(pkgList).filter(Boolean)
args.push('--silent', '--no-progress')
if (options.dev) {
args.push('-D')
}
} else {
args = ['install'].concat(pkgList).filter(Boolean)
args.push('--silent', '--no-progress')
if (options.dev) {
args.push('--save-dev')
} else {
args.push('--save')
}
}
const output = spawn.sync(installer, args, {
stdio: ['ignore', 'pipe', 'inherit']
})
if (output.status) {
pkgList.forEach(dep => {
erroneous.push(dep)
})
}
let matches = null
const peers = []
while ((matches = PEERS.exec(output.stdout))) {
const pkg = matches[1]
const version = matches[2]
if (version.match(' ')) {
peers.push(pkg)
} else {
peers.push(`${pkg}@${version}`)
}
}
if (options.peerDependencies && peers.length) {
console.info('正在安装 peerDependencies...')
installNpmPkg(peers, options)
}
return output
}
async function callPlugin (pluginName, content, file, config) {
const pluginFn = await getNpmPkg(`${taroPluginPrefix}${pluginName}`)
return pluginFn(content, file, config)
}
function callPluginSync (pluginName, content, file, config) {
const pluginFn = getNpmPkgSync(`${taroPluginPrefix}${pluginName}`)
return pluginFn(content, file, config)
}
function getNpmPkgSync (npmName) {
const npmPath = resolveNpmSync(npmName)
const npmFn = require(npmPath)
return npmFn
}
async function getNpmPkg (npmName) {
let npmPath
try {
npmPath = resolveNpmSync(npmName)
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.log(chalk.cyan(`缺少npm包${npmName},开始安装...`))
const installOptions = {}
if (npmName.indexOf(taroPluginPrefix) >= 0) {
installOptions.dev = true
}
installNpmPkg(npmName, installOptions)
npmPath = await resolveNpm(npmName)
}
}
const npmFn = require(npmPath)
return npmFn
}
module.exports = {
taroPluginPrefix,
installNpmPkg,
resolveNpm,
resolveNpmSync,
callPlugin,
callPluginSync,
getNpmPkg,
getNpmPkgSync
}

View File

@ -0,0 +1,270 @@
const fs = require('fs-extra')
const path = require('path')
const resolvePath = require('resolve')
const wxTransformer = require('../../../taro-transformer-wx/lib/src/index.js').default
const babel = require('babel-core')
const traverse = require('babel-traverse').default
const t = require('babel-types')
const generate = require('babel-generator').default
const _ = require('lodash')
const defaultUglifyConfig = require('../config/uglify')
const {
isNpmPkg,
promoteRelativePath,
printLog,
pocessTypeEnum,
PROJECT_CONFIG,
generateEnvList,
REG_TYPESCRIPT,
BUILD_TYPES,
REG_STYLE,
recursiveFindNodeModules
} = require('./index')
const CONFIG = require('../config')
const defaultBabelConfig = require('../config/babel')
const npmProcess = require('./npm')
const excludeNpmPkgs = ['ReactPropTypes']
const resolvedCache = {}
const copyedFiles = {}
const basedir = process.cwd()
const configDir = path.join(basedir, PROJECT_CONFIG)
const projectConfig = require(configDir)(_.merge)
const pluginsConfig = projectConfig.plugins || {}
const outputDirName = projectConfig.outputRoot || CONFIG.OUTPUT_DIR
const babelConfig = _.mergeWith({}, defaultBabelConfig, pluginsConfig.babel, (objValue, srcValue) => {
if (Array.isArray(objValue)) {
return Array.from(new Set(srcValue.concat(objValue)))
}
})
function resolveNpmPkgMainPath (pkgName, isProduction, npmConfig, buildAdapter = BUILD_TYPES.WEAPP, root = basedir) {
try {
return resolvePath.sync(pkgName, { basedir: root })
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.log(`缺少npm包${pkgName},开始安装...`)
const installOptions = {}
if (pkgName.indexOf(npmProcess.taroPluginPrefix) >= 0) {
installOptions.dev = true
}
npmProcess.installNpmPkg(pkgName, installOptions)
return resolveNpmPkgMainPath(pkgName, isProduction, npmConfig, buildAdapter, root)
}
}
}
function resolveNpmFilesPath (pkgName, isProduction, npmConfig, buildAdapter = BUILD_TYPES.WEAPP, root = basedir, compileInclude = []) {
if (!resolvedCache[pkgName]) {
const res = resolveNpmPkgMainPath(pkgName, isProduction, npmConfig, buildAdapter, root)
resolvedCache[pkgName] = {
main: res,
files: []
}
resolvedCache[pkgName].files.push(res)
recursiveRequire(res, resolvedCache[pkgName].files, isProduction, npmConfig, buildAdapter, compileInclude)
}
return resolvedCache[pkgName]
}
function parseAst (ast, filePath, files, isProduction, npmConfig, buildAdapter = BUILD_TYPES.WEAPP, compileInclude) {
const excludeRequire = []
traverse(ast, {
IfStatement (astPath) {
astPath.traverse({
BinaryExpression (astPath) {
const node = astPath.node
const left = node.left
if (generate(left).code === 'process.env.TARO_ENV' &&
node.right.value !== buildAdapter) {
const consequentSibling = astPath.getSibling('consequent')
consequentSibling.traverse({
CallExpression (astPath) {
if (astPath.get('callee').isIdentifier({ name: 'require' })) {
const arg = astPath.get('arguments')[0]
if (t.isStringLiteral(arg.node)) {
excludeRequire.push(arg.node.value)
}
}
}
})
}
}
})
},
Program: {
exit (astPath) {
astPath.traverse({
CallExpression (astPath) {
const node = astPath.node
const callee = node.callee
if (callee.name === 'require') {
const args = node.arguments
let requirePath = args[0].value
if (excludeRequire.indexOf(requirePath) < 0) {
if (isNpmPkg(requirePath)) {
if (excludeNpmPkgs.indexOf(requirePath) < 0) {
const res = resolveNpmFilesPath(requirePath, isProduction, npmConfig, buildAdapter, path.dirname(recursiveFindNodeModules(filePath)), compileInclude)
let relativeRequirePath = promoteRelativePath(path.relative(filePath, res.main))
relativeRequirePath = relativeRequirePath.replace(/node_modules/g, npmConfig.name)
if (buildAdapter === BUILD_TYPES.ALIPAY) {
relativeRequirePath = relativeRequirePath.replace(/@/g, '_')
}
args[0].value = relativeRequirePath
}
} else {
let realRequirePath = path.resolve(path.dirname(filePath), requirePath)
let tempPathWithJS = `${realRequirePath}.js`
let tempPathWithIndexJS = `${realRequirePath}${path.sep}index.js`
if (fs.existsSync(tempPathWithJS)) {
realRequirePath = tempPathWithJS
requirePath += '.js'
} else if (fs.existsSync(tempPathWithIndexJS)) {
realRequirePath = tempPathWithIndexJS
requirePath += '/index.js'
}
if (files.indexOf(realRequirePath) < 0) {
files.push(realRequirePath)
recursiveRequire(realRequirePath, files, isProduction, npmConfig, buildAdapter, compileInclude)
}
args[0].value = requirePath
}
}
}
}
})
}
}
})
return generate(ast).code
}
async function recursiveRequire (filePath, files, isProduction, npmConfig = {}, buildAdapter, compileInclude = []) {
let fileContent = fs.readFileSync(filePath).toString()
let outputNpmPath
if (!npmConfig.dir) {
const cwdRelate2Npm = path.relative(
filePath.slice(0, filePath.search('node_modules')),
process.cwd()
)
outputNpmPath = filePath.replace('node_modules', path.join(cwdRelate2Npm, outputDirName, npmConfig.name))
outputNpmPath = outputNpmPath.replace(/node_modules/g, npmConfig.name)
} else {
let npmFilePath = filePath.match(/(?=(node_modules)).*/)[0]
npmFilePath = npmFilePath.replace(/node_modules/g, npmConfig.name)
outputNpmPath = path.join(path.resolve(configDir, '..', npmConfig.dir), npmFilePath)
}
if (buildAdapter === BUILD_TYPES.ALIPAY) {
outputNpmPath = outputNpmPath.replace(/@/g, '_')
}
if (REG_STYLE.test(path.basename(filePath))) {
return
}
fileContent = npmCodeHack(filePath, fileContent, buildAdapter)
try {
const constantsReplaceList = Object.assign({
'process.env.TARO_ENV': buildAdapter
}, generateEnvList(projectConfig.env || {}))
const transformResult = wxTransformer({
code: fileContent,
sourcePath: filePath,
outputPath: outputNpmPath,
isNormal: true,
adapter: buildAdapter,
isTyped: REG_TYPESCRIPT.test(filePath),
env: constantsReplaceList
})
const ast = babel.transformFromAst(transformResult.ast, '', {
plugins: [
[require('babel-plugin-transform-define').default, constantsReplaceList]
]
}).ast
fileContent = parseAst(ast, filePath, files, isProduction, npmConfig, buildAdapter, compileInclude)
} catch (err) {
console.log(err)
}
if (!copyedFiles[outputNpmPath]) {
if (compileInclude && compileInclude.length) {
const filePathArr = filePath.split(path.sep)
const nodeModulesIndex = filePathArr.indexOf('node_modules')
const npmPkgName = filePathArr[nodeModulesIndex + 1]
if (compileInclude.indexOf(npmPkgName) >= 0) {
const compileScriptRes = await npmProcess.callPlugin('babel', fileContent, filePath, babelConfig)
fileContent = compileScriptRes.code
}
}
if (isProduction) {
const uglifyPluginConfig = pluginsConfig.uglify || { enable: true }
if (uglifyPluginConfig.enable) {
const uglifyConfig = Object.assign(defaultUglifyConfig, uglifyPluginConfig.config || {})
const uglifyResult = npmProcess.callPluginSync('uglifyjs', fileContent, outputNpmPath, uglifyConfig)
if (uglifyResult.error) {
printLog(pocessTypeEnum.ERROR, '压缩错误', `文件${filePath}`)
console.log(uglifyResult.error)
} else {
fileContent = uglifyResult.code
}
}
}
fs.ensureDirSync(path.dirname(outputNpmPath))
fs.writeFileSync(outputNpmPath, fileContent)
let modifyOutput = outputNpmPath.replace(basedir + path.sep, '')
modifyOutput = modifyOutput.split(path.sep).join('/')
printLog(pocessTypeEnum.COPY, 'NPM文件', modifyOutput)
copyedFiles[outputNpmPath] = true
}
}
function npmCodeHack (filePath, content, buildAdapter) {
const basename = path.basename(filePath)
switch (basename) {
case 'lodash.js':
case '_global.js':
case 'lodash.min.js':
if (buildAdapter === BUILD_TYPES.ALIPAY || buildAdapter === BUILD_TYPES.SWAN) {
content = content.replace(/Function\(['"]return this['"]\)\(\)/, '{}')
} else {
content = content.replace(/Function\(['"]return this['"]\)\(\)/, 'this')
}
break
case 'mobx.js':
// 解决支付宝小程序全局window或global不存在的问题
content = content.replace(
/typeof window\s{0,}!==\s{0,}['"]undefined['"]\s{0,}\?\s{0,}window\s{0,}:\s{0,}global/,
'typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}'
)
break
case '_html.js':
content = 'module.exports = false;'
break
case '_microtask.js':
content = content.replace('if(Observer)', 'if(false && Observer)')
// IOS 1.10.2 Promise BUG
content = content.replace('Promise && Promise.resolve', 'false && Promise && Promise.resolve')
break
case '_freeGlobal.js':
content = content.replace('module.exports = freeGlobal;', 'module.exports = freeGlobal || this || global || {};')
break
}
if (buildAdapter === BUILD_TYPES.ALIPAY && content.replace(/\s\r\n/g, '').length <= 0) {
content = '// Empty file'
}
return content
}
function getResolvedCache () {
return resolvedCache
}
module.exports = {
getResolvedCache,
resolveNpmFilesPath,
resolveNpmPkgMainPath
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Adapters;
(function (Adapters) {
Adapters["weapp"] = "weapp";
Adapters["swan"] = "swan";
Adapters["alipay"] = "alipay";
Adapters["quickapp"] = "quickapp";
Adapters["tt"] = "tt";
})(Adapters = exports.Adapters || (exports.Adapters = {}));
const weixinAdapter = {
if: 'wx:if',
else: 'wx:else',
elseif: 'wx:elif',
for: 'wx:for',
forItem: 'wx:for-item',
forIndex: 'wx:for-index',
key: 'wx:key',
type: "weapp" /* weapp */
};
const swanAdapter = {
if: 's-if',
else: 's-else',
elseif: 's-elif',
for: 's-for',
forItem: 's-for-item',
forIndex: 's-for-index',
key: 's-key',
type: "swan" /* swan */
};
const alipayAdapter = {
if: 'a:if',
else: 'a:else',
elseif: 'a:elif',
for: 'a:for',
forItem: 'a:for-item',
forIndex: 'a:for-index',
key: 'a:key',
type: "alipay" /* alipay */
};
const ttAdapter = {
if: 'tt:if',
else: 'tt:else',
elseif: 'tt:elif',
for: 'tt:for',
forItem: 'tt:for-item',
forIndex: 'tt:for-index',
key: 'tt:key',
type: "tt" /* tt */
};
exports.Adapter = weixinAdapter;
function setAdapter(adapter) {
switch (adapter.toLowerCase()) {
case "swan" /* swan */:
exports.Adapter = swanAdapter;
break;
case "alipay" /* alipay */:
exports.Adapter = alipayAdapter;
break;
case "tt" /* tt */:
exports.Adapter = ttAdapter;
break;
default:
exports.Adapter = weixinAdapter;
break;
}
}
exports.setAdapter = setAdapter;
//# sourceMappingURL=adapter.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/adapter.ts"],"names":[],"mappings":";;AAAA,IAAkB,QAMjB;AAND,WAAkB,QAAQ;IACxB,2BAAe,CAAA;IACf,yBAAa,CAAA;IACb,6BAAiB,CAAA;IACjB,iCAAqB,CAAA;IACrB,qBAAS,CAAA;AACX,CAAC,EANiB,QAAQ,GAAR,gBAAQ,KAAR,gBAAQ,QAMzB;AAaD,MAAM,aAAa,GAAY;IAC7B,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,QAAQ;IACb,OAAO,EAAE,aAAa;IACtB,QAAQ,EAAE,cAAc;IACxB,GAAG,EAAE,QAAQ;IACb,IAAI,qBAAgB;CACrB,CAAA;AAED,MAAM,WAAW,GAAY;IAC3B,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,OAAO;IACZ,OAAO,EAAE,YAAY;IACrB,QAAQ,EAAE,aAAa;IACvB,GAAG,EAAE,OAAO;IACZ,IAAI,mBAAe;CACpB,CAAA;AAED,MAAM,aAAa,GAAY;IAC7B,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,OAAO;IACZ,OAAO,EAAE,YAAY;IACrB,QAAQ,EAAE,aAAa;IACvB,GAAG,EAAE,OAAO;IACZ,IAAI,uBAAiB;CACtB,CAAA;AAED,MAAM,SAAS,GAAY;IACzB,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,QAAQ;IACb,OAAO,EAAE,aAAa;IACtB,QAAQ,EAAE,cAAc;IACxB,GAAG,EAAE,QAAQ;IACb,IAAI,eAAa;CAClB,CAAA;AAEU,QAAA,OAAO,GAAY,aAAa,CAAA;AAE3C,SAAgB,UAAU,CAAE,OAAiB;IAC3C,QAAQ,OAAO,CAAC,WAAW,EAAE,EAAE;QAC7B;YACE,eAAO,GAAG,WAAW,CAAA;YACrB,MAAK;QACP;YACE,eAAO,GAAG,aAAa,CAAA;YACvB,MAAK;QACP;YACE,eAAO,GAAG,SAAS,CAAA;YACnB,MAAK;QACP;YACE,eAAO,GAAG,aAAa,CAAA;YACvB,MAAK;KACR;AACH,CAAC;AAfD,gCAeC"}

View File

@ -0,0 +1,641 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
const utils_1 = require("./utils");
const constant_1 = require("./constant");
const lodash_1 = require("lodash");
const render_1 = require("./render");
const jsx_1 = require("./jsx");
const adapter_1 = require("./adapter");
const babel_generator_1 = require("babel-generator");
function buildConstructor() {
const ctor = t.classMethod('constructor', t.identifier('constructor'), [t.identifier('props')], t.blockStatement([
t.expressionStatement(t.callExpression(t.identifier('super'), [
t.identifier('props')
]))
]));
return ctor;
}
function processThisPropsFnMemberProperties(member, path, args, binded) {
const propertyArray = [];
function traverseMember(member) {
const object = member.object;
const property = member.property;
if (t.isIdentifier(property)) {
propertyArray.push(property.name);
}
if (t.isMemberExpression(object)) {
if (t.isThisExpression(object.object) &&
t.isIdentifier(object.property) &&
object.property.name === 'props') {
if ("alipay" /* alipay */ === adapter_1.Adapter.type) {
if (binded)
args.shift();
path.replaceWith(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [
t.stringLiteral(propertyArray.reverse().join('.')),
t.arrayExpression(args)
]));
}
else {
path.replaceWith(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [t.stringLiteral(propertyArray.reverse().join('.')), t.callExpression(t.memberExpression(t.arrayExpression([t.nullLiteral()]), t.identifier('concat')), [t.arrayExpression(args)])]));
}
}
traverseMember(object);
}
}
traverseMember(member);
}
class Transformer {
constructor(path, sourcePath, componentProperies,
//@fix add arg => componentSourceMap
componentSourceMap) {
this.result = {
template: '',
components: [],
componentProperies: []
};
this.methods = new Map();
this.initState = new Set();
this.jsxReferencedIdentifiers = new Set();
this.customComponents = new Map();
this.anonymousMethod = new Map();
this.renderMethod = null;
this.customComponentNames = new Set();
this.usedState = new Set();
this.loopStateName = new Map();
this.customComponentData = [];
this.refs = [];
this.loopRefs = new Map();
this.anonymousFuncCounter = utils_1.incrementId();
this.buildPropsAnonymousFunc = (attr, expr, isBind = false) => {
const { code } = babel_generator_1.default(expr);
if (code.startsWith('this.props')) {
const methodName = utils_1.findMethodName(expr);
const hasMethodName = this.anonymousMethod.has(methodName) || !methodName;
const funcName = hasMethodName
? this.anonymousMethod.get(methodName)
// 测试时使用1个稳定的 uniqueID 便于测试实际使用5个英文字母否则小程序不支持
: process.env.NODE_ENV === 'test' ? lodash_1.uniqueId('funPrivate') : `funPrivate${utils_1.createRandomLetters(5)}`;
this.anonymousMethod.set(methodName, funcName);
const newVal = isBind
? t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(funcName)), t.identifier('bind')), expr.arguments || [])
: t.memberExpression(t.thisExpression(), t.identifier(funcName));
attr.get('value.expression').replaceWith(newVal);
this.methods.set(funcName, null);
this.componentProperies.add(methodName);
if (hasMethodName) {
return;
}
const attrName = attr.node.name;
if (t.isJSXIdentifier(attrName) && attrName.name.startsWith('on')) {
this.componentProperies.add(`__fn_${attrName.name}`);
}
if (methodName.startsWith('on')) {
this.componentProperies.add(`__fn_${methodName}`);
}
const method = t.classMethod('method', t.identifier(funcName), [], t.blockStatement([
t.expressionStatement(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__triggerPropsFn')), [t.stringLiteral(methodName), t.arrayExpression([t.spreadElement(t.identifier('arguments'))])]))
]));
this.classPath.node.body.body = this.classPath.node.body.body.concat(method);
}
};
this.classPath = path;
this.sourcePath = sourcePath;
//@fix add arg => componentSourceMap
this.componentSourceMap = componentSourceMap;
for (let key of this.componentSourceMap) {
const arr = key[0].split('/');
if (arr[arr.length - 1].indexOf('-') !== -1 && key[0].indexOf('components/') !== -1 && key[1].length === 0) {
this.customComponents.set(arr[arr.length - 1], {
sourcePath: key[0],
type: 'default'
});
}
}
this.moduleNames = Object.keys(path.scope.getAllBindings('module'));
this.componentProperies = new Set(componentProperies);
this.compile();
}
setMultipleSlots() {
const body = this.classPath.node.body.body;
if (body.some(c => t.isClassProperty(c) && c.key.name === 'multipleSlots')) {
return;
}
const multipleSlots = t.classProperty(t.identifier('multipleSlots'), t.booleanLiteral(true));
multipleSlots.static = true;
body.push(multipleSlots);
}
createStringRef(componentName, id, refName) {
this.refs.push({
type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component',
id,
refName
});
}
createFunctionRef(componentName, id, fn) {
this.refs.push({
type: constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component',
id,
fn
});
}
handleRefs() {
const objExpr = this.refs.map(ref => {
return t.objectExpression([
t.objectProperty(t.identifier('type'), t.stringLiteral(ref.type)),
t.objectProperty(t.identifier('id'), t.stringLiteral(ref.id)),
t.objectProperty(t.identifier('refName'), t.stringLiteral(ref.refName || '')),
t.objectProperty(t.identifier('fn'), ref.fn ? ref.fn : t.nullLiteral())
]);
});
this.classPath.node.body.body.push(t.classProperty(t.identifier('$$refs'), t.arrayExpression(objExpr)));
}
traverse() {
const self = this;
self.classPath.traverse({
JSXOpeningElement: (path) => {
const jsx = path.node;
const attrs = jsx.attributes;
if (!t.isJSXIdentifier(jsx.name)) {
return;
}
const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
const componentName = jsx.name.name;
const refAttr = jsx_1.findJSXAttrByName(attrs, 'ref');
if (!refAttr) {
return;
}
const idAttr = jsx_1.findJSXAttrByName(attrs, 'id');
let id = utils_1.createRandomLetters(5);
let idExpr;
if (!idAttr) {
if (loopCallExpr && loopCallExpr.isCallExpression()) {
const [func] = loopCallExpr.node.arguments;
let indexId = null;
if (t.isFunctionExpression(func) || t.isArrowFunctionExpression(func)) {
const params = func.params;
indexId = params[1];
}
if (indexId === null || !t.isIdentifier(indexId)) {
throw utils_1.codeFrameError(path.node, '在循环中使用 ref 必须暴露循环的第二个参数 `index`');
}
attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.jSXExpressionContainer(t.binaryExpression('+', t.stringLiteral(id), indexId))));
}
else {
attrs.push(t.jSXAttribute(t.jSXIdentifier('id'), t.stringLiteral(id)));
}
}
else {
const idValue = idAttr.value;
if (t.isStringLiteral(idValue)) {
id = idValue.value;
}
else if (t.isJSXExpressionContainer(idValue)) {
if (t.isStringLiteral(idValue.expression)) {
id = idValue.expression.value;
}
else {
idExpr = idValue.expression;
}
}
}
if (t.isStringLiteral(refAttr.value)) {
if (loopCallExpr) {
throw utils_1.codeFrameError(refAttr, '循环中的 ref 只能使用函数。');
}
this.createStringRef(componentName, id, refAttr.value.value);
}
if (t.isJSXExpressionContainer(refAttr.value)) {
const expr = refAttr.value.expression;
if (t.isStringLiteral(expr)) {
if (loopCallExpr) {
throw utils_1.codeFrameError(refAttr, '循环中的 ref 只能使用函数。');
}
this.createStringRef(componentName, id, expr.value);
}
else if (t.isArrowFunctionExpression(expr) || t.isMemberExpression(expr)) {
const type = constant_1.DEFAULT_Component_SET.has(componentName) ? 'dom' : 'component';
if (loopCallExpr) {
this.loopRefs.set(path.parentPath.node, {
id: idExpr || id,
fn: expr,
type,
component: path.parentPath
});
}
else {
this.refs.push({
type,
id,
fn: expr
});
}
}
else {
throw utils_1.codeFrameError(refAttr, 'ref 仅支持传入字符串、匿名箭头函数和 class 中已声明的函数');
}
}
for (const [index, attr] of attrs.entries()) {
if (attr === refAttr) {
attrs.splice(index, 1);
}
}
},
ClassMethod(path) {
const node = path.node;
if (t.isIdentifier(node.key)) {
const name = node.key.name;
self.methods.set(name, path);
if (name === 'render') {
self.renderMethod = path;
path.traverse({
ReturnStatement(returnPath) {
const arg = returnPath.node.argument;
const ifStem = returnPath.findParent(p => p.isIfStatement());
if (ifStem && ifStem.isIfStatement() && arg === null) {
const consequent = ifStem.get('consequent');
if (consequent.isBlockStatement() && consequent.node.body.includes(returnPath.node)) {
returnPath.get('argument').replaceWith(t.nullLiteral());
}
}
}
});
}
if (name === 'constructor') {
path.traverse({
AssignmentExpression(p) {
if (t.isMemberExpression(p.node.left) &&
t.isThisExpression(p.node.left.object) &&
t.isIdentifier(p.node.left.property) &&
p.node.left.property.name === 'data' &&
t.isObjectExpression(p.node.right)) {
const properties = p.node.right.properties;
properties.forEach(p => {
if (t.isObjectProperty(p) && t.isIdentifier(p.key)) {
self.initState.add(p.key.name);
}
});
}
}
});
}
}
},
IfStatement(path) {
const test = path.get('test');
const consequent = path.get('consequent');
if (utils_1.isContainJSXElement(consequent) && utils_1.hasComplexExpression(test)) {
const scope = self.renderMethod && self.renderMethod.scope || path.scope;
utils_1.generateAnonymousState(scope, test, self.jsxReferencedIdentifiers, true);
}
},
ClassProperty(path) {
const { key: { name }, value } = path.node;
if (t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) {
self.methods.set(name, path);
}
if (name === 'data' && t.isObjectExpression(value)) {
value.properties.forEach(p => {
if (t.isObjectProperty(p)) {
if (t.isIdentifier(p.key)) {
self.initState.add(p.key.name);
}
}
});
}
},
JSXExpressionContainer(path) {
const attr = path.findParent(p => p.isJSXAttribute());
const isFunctionProp = attr && typeof attr.node.name.name === 'string' && attr.node.name.name.startsWith('on');
path.traverse({
MemberExpression(path) {
const sibling = path.getSibling('property');
if (path.get('object').isThisExpression() &&
(path.get('property').isIdentifier({ name: 'props' }) || path.get('property').isIdentifier({ name: 'data' })) &&
sibling.isIdentifier()) {
if (!isFunctionProp) {
self.usedState.add(sibling.node.name);
}
}
}
});
const expression = path.get('expression');
const scope = self.renderMethod && self.renderMethod.scope || path.scope;
const calleeExpr = expression.get('callee');
const parentPath = path.parentPath;
if (utils_1.hasComplexExpression(expression) &&
!isFunctionProp &&
!(calleeExpr &&
calleeExpr.isMemberExpression() &&
calleeExpr.get('object').isMemberExpression() &&
calleeExpr.get('property').isIdentifier({ name: 'bind' })) // is not bind
) {
utils_1.generateAnonymousState(scope, expression, self.jsxReferencedIdentifiers);
}
else {
if (parentPath.isJSXAttribute()) {
if (!(expression.isMemberExpression() || expression.isIdentifier()) && parentPath.node.name.name === 'key') {
utils_1.generateAnonymousState(scope, expression, self.jsxReferencedIdentifiers);
}
}
}
if (!attr)
return;
const key = attr.node.name;
const value = attr.node.value;
if (!t.isJSXIdentifier(key)) {
return;
}
if (t.isJSXIdentifier(key) && key.name.startsWith('on') && t.isJSXExpressionContainer(value)) {
const expr = value.expression;
if (t.isCallExpression(expr) && t.isMemberExpression(expr.callee) && t.isIdentifier(expr.callee.property, { name: 'bind' })) {
self.buildPropsAnonymousFunc(attr, expr, true);
}
else if (t.isMemberExpression(expr)) {
self.buildPropsAnonymousFunc(attr, expr, false);
}
else if (t.isArrowFunctionExpression(expr)) {
const exprPath = attr.get('value.expression');
const stemParent = path.getStatementParent();
const counter = self.anonymousFuncCounter();
const anonymousFuncName = `anonymousFunc${counter}`;
const isCatch = utils_1.isContainStopPropagation(exprPath);
const classBody = self.classPath.node.body.body;
const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
let index;
if (loopCallExpr) {
index = lodash_1.get(loopCallExpr, 'node.arguments[0].params[1]');
if (!t.isIdentifier(index)) {
index = t.identifier('__index' + counter);
lodash_1.set(loopCallExpr, 'node.arguments[0].params[1]', index);
}
classBody.push(t.classProperty(t.identifier(anonymousFuncName + 'Array'), t.arrayExpression([])));
const arrayFunc = t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName + 'Array')), t.identifier(index.name), true);
classBody.push(t.classMethod('method', t.identifier(anonymousFuncName), [t.identifier(index.name), t.identifier('e')], t.blockStatement([
isCatch ? t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('e'), t.identifier('stopPropagation')), [])) : t.emptyStatement(),
t.expressionStatement(t.logicalExpression('&&', arrayFunc, t.callExpression(arrayFunc, [t.identifier('e')])))
])));
exprPath.replaceWith(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)), t.identifier('bind')), [t.thisExpression(), t.identifier(index.name)]));
stemParent.insertBefore(t.expressionStatement(t.assignmentExpression('=', arrayFunc, expr)));
}
else {
classBody.push(t.classMethod('method', t.identifier(anonymousFuncName), [t.identifier('e')], t.blockStatement([
isCatch ? t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('e'), t.identifier('stopPropagation')), [])) : t.emptyStatement()
])));
exprPath.replaceWith(t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)));
stemParent.insertBefore(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(anonymousFuncName)), expr)));
}
}
else {
throw utils_1.codeFrameError(path.node, '组件事件传参只能在使用匿名箭头函数,或使用类作用域下的确切引用(this.handleXX || this.props.handleXX),或使用 bind。');
}
}
const jsx = path.findParent(p => p.isJSXOpeningElement());
if (!jsx)
return;
const jsxName = jsx.node.name;
if (!t.isJSXIdentifier(jsxName))
return;
if (expression.isJSXElement())
return;
if (constant_1.DEFAULT_Component_SET.has(jsxName.name) || expression.isIdentifier() || expression.isMemberExpression() || expression.isLiteral() || expression.isLogicalExpression() || expression.isConditionalExpression() || key.name.startsWith('on') || expression.isCallExpression())
return;
utils_1.generateAnonymousState(scope, expression, self.jsxReferencedIdentifiers);
},
JSXElement(path) {
const id = path.node.openingElement.name;
//@fix extrat xx-xx components to customComponents list
if (t.isJSXIdentifier(id) && id.name.indexOf('-') !== -1) {
for (let key of self.componentSourceMap) {
const arr = key[0].split('/');
if (arr[arr.length - 1] === id.name && key[1].length === 0) {
self.customComponents.set(id.name, {
sourcePath: key[0],
type: 'default'
});
}
}
}
if (t.isJSXIdentifier(id) &&
!constant_1.DEFAULT_Component_SET.has(id.name) &&
self.moduleNames.indexOf(id.name) !== -1) {
const name = id.name;
const binding = self.classPath.scope.getBinding(name);
if (binding && t.isImportDeclaration(binding.path.parent)) {
const sourcePath = binding.path.parent.source.value;
if (binding.path.isImportDefaultSpecifier()) {
self.customComponents.set(name, {
sourcePath,
type: 'default'
});
}
else {
self.customComponents.set(name, {
sourcePath,
type: 'pattern'
});
}
}
}
},
MemberExpression: (path) => {
const object = path.get('object');
const property = path.get('property');
if (!(object.isThisExpression() && property.isIdentifier({ name: 'props' }))) {
return;
}
const parentPath = path.parentPath;
if (parentPath.isMemberExpression()) {
const siblingProp = parentPath.get('property');
if (siblingProp.isIdentifier()) {
const name = siblingProp.node.name;
if (name === 'children') {
parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [], true), t.jSXClosingElement(t.jSXIdentifier('slot')), [], true));
}
else if (/^render[A-Z]/.test(name)) {
const slotName = utils_1.getSlotName(name);
parentPath.replaceWith(t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('slot'), [
t.jSXAttribute(t.jSXIdentifier('name'), t.stringLiteral(slotName))
], true), t.jSXClosingElement(t.jSXIdentifier('slot')), []));
this.setMultipleSlots();
}
else {
self.componentProperies.add(siblingProp.node.name);
}
}
}
else if (parentPath.isVariableDeclarator()) {
const siblingId = parentPath.get('id');
if (siblingId.isObjectPattern()) {
const properties = siblingId.node.properties;
for (const prop of properties) {
if (t.isRestProperty(prop)) {
throw utils_1.codeFrameError(prop.loc, 'this.props 不支持使用 rest property 语法,请把每一个 prop 都单独列出来');
}
else if (t.isIdentifier(prop.key)) {
self.componentProperies.add(prop.key.name);
}
}
}
}
},
CallExpression(path) {
const node = path.node;
const callee = node.callee;
if (t.isMemberExpression(callee) && t.isMemberExpression(callee.object)) {
const property = callee.property;
if (t.isIdentifier(property)) {
if (property.name.startsWith('on')) {
self.componentProperies.add(`__fn_${property.name}`);
processThisPropsFnMemberProperties(callee, path, node.arguments, false);
}
else if (property.name === 'call' || property.name === 'apply') {
self.componentProperies.add(`__fn_${property.name}`);
processThisPropsFnMemberProperties(callee.object, path, node.arguments, true);
}
}
}
}
});
}
setComponents() {
this.customComponents.forEach((component, name) => {
this.result.components.push({
path: utils_1.pathResolver(component.sourcePath, this.sourcePath),
name: lodash_1.kebabCase(name),
type: component.type
});
});
}
setMethods() {
const methods = this.classPath.get('body').get('body');
for (const method of methods) {
if (method.isClassMethod()) {
const key = method.get('key');
if (key.isIdentifier()) {
this.methods.set(key.node.name, method);
}
}
}
}
resetConstructor() {
const body = this.classPath.node.body.body;
if (!this.methods.has('constructor')) {
const ctor = buildConstructor();
body.unshift(ctor);
}
if (process.env.NODE_ENV === 'test') {
return;
}
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
method.kind = 'method';
method.key = t.identifier('_constructor');
if (t.isBlockStatement(method.body)) {
for (const statement of method.body.body) {
if (t.isExpressionStatement(statement)) {
const expr = statement.expression;
if (t.isCallExpression(expr) && (t.isIdentifier(expr.callee, { name: 'super' }) || t.isSuper(expr.callee))) {
expr.callee = t.memberExpression(t.identifier('super'), t.identifier('_constructor'));
}
}
}
}
}
}
}
handleLifecyclePropParam(propParam, properties) {
let propsName = null;
if (!propParam) {
return null;
}
if (t.isIdentifier(propParam)) {
propsName = propParam.name;
}
else if (t.isObjectPattern(propParam)) {
for (const prop of propParam.properties) {
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
properties.add(prop.key.name);
}
else if (t.isRestProperty(prop) && t.isIdentifier(prop.argument)) {
propsName = prop.argument.name;
}
}
}
else {
throw utils_1.codeFrameError(propParam.loc, '此生命周期的第一个参数只支持写标识符或对象解构');
}
return propsName;
}
findMoreProps() {
// 第一个参数是 props 的生命周期
const lifeCycles = new Set([
// 'constructor',
'componentDidUpdate',
'shouldComponentUpdate',
'getDerivedStateFromProps',
'getSnapshotBeforeUpdate',
'componentWillReceiveProps',
'componentWillUpdate'
]);
const properties = new Set();
this.methods.forEach((method, name) => {
if (!lifeCycles.has(name)) {
return;
}
const node = method.node;
let propsName = null;
if (t.isClassMethod(node)) {
propsName = this.handleLifecyclePropParam(node.params[0], properties);
}
else if (t.isArrowFunctionExpression(node.value) || t.isFunctionExpression(node.value)) {
propsName = this.handleLifecyclePropParam(node.value.params[0], properties);
}
if (propsName === null) {
return;
}
method.traverse({
MemberExpression(path) {
if (!path.isReferencedMemberExpression()) {
return;
}
const { object, property } = path.node;
if (t.isIdentifier(object, { name: propsName }) && t.isIdentifier(property)) {
properties.add(property.name);
}
},
VariableDeclarator(path) {
const { id, init } = path.node;
if (t.isObjectPattern(id) && t.isIdentifier(init, { name: propsName })) {
for (const prop of id.properties) {
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
properties.add(prop.key.name);
}
}
}
}
});
properties.forEach((value) => {
this.componentProperies.add(value);
});
});
}
parseRender() {
if (this.renderMethod) {
this.result.template = this.result.template
+ new render_1.RenderParser(this.renderMethod, this.methods, this.initState, this.jsxReferencedIdentifiers, this.usedState, this.loopStateName, this.customComponentNames, this.customComponentData, this.componentProperies, this.loopRefs).outputTemplate;
}
}
compile() {
this.traverse();
this.setMethods();
this.setComponents();
this.resetConstructor();
this.findMoreProps();
this.handleRefs();
this.parseRender();
this.result.componentProperies = [...this.componentProperies];
}
}
exports.Transformer = Transformer;
//# sourceMappingURL=class.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.THIRD_PARTY_COMPONENTS = new Set();
// tslint:disable-next-line:variable-name
exports.DEFAULT_Component_SET = new Set([
'view',
'scroll-view',
'swiper',
'movable-view',
'cover-view',
'cover-image',
'icon',
'text',
'rich-text',
'progress',
'button',
'checkbox',
'form',
'input',
'label',
'picker',
'picker-view',
'picker-view-column',
'radio',
'radio-group',
'checkbox-group',
'slider',
'switch',
'textarea',
'navigator',
'audio',
'image',
'video',
'camera',
'live-player',
'live-pusher',
'map',
'canvas',
'open-data',
'web-view',
'swiper-item',
'movable-area',
'movable-view',
'functional-page-navigator',
'ad',
'block',
'import',
'official-account'
]);
exports.INTERNAL_SAFE_GET = 'internal_safe_get';
exports.TARO_PACKAGE_NAME = '@tarojs/taro';
exports.COMPONENTS_PACKAGE_NAME = '@tarojs/components';
exports.REDUX_PACKAGE_NAME = '@tarojs/redux';
exports.MOBX_PACKAGE_NAME = '@tarojs/mobx';
exports.MAP_CALL_ITERATOR = '__item';
exports.INTERNAL_INLINE_STYLE = 'internal_inline_style';
exports.INTERNAL_GET_ORIGNAL = 'internal_get_original';
exports.GEL_ELEMENT_BY_ID = 'getElementById';
exports.LOOP_STATE = '$loopState';
exports.LOOP_ORIGINAL = '$original';
exports.setLoopOriginal = (s) => exports.LOOP_ORIGINAL = s;
exports.LOOP_CALLEE = '$anonymousCallee_';
exports.SPECIAL_COMPONENT_PROPS = new Map();
exports.SPECIAL_COMPONENT_PROPS.set('Progress', new Set([
'activeColor',
'backgroundColor'
]));
exports.IMAGE_COMPONENTS = new Set([
'Image',
'CoverImage'
]);
exports.swanSpecialAttrs = {
'ScrollView': ['scrollTop', 'scrollLeft', 'scrollIntoView'],
'Input': ['value'],
'Textarea': ['value'],
'MovableView': ['x', 'y'],
'Slider': ['value']
};
exports.ALIPAY_BUBBLE_EVENTS = new Set([
'onTouchStart',
'onTouchMove',
'onTouchEnd',
'onTouchCancel',
'onClick',
'onLongTap'
]);
exports.TRANSFORM_COMPONENT_PROPS = new Map();
exports.TRANSFORM_COMPONENT_PROPS.set("alipay" /* alipay */, {
'Canvas': {
'canvasId': 'id'
}
});
exports.lessThanSignPlacehold = '__LESS_THAN_SIGN_PLACEHOLDER__';
//# sourceMappingURL=constant.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"constant.js","sourceRoot":"","sources":["../../src/constant.ts"],"names":[],"mappings":";;AAEa,QAAA,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAA;AAEvD,yCAAyC;AAC5B,QAAA,qBAAqB,GAAG,IAAI,GAAG,CAAS;IACnD,MAAM;IACN,YAAY;IACZ,QAAQ;IACR,aAAa;IACb,WAAW;IACX,YAAY;IACZ,MAAM;IACN,MAAM;IACN,UAAU;IACV,UAAU;IACV,QAAQ;IACR,UAAU;IACV,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,kBAAkB;IAClB,OAAO;IACP,YAAY;IACZ,eAAe;IACf,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,WAAW;IACX,OAAO;IACP,OAAO;IACP,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,YAAY;IACZ,KAAK;IACL,QAAQ;IACR,UAAU;IACV,SAAS;IACT,YAAY;IACZ,aAAa;IACb,aAAa;IACb,yBAAyB;IACzB,IAAI;IACJ,OAAO;IACP,QAAQ;IACR,iBAAiB;CAClB,CAAC,CAAA;AAEW,QAAA,iBAAiB,GAAG,mBAAmB,CAAA;AAEvC,QAAA,iBAAiB,GAAG,cAAc,CAAA;AAElC,QAAA,uBAAuB,GAAG,oBAAoB,CAAA;AAE9C,QAAA,kBAAkB,GAAG,eAAe,CAAA;AAEpC,QAAA,iBAAiB,GAAG,cAAc,CAAA;AAElC,QAAA,iBAAiB,GAAG,QAAQ,CAAA;AAE5B,QAAA,qBAAqB,GAAG,uBAAuB,CAAA;AAE/C,QAAA,oBAAoB,GAAG,uBAAuB,CAAA;AAE9C,QAAA,iBAAiB,GAAG,gBAAgB,CAAA;AAEpC,QAAA,UAAU,GAAG,YAAY,CAAA;AAE3B,QAAA,aAAa,GAAG,WAAW,CAAA;AAEzB,QAAA,eAAe,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,qBAAa,GAAG,CAAC,CAAA;AAElD,QAAA,WAAW,GAAG,mBAAmB,CAAA;AAEjC,QAAA,uBAAuB,GAAG,IAAI,GAAG,EAAuB,CAAA;AAErE,+BAAuB,CAAC,GAAG,CACzB,UAAU,EACV,IAAI,GAAG,CAAC;IACN,aAAa;IACb,iBAAiB;CAClB,CAAC,CACH,CAAA;AAEY,QAAA,gBAAgB,GAAG,IAAI,GAAG,CAAS;IAC9C,OAAO;IACP,YAAY;CACb,CAAC,CAAA;AAEW,QAAA,gBAAgB,GAAG;IAC9B,YAAY,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,gBAAgB,CAAC;IAC3D,OAAO,EAAE,CAAC,OAAO,CAAC;IAClB,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;IACzB,QAAQ,EAAE,CAAC,OAAO,CAAC;CACpB,CAAA;AAEY,QAAA,oBAAoB,GAAG,IAAI,GAAG,CAAS;IAClD,cAAc;IACd,aAAa;IACb,YAAY;IACZ,eAAe;IACf,SAAS;IACT,WAAW;CACZ,CAAC,CAAA;AAEW,QAAA,yBAAyB,GAAG,IAAI,GAAG,EAA0D,CAAA;AAE1G,iCAAyB,CAAC,GAAG,wBAAkB;IAC7C,QAAQ,EAAE;QACR,UAAU,EAAE,IAAI;KACjB;CACF,CAAC,CAAA;AAEW,QAAA,qBAAqB,GAAG,gCAAgC,CAAA"}

View File

@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const voidHtmlTags = new Set([
// 'image',
'img',
'input',
'import'
]);
if (process.env.NODE_ENV === 'test') {
voidHtmlTags.add('image');
}
function stringifyAttributes(input) {
const attributes = [];
for (const key of Object.keys(input)) {
let value = input[key];
if (value === false) {
continue;
}
if (Array.isArray(value)) {
value = value.join(' ');
}
let attribute = key;
if (value !== true) {
attribute += `="${String(value)}"`;
}
attributes.push(attribute);
}
return attributes.length > 0 ? ' ' + attributes.join(' ') : '';
}
exports.createHTMLElement = (options) => {
options = Object.assign({
name: 'div',
attributes: {},
value: ''
}, options);
const isVoidTag = voidHtmlTags.has(options.name);
let ret = `<${options.name}${stringifyAttributes(options.attributes)}${isVoidTag ? `/` : ''}>`;
if (!isVoidTag) {
ret += `${options.value}</${options.name}>`;
}
return ret;
};
//# sourceMappingURL=create-html-element.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"create-html-element.js","sourceRoot":"","sources":["../../src/create-html-element.ts"],"names":[],"mappings":";;AAAA,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACnC,WAAW;IACX,KAAK;IACL,OAAO;IACP,QAAQ;CACT,CAAC,CAAA;AAEF,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE;IACnC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;CAC1B;AAQD,SAAS,mBAAmB,CAAE,KAAa;IACzC,MAAM,UAAU,GAAa,EAAE,CAAA;IAE/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QACpC,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;QAEtB,IAAI,KAAK,KAAK,KAAK,EAAE;YACnB,SAAQ;SACT;QAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACxB,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;SACxB;QAED,IAAI,SAAS,GAAG,GAAG,CAAA;QAEnB,IAAI,KAAK,KAAK,IAAI,EAAE;YAClB,SAAS,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,GAAG,CAAA;SACnC;QAED,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;KAC3B;IAED,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAEhE,CAAC;AAEY,QAAA,iBAAiB,GAAG,CAAC,OAAgB,EAAE,EAAE;IACpD,OAAO,GAAG,MAAM,CAAC,MAAM,CACrB;QACE,IAAI,EAAE,KAAK;QACX,UAAU,EAAE,EAAE;QACd,KAAK,EAAE,EAAE;KACV,EACD,OAAO,CACR,CAAA;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAEhD,IAAI,GAAG,GAAG,IAAI,OAAO,CAAC,IAAI,GAAG,mBAAmB,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAG,GAAG,CAAA;IAE/F,IAAI,CAAC,SAAS,EAAE;QACd,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,IAAI,GAAG,CAAA;KAC5C;IAED,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA"}

View File

@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const eslint_1 = require("eslint");
const utils_1 = require("./utils");
const cli = new eslint_1.CLIEngine({
baseConfig: {
extends: ['plugin:taro/transformer']
},
useEslintrc: false,
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2018,
ecmaFeatures: {
jsx: true,
legacyDecorators: true
}
}
});
exports.eslintValidation = () => {
return {
visitor: {
Program(_, state) {
const { file: { code } } = state;
const report = cli.executeOnText(code);
if (report.errorCount > 0) {
for (const result of report.results) {
for (const msg of result.messages) {
const err = utils_1.codeFrameError({
start: {
line: msg.line,
column: msg.column
},
end: {
line: msg.endLine,
column: msg.endColumn
}
}, msg.message);
// tslint:disable-next-line
console.warn('\n' + `ESLint(${msg.ruleId}) 错误:` + err.message + '\n');
}
}
}
}
}
};
};
//# sourceMappingURL=eslint.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"eslint.js","sourceRoot":"","sources":["../../src/eslint.ts"],"names":[],"mappings":";;AAAA,mCAAkC;AAElC,mCAAwC;AAExC,MAAM,GAAG,GAAG,IAAI,kBAAS,CAAC;IACxB,UAAU,EAAE;QACV,OAAO,EAAE,CAAC,yBAAyB,CAAC;KACrC;IACD,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,cAAc;IACtB,aAAa,EAAE;QACb,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE;YACZ,GAAG,EAAE,IAAI;YACT,gBAAgB,EAAE,IAAI;SACvB;KACF;CACF,CAAC,CAAA;AAEW,QAAA,gBAAgB,GAEzB,GAAG,EAAE;IACP,OAAO;QACL,OAAO,EAAE;YACP,OAAO,CAAE,CAAC,EAAE,KAAK;gBACf,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAA;gBAChC,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;gBACtC,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,EAAE;oBACzB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE;wBACnC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE;4BACjC,MAAM,GAAG,GAAG,sBAAc,CAAC;gCACzB,KAAK,EAAE;oCACL,IAAI,EAAE,GAAG,CAAC,IAAI;oCACd,MAAM,EAAE,GAAG,CAAC,MAAM;iCACnB;gCACD,GAAG,EAAE;oCACH,IAAI,EAAE,GAAG,CAAC,OAAO;oCACjB,MAAM,EAAE,GAAG,CAAC,SAAS;iCACtB;6BACF,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;4BACf,2BAA2B;4BAC3B,OAAO,CAAC,IAAI,CAAC,IAAI,GAAG,UAAU,GAAG,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;yBACtE;qBACF;iBACF;YACH,CAAC;SACF;KACF,CAAA;AACH,CAAC,CAAA"}

View File

@ -0,0 +1,488 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const babel_traverse_1 = require("babel-traverse");
const babel_generator_1 = require("babel-generator");
const html_1 = require("html");
const babel_core_1 = require("babel-core");
const ts = require("typescript");
const class_1 = require("./class");
const utils_1 = require("./utils");
const t = require("babel-types");
const constant_1 = require("./constant");
const adapter_1 = require("./adapter");
const options_1 = require("./options");
const lodash_1 = require("lodash");
const template = require('babel-template');
function getIdsFromMemberProps(member) {
let ids = [];
const { object, property } = member;
if (t.isMemberExpression(object)) {
ids = ids.concat(getIdsFromMemberProps(object));
}
if (t.isThisExpression(object)) {
ids.push('this');
}
if (t.isIdentifier(object)) {
ids.push(object.name);
}
if (t.isIdentifier(property)) {
ids.push(property.name);
}
return ids;
}
/**
* TS 编译器会把 class property 移到构造器
* 而小程序要求 `config` 和所有函数在初始化(after new Class)之后就收集到所有的函数和 config 信息
* 所以当如构造器里有 this.func = () => {...} 的形式就给他转换成普通的 classProperty function
* 如果有 config 就给他还原
*/
function resetTSClassProperty(body) {
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
if (t.isBlockStatement(method.body)) {
method.body.body = method.body.body.filter(statement => {
if (t.isExpressionStatement(statement) && t.isAssignmentExpression(statement.expression)) {
const expr = statement.expression;
const { left, right } = expr;
if (t.isMemberExpression(left) &&
t.isThisExpression(left.object) &&
t.isIdentifier(left.property)) {
if ((t.isArrowFunctionExpression(right) || t.isFunctionExpression(right))
||
(left.property.name === 'config' && t.isObjectExpression(right))) {
const classProp = t.classProperty(left.property, right);
body.push(classProp);
handleThirdPartyComponent(classProp);
return false;
}
}
}
return true;
});
}
}
}
}
function findDeclarationScope(path, id) {
const scopePath = path.findParent(p => !!p.scope.getOwnBindingIdentifier(id.name));
if (scopePath) {
return scopePath;
}
throw utils_1.codeFrameError(path.node, '该引用从未被定义');
}
function buildFullPathThisPropsRef(id, memberIds, path) {
const scopePath = findDeclarationScope(path, id);
const binding = scopePath.scope.getOwnBinding(id.name);
if (binding) {
const bindingPath = binding.path;
if (bindingPath.isVariableDeclarator()) {
const dclId = bindingPath.get('id');
const dclInit = bindingPath.get('init');
let dclInitIds = [];
if (dclInit.isMemberExpression()) {
dclInitIds = getIdsFromMemberProps(dclInit.node);
if (dclId.isIdentifier()) {
memberIds.shift();
}
if (dclInitIds[0] === 'this' && dclInitIds[1] === 'props') {
return template(dclInitIds.concat(memberIds).join('.'))().expression;
}
}
}
}
}
function handleThirdPartyComponent(expr) {
if (t.isClassProperty(expr) && expr.key.name === 'config' && t.isObjectExpression(expr.value)) {
const properties = expr.value.properties;
for (const prop of properties) {
if (t.isObjectProperty(prop) &&
(t.isIdentifier(prop.key, { name: 'usingComponents' }) || t.isStringLiteral(prop.key, { value: 'usingComponents' })) &&
t.isObjectExpression(prop.value)) {
for (const value of prop.value.properties) {
if (t.isObjectProperty(value)) {
if (t.isStringLiteral(value.key)) {
constant_1.THIRD_PARTY_COMPONENTS.add(value.key.value);
}
if (t.isIdentifier(value.key)) {
constant_1.THIRD_PARTY_COMPONENTS.add(value.key.name);
}
}
}
}
}
}
}
function transform(options) {
if (options.adapter) {
adapter_1.setAdapter(options.adapter);
}
if (adapter_1.Adapter.type === "swan" /* swan */) {
constant_1.setLoopOriginal('privateOriginal');
}
constant_1.THIRD_PARTY_COMPONENTS.clear();
const code = options.isTyped
? ts.transpile(options.code, {
jsx: ts.JsxEmit.Preserve,
target: ts.ScriptTarget.ESNext,
importHelpers: true,
noEmitHelpers: true
})
: options.code;
options.env = Object.assign({ 'process.env.TARO_ENV': options.adapter || 'weapp' }, options.env || {});
options_1.setTransformOptions(options);
utils_1.setting.sourceCode = code;
// babel-traverse 无法生成 Hub
// 导致 Path#getSource|buildCodeFrameError 都无法直接使用
// 原因大概是 babylon.parse 没有生成 File 实例导致 scope 和 path 原型上都没有 `file`
// 将来升级到 babel@7 可以直接用 parse 而不是 transform
const ast = babel_core_1.transform(code, options_1.buildBabelTransformOptions()).ast;
if (options.isNormal) {
return { ast };
}
// transformFromAst(ast, code)
let result;
const componentSourceMap = new Map();
const imageSource = new Set();
const importSources = new Set();
let componentProperies = [];
let mainClass;
let storeName;
let renderMethod;
let isImportTaro = false;
babel_traverse_1.default(ast, {
TemplateLiteral(path) {
const nodes = [];
const { quasis, expressions } = path.node;
let index = 0;
if (path.parentPath.isTaggedTemplateExpression()) {
return;
}
for (const elem of quasis) {
if (elem.value.cooked) {
nodes.push(t.stringLiteral(elem.value.cooked));
}
if (index < expressions.length) {
const expr = expressions[index++];
if (!t.isStringLiteral(expr, { value: '' })) {
nodes.push(expr);
}
}
}
// + 号连接符必须保证第一和第二个 node 都是字符串
if (!t.isStringLiteral(nodes[0]) && !t.isStringLiteral(nodes[1])) {
nodes.unshift(t.stringLiteral(''));
}
let root = nodes[0];
for (let i = 1; i < nodes.length; i++) {
root = t.binaryExpression('+', root, nodes[i]);
}
path.replaceWith(root);
},
ClassDeclaration(path) {
mainClass = path;
const superClass = utils_1.getSuperClassCode(path);
if (superClass) {
try {
componentProperies = transform({
isRoot: false,
isApp: false,
code: superClass.code,
isTyped: true,
sourcePath: superClass.sourcePath,
outputPath: superClass.sourcePath
}).componentProperies;
}
catch (error) {
//
}
}
},
ClassExpression(path) {
mainClass = path;
},
ClassMethod(path) {
if (t.isIdentifier(path.node.key) && path.node.key.name === 'render') {
renderMethod = path;
}
},
IfStatement(path) {
const consequent = path.get('consequent');
if (!consequent.isBlockStatement()) {
consequent.replaceWith(t.blockStatement([
consequent.node
]));
}
},
CallExpression(path) {
const callee = path.get('callee');
if (utils_1.isContainJSXElement(path)) {
return;
}
if (callee.isReferencedMemberExpression()) {
const id = utils_1.findFirstIdentifierFromMemberExpression(callee.node);
const property = callee.node.property;
if (t.isIdentifier(property) && property.name.startsWith('on')) {
const funcExpr = path.findParent(p => p.isFunctionExpression());
if (funcExpr && funcExpr.isFunctionExpression()) {
const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' }));
if (taroAPI && taroAPI.isCallExpression()) {
throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值');
}
}
}
const calleeIds = getIdsFromMemberProps(callee.node);
if (t.isIdentifier(id) && id.name.startsWith('on') && "alipay" /* alipay */ !== adapter_1.Adapter.type) {
const fullPath = buildFullPathThisPropsRef(id, calleeIds, path);
if (fullPath) {
path.replaceWith(t.callExpression(fullPath, path.node.arguments));
}
}
}
if (callee.isReferencedIdentifier()) {
const id = callee.node;
const ids = [id.name];
if (t.isIdentifier(id) && id.name.startsWith('on')) {
const funcExpr = path.findParent(p => p.isFunctionExpression());
if (funcExpr && funcExpr.isFunctionExpression()) {
const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' }));
if (taroAPI && taroAPI.isCallExpression()) {
throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值');
}
}
const fullPath = buildFullPathThisPropsRef(id, ids, path);
if (fullPath) {
path.replaceWith(t.callExpression(fullPath, path.node.arguments));
}
}
}
},
// JSXIdentifier (path) {
// const parentPath = path.parentPath
// if (!parentPath.isJSXAttribute()) {
// return
// }
// const element = parentPath.parentPath
// if (!element.isJSXOpeningElement()) {
// return
// }
// const elementName = element.get('name')
// if (!elementName.isJSXIdentifier()) {
// return
// }
// if (DEFAULT_Component_SET.has(elementName.node.name)) {
// return
// }
// const expr = parentPath.get('value.expression')
// },
JSXElement(path) {
const assignment = path.findParent(p => p.isAssignmentExpression());
if (assignment && assignment.isAssignmentExpression() && !options.isTyped) {
const left = assignment.node.left;
if (t.isIdentifier(left)) {
const binding = assignment.scope.getBinding(left.name);
if (binding && binding.scope === assignment.scope) {
if (binding.path.isVariableDeclarator()) {
binding.path.node.init = path.node;
assignment.remove();
}
else {
throw utils_1.codeFrameError(path.node, '同一个作用域的JSX 变量延时赋值没有意义。详见https://github.com/NervJS/taro/issues/550');
}
}
}
}
const switchStatement = path.findParent(p => p.isSwitchStatement());
if (switchStatement && switchStatement.isSwitchStatement()) {
const { discriminant, cases } = switchStatement.node;
const ifStatement = cases.map((Case, index) => {
const [consequent] = Case.consequent;
if (!t.isBlockStatement(consequent)) {
throw utils_1.codeFrameError(switchStatement.node, '含有 JSX 的 switch case 语句必须每种情况都用花括号 `{}` 包裹结果');
}
const block = t.blockStatement(consequent.body.filter(b => !t.isBreakStatement(b)));
if (index !== cases.length - 1 && t.isNullLiteral(Case.test)) {
throw utils_1.codeFrameError(Case, '含有 JSX 的 switch case 语句只有最后一个 case 才能是 default');
}
const test = Case.test === null ? t.nullLiteral() : t.binaryExpression('===', discriminant, Case.test);
return { block, test };
}).reduceRight((ifStatement, item) => {
if (t.isNullLiteral(item.test)) {
ifStatement.alternate = item.block;
return ifStatement;
}
const newStatement = t.ifStatement(item.test, item.block, t.isBooleanLiteral(ifStatement.test, { value: false })
? ifStatement.alternate
: ifStatement);
return newStatement;
}, t.ifStatement(t.booleanLiteral(false), t.blockStatement([])));
switchStatement.insertAfter(ifStatement);
switchStatement.remove();
}
const isForStatement = (p) => p && (p.isForStatement() || p.isForInStatement() || p.isForOfStatement());
const forStatement = path.findParent(isForStatement);
if (isForStatement(forStatement)) {
throw utils_1.codeFrameError(forStatement.node, '不行使用 for 循环操作 JSX 元素详情https://github.com/NervJS/taro/blob/master/packages/eslint-plugin-taro/docs/manipulate-jsx-as-array.md');
}
const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
if (loopCallExpr && loopCallExpr.isCallExpression()) {
const [func] = loopCallExpr.node.arguments;
if (t.isArrowFunctionExpression(func) && !t.isBlockStatement(func.body)) {
func.body = t.blockStatement([
t.returnStatement(func.body)
]);
}
}
},
JSXOpeningElement(path) {
const { name } = path.node.name;
if (name === 'Provider') {
const modules = path.scope.getAllBindings('module');
const providerBinding = Object.values(modules).some((m) => m.identifier.name === 'Provider');
if (providerBinding) {
path.node.name = t.jSXIdentifier('view');
const store = path.node.attributes.find(attr => attr.name.name === 'store');
if (store && t.isJSXExpressionContainer(store.value) && t.isIdentifier(store.value.expression)) {
storeName = store.value.expression.name;
}
path.node.attributes = [];
}
}
if (constant_1.IMAGE_COMPONENTS.has(name)) {
for (const attr of path.node.attributes) {
if (attr.name.name === 'src') {
if (t.isStringLiteral(attr.value)) {
imageSource.add(attr.value.value);
}
else if (t.isJSXExpressionContainer(attr.value)) {
if (t.isStringLiteral(attr.value.expression)) {
imageSource.add(attr.value.expression.value);
}
}
}
}
}
},
JSXAttribute(path) {
const { name, value } = path.node;
if (!t.isJSXIdentifier(name) || value === null || t.isStringLiteral(value) || t.isJSXElement(value)) {
return;
}
const expr = value.expression;
const exprPath = path.get('value.expression');
const classDecl = path.findParent(p => p.isClassDeclaration());
const classDeclName = classDecl && classDecl.isClassDeclaration() && lodash_1.get(classDecl, 'node.id.name', '');
let isConverted = false;
if (classDeclName) {
isConverted = classDeclName === '_C' || classDeclName.endsWith('Tmpl');
}
if (!t.isBinaryExpression(expr, { operator: '+' }) && !t.isLiteral(expr) && name.name === 'style' && !isConverted) {
const jsxID = path.findParent(p => p.isJSXOpeningElement()).get('name');
if (jsxID && jsxID.isJSXIdentifier() && constant_1.DEFAULT_Component_SET.has(jsxID.node.name)) {
exprPath.replaceWith(t.callExpression(t.identifier(constant_1.INTERNAL_INLINE_STYLE), [expr]));
}
}
if (name.name.startsWith('on')) {
if (exprPath.isReferencedIdentifier()) {
const ids = [expr.name];
const fullPath = buildFullPathThisPropsRef(expr, ids, path);
if (fullPath) {
exprPath.replaceWith(fullPath);
}
}
if (exprPath.isReferencedMemberExpression()) {
const id = utils_1.findFirstIdentifierFromMemberExpression(expr);
const ids = getIdsFromMemberProps(expr);
if (t.isIdentifier(id)) {
const fullPath = buildFullPathThisPropsRef(id, ids, path);
if (fullPath) {
exprPath.replaceWith(fullPath);
}
}
}
// @TODO: bind 的处理待定
}
},
ImportDeclaration(path) {
const source = path.node.source.value;
if (importSources.has(source)) {
throw utils_1.codeFrameError(path.node, '无法在同一文件重复 import 相同的包。');
}
else {
importSources.add(source);
}
const names = [];
if (source === constant_1.TARO_PACKAGE_NAME) {
isImportTaro = true;
path.node.specifiers.push(t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)), t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)), t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE)), t.importSpecifier(t.identifier(constant_1.GEL_ELEMENT_BY_ID), t.identifier(constant_1.GEL_ELEMENT_BY_ID)));
}
if (source === constant_1.REDUX_PACKAGE_NAME || source === constant_1.MOBX_PACKAGE_NAME) {
path.node.specifiers.forEach((s, index, specs) => {
if (s.local.name === 'Provider') {
specs.splice(index, 1);
specs.push(t.importSpecifier(t.identifier('setStore'), t.identifier('setStore')));
}
});
}
path.traverse({
ImportDefaultSpecifier(path) {
const name = path.node.local.name;
constant_1.DEFAULT_Component_SET.has(name) || names.push(name);
},
ImportSpecifier(path) {
const name = path.node.imported.name;
constant_1.DEFAULT_Component_SET.has(name) || names.push(name);
if (source === constant_1.TARO_PACKAGE_NAME && name === 'Component') {
path.node.local = t.identifier('__BaseComponent');
}
}
});
componentSourceMap.set(source, names);
}
});
if (!isImportTaro) {
ast.program.body.unshift(t.importDeclaration([
t.importDefaultSpecifier(t.identifier('Taro')),
t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)),
t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)),
t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE))
], t.stringLiteral('@tarojs/taro')));
}
if (!mainClass) {
throw new Error('未找到 Taro.Component 的类定义');
}
mainClass.node.body.body.forEach(handleThirdPartyComponent);
const storeBinding = mainClass.scope.getBinding(storeName);
mainClass.scope.rename('Component', '__BaseComponent');
if (storeBinding) {
const statementPath = storeBinding.path.getStatementParent();
if (statementPath) {
ast.program.body.forEach((node, index, body) => {
if (node === statementPath.node) {
body.splice(index + 1, 0, t.expressionStatement(t.callExpression(t.identifier('setStore'), [
t.identifier(storeName)
])));
}
});
}
}
resetTSClassProperty(mainClass.node.body.body);
if (options.isApp) {
renderMethod.replaceWith(t.classMethod('method', t.identifier('_createData'), [], t.blockStatement([])));
return { ast };
}
//@fix add arg => componentSourceMap
result = new class_1.Transformer(mainClass, options.sourcePath, componentProperies, componentSourceMap).result;
result.code = babel_generator_1.default(ast).code;
result.ast = ast;
const lessThanSignReg = new RegExp(constant_1.lessThanSignPlacehold, 'g');
result.compressedTemplate = result.template;
result.template = html_1.prettyPrint(result.template, {
max_char: 0,
unformatted: process.env.NODE_ENV === 'test' ? [] : ['text']
});
result.template = result.template.replace(lessThanSignReg, '<');
result.imageSrcs = Array.from(imageSource);
return result;
}
exports.default = transform;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,258 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const babel_generator_1 = require("babel-generator");
const t = require("babel-types");
const lodash_1 = require("lodash");
const constant_1 = require("./constant");
const create_html_element_1 = require("./create-html-element");
const utils_1 = require("./utils");
const adapter_1 = require("./adapter");
function isStartWithWX(str) {
return str[0] === 'w' && str[1] === 'x';
}
exports.isStartWithWX = isStartWithWX;
const specialComponentName = ['block', 'Block', 'slot', 'Slot'];
function removeJSXThisProperty(path) {
if (!path.parentPath.isCallExpression()) {
const p = path.getSibling('property');
if (p.isIdentifier({ name: 'props' }) ||
p.isIdentifier({ name: 'data' })) {
path.parentPath.replaceWithSourceString('this');
}
else {
path.parentPath.replaceWith(p);
}
}
}
exports.removeJSXThisProperty = removeJSXThisProperty;
function findJSXAttrByName(attrs, name) {
for (const attr of attrs) {
if (!t.isJSXIdentifier(attr.name)) {
break;
}
if (attr.name.name === name) {
return attr;
}
}
return null;
}
exports.findJSXAttrByName = findJSXAttrByName;
function buildRefTemplate(name, refName, loop, key) {
const attrs = [
t.jSXAttribute(t.jSXIdentifier('is'), t.stringLiteral(name)),
t.jSXAttribute(t.jSXIdentifier('data'), t.stringLiteral(`{{...${refName ? `${loop ? '' : '$$'}${refName}` : '__data'}}}`))
];
if (key) {
attrs.push(key);
}
return t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('template'), attrs), t.jSXClosingElement(t.jSXIdentifier('template')), []);
}
exports.buildRefTemplate = buildRefTemplate;
function buildJSXAttr(name, value) {
return t.jSXAttribute(t.jSXIdentifier(name), t.jSXExpressionContainer(value));
}
exports.buildJSXAttr = buildJSXAttr;
function newJSXIfAttr(jsx, value) {
jsx.openingElement.attributes.push(buildJSXAttr(adapter_1.Adapter.if, value));
}
exports.newJSXIfAttr = newJSXIfAttr;
function setJSXAttr(jsx, name, value, path) {
const element = jsx.openingElement;
if (!t.isJSXIdentifier(element.name)) {
return;
}
if (element.name.name === 'Block' || element.name.name === 'block' || !path) {
jsx.openingElement.attributes.push(t.jSXAttribute(t.jSXIdentifier(name), value));
}
else {
const block = buildBlockElement();
setJSXAttr(block, name, value);
block.children = [jsx];
path.node = block;
}
}
exports.setJSXAttr = setJSXAttr;
function isAllLiteral(...args) {
return args.every(p => t.isLiteral(p));
}
exports.isAllLiteral = isAllLiteral;
function buildBlockElement() {
return t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('block'), []), t.jSXClosingElement(t.jSXIdentifier('block')), []);
}
exports.buildBlockElement = buildBlockElement;
function parseJSXChildren(children) {
return children
.filter(child => {
return !(t.isJSXText(child) && child.value.trim() === '');
})
.reduce((str, child) => {
if (t.isJSXText(child)) {
const strings = [];
child.value.split(/(\r?\n\s*)/).forEach((val) => {
const value = val.replace(/\u00a0/g, '&nbsp;').trimLeft();
if (!value) {
return;
}
if (value.startsWith('\n')) {
return;
}
strings.push(value);
});
return str + strings.join('');
}
if (t.isJSXElement(child)) {
return str + parseJSXElement(child);
}
if (t.isJSXExpressionContainer(child)) {
if (t.isJSXElement(child.expression)) {
return str + parseJSXElement(child.expression);
}
return str + `{${utils_1.decodeUnicode(babel_generator_1.default(child, {
quotes: 'single',
jsonCompatibleStrings: true
})
.code)
.replace(/(this\.props\.)|(this\.data\.)/g, '')
.replace(/(props\.)|(data\.)/g, '')
.replace(/this\./g, '')
.replace(/</g, constant_1.lessThanSignPlacehold)}}`;
}
return str;
}, '');
}
function parseJSXElement(element) {
const children = element.children;
const { attributes, name } = element.openingElement;
//const TRIGGER_OBSERER = adapter_1.Adapter.type === "swan" /* swan */ ? 'privateTriggerObserer' : '__triggerObserer';
if (t.isJSXMemberExpression(name)) {
throw utils_1.codeFrameError(name.loc, '暂不支持 JSX 成员表达式');
}
const componentName = name.name;
const isDefaultComponent = constant_1.DEFAULT_Component_SET.has(componentName);
const componentSpecialProps = constant_1.SPECIAL_COMPONENT_PROPS.get(componentName);
const componentTransfromProps = constant_1.TRANSFORM_COMPONENT_PROPS.get(adapter_1.Adapter.type);
let hasElseAttr = false;
attributes.forEach((a, index) => {
if (a.name.name === adapter_1.Adapter.else && !['block', 'Block'].includes(componentName) && !isDefaultComponent) {
hasElseAttr = true;
attributes.splice(index, 1);
}
});
if (hasElseAttr) {
return create_html_element_1.createHTMLElement({
name: 'block',
attributes: {
[adapter_1.Adapter.else]: true
},
value: parseJSXChildren([element])
});
}
let attributesTrans = {};
if (attributes.length) {
attributesTrans = attributes.reduce((obj, attr) => {
if (t.isJSXSpreadAttribute(attr)) {
throw utils_1.codeFrameError(attr.loc, 'JSX 参数暂不支持 ...spread 表达式');
}
let name = attr.name.name;
if (constant_1.DEFAULT_Component_SET.has(componentName)) {
if (name === 'className') {
name = 'class';
}
}
let value = true;
let attrValue = attr.value;
if (typeof name === 'string') {
const isAlipayEvent = adapter_1.Adapter.type === "alipay" /* alipay */ && /(^on[A-Z_])|(^catch[A-Z_])/.test(name);
if (t.isStringLiteral(attrValue)) {
value = attrValue.value;
}
else if (t.isJSXExpressionContainer(attrValue)) {
let isBindEvent = (name.startsWith('bind') && name !== 'bind') || (name.startsWith('catch') && name !== 'catch');
let code = utils_1.decodeUnicode(babel_generator_1.default(attrValue.expression, {
quotes: 'single',
concise: true
}).code)
.replace(/"/g, "'")
.replace(/(this\.props\.)|(this\.data\.)/g, '')
.replace(/this\./g, '');
if ("swan" /* swan */ === adapter_1.Adapter.type &&
code !== 'true' &&
code !== 'false' &&
constant_1.swanSpecialAttrs[componentName] &&
constant_1.swanSpecialAttrs[componentName].includes(name)) {
value = `{= ${code} =}`;
}
else {
if (adapter_1.Adapter.key === name) {
const splitCode = code.split('.');
if (splitCode.length > 1) {
value = splitCode.slice(1).join('.');
}
else {
value = code;
}
}
else {
value = isBindEvent || isAlipayEvent ? code : `{{${code}}}`;
}
}
if (adapter_1.Adapter.type === "swan" /* swan */ && name === adapter_1.Adapter.for) {
value = code;
}
if (t.isStringLiteral(attrValue.expression)) {
value = attrValue.expression.value;
}
}
else if (attrValue === null && name !== adapter_1.Adapter.else) {
value = `{{true}}`;
}
if (constant_1.THIRD_PARTY_COMPONENTS.has(componentName) && /^bind/.test(name) && name.includes('-')) {
name = name.replace(/^bind/, 'bind:');
}
if (componentTransfromProps && componentTransfromProps[componentName]) {
const transfromProps = componentTransfromProps[componentName];
Object.keys(transfromProps).forEach(oriName => {
if (transfromProps.hasOwnProperty(name)) {
name = transfromProps[oriName];
}
});
}
if ((componentName === 'Input' || componentName === 'input') && name === 'maxLength') {
obj['maxlength'] = value;
}
else if (componentSpecialProps && componentSpecialProps.has(name) ||
name.startsWith('__fn_') ||
isAlipayEvent) {
obj[name] = value;
}
else {
obj[isDefaultComponent && !name.includes('-') && !name.includes(':') ? lodash_1.kebabCase(name) : name] = value;
}
}
if (!isDefaultComponent && !specialComponentName.includes(componentName)) {
//obj[TRIGGER_OBSERER] = '{{ _triggerObserer }}';
}
return obj;
}, {});
}
else if (!isDefaultComponent && !specialComponentName.includes(componentName)) {
//attributesTrans[TRIGGER_OBSERER] = '{{ _triggerObserer }}';
}
return create_html_element_1.createHTMLElement({
name: lodash_1.kebabCase(componentName),
attributes: attributesTrans,
value: parseJSXChildren(children)
});
}
exports.parseJSXElement = parseJSXElement;
function generateHTMLTemplate(template, name) {
return create_html_element_1.createHTMLElement({
name: 'template',
attributes: {
name
},
value: parseJSXElement(template)
});
}
exports.generateHTMLTemplate = generateHTMLTemplate;
//# sourceMappingURL=jsx.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Lifecycle;
(function (Lifecycle) {
Lifecycle["constructor"] = "constructor";
Lifecycle["componentWillMount"] = "componentWillMount";
Lifecycle["componentDidMount"] = "componentDidMount";
Lifecycle["componentWillUpdate"] = "componentWillUpdate";
Lifecycle["componentDidUpdate"] = "componentDidUpdate";
Lifecycle["componentWillUnmount"] = "componentWillUnmount";
Lifecycle["componentDidCatch"] = "componentDidCatch";
Lifecycle["componentDidShow"] = "componentDidShow";
Lifecycle["componentDidHide"] = "componentDidHide";
Lifecycle["componentDidAttached"] = "componentDidAttached";
Lifecycle["componentDidMoved"] = "componentDidMoved";
Lifecycle["shouldComponentUpdate"] = "shouldComponentUpdate";
Lifecycle["componentWillReceiveProps"] = "componentWillReceiveProps";
})(Lifecycle = exports.Lifecycle || (exports.Lifecycle = {}));
exports.PageLifecycle = {
[Lifecycle.componentDidMount]: 'onLaunch',
[Lifecycle.componentWillMount]: 'onLoad',
[Lifecycle.componentWillUnmount]: 'onUnload',
[Lifecycle.componentDidShow]: 'onShow',
[Lifecycle.componentDidHide]: 'onHide'
};
exports.ComponentLifeCycle = {
[Lifecycle.componentWillMount]: 'created',
[Lifecycle.componentDidAttached]: 'attached',
[Lifecycle.componentDidMount]: 'ready',
[Lifecycle.componentDidMoved]: 'moved',
[Lifecycle.componentWillUnmount]: 'detached'
};
//# sourceMappingURL=lifecycle.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/lifecycle.ts"],"names":[],"mappings":";;AAAA,IAAY,SAcX;AAdD,WAAY,SAAS;IACnB,wCAA2B,CAAA;IAC3B,sDAAyC,CAAA;IACzC,oDAAuC,CAAA;IACvC,wDAA2C,CAAA;IAC3C,sDAAyC,CAAA;IACzC,0DAA6C,CAAA;IAC7C,oDAAuC,CAAA;IACvC,kDAAqC,CAAA;IACrC,kDAAqC,CAAA;IACrC,0DAA6C,CAAA;IAC7C,oDAAuC,CAAA;IACvC,4DAA+C,CAAA;IAC/C,oEAAuD,CAAA;AACzD,CAAC,EAdW,SAAS,GAAT,iBAAS,KAAT,iBAAS,QAcpB;AAEY,QAAA,aAAa,GAAG;IAC3B,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,UAAU;IACzC,CAAC,SAAS,CAAC,kBAAkB,CAAC,EAAE,QAAQ;IACxC,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,UAAU;IAC5C,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,QAAQ;IACtC,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE,QAAQ;CACvC,CAAA;AAEY,QAAA,kBAAkB,GAAG;IAChC,CAAC,SAAS,CAAC,kBAAkB,CAAC,EAAE,SAAS;IACzC,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,UAAU;IAC5C,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,OAAO;IACtC,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,OAAO;IACtC,CAAC,SAAS,CAAC,oBAAoB,CAAC,EAAE,UAAU;CAC7C,CAAA"}

View File

@ -0,0 +1,227 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
const utils_1 = require("./utils");
const jsx_1 = require("./jsx");
const constant_1 = require("./constant");
const adapter_1 = require("./adapter");
// @TODO
// 重构 parseRender 和 parseLoop 失败
// 尚不清楚 babel 的 data 和 context 传参机制
// 目前先写两份代码,有时间看看 babel 具体对 data 和 context 做了什么导致传参失败
function parseLoopBody(body, jsxDeclarations,
// @TODO
// 把 templates 换成 Map 可以支持 shalow variables declared
// 现在先用 ESLint 的 no-shalow 顶着
templates, loopScopes, finalReturnElement, returnedPaths) {
const bodyScope = body.scope;
body.traverse({
JSXElement(jsxElementPath) {
const parentNode = jsxElementPath.parent;
const parentPath = jsxElementPath.parentPath;
const isFinalReturn = jsxElementPath.getFunctionParent().isClassMethod();
const isJSXChildren = t.isJSXElement(parentNode);
if (!isJSXChildren) {
let statementParent = jsxElementPath.getStatementParent();
if (!(statementParent.isVariableDeclaration() ||
statementParent.isExpressionStatement())) {
statementParent = statementParent.findParent(s => s.isVariableDeclaration() || s.isExpressionStatement());
}
jsxDeclarations.add(statementParent);
if (t.isVariableDeclarator(parentNode)) {
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
// setTemplate(name, path, templates)
name && templates.set(name, jsxElementPath.node);
}
}
else if (t.isLogicalExpression(parentNode)) {
const { left, operator } = parentNode;
if (operator === '&&') {
if (t.isExpression(left)) {
utils_1.newJSXIfAttr(jsxElementPath.node, left);
parentPath.replaceWith(jsxElementPath.node);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
// name && templates.set(name, path.node)
}
}
}
}
else if (t.isConditionalExpression(parentNode)) {
const { test, consequent, alternate } = parentNode;
const block = jsx_1.buildBlockElement();
if (t.isJSXElement(consequent) && t.isLiteral(alternate)) {
const { value, confident } = parentPath.get('alternate').evaluate();
if (confident && !value) {
utils_1.newJSXIfAttr(block, test);
block.children = [jsxElementPath.node];
// newJSXIfAttr(jsxElementPath.node, test)
parentPath.replaceWith(block);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
// name && templates.set(name, path.node)
}
}
}
else if (t.isLiteral(consequent) && t.isJSXElement(consequent)) {
if (t.isNullLiteral(consequent)) {
utils_1.newJSXIfAttr(block, utils_1.reverseBoolean(test));
// newJSXIfAttr(jsxElementPath.node, reverseBoolean(test))
parentPath.replaceWith(block);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
// name && templates.set(name, path.node)
}
}
}
else if (t.isJSXElement(consequent) && t.isJSXElement(alternate)) {
const block2 = jsx_1.buildBlockElement();
block.children = [consequent];
utils_1.newJSXIfAttr(block, test);
jsx_1.setJSXAttr(block2, adapter_1.Adapter.else);
block2.children = [alternate];
const parentBlock = jsx_1.buildBlockElement();
parentBlock.children = [block, block2];
parentPath.replaceWith(parentBlock);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
utils_1.setTemplate(name, jsxElementPath, templates);
}
}
else {
// console.log('todo')
}
}
else if (t.isReturnStatement(parentNode)) {
if (!isFinalReturn) {
const caller = parentPath.findParent(p => p.isCallExpression());
if (caller.isCallExpression()) {
const callee = caller.node.callee;
if (t.isMemberExpression(callee) &&
t.isIdentifier(callee.property) &&
callee.property.name === 'map') {
let ary = callee.object;
const blockStatementPath = parentPath.findParent(p => p.isBlockStatement());
const body = blockStatementPath.node.body;
let stateToBeAssign = new Set();
for (const statement of body) {
if (t.isVariableDeclaration(statement)) {
for (const dcl of statement.declarations) {
if (t.isIdentifier(dcl.id)) {
const scope = blockStatementPath.scope;
const stateName = scope.generateUid(constant_1.LOOP_STATE);
stateToBeAssign.add(stateName);
blockStatementPath.scope.rename(dcl.id.name, stateName);
}
}
}
}
if (t.isCallExpression(ary) || utils_1.isContainFunction(caller.get('callee').get('object'))) {
const variableName = `anonymousState_${bodyScope.generateUid()}`;
caller.getStatementParent().insertBefore(utils_1.buildConstVariableDeclaration(variableName, ary));
ary = t.identifier(variableName);
}
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.for, t.jSXExpressionContainer(ary));
const [func] = caller.node.arguments;
if (t.isFunctionExpression(func) ||
t.isArrowFunctionExpression(func)) {
const [item, index] = func.params;
if (t.isIdentifier(item)) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.forItem, t.stringLiteral(item.name));
loopScopes.add(item.name);
}
else {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.forItem, t.stringLiteral('__item'));
}
if (t.isIdentifier(index)) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.forIndex, t.stringLiteral(index.name));
loopScopes.add(index.name);
}
caller.replaceWith(jsxElementPath.node);
if (statementParent) {
const name = utils_1.findIdentifierFromStatement(statementParent.node);
// setTemplate(name, path, templates)
name && templates.set(name, jsxElementPath.node);
}
}
}
}
}
else {
const ifStatement = parentPath.findParent(p => p.isIfStatement());
const blockStatement = parentPath.findParent(p => p.isBlockStatement());
const block = finalReturnElement || jsx_1.buildBlockElement();
if (utils_1.isBlockIfStatement(ifStatement, blockStatement)) {
const { test, alternate, consequent } = ifStatement.node;
if (alternate === blockStatement.node) {
throw utils_1.codeFrameError(parentNode.loc, '不必要的 else 分支,请遵从 ESLint consistent-return: https://eslint.org/docs/rules/consistent-return');
}
else if (consequent === blockStatement.node) {
const parentIfStatement = ifStatement.findParent(p => p.isIfStatement());
if (parentIfStatement) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.elseif, t.jSXExpressionContainer(test));
}
else {
utils_1.newJSXIfAttr(jsxElementPath.node, test);
}
}
}
else if (block.children.length !== 0) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.else);
}
block.children.push(jsxElementPath.node);
finalReturnElement = block;
returnedPaths.push(parentPath);
}
}
else if (t.isArrowFunctionExpression(parentNode)) {
//
}
else if (t.isAssignmentExpression(parentNode)) {
if (t.isIdentifier(parentNode.left)) {
const name = parentNode.left.name;
const bindingNode = bodyScope.getOwnBinding(name).path.node;
const block = templates.get(name) || jsx_1.buildBlockElement();
if (utils_1.isEmptyDeclarator(bindingNode)) {
const ifStatement = parentPath.findParent(p => p.isIfStatement());
const blockStatement = parentPath.findParent(p => p.isBlockStatement());
if (utils_1.isBlockIfStatement(ifStatement, blockStatement)) {
const { test, alternate, consequent } = ifStatement.node;
if (alternate === blockStatement.node) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.else);
}
else if (consequent === blockStatement.node) {
const parentIfStatement = ifStatement.findParent(p => p.isIfStatement());
if (parentIfStatement && parentIfStatement.get('alternate') === ifStatement) {
jsx_1.setJSXAttr(jsxElementPath.node, adapter_1.Adapter.elseif, t.jSXExpressionContainer(test));
}
else {
if (parentIfStatement) {
utils_1.newJSXIfAttr(block, parentIfStatement.node.test);
}
utils_1.newJSXIfAttr(jsxElementPath.node, test);
}
}
block.children.push(jsxElementPath.node);
// setTemplate(name, path, templates)
name && templates.set(name, block);
}
}
else {
throw utils_1.codeFrameError(jsxElementPath.node.loc, '请将 JSX 赋值表达式初始化为 null然后再进行 if 条件表达式赋值。');
}
}
}
else if (!t.isJSXElement(parentNode)) {
// throwError(path, '考虑只对 JSX 元素赋值一次。')
}
}
}
});
}
exports.parseLoopBody = parseLoopBody;
//# sourceMappingURL=loop-component.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const eslint_1 = require("./eslint");
exports.transformOptions = {};
exports.setTransformOptions = (options) => {
for (const key in options) {
if (options.hasOwnProperty(key)) {
exports.transformOptions[key] = options[key];
}
}
};
exports.buildBabelTransformOptions = () => {
return {
parserOpts: {
sourceType: 'module',
plugins: [
'classProperties',
'jsx',
'flow',
'flowComment',
'trailingFunctionCommas',
'asyncFunctions',
'exponentiationOperator',
'asyncGenerators',
'objectRestSpread',
'decorators',
'dynamicImport'
]
},
plugins: [
require('babel-plugin-transform-flow-strip-types'),
[require('babel-plugin-transform-define').default, exports.transformOptions.env]
].concat(process.env.ESLINT === 'false' || exports.transformOptions.isNormal || exports.transformOptions.isTyped ? [] : eslint_1.eslintValidation)
.concat((process.env.NODE_ENV === 'test') ? [] : require('babel-plugin-remove-dead-code').default)
};
};
//# sourceMappingURL=options.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/options.ts"],"names":[],"mappings":";;AACA,qCAA2C;AAe9B,QAAA,gBAAgB,GAAY,EAAS,CAAA;AAErC,QAAA,mBAAmB,GAAG,CAAC,OAAgB,EAAE,EAAE;IACtD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE;QACzB,IAAI,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE;YAC/B,wBAAgB,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;SACrC;KACF;AACH,CAAC,CAAA;AAEY,QAAA,0BAA0B,GAA2B,GAAG,EAAE;IACrE,OAAO;QACL,UAAU,EAAE;YACV,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE;gBACP,iBAAiB;gBACjB,KAAK;gBACL,MAAM;gBACN,aAAa;gBACb,wBAAwB;gBACxB,gBAAgB;gBAChB,wBAAwB;gBACxB,iBAAiB;gBACjB,kBAAkB;gBAClB,YAAY;gBACZ,eAAe;aACP;SACX;QACD,OAAO,EAAE;YACP,OAAO,CAAC,yCAAyC,CAAC;YAClD,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,OAAO,EAAE,wBAAgB,CAAC,GAAG,CAAC;SACzE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,OAAO,IAAI,wBAAgB,CAAC,QAAQ,IAAI,wBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,yBAAgB,CAAC;aACxH,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,OAAO,CAAC;KACnG,CAAA;AACH,CAAC,CAAA"}

View File

@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
function isString(node) {
return t.isLiteral(node) && typeof node.value === 'string';
}
function buildBinaryExpression(left, right) {
return t.binaryExpression('+', left, right);
}
function templateLiterals(path, state) {
let nodes = [];
const expressions = path.get('expressions');
for (const elem of (path.node.quasis)) {
nodes.push(t.stringLiteral(elem.value.cooked));
const expr = expressions.shift();
if (expr) {
// tslint:disable-next-line:no-multi-spaces
if (state.opts.spec && !expr.isBaseType('string') && !expr.isBaseType('number')) {
nodes.push(t.callExpression(t.identifier('String'), [expr.node]));
}
else {
nodes.push(expr.node);
}
}
}
// filter out empty string literals
nodes = nodes.filter((n) => !t.isLiteral(n, { value: '' }));
// since `+` is left-to-right associative
// ensure the first node is a string if first/second isn't
if (!isString(nodes[0]) && !isString(nodes[1])) {
nodes.unshift(t.stringLiteral(''));
}
if (nodes.length > 1) {
let root = buildBinaryExpression(nodes.shift(), nodes.shift());
for (const node of nodes) {
root = buildBinaryExpression(root, node);
}
path.replaceWith(root);
}
else {
path.replaceWith(nodes[0]);
}
}
exports.templateLiterals = templateLiterals;
//# sourceMappingURL=plugins.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../src/plugins.ts"],"names":[],"mappings":";;AAAA,iCAAgC;AAEhC,SAAS,QAAQ,CAAE,IAAI;IACrB,OAAO,CAAC,CAAC,SAAS,CAAC,IAAW,CAAC,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAA;AACnE,CAAC;AAED,SAAS,qBAAqB,CAAE,IAAI,EAAE,KAAK;IACzC,OAAO,CAAC,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;AAC7C,CAAC;AACD,SAAgB,gBAAgB,CAAE,IAAI,EAAE,KAAK;IAE3C,IAAI,KAAK,GAAkB,EAAE,CAAA;IAE7B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IAE3C,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QACrC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;QAE9C,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,CAAA;QAChC,IAAI,IAAI,EAAE;YACR,2CAA2C;YAC3C,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAG;gBAChF,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;aAClE;iBAAM;gBACL,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;aACtB;SACF;KACF;IAED,mCAAmC;IACnC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAE3D,yCAAyC;IACzC,0DAA0D;IAC1D,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAA;KACnC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;QACpB,IAAI,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QAE9D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,IAAI,GAAG,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;SACzC;QAED,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;KACvB;SAAM;QACL,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;KAC3B;AACH,CAAC;AAxCD,4CAwCC"}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,493 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const t = require("babel-types");
const babel_generator_1 = require("babel-generator");
const code_frame_1 = require("@babel/code-frame");
const constant_1 = require("./constant");
const lodash_1 = require("lodash");
const fs = require("fs");
const path = require("path");
const jsx_1 = require("./jsx");
const adapter_1 = require("./adapter");
const options_1 = require("./options");
const template = require('babel-template');
exports.incrementId = () => {
let id = 0;
return () => id++;
};
function getSuperClassCode(path) {
const superClass = path.node.superClass;
if (t.isIdentifier(superClass)) {
const binding = path.scope.getBinding(superClass.name);
if (binding && binding.kind === 'module') {
const bindingPath = binding.path.parentPath;
if (bindingPath.isImportDeclaration()) {
const source = bindingPath.node.source;
if (source.value === constant_1.TARO_PACKAGE_NAME) {
return;
}
try {
const p = pathResolver(source.value, options_1.transformOptions.sourcePath) + (options_1.transformOptions.isTyped ? '.tsx' : '.js');
const code = fs.readFileSync(p, 'utf8');
return {
code,
sourcePath: source.value
};
}
catch (error) {
return;
}
}
}
}
}
exports.getSuperClassCode = getSuperClassCode;
function isContainStopPropagation(path) {
let matched = false;
if (path) {
path.traverse({
Identifier(p) {
if (p.node.name === 'stopPropagation' &&
p.parentPath.parentPath.isCallExpression()) {
matched = true;
}
}
});
}
return matched;
}
exports.isContainStopPropagation = isContainStopPropagation;
function decodeUnicode(s) {
return unescape(s.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1'));
}
exports.decodeUnicode = decodeUnicode;
function isVarName(str) {
if (typeof str !== 'string') {
return false;
}
if (str.trim() !== str) {
return false;
}
try {
// tslint:disable-next-line:no-unused-expression
new Function(str, 'var ' + str);
}
catch (e) {
return false;
}
return true;
}
exports.isVarName = isVarName;
function findMethodName(expression) {
let methodName;
if (t.isIdentifier(expression) ||
t.isJSXIdentifier(expression)) {
methodName = expression.name;
}
else if (t.isStringLiteral(expression)) {
methodName = expression.value;
}
else if (t.isMemberExpression(expression) &&
t.isIdentifier(expression.property)) {
const { code } = babel_generator_1.default(expression);
const ids = code.split('.');
if (ids[0] === 'this' && ids[1] === 'props' && ids[2]) {
methodName = code.replace('this.props.', '');
}
else {
methodName = expression.property.name;
}
}
else if (t.isCallExpression(expression) &&
t.isMemberExpression(expression.callee) &&
t.isIdentifier(expression.callee.object)) {
methodName = expression.callee.object.name;
}
else if (t.isCallExpression(expression) &&
t.isMemberExpression(expression.callee) &&
t.isMemberExpression(expression.callee.object) &&
t.isIdentifier(expression.callee.property) &&
expression.callee.property.name === 'bind' &&
t.isIdentifier(expression.callee.object.property)) {
methodName = expression.callee.object.property.name;
}
else {
throw codeFrameError(expression.loc, '当 props 为事件时(props name 以 `on` 开头),只能传入一个 this 作用域下的函数。');
}
return methodName;
}
exports.findMethodName = findMethodName;
function setParentCondition(jsx, expr, array = false) {
const conditionExpr = jsx.findParent(p => p.isConditionalExpression());
const logicExpr = jsx.findParent(p => p.isLogicalExpression({ operator: '&&' }));
if (array) {
const ifAttrSet = new Set([
adapter_1.Adapter.if,
adapter_1.Adapter.else
]);
const logicalJSX = jsx.findParent(p => p.isJSXElement() && p.node.openingElement.attributes.some(a => ifAttrSet.has(a.name.name)));
if (logicalJSX) {
const attr = logicalJSX.node.openingElement.attributes.find(a => ifAttrSet.has(a.name.name));
if (attr) {
if (attr.name.name === adapter_1.Adapter.else) {
const prevElement = logicalJSX.getPrevSibling();
if (prevElement && prevElement.isJSXElement()) {
const attr = prevElement.node.openingElement.attributes.find(a => a.name.name === adapter_1.Adapter.if);
if (attr && t.isJSXExpressionContainer(attr.value)) {
expr = t.conditionalExpression(reverseBoolean(lodash_1.cloneDeep(attr.value.expression)), expr, t.arrayExpression());
return expr;
}
}
}
else if (t.isJSXExpressionContainer(attr.value)) {
expr = t.conditionalExpression(lodash_1.cloneDeep(attr.value.expression), expr, t.arrayExpression());
return expr;
}
}
}
}
if (conditionExpr && conditionExpr.isConditionalExpression()) {
const consequent = conditionExpr.get('consequent');
if (consequent === jsx || jsx.findParent(p => p === consequent)) {
expr = t.conditionalExpression(lodash_1.cloneDeep(conditionExpr.get('test').node), expr, array ? t.arrayExpression([]) : t.nullLiteral());
}
}
if (logicExpr && logicExpr.isLogicalExpression({ operator: '&&' })) {
const consequent = logicExpr.get('right');
if (consequent === jsx || jsx.findParent(p => p === consequent)) {
expr = t.conditionalExpression(lodash_1.cloneDeep(logicExpr.get('left').node), expr, array ? t.arrayExpression([]) : t.nullLiteral());
}
}
return expr;
}
exports.setParentCondition = setParentCondition;
function generateAnonymousState(scope, expression, refIds, isLogical) {
let variableName = `anonymousState_${scope.generateUid()}`;
let statementParent = expression.getStatementParent();
if (!statementParent) {
throw codeFrameError(expression.node.loc, '无法生成匿名 State尝试先把值赋到一个变量上再把变量调换。');
}
const jsx = isLogical ? expression : expression.findParent(p => p.isJSXElement());
const callExpr = jsx.findParent(p => p.isCallExpression() && isArrayMapCallExpression(p));
const ifExpr = jsx.findParent(p => p.isIfStatement());
const blockStatement = jsx.findParent(p => p.isBlockStatement() && p.parentPath === ifExpr);
const expr = setParentCondition(jsx, lodash_1.cloneDeep(expression.node));
if (!callExpr) {
refIds.add(t.identifier(variableName));
statementParent.insertBefore(buildConstVariableDeclaration(variableName, expr));
if (blockStatement && blockStatement.isBlockStatement()) {
blockStatement.traverse({
VariableDeclarator: (p) => {
const { id, init } = p.node;
if (t.isIdentifier(id) && !id.name.startsWith(constant_1.LOOP_STATE)) {
const newId = scope.generateDeclaredUidIdentifier('$' + id.name);
refIds.forEach((refId) => {
if (refId.name === variableName && !variableName.startsWith('_$')) {
refIds.delete(refId);
}
});
variableName = newId.name;
refIds.add(t.identifier(variableName));
blockStatement.scope.rename(id.name, newId.name);
p.parentPath.replaceWith(template('ID = INIT;')({ ID: newId, INIT: init }));
}
}
});
}
}
else {
variableName = `${constant_1.LOOP_STATE}_${callExpr.scope.generateUid()}`;
const func = callExpr.node.arguments[0];
if (t.isArrowFunctionExpression(func)) {
if (!t.isBlockStatement(func.body)) {
func.body = t.blockStatement([
buildConstVariableDeclaration(variableName, expr),
t.returnStatement(func.body)
]);
}
else {
func.body.body.splice(func.body.body.length - 1, 0, buildConstVariableDeclaration(variableName, expr));
}
}
}
const id = t.identifier(variableName);
expression.replaceWith(id);
return id;
}
exports.generateAnonymousState = generateAnonymousState;
function isArrayMapCallExpression(callExpression) {
return callExpression &&
t.isCallExpression(callExpression.node) &&
t.isMemberExpression(callExpression.node.callee) &&
t.isIdentifier(callExpression.node.callee.property, { name: 'map' });
}
exports.isArrayMapCallExpression = isArrayMapCallExpression;
function buildConstVariableDeclaration(variableName, expresion) {
return t.variableDeclaration('const', [
t.variableDeclarator(t.identifier(variableName), expresion)
]);
}
exports.buildConstVariableDeclaration = buildConstVariableDeclaration;
function setTemplate(name, path, templates) {
const parentPath = path.parentPath;
const jsxChildren = parentPath.findParent(p => p.isJSXElement());
if (name && !jsxChildren) {
templates.set(name, path.node);
}
}
exports.setTemplate = setTemplate;
function isContainFunction(p) {
let bool = false;
p.traverse({
CallExpression() {
bool = true;
}
});
return bool;
}
exports.isContainFunction = isContainFunction;
function slash(input) {
const isExtendedLengthPath = /^\\\\\?\\/.test(input);
const hasNonAscii = /[^\u0000-\u0080]+/.test(input);
const hasChinese = /[^\u4e00-\u9fa5]+/.test(input); // has Chinese characters
if (isExtendedLengthPath || (hasNonAscii && !hasChinese)) {
return input;
}
return input.replace(/\\/g, '/');
}
function pathResolver(source, location) {
const extName = path.extname(source);
const promotedPath = source;
if (!['js', 'tsx'].includes(extName)) {
try {
const pathExist = fs.existsSync(path.resolve(path.dirname(location), source, 'index.js'));
const tsxPathExist = fs.existsSync(path.resolve(path.dirname(location), source, 'index.tsx'));
if (pathExist || tsxPathExist) {
let p = path.join(promotedPath, 'index');
if (!p.startsWith('.')) {
p = './' + p;
}
return slash(p);
}
return slash(promotedPath);
}
catch (error) {
return slash(promotedPath);
}
}
return slash(promotedPath.split('.').slice(0, -1).join('.'));
}
exports.pathResolver = pathResolver;
function codeFrameError(node, msg) {
let errMsg = '';
try {
errMsg = code_frame_1.codeFrameColumns(exports.setting.sourceCode, node && node.type && node.loc ? node.loc : node, {
highlightCode: true
});
}
catch (error) {
errMsg = 'failed to locate source';
}
return new Error(`${msg}
-----
${errMsg}`);
}
exports.codeFrameError = codeFrameError;
exports.setting = {
sourceCode: ''
};
function createUUID() {
return '$' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16 | 0;
let v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
}).replace(/-/g, '').slice(0, 8);
}
exports.createUUID = createUUID;
function createRandomLetters(n) {
const str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
return Array(n).join().split(',').map(function () { return str.charAt(Math.floor(Math.random() * str.length)); }).join('');
}
exports.createRandomLetters = createRandomLetters;
function isBlockIfStatement(ifStatement, blockStatement) {
return ifStatement && blockStatement &&
ifStatement.isIfStatement() &&
blockStatement.isBlockStatement();
}
exports.isBlockIfStatement = isBlockIfStatement;
function buildCodeFrame(code) {
return (loc) => code_frame_1.codeFrameColumns(code, loc);
}
exports.buildCodeFrame = buildCodeFrame;
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
exports.isNumeric = isNumeric;
function buildJSXAttr(name, value) {
return t.jSXAttribute(t.jSXIdentifier(name), t.jSXExpressionContainer(value));
}
exports.buildJSXAttr = buildJSXAttr;
function newJSXIfAttr(jsx, value, path) {
const element = jsx.openingElement;
if (!t.isJSXIdentifier(element.name)) {
return;
}
if (element.name.name === 'Block' || element.name.name === 'block' || !path) {
element.attributes.push(buildJSXAttr(adapter_1.Adapter.if, value));
}
else {
const block = jsx_1.buildBlockElement();
newJSXIfAttr(block, value);
block.children.push(jsx);
path.node = block;
}
}
exports.newJSXIfAttr = newJSXIfAttr;
function getSlotName(name) {
return name.slice(6).toLowerCase();
}
exports.getSlotName = getSlotName;
function isContainJSXElement(path) {
let matched = false;
path.traverse({
JSXElement(p) {
matched = true;
p.stop();
}
});
return matched;
}
exports.isContainJSXElement = isContainJSXElement;
function hasComplexExpression(path) {
let matched = false;
if (isContainJSXElement(path)) {
return false;
}
if (path.isObjectExpression()) {
return true;
}
if (path.isTemplateLiteral() || path.isCallExpression()) {
return true;
}
if (path.isArrayExpression()) {
const { elements } = path.node;
if (elements.some(el => t.isObjectExpression(el) || t.isArrayExpression(el))) {
return true;
}
}
path.traverse({
CallExpression: (p) => {
matched = true;
p.stop();
},
TemplateLiteral(p) {
matched = true;
p.stop();
},
ObjectExpression(p) {
matched = true;
p.stop();
},
ArrayExpression(p) {
const { elements } = p.node;
if (elements.some(el => t.isObjectExpression(el))) {
return true;
}
},
TaggedTemplateExpression(p) {
matched = true;
p.stop();
},
MemberExpression(path) {
// @fix 放弃这个逻辑,导致后续变更 this.update 的 data 对应不上了
// const jsxElement = path.findParent(p => p.isJSXExpressionContainer());
// const object = path.get('object');
// const property = path.get('property');
// const parentPath = path.parentPath;
// if (jsxElement &&
// object.isThisExpression() &&
// property.isIdentifier({ name: 'data' }) &&
// parentPath.isMemberExpression() &&
// parentPath.parentPath.isMemberExpression()) {
// const sourceCode = parentPath.parentPath.getSource();
// if (sourceCode.includes('[') && sourceCode.includes(']')) {
// matched = true;
// path.stop();
// }
// }
}
});
return matched;
}
exports.hasComplexExpression = hasComplexExpression;
function findFirstIdentifierFromMemberExpression(node, member) {
let id;
let object = node.object;
while (true) {
if (t.identifier(object) && !t.isMemberExpression(object)) {
id = object;
if (member) {
object = member;
}
break;
}
object = object.object;
}
return id;
}
exports.findFirstIdentifierFromMemberExpression = findFirstIdentifierFromMemberExpression;
function getArgumentName(arg) {
if (t.isThisExpression(arg)) {
return 'this';
}
else if (t.isNullLiteral(arg)) {
return 'null';
}
else if (t.isStringLiteral(arg) || t.isNumericLiteral(arg)) {
return arg.value;
}
else if (t.isIdentifier(arg)) {
return arg.name;
}
else {
return babel_generator_1.default(arg).code;
}
throw new Error(`bind 不支持传入该参数: ${arg}`);
}
exports.getArgumentName = getArgumentName;
function isAllLiteral(...args) {
return args.every(p => t.isLiteral(p));
}
exports.isAllLiteral = isAllLiteral;
function reverseBoolean(expression) {
return t.unaryExpression('!', expression);
}
exports.reverseBoolean = reverseBoolean;
function isEmptyDeclarator(node) {
if (t.isVariableDeclarator(node) &&
(node.init === null ||
t.isNullLiteral(node.init))) {
return true;
}
return false;
}
exports.isEmptyDeclarator = isEmptyDeclarator;
function toLetters(num) {
let mod = num % 26;
let pow = num / 26 | 0;
let out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
const letter = pow ? toLetters(pow) + out : out;
return letter.toLowerCase();
}
exports.toLetters = toLetters;
function findIdentifierFromStatement(statement) {
if (t.isVariableDeclaration(statement)) {
const declarator = statement.declarations.find(s => t.isIdentifier(s.id));
if (declarator && t.isIdentifier(declarator.id)) {
return declarator.id.name;
}
}
return '__return';
}
exports.findIdentifierFromStatement = findIdentifierFromStatement;
//# sourceMappingURL=utils.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,93 @@
{
"name": "@tarojs/transformer-wx",
"version": "1.2.13",
"description": "Transfrom Nerv Component to Wechat mini program.",
"repository": {
"type": "git",
"url": "git+https://github.com/NervJS/taro.git"
},
"main": "index.js",
"files": [
"index.js",
"lib",
"cli.js",
"dist"
],
"scripts": {
"test:cov": "jest --coverage && npm run lint",
"test": "jest",
"dev": "tsc -w --pretty",
"lint": "tslint",
"build": "tsc"
},
"author": "O2Team",
"license": "MIT",
"jest": {
"testEnvironment": "node",
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"testPathIgnorePatterns": [
"node_modules",
"utils"
]
},
"dependencies": {
"@babel/code-frame": "^7.0.0-beta.44",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-helper-evaluate-path": "^0.5.0",
"babel-helper-mark-eval-scopes": "^0.4.3",
"babel-helper-remove-or-void": "^0.4.3",
"babel-plugin-danger-remove-unused-import": "^1.1.1",
"babel-plugin-minify-dead-code": "^0.5.2",
"babel-plugin-remove-dead-code": "^1.3.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-define": "^1.3.0",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"eslint": "^4.15.0",
"eslint-plugin-taro": "1.2.13",
"html": "^1.0.0",
"lodash": "^4.17.5",
"prettier": "^1.14.2",
"typescript": "^3.2.2"
},
"devDependencies": {
"@tarojs/taro": "1.2.13",
"@types/babel-core": "^6.25.5",
"@types/babel-generator": "^6.25.1",
"@types/babel-template": "^6.25.0",
"@types/babel-traverse": "6.25.3",
"@types/babel-types": "^6.25.2",
"@types/eslint": "^4.16.5",
"@types/jest": "^22.2.3",
"@types/lodash": "^4.14.105",
"@types/node": "^9.6.2",
"jest": "^23.0.1",
"jest-cli": "^22.1.4",
"ts-jest": "^22.4.6",
"tslint": "^5.10.0",
"tslint-config-prettier": "^1.10.0",
"tslint-config-standard": "^7.0.0"
},
"publishConfig": {
"access": "public"
},
"bugs": {
"url": "https://github.com/NervJS/taro/issues"
},
"homepage": "https://github.com/NervJS/taro#readme"
}

View File

@ -0,0 +1,10 @@
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
/* padding: 20rpx 0; */
box-sizing: border-box;
}

View File

@ -0,0 +1,113 @@
import './app.css'
import './pages/list/index'
import { render, WeElement, define } from 'omi'
define('my-app', class extends WeElement {
config = {
pages: [
'pages/list/index',
'pages/detail/index',
'pages/mp/index',
'pages/import/index',
'pages/index/index',
'pages/about/index',
'pages/code/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'Omi Cloud',
navigationBarTextStyle: 'black'
},
tabBar: {
color: "#575656",
selectedColor: "white",
borderStyle: "black",
backgroundColor: "black",
list: [
{
pagePath: "pages/list/index",
text: "首页",
iconPath: "images/home-unslt.png",
"selectedIconPath": "images/home.png"
},
{
pagePath: "pages/mp/index",
text: "小程序",
iconPath: "images/mp-unslt.png",
selectedIconPath: "images/mp.png"
},
{
pagePath: "pages/about/index",
text: "omijs.org",
iconPath: "images/omi-unslt.png",
selectedIconPath: "images/omi.png"
}
]
}
}
globalData = {
userInfo: null
}
install() {
// 展示本地存储能力
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
}
})
// 获取用户信息
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
wx.getUserInfo({
success: res => {
// 可以将 res 发送给后台解码出 unionId
this.globalData.userInfo = res.userInfo
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(res)
}
}
})
}
}
})
if (!wx.cloud) {
console.error('请使用 2.2.3 或以上的基础库以使用云能力')
} else {
wx.cloud.init({
traceUser: true,
})
this.globalData.db = wx.cloud.database({
env: 'test-06eb2e'
})
}
}
onShow() { }
onHide() { }
render() {
return (
<page-list />
)
}
})
render(<my-app />, '#app')

View File

@ -0,0 +1,31 @@
// 云函数模板
// 部署:在 cloud-functions/login 文件夹右击选择 “上传并部署”
const cloud = require('wx-server-sdk')
// 初始化 cloud
cloud.init()
/**
* 这个示例将经自动鉴权过的小程序用户 openid 返回给小程序端
*
* event 参数包含小程序端调用传入的 data
*
*/
exports.main = (event, context) => {
console.log(event)
console.log(context)
// 可执行其他自定义逻辑
// console.log 的内容可以在云开发云函数调用日志查看
// 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID需满足 UNIONID 获取条件)
const wxContext = cloud.getWXContext()
return {
event,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
}
}

View File

@ -0,0 +1,14 @@
{
"name": "login",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "latest"
}
}

View File

@ -0,0 +1,18 @@
// 云函数模板
const cloud = require('wx-server-sdk')
// 初始化 cloud
cloud.init()
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
try {
return await db.collection('todo').where({
done: true
}).remove()
} catch (e) {
console.error(e)
}
}

View File

@ -0,0 +1,14 @@
{
"name": "login",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"wx-server-sdk": "latest"
}
}

View File

@ -0,0 +1,7 @@
var WxParse = require('./wxParse.js');
module.exports = function comi(md, scope) {
WxParse.wxParse('article', 'md', md, scope, 5);
}

View File

@ -0,0 +1,3 @@
<import src="./wxParse.wxml"/>
<template is="wxParse" data="{{wxParseData:article.nodes}}"/>

View File

@ -0,0 +1,2 @@
@import './wxParse.wxss';
@import './prism.wxss';

View File

@ -0,0 +1,303 @@
/**
* html2Json 改造来自: https://github.com/Jxck/html2json
*
*
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
var __placeImgeUrlHttps = "https";
var __emojisReg = '';
var __emojisBaseSrc = '';
var __emojis = {};
var wxDiscode = require('./wxDiscode.js');
var HTMLParser = require('./htmlparser.js');
// Empty Elements - HTML 5
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr");
// Block Elements - HTML 5
var block = makeMap("br,a,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video");
// Inline Elements - HTML 5
var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
// Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
// Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
// Special Elements (can contain anything)
var special = makeMap("wxxxcode-style,script,style,view,scroll-view,block");
function makeMap(str) {
var obj = {}, items = str.split(",");
for (var i = 0; i < items.length; i++)
obj[items[i]] = true;
return obj;
}
function q(v) {
return '"' + v + '"';
}
function removeDOCTYPE(html) {
return html
.replace(/<\?xml.*\?>\n/, '')
.replace(/<.*!doctype.*\>\n/, '')
.replace(/<.*!DOCTYPE.*\>\n/, '');
}
function trimHtml(html) {
return html
//.replace(/\r?\n+/g, '')
.replace(/<!--.*?-->/ig, '')
.replace(/\/\*.*?\*\//ig, '')
.replace(/[ ]+</ig, '<')
}
function html2json(html, bindName) {
//处理字符串
html = removeDOCTYPE(html);
html = trimHtml(html);
html = wxDiscode.strDiscode(html);
//生成node节点
var bufArray = [];
var results = {
node: bindName,
nodes: [],
images:[],
imageUrls:[]
};
var index = 0;
HTMLParser(html, {
start: function (tag, attrs, unary) {
//debug(tag, attrs, unary);
// node for this element
var node = {
node: 'element',
tag: tag,
};
if (bufArray.length === 0) {
node.index = index.toString()
index += 1
} else {
var parent = bufArray[0];
if (parent.nodes === undefined) {
parent.nodes = [];
}
node.index = parent.index + '.' + parent.nodes.length
}
if (block[tag]) {
node.tagType = "block";
} else if (inline[tag]) {
node.tagType = "inline";
} else if (closeSelf[tag]) {
node.tagType = "closeSelf";
}
if (attrs.length !== 0) {
node.attr = attrs.reduce(function (pre, attr) {
var name = attr.name;
var value = attr.value;
if (name == 'class') {
console.dir(value);
// value = value.join("")
node.classStr = value;
}
// has multi attibutes
// make it array of attribute
if (name == 'style') {
console.dir(value);
// value = value.join("")
node.styleStr = value;
}
if (value.match(/ /)) {
value = value.split(' ');
}
// if attr already exists
// merge it
if (pre[name]) {
if (Array.isArray(pre[name])) {
// already array, push to last
pre[name].push(value);
} else {
// single value, make it array
pre[name] = [pre[name], value];
}
} else {
// not exist, put it
pre[name] = value;
}
return pre;
}, {});
}
//对img添加额外数据
if (node.tag === 'img') {
node.imgIndex = results.images.length;
var imgUrl = node.attr.src;
if (imgUrl[0] == '') {
imgUrl.splice(0, 1);
}
imgUrl = wxDiscode.urlToHttpUrl(imgUrl, __placeImgeUrlHttps);
node.attr.src = imgUrl;
node.from = bindName;
results.images.push(node);
results.imageUrls.push(imgUrl);
}
// 处理font标签样式属性
if (node.tag === 'font') {
var fontSize = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', '-webkit-xxx-large'];
var styleAttrs = {
'color': 'color',
'face': 'font-family',
'size': 'font-size'
};
if (!node.attr.style) node.attr.style = [];
if (!node.styleStr) node.styleStr = '';
for (var key in styleAttrs) {
if (node.attr[key]) {
var value = key === 'size' ? fontSize[node.attr[key]-1] : node.attr[key];
node.attr.style.push(styleAttrs[key]);
node.attr.style.push(value);
node.styleStr += styleAttrs[key] + ': ' + value + ';';
}
}
}
//临时记录source资源
if(node.tag === 'source'){
results.source = node.attr.src;
}
if (unary) {
// if this tag doesn't have end tag
// like <img src="hoge.png"/>
// add to parents
var parent = bufArray[0] || results;
if (parent.nodes === undefined) {
parent.nodes = [];
}
parent.nodes.push(node);
} else {
bufArray.unshift(node);
}
},
end: function (tag) {
//debug(tag);
// merge into parent tag
var node = bufArray.shift();
if (node.tag !== tag) console.error('invalid state: mismatch end tag');
//当有缓存source资源时于于video补上src资源
if(node.tag === 'video' && results.source){
node.attr.src = results.source;
delete results.source;
}
if (bufArray.length === 0) {
results.nodes.push(node);
} else {
var parent = bufArray[0];
if (parent.nodes === undefined) {
parent.nodes = [];
}
parent.nodes.push(node);
}
},
chars: function (text) {
//debug(text);
var node = {
node: 'text',
text: text,
textArray:transEmojiStr(text)
};
if (bufArray.length === 0) {
node.index = index.toString()
index += 1
results.nodes.push(node);
} else {
var parent = bufArray[0];
if (parent.nodes === undefined) {
parent.nodes = [];
}
node.index = parent.index + '.' + parent.nodes.length
parent.nodes.push(node);
}
},
comment: function (text) {
//debug(text);
// var node = {
// node: 'comment',
// text: text,
// };
// var parent = bufArray[0];
// if (parent.nodes === undefined) {
// parent.nodes = [];
// }
// parent.nodes.push(node);
},
});
return results;
};
function transEmojiStr(str){
// var eReg = new RegExp("["+__reg+' '+"]");
// str = str.replace(/\[([^\[\]]+)\]/g,':$1:')
var emojiObjs = [];
//如果正则表达式为空
if(__emojisReg.length == 0 || !__emojis){
var emojiObj = {}
emojiObj.node = "text";
emojiObj.text = str;
array = [emojiObj];
return array;
}
//这个地方需要调整
str = str.replace(/\[([^\[\]]+)\]/g,':$1:')
var eReg = new RegExp("[:]");
var array = str.split(eReg);
for(var i = 0; i < array.length; i++){
var ele = array[i];
var emojiObj = {};
if(__emojis[ele]){
emojiObj.node = "element";
emojiObj.tag = "emoji";
emojiObj.text = __emojis[ele];
emojiObj.baseSrc= __emojisBaseSrc;
}else{
emojiObj.node = "text";
emojiObj.text = ele;
}
emojiObjs.push(emojiObj);
}
return emojiObjs;
}
function emojisInit(reg='',baseSrc="/wxParse/emojis/",emojis){
__emojisReg = reg;
__emojisBaseSrc=baseSrc;
__emojis=emojis;
}
module.exports = {
html2json: html2json,
emojisInit:emojisInit
};

View File

@ -0,0 +1,192 @@
/**
*
* htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
*
* author: Di (微信小程序开发工程师)
* organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
* 垂直微信小程序开发交流社区
*
* github地址: https://github.com/icindy/wxParse
*
* for: 微信小程序富文本解析
* detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
*/
// Regular Expressions for parsing tags and attributes
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/,
attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
// Empty Elements - HTML 5
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr");
// Block Elements - HTML 5
var block = makeMap("a,address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video");
// Inline Elements - HTML 5
var inline = makeMap("abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
// Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
// Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
// Special Elements (can contain anything)
var special = makeMap("wxxxcode-style,script,style,view,scroll-view,block");
function HTMLParser(html, handler) {
var index, chars, match, stack = [], last = html;
stack.last = function () {
return this[this.length - 1];
};
while (html) {
chars = true;
// Make sure we're not in a script or style element
if (!stack.last() || !special[stack.last()]) {
// Comment
if (html.indexOf("<!--") == 0) {
index = html.indexOf("-->");
if (index >= 0) {
if (handler.comment)
handler.comment(html.substring(4, index));
html = html.substring(index + 3);
chars = false;
}
// end tag
} else if (html.indexOf("</") == 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
}
// start tag
} else if (html.indexOf("<") == 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf("<");
var text = ''
while (index === 0) {
text += "<";
html = html.substring(1);
index = html.indexOf("<");
}
text += index < 0 ? html : html.substring(0, index);
html = index < 0 ? "" : html.substring(index);
if (handler.chars)
handler.chars(text);
}
} else {
html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, "$1$2");
if (handler.chars)
handler.chars(text);
return "";
});
parseEndTag("", stack.last());
}
if (html == last)
throw "Parse Error: " + html;
last = html;
}
// Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag("", stack.last());
}
}
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag("", tagName);
}
unary = empty[tagName] || !!unary;
if (!unary)
stack.push(tagName);
if (handler.start) {
var attrs = [];
rest.replace(attr, function (match, name) {
var value = arguments[2] ? arguments[2] :
arguments[3] ? arguments[3] :
arguments[4] ? arguments[4] :
fillAttrs[name] ? name : "";
attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
if (!tagName)
var pos = 0;
// Find the closest opened tag of the same type
else {
tagName = tagName.toLowerCase();
for (var pos = stack.length - 1; pos >= 0; pos--)
if (stack[pos] == tagName)
break;
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--)
if (handler.end)
handler.end(stack[i]);
// Remove the open elements from the stack
stack.length = pos;
}
}
};
function makeMap(str) {
var obj = {}, items = str.split(",");
for (var i = 0; i < items.length; i++)
obj[items[i]] = true;
return obj;
}
module.exports = HTMLParser;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,220 @@
/* PrismJS 1.16.0
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+json+typescript+jsx+tsx&plugins=line-highlight+line-numbers */
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
.code[class*="language-"],
.pre[class*="language-"] {
color: #f8f8f2;
background: none;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 25rpx;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
.pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(.pre) > .code[class*="language-"],
.pre[class*="language-"] {
background: #272822;
}
/* Inline code */
:not(.pre) > .code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #a6e22e;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name {
color: #e6db74;
}
.token.keyword {
color: #66d9ef;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.pre[data-line] {
position: relative;
padding: 1em 0 1em 3em;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em; /* Same as .prisms padding-top */
background: hsla(24, 20%, 50%,.08);
background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
pointer-events: none;
line-height: inherit;
white-space: pre;
}
.line-highlight:before,
.line-highlight[data-end]:after {
content: attr(data-start);
position: absolute;
top: .4em;
left: .6em;
min-width: 1em;
padding: 0 .5em;
background-color: hsla(24, 20%, 50%,.4);
color: hsl(24, 20%, 95%);
font: bold 65%/1.5 sans-serif;
text-align: center;
vertical-align: .3em;
border-radius: 999px;
text-shadow: none;
box-shadow: 0 1px white;
}
.line-highlight[data-end]:after {
content: attr(data-end);
top: auto;
bottom: .4em;
}
.line-numbers .line-highlight:before,
.line-numbers .line-highlight:after {
content: none;
}
.pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
.pre[class*="language-"].line-numbers > .code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}

Some files were not shown because too many files have changed in this diff Show More