├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── docs
└── navigation-bar.gif
├── gulpfile.js
├── package.json
├── src
├── images
│ ├── nav_icon_black.png
│ └── nav_icon_white.png
├── index.js
├── index.json
├── index.wxml
└── index.wxss
├── test
└── utils.js
└── tools
├── build.js
├── checkcomponents.js
├── config.js
├── demo
├── app.js
├── app.json
├── app.wxss
├── package.json
├── pages
│ ├── index
│ │ ├── index.js
│ │ └── index.wxml
│ └── nav
│ │ ├── nav.js
│ │ ├── nav.json
│ │ ├── nav.wxml
│ │ └── nav.wxss
└── project.config.json
├── 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 | 'arrow-parens': 'off',
25 | 'comma-dangle': [
26 | 'error',
27 | 'only-multiline'
28 | ],
29 | 'complexity': ['error', 10],
30 | 'func-names': 'off',
31 | 'global-require': 'off',
32 | 'handle-callback-err': [
33 | 'error',
34 | '^(err|error)$'
35 | ],
36 | 'import/no-unresolved': [
37 | 'error',
38 | {
39 | 'caseSensitive': true,
40 | 'commonjs': true,
41 | 'ignore': ['^[^.]']
42 | }
43 | ],
44 | 'import/prefer-default-export': 'off',
45 | 'linebreak-style': 'off',
46 | 'no-catch-shadow': 'error',
47 | 'no-continue': 'off',
48 | 'no-div-regex': 'warn',
49 | 'no-else-return': 'off',
50 | 'no-param-reassign': 'off',
51 | 'no-plusplus': 'off',
52 | 'no-shadow': 'off',
53 | 'no-multi-assign': 'off',
54 | 'no-underscore-dangle': 'off',
55 | 'node/no-deprecated-api': 'error',
56 | 'node/process-exit-as-throw': 'error',
57 | 'object-curly-spacing': [
58 | 'error',
59 | 'never'
60 | ],
61 | 'operator-linebreak': [
62 | 'error',
63 | 'after',
64 | {
65 | 'overrides': {
66 | ':': 'before',
67 | '?': 'before'
68 | }
69 | }
70 | ],
71 | 'prefer-arrow-callback': 'off',
72 | 'prefer-destructuring': 'off',
73 | 'prefer-template': 'off',
74 | 'quote-props': [
75 | 1,
76 | 'as-needed',
77 | {
78 | 'unnecessary': true
79 | }
80 | ],
81 | 'semi': [
82 | 'error',
83 | 'never'
84 | ]
85 | },
86 | 'globals': {
87 | 'window': true,
88 | 'document': true,
89 | 'App': true,
90 | 'Page': true,
91 | 'Component': true,
92 | 'Behavior': true,
93 | 'wx': true,
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/.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 wechat-miniprogram
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 | > 本仓库不再维护,有需求请使用[weui-miniprogram](https://github.com/wechat-miniprogram/weui-miniprogram)的navigation-bar组件。
2 |
3 | # navigation-bar
4 |
5 | 小程序自定义组件
6 |
7 | > 使用此组件需要依赖小程序基础库 2.2.1 以上版本,同时依赖开发者工具的 npm 构建。具体详情可查阅[官方 npm 文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)。
8 |
9 | ## 使用效果
10 | 
11 |
12 | ## 使用方法
13 |
14 | 1. 安装 navigation-bar:
15 |
16 | ```
17 | npm install --save miniprogram-navigation-bar
18 | ```
19 | 2. 在app.json中设置:
20 | ```json
21 | {
22 | "window": {
23 | "navigationStyle": "custom"
24 | }
25 | }
26 | ```
27 |
28 | 3. 在需要使用 navigation-bar 的页面 page.json 中添加 navigation-bar 自定义组件配置
29 |
30 | ```json
31 | {
32 | "usingComponents": {
33 | "navigation-bar": "miniprogram-navigation-bar"
34 | }
35 | }
36 | ```
37 | 4. WXML 文件中引用 navigation-bar
38 |
39 | ``` xml
40 |
41 | ```
42 | **navigation-bar的属性介绍如下:**
43 |
44 | | 属性名 | 类型 | 默认值 | 是否必须 | 说明 |
45 | |------------------------|-------------|------------|----------------|---------------------------------------------------|
46 | | title | String | 微信 | 否 | 导航栏标题文字内容 |
47 | | enable | Boolean | true | 否 | 是否可以返回上一页面 |
48 | | delta | Number | 1 | 否 | 返回的页面数,如果delta大于现有页面数,则返回到首页 |
49 | | show-loading |Boolean | false | 否 | 是否展示加载动画 |
50 | | bg-style | String | | 否 | 可设定导航栏样式,如'background-color: green' |
51 | | title-style | String | | 否 | 可设定导航栏标题样式,如'color: black' |
52 | | text-style | String |white | 否 |设置返回按钮颜色, 仅支持 white 和 black |
53 |
54 |
55 | **Tip:** navigation-bar 中包含 slot 节点,位置固定于 navigation-bar 的最右侧,用于承载使用者提供的 wxml 结构
56 |
--------------------------------------------------------------------------------
/docs/navigation-bar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechat-miniprogram/navigation-bar/f285ea6df432a261439717729a1dcce7ac3cf59c/docs/navigation-bar.gif
--------------------------------------------------------------------------------
/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 | new BuildTask(id, config.entry);
10 |
11 | // clean the generated folders and files
12 | gulp.task('clean', gulp.series(() => {
13 | return gulp.src(config.distPath, { read: false, allowEmpty: true })
14 | .pipe(clean())
15 | }, done => {
16 | if (config.isDev) {
17 | return gulp.src(config.demoDist, { read: false, allowEmpty: true })
18 | .pipe(clean());
19 | }
20 |
21 | done();
22 | }));
23 | // watch files and build
24 | gulp.task('watch', gulp.series(`${id}-watch`));
25 | // build for develop
26 | gulp.task('dev', gulp.series(`${id}-dev`));
27 | // build for publish
28 | gulp.task('default', gulp.series(`${id}-default`));
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "miniprogram-navigation-bar",
3 | "version": "0.0.4",
4 | "description": "miniprogram custom component",
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 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/wechat-miniprogram/navigation-bar.git"
22 | },
23 | "author": "wechat-miniprogram",
24 | "license": "MIT",
25 | "devDependencies": {
26 | "babel-core": "^6.26.3",
27 | "babel-loader": "^7.1.5",
28 | "babel-plugin-module-resolver": "^3.1.1",
29 | "babel-preset-env": "^1.7.0",
30 | "colors": "^1.3.1",
31 | "eslint": "^5.3.0",
32 | "eslint-loader": "^2.1.0",
33 | "gulp": "^4.0.0",
34 | "gulp-clean": "^0.4.0",
35 | "gulp-if": "^2.0.2",
36 | "gulp-install": "^1.1.0",
37 | "gulp-less": "^3.5.0",
38 | "gulp-rename": "^1.4.0",
39 | "gulp-sourcemaps": "^2.6.4",
40 | "j-component": "git+https://github.com/JuneAndGreen/j-component.git",
41 | "jest": "^23.5.0",
42 | "through2": "^2.0.3",
43 | "webpack": "^4.16.5",
44 | "webpack-node-externals": "^1.7.2",
45 | "eslint-config-airbnb-base": "13.1.0",
46 | "eslint-plugin-import": "^2.14.0",
47 | "eslint-plugin-node": "^7.0.1",
48 | "eslint-plugin-promise": "^3.8.0"
49 | },
50 | "dependencies": {},
51 | "jest": {
52 | "testEnvironment": "jsdom",
53 | "testURL": "https://jest.test",
54 | "collectCoverageFrom": [
55 | "src/**/*.js"
56 | ],
57 | "moduleDirectories": [
58 | "node_modules",
59 | "src"
60 | ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/images/nav_icon_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechat-miniprogram/navigation-bar/f285ea6df432a261439717729a1dcce7ac3cf59c/src/images/nav_icon_black.png
--------------------------------------------------------------------------------
/src/images/nav_icon_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechat-miniprogram/navigation-bar/f285ea6df432a261439717729a1dcce7ac3cf59c/src/images/nav_icon_white.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const _barHeight = wx.getSystemInfoSync().statusBarHeight
2 |
3 | Component({
4 | /**
5 | * 组件的属性列表
6 | */
7 | properties: {
8 | // 决定navback是否有效
9 | enable: {
10 | type: Boolean,
11 | value: 'true'
12 | },
13 | // 可传入改变navbar样式
14 | bgStyle: {
15 | type: String,
16 | value: 'background-color:#000;',
17 | },
18 | // 可传入改变navbar title样式
19 | titleStyle: {
20 | type: String,
21 | value: 'color: white;',
22 | },
23 | title: {
24 | type: String,
25 | value: '微信',
26 | observer: '_changeTitle',
27 | },
28 | // 可传入改变nav back页面数
29 | delta: {
30 | type: Number,
31 | value: 1
32 | },
33 | // 决定是否显示loading
34 | showLoading: {
35 | type: Boolean,
36 | value: false
37 | },
38 | textStyle: {
39 | type: String,
40 | value: 'white',
41 | observer: '_changeTextStyle'
42 | }
43 | },
44 |
45 | /**
46 | * 组件的初始数据
47 | */
48 | data: {
49 | barHeight: _barHeight,
50 | navIconUrl: './images/nav_icon_white.png',
51 | navTitleStyle: 'color: white;',
52 | navBgStyle: 'background-color:#000;',
53 | },
54 |
55 | /**
56 | * 组件的方法列表
57 | */
58 | attach() {
59 | // eslint-disable-next-line no-console
60 | console.log(wx.getCurrent)
61 | },
62 | methods: {
63 | // title监听函数
64 | _changeTitle() {
65 | if (this.data.title === '') {
66 | this.setData({
67 | title: '微信'
68 | })
69 | }
70 | },
71 | _changeTextStyle() {
72 | if (this.data.textStyle === 'black') {
73 | wx.setNavigationBarColor({
74 | frontColor: '#000000',
75 | })
76 | this.setData({
77 | navIconUrl: './images/nav_icon_black.png',
78 | navTitleStyle: 'color: black;',
79 | navBgStyle: 'background-color:#fff;',
80 | })
81 | } else {
82 | wx.setNavigationBarColor({
83 | frontColor: '#ffffff',
84 | })
85 | this.setData({
86 | navIconUrl: './images/nav_icon_white.png',
87 | navTitleStyle: 'color: white;',
88 | navBgStyle: 'background-color:#000;',
89 | })
90 | }
91 | },
92 | // navback监听函数
93 | _onTap() {
94 | this.triggerEvent('back', {})
95 | if (this.data.enable) {
96 | wx.navigateBack({
97 | delta: this.data.delta
98 | })
99 | }
100 | }
101 | }
102 | })
103 |
--------------------------------------------------------------------------------
/src/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/src/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{title}}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/index.wxss:
--------------------------------------------------------------------------------
1 | ._nav {
2 | width: 100%;
3 | box-sizing: border-box;
4 | position: fixed;
5 | text-align: center;
6 | display: flex;
7 | flex-direction: row;
8 | z-index: 9999;
9 | }
10 | .nav-icon {
11 | width: 25rpx;
12 | height: 40rpx;
13 | padding: 25rpx 25rpx;
14 | }
15 | .loading-icon {
16 | width: 35rpx;
17 | height: 35rpx;
18 | animation: loading-animate 1s steps(12, end) infinite;
19 | content: " ";
20 | display: inline-block;
21 | vertical-align: middle;
22 | background: transparent url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iciIgd2lkdGg9JzEyMHB4JyBoZWlnaHQ9JzEyMHB4JyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj4KICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiBmaWxsPSJub25lIiBjbGFzcz0iYmsiPjwvcmVjdD4KICAgIDxyZWN0IHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjRTlFOUU5JwogICAgICAgICAgdHJhbnNmb3JtPSdyb3RhdGUoMCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+CiAgICA8L3JlY3Q+CiAgICA8cmVjdCB4PSc0Ni41JyB5PSc0MCcgd2lkdGg9JzcnIGhlaWdodD0nMjAnIHJ4PSc1JyByeT0nNScgZmlsbD0nIzk4OTY5NycKICAgICAgICAgIHRyYW5zZm9ybT0ncm90YXRlKDMwIDUwIDUwKSB0cmFuc2xhdGUoMCAtMzApJz4KICAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0naW5kZWZpbml0ZScvPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyM5Qjk5OUEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSg2MCA1MCA1MCkgdHJhbnNsYXRlKDAgLTMwKSc+CiAgICAgICAgICAgICAgICAgcmVwZWF0Q291bnQ9J2luZGVmaW5pdGUnLz4KICAgIDwvcmVjdD4KICAgIDxyZWN0IHg9JzQ2LjUnIHk9JzQwJyB3aWR0aD0nNycgaGVpZ2h0PScyMCcgcng9JzUnIHJ5PSc1JyBmaWxsPScjQTNBMUEyJwogICAgICAgICAgdHJhbnNmb3JtPSdyb3RhdGUoOTAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNBQkE5QUEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgxMjAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNCMkIyQjInCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgxNTAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNCQUI4QjknCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgxODAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNDMkMwQzEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgyMTAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNDQkNCQ0InCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgyNDAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNEMkQyRDInCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgyNzAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNEQURBREEnCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgzMDAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0PgogICAgPHJlY3QgeD0nNDYuNScgeT0nNDAnIHdpZHRoPSc3JyBoZWlnaHQ9JzIwJyByeD0nNScgcnk9JzUnIGZpbGw9JyNFMkUyRTInCiAgICAgICAgICB0cmFuc2Zvcm09J3JvdGF0ZSgzMzAgNTAgNTApIHRyYW5zbGF0ZSgwIC0zMCknPgogICAgPC9yZWN0Pgo8L3N2Zz4=) no-repeat;
23 | background-size: 100%;
24 |
25 | }
26 | .nav-back {
27 | position: absolute;
28 | box-sizing: border-box;
29 | }
30 | .slot {
31 | position: absolute;
32 | right:0px;
33 | }
34 | .loading {
35 | display: flex;
36 | margin: auto;
37 | padding: 25rpx 0rpx;
38 | }
39 | .nav-text {
40 | font-weight: bold;
41 | }
42 | @keyframes loading-animate {
43 | 0% {
44 | transform: rotate3d(0, 0, 1, 0deg);
45 | }
46 |
47 | 100% {
48 | transform: rotate3d(0, 0, 1, 360deg);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | module.exports = require('../tools/test/helper')
2 |
--------------------------------------------------------------------------------
/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 | copy: ['./wxml', './wxss', './wxs', './images'],
66 | }
67 |
--------------------------------------------------------------------------------
/tools/demo/app.js:
--------------------------------------------------------------------------------
1 | App({});
2 |
--------------------------------------------------------------------------------
/tools/demo/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/nav/nav"
5 | ],
6 | "window": {
7 | "navigationBarBackgroundColor": "#000",
8 | "navigationBarTitleText": "WeChat",
9 | "navigationBarTextStyle": "white",
10 | "navigationStyle": "custom"
11 | }
12 | }
--------------------------------------------------------------------------------
/tools/demo/app.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100%;
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | justify-content: space-between;
7 | padding: 200rpx 0;
8 | box-sizing: border-box;
9 | }
--------------------------------------------------------------------------------
/tools/demo/package.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/tools/demo/pages/index/index.js:
--------------------------------------------------------------------------------
1 | Page({
2 | data: {
3 | },
4 | // 事件处理函数
5 | onTap() {
6 | wx.navigateTo({
7 | url: '../nav/nav'
8 | })
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/tools/demo/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tools/demo/pages/nav/nav.js:
--------------------------------------------------------------------------------
1 | Page({
2 | data: {
3 | enable: true,
4 | loading: false,
5 | title: '',
6 | titleStyle: '',
7 | bgStyle: 'position: fixed',
8 | delta: 2,
9 | textStyle: 'white'
10 | },
11 | onLoad() {
12 | },
13 | onnavback(e) {
14 | console.log('navback', e)
15 | },
16 | setTitle() {
17 | if (this.data.title !== '微信') {
18 | this.setData({
19 | title: '微信'
20 | })
21 | } else {
22 | this.setData({
23 | title: 'WeChat'
24 | })
25 | }
26 | },
27 | setLoading() {
28 | if (this.data.loading === false) {
29 | this.setData({
30 | loading: true
31 | })
32 | } else {
33 | this.setData({
34 | loading: false
35 | })
36 | }
37 | },
38 | setNavBack() {
39 | if (this.data.enable === false) {
40 | this.setData({
41 | enable: true
42 | })
43 | } else {
44 | this.setData({
45 | enable: false
46 | })
47 | }
48 | },
49 | setTitleStyle() {
50 | if (this.data.titleStyle !== 'color: #333300;') {
51 | this.setData({
52 | titleStyle: 'color: #333300;'
53 | })
54 | } else {
55 | this.setData({
56 | titleStyle: 'color: #fff;'
57 | })
58 | }
59 | },
60 | setBgStyle() {
61 | if (this.data.bgStyle !== 'background-color: #60A718;') {
62 | this.setData({
63 | bgStyle: 'background-color: #60A718;'
64 | })
65 | } else {
66 | this.setData({
67 | bgStyle: 'background-color: #000;'
68 | })
69 | }
70 | }
71 | })
72 |
--------------------------------------------------------------------------------
/tools/demo/pages/nav/nav.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "navigation-bar": "/components/index"
4 | }
5 | }
--------------------------------------------------------------------------------
/tools/demo/pages/nav/nav.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tools/demo/pages/nav/nav.wxss:
--------------------------------------------------------------------------------
1 | .button-area {
2 | display: flex;
3 | flex-direction: column;
4 | box-sizing: border-box;
5 | padding-top: 61px;
6 | }
7 | .button-area button {margin: 10px;}
8 |
--------------------------------------------------------------------------------
/tools/demo/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": true,
9 | "postcss": true,
10 | "minified": true,
11 | "newFeature": true,
12 | "nodeModules": true
13 | },
14 | "compileType": "miniprogram",
15 | "libVersion": "2.2.3",
16 | "appid": "",
17 | "projectname": "navigation-bar",
18 | "isGameTourist": false,
19 | "condition": {
20 | "search": {
21 | "current": -1,
22 | "list": []
23 | },
24 | "conversation": {
25 | "current": -1,
26 | "list": []
27 | },
28 | "game": {
29 | "currentL": -1,
30 | "list": []
31 | },
32 | "miniprogram": {
33 | "current": -1,
34 | "list": []
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------