├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── gulpfile.js
├── package.json
├── src
├── index.js
├── index.json
├── index.wxml
└── index.wxss
├── test
├── index.test.js
├── utils.js
└── wx.test.js
└── tools
├── build.js
├── checkcomponents.js
├── config.js
├── test
└── helper.js
└── utils.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | ["module-resolver", {
4 | "root": ["./src"],
5 | "alias": {}
6 | }]
7 | ],
8 | "presets": [
9 | ["env", {"loose": true, "modules": "commonjs"}]
10 | ]
11 | }
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'extends': [
3 | 'airbnb-base',
4 | 'plugin:promise/recommended'
5 | ],
6 | 'parserOptions': {
7 | 'ecmaVersion': 9,
8 | 'ecmaFeatures': {
9 | 'jsx': false
10 | },
11 | 'sourceType': 'module'
12 | },
13 | 'env': {
14 | 'es6': true,
15 | 'node': true,
16 | 'jest': true
17 | },
18 | 'plugins': [
19 | 'import',
20 | 'node',
21 | 'promise'
22 | ],
23 | 'rules': {
24 | 'no-console': 'off',
25 | "class-methods-use-this": "off",
26 | 'arrow-parens': 'off',
27 | 'comma-dangle': [
28 | 'error',
29 | 'only-multiline'
30 | ],
31 | 'complexity': ['error', 10],
32 | 'func-names': 'off',
33 | 'global-require': 'off',
34 | 'handle-callback-err': [
35 | 'error',
36 | '^(err|error)$'
37 | ],
38 | 'import/no-unresolved': [
39 | 'error',
40 | {
41 | 'caseSensitive': true,
42 | 'commonjs': true,
43 | 'ignore': ['^[^.]']
44 | }
45 | ],
46 | 'import/prefer-default-export': 'off',
47 | 'linebreak-style': 'off',
48 | 'no-catch-shadow': 'error',
49 | 'no-continue': 'off',
50 | 'no-div-regex': 'warn',
51 | 'no-else-return': 'off',
52 | 'no-param-reassign': 'off',
53 | 'no-plusplus': 'off',
54 | 'no-shadow': 'off',
55 | 'no-multi-assign': 'off',
56 | 'no-underscore-dangle': 'off',
57 | 'node/no-deprecated-api': 'error',
58 | 'node/process-exit-as-throw': 'error',
59 | 'object-curly-spacing': [
60 | 'error',
61 | 'never'
62 | ],
63 | 'operator-linebreak': [
64 | 'error',
65 | 'after',
66 | {
67 | 'overrides': {
68 | ':': 'before',
69 | '?': 'before'
70 | }
71 | }
72 | ],
73 | 'prefer-arrow-callback': 'off',
74 | 'prefer-destructuring': 'off',
75 | 'prefer-template': 'off',
76 | 'quote-props': [
77 | 1,
78 | 'as-needed',
79 | {
80 | 'unnecessary': true
81 | }
82 | ],
83 | 'semi': [
84 | 'error',
85 | 'never'
86 | ]
87 | },
88 | 'globals': {
89 | 'window': true,
90 | 'document': true,
91 | 'App': true,
92 | 'Page': true,
93 | 'Component': true,
94 | 'Behavior': true,
95 | 'wx': true,
96 | 'getCurrentPages': true,
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | package-lock.json
4 |
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | miniprogram_dist
12 | miniprogram_dev
13 | node_modules
14 | coverage
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | package-lock.json
4 |
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 |
11 | test
12 | tools
13 | docs
14 | miniprogram_dev
15 | node_modules
16 | coverage
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 sima.zhang1990@gmail.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @antv/f2-canvas
2 |
3 | 微信小程序 F2 图表组件
4 |
5 | ## 安装
6 |
7 | ```bash
8 | npm i @antv/f2-canvas
9 | ```
10 |
11 | ## 快速开始
12 |
13 | 下面我们就开始使用 `f2-canvas` 组件来绘制图表吧,这里假设用户已经初步了解微信小程序的基础框架,如不了解,请先阅读官网教程: [官方教程](https://developers.weixin.qq.com/miniprogram/dev/index.html)。
14 |
15 | 以绘制柱状图为例:
16 |
17 |
18 |
19 | * STEP 1:在 pages 目录下新建 column 目录,该目录需要包含以下几个文件:
20 | + index.js
21 | + index.json
22 | + index.wxml
23 | + index.wxss
24 |
25 | 各个文件的内容如下:
26 |
27 | + `index.json` 配置文件,引入 f2-canvas 组件,由于微信小程序组件名不允许包含数字,所以这里将其命名为 ff-canvas
28 |
29 | ```js
30 | // index.json
31 | {
32 | "usingComponents": {
33 | "ff-canvas": "@antv/f2-canvas"
34 | }
35 | }
36 | ```
37 |
38 | + `index.wxml` 视图,使用 ff-canvas 组件,其中 `opts` 是一个我们在 index.js 中定义的对象,**必设属性**,它使得图表能够在页面加载后被初始化并设置,详见 index.js 中的使用。
39 |
40 | ```html
41 |
42 |
43 |
44 |
45 | ```
46 |
47 | + `index.js` 逻辑处理,这里还需要引入 F2 用于绘制图表,结构如下,注意路径正确。
48 |
49 | ```js
50 | // index.js
51 | import F2 from '@antv/wx-f2'; // 注:也可以不引入, initChart 方法已经将 F2 传入,如果需要引入,注意需要安装 @antv/wx-f2 依赖
52 |
53 | let chart = null;
54 |
55 | function initChart(canvas, width, height, F2) { // 使用 F2 绘制图表
56 | const data = [
57 | { year: '1951 年', sales: 38 },
58 | { year: '1952 年', sales: 52 },
59 | { year: '1956 年', sales: 61 },
60 | { year: '1957 年', sales: 145 },
61 | { year: '1958 年', sales: 48 },
62 | { year: '1959 年', sales: 38 },
63 | { year: '1960 年', sales: 38 },
64 | { year: '1962 年', sales: 38 },
65 | ];
66 | chart = new F2.Chart({
67 | el: canvas,
68 | width,
69 | height
70 | });
71 |
72 | chart.source(data, {
73 | sales: {
74 | tickCount: 5
75 | }
76 | });
77 | chart.tooltip({
78 | showItemMarker: false,
79 | onShow(ev) {
80 | const { items } = ev;
81 | items[0].name = null;
82 | items[0].name = items[0].title;
83 | items[0].value = '¥ ' + items[0].value;
84 | }
85 | });
86 | chart.interval().position('year*sales');
87 | chart.render();
88 | return chart;
89 | }
90 |
91 | Page({
92 | data: {
93 | opts: {
94 | onInit: initChart
95 | }
96 | },
97 |
98 | onReady() {
99 | }
100 | });
101 | ```
102 |
103 | 由于 f2-canvas 组件主要是对小程序的画布上下文和 html5 canvas 的上下文进行了适配以支持 F2 在小程序端的渲染,所以 **F2 能绘制什么图表,小程序端就能绘制什么图表**,使用时也只需按照 F2 的语法来写即可。
104 |
105 | 本项目只展示了部分 demos,更详细的请见 [AntV F2](https://antv.alipay.com/zh-cn/f2/3.x/demo/index.html)。
106 |
107 | 需要注意的是,在创建 chart 的时候,需要使用 'el' 属性来指定容器,对应 `this.data.opts.onInit` 方法形参中的 `canvas` 属性,另外该方法还会返回 `width`, `height` 属性分别对应画布的宽和高。
108 |
109 | ```js
110 | chart = new F2.Chart({
111 | el: canvas,
112 | width,
113 | height
114 | });
115 | ```
116 |
117 |
118 | ## 不支持的功能
119 |
120 | 目前由于小程序不支持 document,所以 Guide.Html 辅助元素组件目前仍无法使用,其他 F2 的功能全部支持。
121 |
122 |
123 | ## 微信版本要求
124 |
125 | * 微信版本 **>= 6.6.3**
126 | * 基础库版本 **>= 2.2.1**
127 | * 开发者工具版本 **>= 1.02.1808300**
128 |
129 | ## 常见问题
130 |
131 | ### 问题 1
132 | 按照 demo 实例操作,但是报如下错误:
133 |
134 | ```js
135 | VM4466:1 TypeError: Cannot read property 'defaultView' of undefined
136 | at Object.getStyle (f2.js? [sm]:1)
137 | at Object.getWidth (f2.js? [sm]:1)
138 | at t._initCanvas (f2.js? [sm]:1)
139 | at new t (f2.js? [sm]:1)
140 | at e._initCanvas (f2.js? [sm]:1)
141 | at e._init (f2.js? [sm]:1)
142 | at new e (f2.js? [sm]:1)
143 | at Object.initChart [as onInit] (test.js? [sm]:18)
144 | at t. (f2-canvas.js? [sm]:94)
145 | at WAService.js:12
146 | ```
147 |
148 | **解决**
149 | 检查是否有在 .wxss 文件中为 ff-canvas 组件定义 width 和 height 样式属性,如没有,加上即可,如此代码所示:https://github.com/antvis/wx-f2/blob/master/app.wxss#L16
150 |
151 | ## 如何贡献
152 |
153 | 如果您在使用的过程中碰到问题,可以先通过 [issues](https://github.com/antvis/f2-canvas/issues) 看看有没有类似的 bug 或者建议。
154 |
155 | ## License
156 |
157 | [MIT license](https://github.com/antvis/f2-canvas/blob/master/LICENSE)
158 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp')
2 | const clean = require('gulp-clean')
3 |
4 | const config = require('./tools/config')
5 | const BuildTask = require('./tools/build')
6 | const id = require('./package.json').name || 'miniprogram-custom-component'
7 |
8 | // build task instance
9 | // eslint-disable-next-line no-new
10 | new BuildTask(id, config.entry)
11 |
12 | // clean the generated folders and files
13 | gulp.task('clean', gulp.series(() => gulp.src(config.distPath, {read: false, allowEmpty: true}).pipe(clean()), done => {
14 | if (config.isDev) {
15 | return gulp.src(config.demoDist, {read: false, allowEmpty: true})
16 | .pipe(clean())
17 | }
18 |
19 | return done()
20 | }))
21 | // watch files and build
22 | gulp.task('watch', gulp.series(`${id}-watch`))
23 | // build for develop
24 | gulp.task('dev', gulp.series(`${id}-dev`))
25 | // build for publish
26 | gulp.task('default', gulp.series(`${id}-default`))
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@antv/f2-canvas",
3 | "version": "1.0.3",
4 | "description": "微信小程序 F2 图表组件",
5 | "main": "miniprogram_dist/index.js",
6 | "scripts": {
7 | "dev": "gulp dev --develop",
8 | "watch": "gulp watch --develop --watch",
9 | "build": "gulp",
10 | "dist": "npm run build",
11 | "clean-dev": "gulp clean --develop",
12 | "clean": "gulp clean",
13 | "test": "jest ./test/* --silent --bail",
14 | "coverage": "jest ./test/* --coverage --bail",
15 | "lint": "eslint \"src/**/*.js\"",
16 | "lint-tools": "eslint \"tools/**/*.js\" --rule \"import/no-extraneous-dependencies: false\""
17 | },
18 | "miniprogram": "miniprogram_dist",
19 | "jest": {
20 | "testEnvironment": "jsdom",
21 | "testURL": "https://jest.test",
22 | "collectCoverageFrom": [
23 | "src/**/*.js"
24 | ],
25 | "moduleDirectories": [
26 | "node_modules",
27 | "src"
28 | ]
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/simaQ/f2-canvas.git"
33 | },
34 | "author": "sima.zhang1990@gmail.com",
35 | "license": "MIT",
36 | "devDependencies": {
37 | "babel-core": "^6.26.3",
38 | "babel-loader": "^7.1.5",
39 | "babel-plugin-module-resolver": "^3.1.1",
40 | "babel-preset-env": "^1.7.0",
41 | "colors": "^1.3.1",
42 | "eslint": "^5.3.0",
43 | "eslint-loader": "^2.1.0",
44 | "gulp": "^4.0.0",
45 | "gulp-clean": "^0.4.0",
46 | "gulp-if": "^2.0.2",
47 | "gulp-install": "^1.1.0",
48 | "gulp-less": "^3.5.0",
49 | "gulp-rename": "^1.4.0",
50 | "gulp-sourcemaps": "^2.6.4",
51 | "j-component": "git+https://github.com/JuneAndGreen/j-component.git",
52 | "jest": "^23.5.0",
53 | "through2": "^2.0.3",
54 | "webpack": "^4.16.5",
55 | "webpack-node-externals": "^1.7.2",
56 | "eslint-config-airbnb-base": "13.1.0",
57 | "eslint-plugin-import": "^2.14.0",
58 | "eslint-plugin-node": "^7.0.1",
59 | "eslint-plugin-promise": "^3.8.0"
60 | },
61 | "dependencies": {
62 | "@antv/wx-f2": "~1.0.0"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // f2-canvas.js
2 | import F2 from '@antv/wx-f2'
3 |
4 | Component({
5 | /**
6 | * 组件的属性列表
7 | */
8 | properties: {
9 | canvasId: {
10 | type: String,
11 | value: 'f2-canvas'
12 | },
13 |
14 | opts: {
15 | type: Object
16 | }
17 | },
18 |
19 | /**
20 | * 组件的初始数据
21 | */
22 | data: {
23 |
24 | },
25 |
26 | ready() {
27 | if (!this.data.opts) {
28 | console.warn('组件需绑定 opts 变量,例:')
29 | return
30 | }
31 |
32 | if (!this.data.opts.lazyLoad) {
33 | this.init()
34 | }
35 | },
36 |
37 | /**
38 | * 组件的方法列表
39 | */
40 | methods: {
41 | init(callback) {
42 | const version = wx.version.version.split('.').map(n => parseInt(n, 10))
43 | const isValid = version[0] > 1 || (version[0] === 1 && version[1] > 9) ||
44 | (version[0] === 1 && version[1] === 9 && version[2] >= 91)
45 | if (!isValid) {
46 | console.error('微信基础库版本过低,需大于等于 1.9.91。')
47 | return
48 | }
49 |
50 | const ctx = wx.createCanvasContext(this.data.canvasId, this) // 获取小程序上下文
51 | const canvas = new F2.Renderer(ctx)
52 | this.canvas = canvas
53 |
54 | const query = wx.createSelectorQuery().in(this)
55 | query.select('.f2-canvas').boundingClientRect(res => {
56 | if (typeof callback === 'function') {
57 | this.chart = callback(canvas, res.width, res.height, F2)
58 | } else if (this.data.opts && this.data.opts.onInit) {
59 | this.chart = this.data.opts.onInit(canvas, res.width, res.height, F2)
60 | }
61 | }).exec()
62 | },
63 | touchStart(e) {
64 | if (this.canvas) {
65 | this.canvas.emitEvent('touchstart', [e])
66 | }
67 | },
68 | touchMove(e) {
69 | if (this.canvas) {
70 | this.canvas.emitEvent('touchmove', [e])
71 | }
72 | },
73 | touchEnd(e) {
74 | if (this.canvas) {
75 | this.canvas.emitEvent('touchend', [e])
76 | }
77 | },
78 | press(e) {
79 | if (this.canvas) {
80 | this.canvas.emitEvent('press', [e])
81 | }
82 | }
83 | }
84 | })
85 |
--------------------------------------------------------------------------------
/src/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/src/index.wxml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/index.wxss:
--------------------------------------------------------------------------------
1 | /* f2-canvas.wxss */
2 | .f2-canvas {
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | const _ = require('./utils')
2 |
3 | let componentId
4 | let component
5 |
6 | beforeAll(async () => {
7 | componentId = await _.load('index', 'comp')
8 | })
9 |
10 | test('render', async () => {
11 | component = _.render(componentId, {prop: 'index.test.properties'})
12 |
13 | const parent = document.createElement('parent-wrapper')
14 | component.attach(parent)
15 |
16 | expect(_.match(component.dom, 'index.test.properties')).toBe(true)
17 | })
18 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | module.exports = require('../tools/test/helper')
2 |
3 | const noop = () => {};
4 |
5 | global.wx = {
6 | request: noop,
7 |
8 | uploadFile: noop,
9 | downloadFile: noop,
10 |
11 | connectSocket: noop,
12 | onSocketOpen: noop,
13 | onSocketError: noop,
14 | sendSocketMessage: noop,
15 | onSocketMessage: noop,
16 | closeSocket: noop,
17 | onSocketClose: noop,
18 |
19 | chooseImage: noop,
20 | previewImage: noop,
21 | getImageInfo: noop,
22 | saveImageToPhotosAlbum: noop,
23 |
24 | startRecord: noop,
25 | stopRecord: noop,
26 |
27 | getRecorderManager: noop,
28 |
29 | playVoice: noop,
30 | pauseVoice: noop,
31 | stopVoice: noop,
32 |
33 | getBackgroundAudioPlayerState: noop,
34 | playBackgroundAudio: noop,
35 | pauseBackgroundAudio: noop,
36 | seekBackgroundAudio: noop,
37 | stopBackgroundAudio: noop,
38 | onBackgroundAudioPlay: noop,
39 | onBackgroundAudioPause: noop,
40 | onBackgroundAudioStop: noop,
41 |
42 | getBackgroundAudioManager: noop,
43 |
44 | createAudioContext: noop,
45 | createInnerAudioContext: noop,
46 | getAvailableAudioSources: noop,
47 |
48 | chooseVideo: noop,
49 | saveVideoToPhotosAlbum: noop,
50 |
51 | createVideoContext: noop,
52 |
53 | createCameraContext: noop,
54 |
55 | createLivePlayerContext: noop,
56 | createLivePusherContext: noop,
57 |
58 | loadFontFace: noop,
59 |
60 | saveFile: noop,
61 | getFileInfo: noop,
62 | getSavedFileList: noop,
63 | getSavedFileInfo: noop,
64 | removeSavedFile: noop,
65 | openDocument: noop,
66 |
67 | setStorage: noop,
68 | setStorageSync: noop,
69 | getStorage: noop,
70 | getStorageSync: noop,
71 | getStorageInfo: noop,
72 | getStorageInfoSync: noop,
73 | removeStorage: noop,
74 | removeStorageSync: noop,
75 | clearStorage: noop,
76 | clearStorageSync: noop,
77 |
78 | getLocation: noop,
79 | chooseLocation: noop,
80 |
81 | openLocation: noop,
82 |
83 | createMapContext: noop,
84 |
85 | getSystemInfo(options = {}) {
86 | const res = {
87 | errMsg: 'getSystemInfo:ok',
88 | SDKVersion: '2.3.0',
89 | batteryLevel: 100,
90 | benchmarkLevel: 1,
91 | brand: 'devtools',
92 | fontSizeSetting: 16,
93 | language: 'zh_CN',
94 | model: 'iPhone 7 Plus',
95 | pixelRatio: 3,
96 | platform: 'devtools',
97 | screenHeight: 736,
98 | screenWidth: 414,
99 | statusBarHeight: 20,
100 | system: 'iOS 10.0.1',
101 | version: '6.6.3',
102 | windowHeight: 672,
103 | windowWidth: 414,
104 | }
105 | typeof options.success === 'function' && options.success(res)
106 | typeof options.complete === 'function' && options.complete(res)
107 | },
108 | getSystemInfoSync() {
109 | let info = {}
110 | wx.getSystemInfo({
111 | success(res) {
112 | info = res
113 | }
114 | })
115 |
116 | delete info.errMsg
117 | return info
118 | },
119 | canIUse: noop,
120 |
121 | onMemoryWarning: noop,
122 |
123 | getNetworkType: noop,
124 | onNetworkStatusChange: noop,
125 |
126 | onAccelerometerChange: noop,
127 | startAccelerometer: noop,
128 | stopAccelerometer: noop,
129 |
130 | onCompassChange: noop,
131 | startCompass: noop,
132 | stopCompass: noop,
133 |
134 | makePhoneCall: noop,
135 |
136 | scanCode: noop,
137 |
138 | setClipboardData: noop,
139 | getClipboardData: noop,
140 |
141 | openBluetoothAdapter: noop,
142 | closeBluetoothAdapter: noop,
143 | getBluetoothAdapterState: noop,
144 | onBluetoothAdapterStateChange: noop,
145 | startBluetoothDevicesDiscovery: noop,
146 | stopBluetoothDevicesDiscovery: noop,
147 | getBluetoothDevices: noop,
148 | getConnectedBluetoothDevices: noop,
149 | onBluetoothDeviceFound: noop,
150 | createBLEConnection: noop,
151 | closeBLEConnection: noop,
152 | getBLEDeviceServices: noop,
153 | getBLEDeviceCharacteristics: noop,
154 | readBLECharacteristicValue: noop,
155 | writeBLECharacteristicValue: noop,
156 | notifyBLECharacteristicValueChange: noop,
157 | onBLEConnectionStateChange: noop,
158 | onBLECharacteristicValueChange: noop,
159 |
160 | startBeaconDiscovery: noop,
161 | stopBeaconDiscovery: noop,
162 | getBeacons: noop,
163 | onBeaconUpdate: noop,
164 | onBeaconServiceChange: noop,
165 |
166 | setScreenBrightness: noop,
167 | getScreenBrightness: noop,
168 | setKeepScreenOn: noop,
169 |
170 | onUserCaptureScreen: noop,
171 |
172 | vibrateLong: noop,
173 | vibrateShort: noop,
174 |
175 | addPhoneContact: noop,
176 |
177 | getHCEState: noop,
178 | startHCE: noop,
179 | stopHCE: noop,
180 | onHCEMessage: noop,
181 | sendHCEMessage: noop,
182 |
183 | startWifi: noop,
184 | stopWifi: noop,
185 | connectWifi: noop,
186 | getWifiList: noop,
187 | onGetWifiList: noop,
188 | setWifiList: noop,
189 | onWifiConnected: noop,
190 | getConnectedWifi: noop,
191 |
192 | showToast: noop,
193 | showLoading: noop,
194 | hideToast: noop,
195 | hideLoading: noop,
196 | showModal: noop,
197 | showActionSheet: noop,
198 |
199 | setNavigationBarTitle: noop,
200 | showNavigationBarLoading: noop,
201 | hideNavigationBarLoading: noop,
202 | setNavigationBarColor: noop,
203 |
204 | setTabBarBadge: noop,
205 | removeTabBarBadge: noop,
206 | showTabBarRedDot: noop,
207 | hideTabBarRedDot: noop,
208 | setTabBarStyle: noop,
209 | setTabBarItem: noop,
210 | showTabBar: noop,
211 | hideTabBar: noop,
212 |
213 | setBackgroundColor: noop,
214 | setBackgroundTextStyle: noop,
215 |
216 | setTopBarText: noop,
217 |
218 | navigateTo: noop,
219 | redirectTo: noop,
220 | switchTab: noop,
221 | navigateBack: noop,
222 | reLaunch: noop,
223 |
224 | createAnimation: noop,
225 |
226 | pageScrollTo: noop,
227 |
228 | createCanvasContext: noop,
229 | createContext: noop,
230 | drawCanvas: noop,
231 | canvasToTempFilePath: noop,
232 | canvasGetImageData: noop,
233 | canvasPutImageData: noop,
234 |
235 | startPullDownRefresh: noop,
236 | stopPullDownRefresh: noop,
237 |
238 | createSelectorQuery: noop,
239 |
240 | createIntersectionObserver: noop,
241 |
242 | getExtConfig: noop,
243 | getExtConfigSync: noop,
244 |
245 | login: noop,
246 | checkSession: noop,
247 |
248 | authorize: noop,
249 |
250 | getUserInfo: noop,
251 |
252 | requestPayment: noop,
253 |
254 | showShareMenu: noop,
255 | hideShareMenu: noop,
256 | updateShareMenu: noop,
257 | getShareInfo: noop,
258 |
259 | chooseAddress: noop,
260 |
261 | addCard: noop,
262 | openCard: noop,
263 |
264 | openSetting: noop,
265 | getSetting: noop,
266 |
267 | getWeRunData: noop,
268 |
269 | getAccountInfoSync: noop,
270 |
271 | navigateToMiniProgram: noop,
272 | navigateBackMiniProgram: noop,
273 |
274 | chooseInvoiceTitle: noop,
275 |
276 | checkIsSupportSoterAuthentication: noop,
277 | startSoterAuthentication: noop,
278 | checkIsSoterEnrolledInDevice: noop,
279 |
280 | getUpdateManager: noop,
281 |
282 | createWorker: noop,
283 |
284 | getLogManager: noop,
285 |
286 | reportMonitor: noop,
287 |
288 | setEnableDebug: noop,
289 | }
290 |
--------------------------------------------------------------------------------
/test/wx.test.js:
--------------------------------------------------------------------------------
1 | const _ = require('./utils')
2 |
3 | test('wx.getSystemInfo', async () => {
4 | wx.getSystemInfo({
5 | success(res) {
6 | expect(res.errMsg).toBe('getSystemInfo:ok')
7 | },
8 | complete(res) {
9 | expect(res.errMsg).toBe('getSystemInfo:ok')
10 | },
11 | })
12 | })
13 |
14 | test('wx.getSystemInfoSync', async () => {
15 | const info = wx.getSystemInfoSync()
16 | expect(info.SDKVersion).toBe('2.3.0')
17 | })
18 |
--------------------------------------------------------------------------------
/tools/build.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const gulp = require('gulp')
4 | const clean = require('gulp-clean')
5 | const less = require('gulp-less')
6 | const rename = require('gulp-rename')
7 | const gulpif = require('gulp-if')
8 | const sourcemaps = require('gulp-sourcemaps')
9 | const webpack = require('webpack')
10 | const gulpInstall = require('gulp-install')
11 |
12 | const config = require('./config')
13 | const checkComponents = require('./checkcomponents')
14 | const _ = require('./utils')
15 |
16 | const wxssConfig = config.wxss || {}
17 | const srcPath = config.srcPath
18 | const distPath = config.distPath
19 |
20 | /**
21 | * get wxss stream
22 | */
23 | function wxss(wxssFileList) {
24 | if (!wxssFileList.length) return false
25 |
26 | return gulp.src(wxssFileList, {cwd: srcPath, base: srcPath})
27 | .pipe(gulpif(wxssConfig.less && wxssConfig.sourcemap, sourcemaps.init()))
28 | .pipe(gulpif(wxssConfig.less, less({paths: [srcPath]})))
29 | .pipe(rename({extname: '.wxss'}))
30 | .pipe(gulpif(wxssConfig.less && wxssConfig.sourcemap, sourcemaps.write('./')))
31 | .pipe(_.logger(wxssConfig.less ? 'generate' : undefined))
32 | .pipe(gulp.dest(distPath))
33 | }
34 |
35 | /**
36 | * get js stream
37 | */
38 | function js(jsFileMap, scope) {
39 | const webpackConfig = config.webpack
40 | const webpackCallback = (err, stats) => {
41 | if (!err) {
42 | // eslint-disable-next-line no-console
43 | console.log(stats.toString({
44 | assets: true,
45 | cached: false,
46 | colors: true,
47 | children: false,
48 | errors: true,
49 | warnings: true,
50 | version: true,
51 | modules: false,
52 | publicPath: true,
53 | }))
54 | } else {
55 | // eslint-disable-next-line no-console
56 | console.log(err)
57 | }
58 | }
59 |
60 | webpackConfig.entry = jsFileMap
61 | webpackConfig.output.path = distPath
62 |
63 | if (scope.webpackWatcher) {
64 | scope.webpackWatcher.close()
65 | scope.webpackWatcher = null
66 | }
67 |
68 | if (config.isWatch) {
69 | scope.webpackWatcher = webpack(webpackConfig).watch({
70 | ignored: /node_modules/,
71 | }, webpackCallback)
72 | } else {
73 | webpack(webpackConfig).run(webpackCallback)
74 | }
75 | }
76 |
77 | /**
78 | * copy file
79 | */
80 | function copy(copyFileList) {
81 | if (!copyFileList.length) return false
82 |
83 | return gulp.src(copyFileList, {cwd: srcPath, base: srcPath})
84 | .pipe(_.logger())
85 | .pipe(gulp.dest(distPath))
86 | }
87 |
88 | /**
89 | * install packages
90 | */
91 | function install() {
92 | return gulp.series(async () => {
93 | const demoDist = config.demoDist
94 | const demoPackageJsonPath = path.join(demoDist, 'package.json')
95 | const packageJson = _.readJson(path.resolve(__dirname, '../package.json'))
96 | const dependencies = packageJson.dependencies || {}
97 |
98 | await _.writeFile(demoPackageJsonPath, JSON.stringify({dependencies}, null, '\t')) // write dev demo's package.json
99 | }, () => {
100 | const demoDist = config.demoDist
101 | const demoPackageJsonPath = path.join(demoDist, 'package.json')
102 |
103 | return gulp.src(demoPackageJsonPath)
104 | .pipe(gulpInstall({production: true}))
105 | })
106 | }
107 |
108 | class BuildTask {
109 | constructor(id, entry) {
110 | if (!entry) return
111 |
112 | this.id = id
113 | this.entries = Array.isArray(config.entry) ? config.entry : [config.entry]
114 | this.copyList = Array.isArray(config.copy) ? config.copy : []
115 | this.componentListMap = {}
116 | this.cachedComponentListMap = {}
117 |
118 | this.init()
119 | }
120 |
121 | init() {
122 | const id = this.id
123 |
124 | /**
125 | * clean the dist folder
126 | */
127 | gulp.task(`${id}-clean-dist`, () => gulp.src(distPath, {read: false, allowEmpty: true}).pipe(clean()))
128 |
129 | /**
130 | * copy demo to the dev folder
131 | */
132 | let isDemoExists = false
133 | gulp.task(`${id}-demo`, gulp.series(async () => {
134 | const demoDist = config.demoDist
135 |
136 | isDemoExists = await _.checkFileExists(path.join(demoDist, 'project.config.json'))
137 | }, done => {
138 | if (!isDemoExists) {
139 | const demoSrc = config.demoSrc
140 | const demoDist = config.demoDist
141 |
142 | return gulp.src('**/*', {cwd: demoSrc, base: demoSrc})
143 | .pipe(gulp.dest(demoDist))
144 | }
145 |
146 | return done()
147 | }))
148 |
149 | /**
150 | * install packages for dev
151 | */
152 | gulp.task(`${id}-install`, install())
153 |
154 | /**
155 | * check custom components
156 | */
157 | gulp.task(`${id}-component-check`, async () => {
158 | const entries = this.entries
159 | const mergeComponentListMap = {}
160 | for (let i = 0, len = entries.length; i < len; i++) {
161 | let entry = entries[i]
162 | entry = path.join(srcPath, `${entry}.json`)
163 | // eslint-disable-next-line no-await-in-loop
164 | const newComponentListMap = await checkComponents(entry)
165 |
166 | _.merge(mergeComponentListMap, newComponentListMap)
167 | }
168 |
169 | this.cachedComponentListMap = this.componentListMap
170 | this.componentListMap = mergeComponentListMap
171 | })
172 |
173 | /**
174 | * write json to the dist folder
175 | */
176 | gulp.task(`${id}-component-json`, done => {
177 | const jsonFileList = this.componentListMap.jsonFileList
178 |
179 | if (jsonFileList && jsonFileList.length) {
180 | return copy(this.componentListMap.jsonFileList)
181 | }
182 |
183 | return done()
184 | })
185 |
186 | /**
187 | * copy wxml to the dist folder
188 | */
189 | gulp.task(`${id}-component-wxml`, done => {
190 | const wxmlFileList = this.componentListMap.wxmlFileList
191 |
192 | if (wxmlFileList &&
193 | wxmlFileList.length &&
194 | !_.compareArray(this.cachedComponentListMap.wxmlFileList, wxmlFileList)) {
195 | return copy(wxmlFileList)
196 | }
197 |
198 | return done()
199 | })
200 |
201 | /**
202 | * generate wxss to the dist folder
203 | */
204 | gulp.task(`${id}-component-wxss`, done => {
205 | const wxssFileList = this.componentListMap.wxssFileList
206 |
207 | if (wxssFileList &&
208 | wxssFileList.length &&
209 | !_.compareArray(this.cachedComponentListMap.wxssFileList, wxssFileList)) {
210 | return wxss(wxssFileList, srcPath, distPath)
211 | }
212 |
213 | return done()
214 | })
215 |
216 | /**
217 | * generate js to the dist folder
218 | */
219 | gulp.task(`${id}-component-js`, done => {
220 | const jsFileList = this.componentListMap.jsFileList
221 |
222 | if (jsFileList &&
223 | jsFileList.length &&
224 | !_.compareArray(this.cachedComponentListMap.jsFileList, jsFileList)) {
225 | js(this.componentListMap.jsFileMap, this)
226 | }
227 |
228 | return done()
229 | })
230 |
231 | /**
232 | * copy resources to dist folder
233 | */
234 | gulp.task(`${id}-copy`, gulp.parallel(done => {
235 | const copyList = this.copyList
236 | const copyFileList = copyList.map(dir => path.join(dir, '**/*.!(wxss)'))
237 |
238 | if (copyFileList.length) return copy(copyFileList)
239 |
240 | return done()
241 | }, done => {
242 | const copyList = this.copyList
243 | const copyFileList = copyList.map(dir => path.join(dir, '**/*.wxss'))
244 |
245 | if (copyFileList.length) return wxss(copyFileList, srcPath, distPath)
246 |
247 | return done()
248 | }))
249 |
250 | /**
251 | * watch json
252 | */
253 | gulp.task(`${id}-watch-json`, () => gulp.watch(this.componentListMap.jsonFileList, {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-check`, gulp.parallel(`${id}-component-wxml`, `${id}-component-wxss`, `${id}-component-js`, `${id}-component-json`))))
254 |
255 | /**
256 | * watch wxml
257 | */
258 | gulp.task(`${id}-watch-wxml`, () => {
259 | this.cachedComponentListMap.wxmlFileList = null
260 | return gulp.watch(this.componentListMap.wxmlFileList, {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-wxml`))
261 | })
262 |
263 | /**
264 | * watch wxss
265 | */
266 | gulp.task(`${id}-watch-wxss`, () => {
267 | this.cachedComponentListMap.wxssFileList = null
268 | return gulp.watch('**/*.wxss', {cwd: srcPath, base: srcPath}, gulp.series(`${id}-component-wxss`))
269 | })
270 |
271 | /**
272 | * watch resources
273 | */
274 | gulp.task(`${id}-watch-copy`, () => {
275 | const copyList = this.copyList
276 | const copyFileList = copyList.map(dir => path.join(dir, '**/*'))
277 | const watchCallback = filePath => copy([filePath])
278 |
279 | return gulp.watch(copyFileList, {cwd: srcPath, base: srcPath})
280 | .on('change', watchCallback)
281 | .on('add', watchCallback)
282 | .on('unlink', watchCallback)
283 | })
284 |
285 | /**
286 | * watch demo
287 | */
288 | gulp.task(`${id}-watch-demo`, () => {
289 | const demoSrc = config.demoSrc
290 | const demoDist = config.demoDist
291 | const watchCallback = filePath => gulp.src(filePath, {cwd: demoSrc, base: demoSrc})
292 | .pipe(gulp.dest(demoDist))
293 |
294 | return gulp.watch('**/*', {cwd: demoSrc, base: demoSrc})
295 | .on('change', watchCallback)
296 | .on('add', watchCallback)
297 | .on('unlink', watchCallback)
298 | })
299 |
300 | /**
301 | * watch installed packages
302 | */
303 | gulp.task(`${id}-watch-install`, () => gulp.watch(path.resolve(__dirname, '../package.json'), install()))
304 |
305 | /**
306 | * build custom component
307 | */
308 | gulp.task(`${id}-build`, gulp.series(`${id}-clean-dist`, `${id}-component-check`, gulp.parallel(`${id}-component-wxml`, `${id}-component-wxss`, `${id}-component-js`, `${id}-component-json`, `${id}-copy`)))
309 |
310 | gulp.task(`${id}-watch`, gulp.series(`${id}-build`, `${id}-demo`, `${id}-install`, gulp.parallel(`${id}-watch-wxml`, `${id}-watch-wxss`, `${id}-watch-json`, `${id}-watch-copy`, `${id}-watch-install`, `${id}-watch-demo`)))
311 |
312 | gulp.task(`${id}-dev`, gulp.series(`${id}-build`, `${id}-demo`, `${id}-install`))
313 |
314 | gulp.task(`${id}-default`, gulp.series(`${id}-build`))
315 | }
316 | }
317 |
318 | module.exports = BuildTask
319 |
--------------------------------------------------------------------------------
/tools/checkcomponents.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const _ = require('./utils')
4 | const config = require('./config')
5 |
6 | const srcPath = config.srcPath
7 |
8 | /**
9 | * get json path's info
10 | */
11 | function getJsonPathInfo(jsonPath) {
12 | const dirPath = path.dirname(jsonPath)
13 | const fileName = path.basename(jsonPath, '.json')
14 | const relative = path.relative(srcPath, dirPath)
15 | const fileBase = path.join(relative, fileName)
16 |
17 | return {
18 | dirPath, fileName, relative, fileBase
19 | }
20 | }
21 |
22 | /**
23 | * check included components
24 | */
25 | const checkProps = ['usingComponents', 'componentGenerics']
26 | async function checkIncludedComponents(jsonPath, componentListMap) {
27 | const json = _.readJson(jsonPath)
28 | if (!json) throw new Error(`json is not valid: "${jsonPath}"`)
29 |
30 | const {dirPath, fileName, fileBase} = getJsonPathInfo(jsonPath)
31 |
32 | for (let i = 0, len = checkProps.length; i < len; i++) {
33 | const checkProp = checkProps[i]
34 | const checkPropValue = json[checkProp] || {}
35 | const keys = Object.keys(checkPropValue)
36 |
37 | for (let j = 0, jlen = keys.length; j < jlen; j++) {
38 | const key = keys[j]
39 | let value = typeof checkPropValue[key] === 'object' ? checkPropValue[key].default : checkPropValue[key]
40 | if (!value) continue
41 |
42 | value = _.transformPath(value, path.sep)
43 |
44 | // check relative path
45 | const componentPath = `${path.join(dirPath, value)}.json`
46 | // eslint-disable-next-line no-await-in-loop
47 | const isExists = await _.checkFileExists(componentPath)
48 | if (isExists) {
49 | // eslint-disable-next-line no-await-in-loop
50 | await checkIncludedComponents(componentPath, componentListMap)
51 | }
52 | }
53 | }
54 |
55 | // checked
56 | componentListMap.wxmlFileList.push(`${fileBase}.wxml`)
57 | componentListMap.wxssFileList.push(`${fileBase}.wxss`)
58 | componentListMap.jsonFileList.push(`${fileBase}.json`)
59 | componentListMap.jsFileList.push(`${fileBase}.js`)
60 |
61 | componentListMap.jsFileMap[fileBase] = `${path.join(dirPath, fileName)}.js`
62 | }
63 |
64 | module.exports = async function (entry) {
65 | const componentListMap = {
66 | wxmlFileList: [],
67 | wxssFileList: [],
68 | jsonFileList: [],
69 | jsFileList: [],
70 |
71 | jsFileMap: {}, // for webpack entry
72 | }
73 |
74 | const isExists = await _.checkFileExists(entry)
75 | if (!isExists) {
76 | const {dirPath, fileName, fileBase} = getJsonPathInfo(entry)
77 |
78 | componentListMap.jsFileList.push(`${fileBase}.js`)
79 | componentListMap.jsFileMap[fileBase] = `${path.join(dirPath, fileName)}.js`
80 |
81 | return componentListMap
82 | }
83 |
84 | await checkIncludedComponents(entry, componentListMap)
85 |
86 | return componentListMap
87 | }
88 |
--------------------------------------------------------------------------------
/tools/config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const webpack = require('webpack')
4 | const nodeExternals = require('webpack-node-externals')
5 |
6 | const isDev = process.argv.indexOf('--develop') >= 0
7 | const isWatch = process.argv.indexOf('--watch') >= 0
8 | const demoSrc = path.resolve(__dirname, './demo')
9 | const demoDist = path.resolve(__dirname, '../miniprogram_dev')
10 | const src = path.resolve(__dirname, '../src')
11 | const dev = path.join(demoDist, 'components')
12 | const dist = path.resolve(__dirname, '../miniprogram_dist')
13 |
14 | module.exports = {
15 | entry: ['index'],
16 |
17 | isDev,
18 | isWatch,
19 | srcPath: src,
20 | distPath: isDev ? dev : dist,
21 |
22 | demoSrc,
23 | demoDist,
24 |
25 | wxss: {
26 | less: false, // compile wxss with less
27 | sourcemap: false, // source map for less
28 | },
29 |
30 | webpack: {
31 | mode: 'production',
32 | output: {
33 | filename: '[name].js',
34 | libraryTarget: 'commonjs2',
35 | },
36 | target: 'node',
37 | externals: [nodeExternals()], // ignore node_modules
38 | module: {
39 | rules: [{
40 | test: /\.js$/i,
41 | use: [
42 | 'babel-loader',
43 | 'eslint-loader'
44 | ],
45 | exclude: /node_modules/
46 | }],
47 | },
48 | resolve: {
49 | modules: [src, 'node_modules'],
50 | extensions: ['.js', '.json'],
51 | },
52 | plugins: [
53 | new webpack.DefinePlugin({}),
54 | new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}),
55 | ],
56 | optimization: {
57 | minimize: false,
58 | },
59 | // devtool: 'nosources-source-map', // source map for js
60 | performance: {
61 | hints: 'warning',
62 | assetFilter: assetFilename => assetFilename.endsWith('.js')
63 | }
64 | },
65 |
66 | copy: ['./wxml', './wxss', './wxs', './images'], // will copy to dist
67 | }
68 |
--------------------------------------------------------------------------------
/tools/test/helper.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const jComponent = require('j-component')
4 |
5 | const config = require('../config')
6 | const _ = require('../utils')
7 |
8 | const srcPath = config.srcPath
9 | const componentMap = {}
10 | let nowLoad = null
11 |
12 | /**
13 | * register custom component
14 | */
15 | global.Component = options => {
16 | const component = nowLoad
17 | const definition = Object.assign({
18 | template: component.wxml,
19 | usingComponents: component.json.usingComponents,
20 | tagName: component.tagName,
21 | }, options)
22 |
23 | component.id = jComponent.register(definition)
24 | }
25 |
26 | /**
27 | * register behavior
28 | */
29 | global.Behavior = options => jComponent.behavior(options)
30 |
31 | /**
32 | * register global components
33 | */
34 | // eslint-disable-next-line semi-style
35 | ;[
36 | 'view', 'scroll-view', 'swiper', 'movable-view', 'cover-view', 'cover-view',
37 | 'icon', 'text', 'rich-text', 'progress',
38 | 'button', 'checkbox', 'form', 'input', 'label', 'picker', 'picker', 'picker-view', 'radio', 'slider', 'switch', 'textarea',
39 | 'navigator', 'function-page-navigator',
40 | 'audio', 'image', 'video', 'camera', 'live-player', 'live-pusher',
41 | 'map',
42 | 'canvas',
43 | 'open-data', 'web-view', 'ad'
44 | ].forEach(name => {
45 | jComponent.register({
46 | id: name,
47 | tagName: `wx-${name}`,
48 | template: ''
49 | })
50 | })
51 |
52 | /**
53 | * Touch polyfill
54 | */
55 | class Touch {
56 | constructor(options = {}) {
57 | this.clientX = 0
58 | this.clientY = 0
59 | this.identifier = 0
60 | this.pageX = 0
61 | this.pageY = 0
62 | this.screenX = 0
63 | this.screenY = 0
64 | this.target = null
65 |
66 | Object.keys(options).forEach(key => {
67 | this[key] = options[key]
68 | })
69 | }
70 | }
71 | global.Touch = window.Touch = Touch
72 |
73 | /**
74 | * load component
75 | */
76 | async function load(componentPath, tagName) {
77 | if (typeof componentPath === 'object') {
78 | const definition = componentPath
79 |
80 | return jComponent.register(definition)
81 | }
82 |
83 | const wholePath = path.join(srcPath, componentPath)
84 |
85 | const oldLoad = nowLoad
86 | const component = nowLoad = {}
87 |
88 | component.tagName = tagName
89 | component.wxml = await _.readFile(`${wholePath}.wxml`)
90 | component.wxss = await _.readFile(`${wholePath}.wxss`)
91 | component.json = _.readJson(`${wholePath}.json`)
92 |
93 | if (!component.json) {
94 | throw new Error(`invalid component: ${wholePath}`)
95 | }
96 |
97 | // preload using components
98 | const usingComponents = component.json.usingComponents || {}
99 | const usingComponentKeys = Object.keys(usingComponents)
100 | for (let i = 0, len = usingComponentKeys.length; i < len; i++) {
101 | const key = usingComponentKeys[i]
102 | const usingPath = path.join(path.dirname(componentPath), usingComponents[key])
103 | // eslint-disable-next-line no-await-in-loop
104 | const id = await load(usingPath)
105 |
106 | usingComponents[key] = id
107 | }
108 |
109 | // require js
110 | // eslint-disable-next-line import/no-dynamic-require
111 | require(wholePath)
112 |
113 | nowLoad = oldLoad
114 | componentMap[wholePath] = component
115 |
116 | return component.id
117 | }
118 |
119 | /**
120 | * render component
121 | */
122 | function render(componentId, properties) {
123 | if (!componentId) throw new Error('you need to pass the componentId')
124 |
125 | return jComponent.create(componentId, properties)
126 | }
127 |
128 | /**
129 | * test a dom is similar to the html
130 | */
131 | function match(dom, html) {
132 | if (!(dom instanceof window.Element) || !html || typeof html !== 'string') return false
133 |
134 | // remove some
135 | html = html.trim()
136 | .replace(/(>)[\n\r\s\t]+(<)/g, '$1$2')
137 |
138 | const a = dom.cloneNode()
139 | const b = dom.cloneNode()
140 |
141 | a.innerHTML = dom.innerHTML
142 | b.innerHTML = html
143 |
144 | return a.isEqualNode(b)
145 | }
146 |
147 | /**
148 | * wait for some time
149 | */
150 | function sleep(time = 0) {
151 | return new Promise(resolve => {
152 | setTimeout(() => {
153 | resolve()
154 | }, time)
155 | })
156 | }
157 |
158 | module.exports = {
159 | load,
160 | render,
161 | match,
162 | sleep,
163 | }
164 |
--------------------------------------------------------------------------------
/tools/utils.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 |
4 | // eslint-disable-next-line no-unused-vars
5 | const colors = require('colors')
6 | const through = require('through2')
7 |
8 | /**
9 | * async function wrapper
10 | */
11 | function wrap(func, scope) {
12 | return function (...args) {
13 | if (args.length) {
14 | const temp = args.pop()
15 | if (typeof temp !== 'function') {
16 | args.push(temp)
17 | }
18 | }
19 |
20 | return new Promise(function (resolve, reject) {
21 | args.push(function (err, data) {
22 | if (err) reject(err)
23 | else resolve(data)
24 | })
25 |
26 | func.apply((scope || null), args)
27 | })
28 | }
29 | }
30 |
31 | const accessSync = wrap(fs.access)
32 | const statSync = wrap(fs.stat)
33 | const renameSync = wrap(fs.rename)
34 | const mkdirSync = wrap(fs.mkdir)
35 | const readFileSync = wrap(fs.readFile)
36 | const writeFileSync = wrap(fs.writeFile)
37 |
38 | /**
39 | * transform path segment separator
40 | */
41 | function transformPath(filePath, sep = '/') {
42 | return filePath.replace(/[\\/]/g, sep)
43 | }
44 |
45 | /**
46 | * check file exists
47 | */
48 | async function checkFileExists(filePath) {
49 | try {
50 | await accessSync(filePath)
51 | return true
52 | } catch (err) {
53 | return false
54 | }
55 | }
56 |
57 | /**
58 | * create folder
59 | */
60 | async function recursiveMkdir(dirPath) {
61 | const prevDirPath = path.dirname(dirPath)
62 | try {
63 | await accessSync(prevDirPath)
64 | } catch (err) {
65 | // prevDirPath is not exist
66 | await recursiveMkdir(prevDirPath)
67 | }
68 |
69 | try {
70 | await accessSync(dirPath)
71 |
72 | const stat = await statSync(dirPath)
73 | if (stat && !stat.isDirectory()) {
74 | // dirPath already exists but is not a directory
75 | await renameSync(dirPath, `${dirPath}.bak`) // rename to a file with the suffix ending in '.bak'
76 | await mkdirSync(dirPath)
77 | }
78 | } catch (err) {
79 | // dirPath is not exist
80 | await mkdirSync(dirPath)
81 | }
82 | }
83 |
84 | /**
85 | * read json
86 | */
87 | function readJson(filePath) {
88 | try {
89 | // eslint-disable-next-line import/no-dynamic-require
90 | const content = require(filePath)
91 | delete require.cache[require.resolve(filePath)]
92 | return content
93 | } catch (err) {
94 | return null
95 | }
96 | }
97 |
98 | /**
99 | * read file
100 | */
101 | async function readFile(filePath) {
102 | try {
103 | return await readFileSync(filePath, 'utf8')
104 | } catch (err) {
105 | // eslint-disable-next-line no-console
106 | return console.error(err)
107 | }
108 | }
109 |
110 | /**
111 | * write file
112 | */
113 | async function writeFile(filePath, data) {
114 | try {
115 | await recursiveMkdir(path.dirname(filePath))
116 | return await writeFileSync(filePath, data, 'utf8')
117 | } catch (err) {
118 | // eslint-disable-next-line no-console
119 | return console.error(err)
120 | }
121 | }
122 |
123 | /**
124 | * time format
125 | */
126 | function format(time, reg) {
127 | const date = typeof time === 'string' ? new Date(time) : time
128 | const map = {}
129 | map.yyyy = date.getFullYear()
130 | map.yy = ('' + map.yyyy).substr(2)
131 | map.M = date.getMonth() + 1
132 | map.MM = (map.M < 10 ? '0' : '') + map.M
133 | map.d = date.getDate()
134 | map.dd = (map.d < 10 ? '0' : '') + map.d
135 | map.H = date.getHours()
136 | map.HH = (map.H < 10 ? '0' : '') + map.H
137 | map.m = date.getMinutes()
138 | map.mm = (map.m < 10 ? '0' : '') + map.m
139 | map.s = date.getSeconds()
140 | map.ss = (map.s < 10 ? '0' : '') + map.s
141 |
142 | return reg.replace(/\byyyy|yy|MM|M|dd|d|HH|H|mm|m|ss|s\b/g, $1 => map[$1])
143 | }
144 |
145 | /**
146 | * logger plugin
147 | */
148 | function logger(action = 'copy') {
149 | return through.obj(function (file, enc, cb) {
150 | const type = path.extname(file.path).slice(1).toLowerCase()
151 |
152 | // eslint-disable-next-line no-console
153 | console.log(`[${format(new Date(), 'yyyy-MM-dd HH:mm:ss').grey}] [${action.green} ${type.green}] ${'=>'.cyan} ${file.path}`)
154 |
155 | this.push(file)
156 | cb()
157 | })
158 | }
159 |
160 | /**
161 | * compare arrays
162 | */
163 | function compareArray(arr1, arr2) {
164 | if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false
165 | if (arr1.length !== arr2.length) return false
166 |
167 | for (let i = 0, len = arr1.length; i < len; i++) {
168 | if (arr1[i] !== arr2[i]) return false
169 | }
170 |
171 | return true
172 | }
173 |
174 | /**
175 | * merge two object
176 | */
177 | function merge(obj1, obj2) {
178 | Object.keys(obj2).forEach(key => {
179 | if (Array.isArray(obj1[key]) && Array.isArray(obj2[key])) {
180 | obj1[key] = obj1[key].concat(obj2[key])
181 | } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
182 | obj1[key] = Object.assign(obj1[key], obj2[key])
183 | } else {
184 | obj1[key] = obj2[key]
185 | }
186 | })
187 |
188 | return obj1
189 | }
190 |
191 | /**
192 | * get random id
193 | */
194 | let seed = +new Date()
195 | function getId() {
196 | return ++seed
197 | }
198 |
199 | module.exports = {
200 | wrap,
201 | transformPath,
202 |
203 | checkFileExists,
204 | readJson,
205 | readFile,
206 | writeFile,
207 |
208 | logger,
209 | format,
210 | compareArray,
211 | merge,
212 | getId,
213 | }
214 |
--------------------------------------------------------------------------------