├── .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 | ![navigation-bar](./docs/navigation-bar.gif) 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 | --------------------------------------------------------------------------------