├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── build
├── miniprogram.config.js
├── webpack.base.config.js
├── webpack.dev.config.js
├── webpack.mp.config.js
└── webpack.prod.config.js
├── index.html
├── package-lock.json
├── package.json
└── src
├── components
└── counter
│ ├── index.css
│ └── index.jsx
├── index.jsx
└── log.jsx
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "stage-3",
5 | "react"
6 | ],
7 | "plugins": [
8 | "transform-runtime"
9 | ]
10 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | # others
64 | .idea
65 | .DS_Store
66 | dist
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 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 | # react-kbone
2 |
3 | 使用 react 多端开发(小程序和Web),基于 [kbone](https://github.com/Tencent/kbone) 的 element 和 render。
4 |
5 | ## 特性
6 |
7 | * 一键接入,立即使用
8 | * 支持完整 JSX 语法,任意位置任意方式书写 JSX
9 |
10 | ## 一套语法多端运行
11 |
12 | ```jsx
13 | function Counter() {
14 | const [count, setCount] = useState(0)
15 | return (
16 |
17 |
18 |
{count}
19 |
20 |
跳转
21 |
22 | )
23 | }
24 |
25 | function clickHandle() {
26 | if ('undefined' != typeof wx && wx.getSystemInfoSync) {
27 | wx.navigateTo({
28 | url: '../log/index?id=1',
29 | })
30 | } else {
31 | location.href = 'log.html'
32 | }
33 | }
34 |
35 | export default Counter
36 | ```
37 |
38 | ## 快速开始
39 |
40 | ```
41 | npx kbone-cli init my-app
42 | cd my-app
43 | npm run mp // 开发小程序
44 | npm run build:mp // 构建小程序
45 | npm run web // 开发 web
46 | npm run build // 构建 web
47 | ```
48 |
49 |
50 | ## 目录说明
51 |
52 | ```
53 | ├─ dist
54 | │ ├─ mp // 微信开发者工具指向的目录,用于生产环境
55 | │ ├─ web // web 编译出的文件,用于生产环境
56 | ├─ build // 构建相关
57 | ├─ src
58 | │ ├─ assets
59 | │ ├─ components // 存放所有组件
60 | │ ├─ log.jsx // 入口文件,会 build 成 log.html
61 | │ └─ index.jsx // 入口文件,会 build 成 index.html
62 | ```
63 |
64 | ## 注意事项
65 |
66 | react 并没有提供根组件实例的销毁方法(如 vue.$destroy),所以在多页应用中页面关闭时不会触发该页面组件的 componentWillUnmount 钩子。开发者可自行监听 wxunload 或 beforeunload 事件来进行页面的销毁工作,比如调用 render 方法渲染一个空节点,强行触发页面组件的 componentWillUnmount 钩子。
67 |
68 | ## 谁在使用 kbone?
69 |
70 |
89 |
90 | ## License
91 |
92 | MIT
93 |
--------------------------------------------------------------------------------
/build/miniprogram.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 配置参考:https://wechat-miniprogram.github.io/kbone/docs/config/
3 | */
4 |
5 | module.exports = {
6 | origin: 'https://test.miniprogram.com',
7 | entry: '/',
8 | router: {
9 | home: [
10 | '/(home|index)?',
11 | '/index.html',
12 | '/test/(home|index)',
13 | ],
14 | other: [
15 | '/test/list/:id',
16 | '/test/detail/:id',
17 | ],
18 | },
19 | redirect: {
20 | notFound: 'home',
21 | accessDenied: 'home',
22 | },
23 | generate: {
24 | autoBuildNpm: 'npm',
25 | },
26 | app: {
27 | navigationBarTitleText: 'miniprogram-project',
28 | },
29 | appExtraConfig: {
30 | sitemapLocation: 'sitemap.json',
31 | },
32 | global: {},
33 | pages: {},
34 | optimization: {
35 | domSubTreeLevel: 10,
36 |
37 | elementMultiplexing: true,
38 | textMultiplexing: true,
39 | commentMultiplexing: true,
40 | domExtendMultiplexing: true,
41 |
42 | styleValueReduce: 5000,
43 | attrValueReduce: 5000,
44 | },
45 | projectConfig: {
46 | projectname: 'kbone-template-react',
47 | appid: '',
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/build/webpack.base.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | context: path.resolve(__dirname, '../'),
5 | entry: {
6 | index: path.resolve(__dirname, '../src/index.jsx'),
7 | log: path.resolve(__dirname, '../src/log.jsx'),
8 | },
9 | output: {
10 | path: path.resolve(__dirname, '../dist/web'),
11 | filename: '[name].js',
12 | publicPath: '/',
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.[t|j]sx?$/,
18 | loader: 'babel-loader',
19 | exclude: /node_modules/,
20 | },
21 | {
22 | test: /\.(png|jpg|gif|svg)$/,
23 | loader: 'file-loader',
24 | options: {
25 | name: '[name].[ext]?[hash]',
26 | },
27 | },
28 | ],
29 | },
30 | resolve: {
31 | extensions: ['*', '.js', '.jsx', '.json']
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/build/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const { merge } = require('webpack-merge')
3 | const baseWebpackConfig = require('./webpack.base.config')
4 | const HtmlWebpackPlugin = require('html-webpack-plugin')
5 | const portfinder = require('portfinder')
6 |
7 | const htmlPluginList = Object.keys(baseWebpackConfig.entry).map(name => {
8 | return new HtmlWebpackPlugin({
9 | filename: `${name}.html`,
10 | template: 'index.html',
11 | inject: true,
12 | chunks: [name],
13 | })
14 | })
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | mode: 'development',
18 | devServer: {
19 | historyApiFallback: {
20 | rewrites: [{from: /.*/, to: '/index.html'}],
21 | },
22 | hot: true,
23 | compress: true,
24 | host: process.env.HOST || 'localhost',
25 | port: +process.env.PORT || 8080,
26 | open: true, // 自动打开浏览器
27 | client: {
28 | logging: 'warn',
29 | overlay: { // 展示全屏报错
30 | warnings: false,
31 | errors: true
32 | },
33 | },
34 | static: {
35 | publicPath: '/',
36 | },
37 | proxy: {},
38 | },
39 | watchOptions: {
40 | poll: false,
41 | },
42 | devtool: 'cheap-module-eval-source-map',
43 | module: {
44 | rules: [
45 | {
46 | test: /\.css$/,
47 | use: ['style-loader', 'css-loader'],
48 | },
49 | ],
50 | },
51 | plugins: [
52 | new webpack.DefinePlugin({
53 | 'process.env': {
54 | NODE_ENV: '"development"',
55 | },
56 | }),
57 | new webpack.HotModuleReplacementPlugin(),
58 | ...htmlPluginList,
59 | ],
60 | })
61 |
62 | module.exports = new Promise((resolve, reject) => {
63 | portfinder.basePort = +process.env.PORT || 8080
64 | portfinder.getPort((err, port) => {
65 | if (err) {
66 | reject(err)
67 | } else {
68 | devWebpackConfig.devServer.port = port
69 |
70 | resolve(devWebpackConfig)
71 | }
72 | })
73 | })
74 |
--------------------------------------------------------------------------------
/build/webpack.mp.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
4 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
5 | const TerserPlugin = require('terser-webpack-plugin')
6 | const MpPlugin = require('mp-webpack-plugin')
7 |
8 | const isOptimize = false // 是否压缩业务代码,开发者工具可能无法完美支持业务代码使用到的 es 特性,建议自己做代码压缩
9 |
10 | module.exports = {
11 | mode: 'production',
12 | entry: {
13 | index: path.resolve(__dirname, '../src/index.jsx'),
14 | log: path.resolve(__dirname, '../src/log.jsx'),
15 | },
16 | output: {
17 | path: path.resolve(__dirname, '../dist/mp/common'), // 放到小程序代码目录中的 common 目录下
18 | filename: '[name].js', // 必需字段,不能修改
19 | library: 'createApp', // 必需字段,不能修改
20 | libraryExport: 'default', // 必需字段,不能修改
21 | libraryTarget: 'window', // 必需字段,不能修改
22 | },
23 | target: 'web', // 必需字段,不能修改
24 | optimization: {
25 | runtimeChunk: false, // 必需字段,不能修改
26 | splitChunks: {
27 | // 代码分隔配置,不建议修改
28 | chunks: 'all',
29 | minSize: 1000,
30 | maxSize: 0,
31 | minChunks: 1,
32 | maxAsyncRequests: 100,
33 | maxInitialRequests: 100,
34 | automaticNameDelimiter: '~',
35 | name: true,
36 | cacheGroups: {
37 | vendors: {
38 | test: /[\\/]node_modules[\\/]/,
39 | priority: -10,
40 | },
41 | default: {
42 | minChunks: 2,
43 | priority: -20,
44 | reuseExistingChunk: true,
45 | },
46 | },
47 | },
48 |
49 | minimizer: isOptimize
50 | ? [
51 | // 压缩CSS
52 | new OptimizeCSSAssetsPlugin({
53 | assetNameRegExp: /\.(css|wxss)$/g,
54 | cssProcessor: require('cssnano'),
55 | cssProcessorPluginOptions: {
56 | preset: [
57 | 'default',
58 | {
59 | discardComments: {
60 | removeAll: true,
61 | },
62 | minifySelectors: false, // 因为 wxss 编译器不支持 .some>:first-child 这样格式的代码,所以暂时禁掉这个
63 | },
64 | ],
65 | },
66 | canPrint: false,
67 | }),
68 | // 压缩 js
69 | new TerserPlugin({
70 | test: /\.js(\?.*)?$/i,
71 | parallel: true,
72 | }),
73 | ]
74 | : [],
75 | },
76 | module: {
77 | rules: [
78 | {
79 | test: /\.css$/,
80 | use: [MiniCssExtractPlugin.loader, 'css-loader'],
81 | },
82 | {
83 | test: /\.[t|j]sx?$/,
84 | loader: 'babel-loader',
85 | exclude: /node_modules/,
86 | },
87 | {
88 | test: /\.(png|jpg|gif|svg)$/,
89 | loader: 'file-loader',
90 | options: {
91 | name: '[name].[ext]?[hash]',
92 | },
93 | },
94 | ],
95 | },
96 | resolve: {
97 | extensions: ['*', '.js', '.jsx', '.json'],
98 | },
99 | plugins: [
100 | new webpack.DefinePlugin({
101 | 'process.env.isMiniprogram': process.env.isMiniprogram, // 注入环境变量,用于业务代码判断
102 | }),
103 | new MiniCssExtractPlugin({
104 | filename: '[name].wxss',
105 | }),
106 | new MpPlugin(require('./miniprogram.config')),
107 | ],
108 | }
109 |
--------------------------------------------------------------------------------
/build/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const { merge } = require('webpack-merge')
4 | const baseWebpackConfig = require('./webpack.base.config')
5 | const HtmlWebpackPlugin = require('html-webpack-plugin')
6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
7 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
8 | const TerserPlugin = require('terser-webpack-plugin')
9 |
10 | const htmlPluginList = Object.keys(baseWebpackConfig.entry).map(name => {
11 | return new HtmlWebpackPlugin({
12 | filename: path.resolve(__dirname, `../dist/web/${name}.html`),
13 | template: 'index.html',
14 | inject: true,
15 | minify: {
16 | removeComments: true,
17 | collapseWhitespace: true,
18 | removeAttributeQuotes: true,
19 | },
20 | chunks: [name],
21 | })
22 | })
23 |
24 | const webpackConfig = merge(baseWebpackConfig, {
25 | mode: 'production',
26 | output: {
27 | path: path.resolve(__dirname, '../dist/web'),
28 | filename: path.posix.join('static', 'js/[name].[chunkhash].js'),
29 | chunkFilename: path.posix.join('static', 'js/[id].[chunkhash].js'),
30 | },
31 | optimization: {
32 | splitChunks: {
33 | // 代码分割配置
34 | chunks: 'async',
35 | minSize: 30000,
36 | maxSize: 0,
37 | minChunks: 1,
38 | maxAsyncRequests: 5,
39 | maxInitialRequests: 3,
40 | automaticNameDelimiter: '~',
41 | name: true,
42 | cacheGroups: {
43 | vendors: {
44 | test: /[\\/]node_modules[\\/]/,
45 | priority: -10,
46 | },
47 | default: {
48 | minChunks: 2,
49 | priority: -20,
50 | reuseExistingChunk: true,
51 | },
52 | },
53 | },
54 | minimizer: [
55 | // 压缩CSS
56 | new OptimizeCSSAssetsPlugin({
57 | assetNameRegExp: /\.css$/g,
58 | cssProcessor: require('cssnano'),
59 | cssProcessorPluginOptions: {
60 | preset: [
61 | 'default',
62 | {
63 | discardComments: {
64 | removeAll: true,
65 | },
66 | },
67 | ],
68 | },
69 | canPrint: false,
70 | }),
71 | // 压缩 js
72 | new TerserPlugin({
73 | test: /\.js(\?.*)?$/i,
74 | parallel: true,
75 | }),
76 | ],
77 | },
78 | devtool: false,
79 | module: {
80 | rules: [
81 | {
82 | test: /\.css$/,
83 | use: [MiniCssExtractPlugin.loader, 'css-loader'],
84 | },
85 | ],
86 | },
87 | plugins: [
88 | new webpack.DefinePlugin({
89 | 'process.env': {
90 | NODE_ENV: '"production"',
91 | },
92 | }),
93 | // 分离 css 文件
94 | new MiniCssExtractPlugin({
95 | filename: path.posix.join('static', 'css/[name].[hash].css'),
96 | }),
97 | ...htmlPluginList,
98 | // 当 vendor 模块没有改变时,保证模块 id 不变
99 | new webpack.HashedModuleIdsPlugin(),
100 | ],
101 | })
102 |
103 | module.exports = webpackConfig
104 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | react
10 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-kbone",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "wechat-miniprogram",
6 | "keywords": [
7 | "react",
8 | "kbone",
9 | "mp"
10 | ],
11 | "scripts": {
12 | "start": "npm run mp",
13 | "web": "cross-env NODE_ENV=development webpack-dev-server --progress --config build/webpack.dev.config.js",
14 | "mp": "rimraf dist/mp/common && cross-env NODE_ENV=development webpack --config build/webpack.mp.config.js --watch --progress",
15 | "build": "rimraf dist/web && cross-env NODE_ENV=production webpack --config build/webpack.prod.config.js --progress",
16 | "build:mp": "rimraf dist/mp/common && cross-env NODE_ENV=production webpack --config build/webpack.mp.config.js --progress"
17 | },
18 | "dependencies": {
19 | "react": "^16.9.0",
20 | "react-dom": "^16.9.0"
21 | },
22 | "browserslist": [
23 | "> 1%",
24 | "last 2 versions",
25 | "not ie <= 8"
26 | ],
27 | "devDependencies": {
28 | "@webpack-cli/serve": "^1.6.1",
29 | "babel-core": "^6.26.0",
30 | "babel-loader": "^7.1.2",
31 | "babel-plugin-transform-runtime": "^6.23.0",
32 | "babel-preset-env": "^1.7.0",
33 | "babel-preset-react": "^6.24.1",
34 | "babel-preset-stage-3": "^6.24.1",
35 | "cross-env": "^5.0.5",
36 | "css-loader": "^0.28.7",
37 | "file-loader": "^1.1.4",
38 | "html-webpack-plugin": "^4.5.2",
39 | "mini-css-extract-plugin": "^0.5.0",
40 | "mp-webpack-plugin": "latest",
41 | "optimize-css-assets-webpack-plugin": "^5.0.8",
42 | "portfinder": "^1.0.28",
43 | "rimraf": "^2.7.1",
44 | "style-loader": "^2.0.0",
45 | "stylehacks": "^4.0.3",
46 | "terser-webpack-plugin": "^4.2.3",
47 | "webpack": "^4.29.6",
48 | "webpack-cli": "^4.9.2",
49 | "webpack-dev-server": "^4.7.4",
50 | "webpack-merge": "^5.8.0"
51 | },
52 | "license": "MIT"
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/counter/index.css:
--------------------------------------------------------------------------------
1 | span{
2 | color: red;
3 | }
--------------------------------------------------------------------------------
/src/components/counter/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import './index.css'
3 |
4 | function Counter() {
5 | const [count, setCount] = useState(0)
6 | return (
7 |
8 |
9 |
{count}
10 |
11 |
跳转
12 |
13 | )
14 | }
15 |
16 | function clickHandle() {
17 | if ('undefined' != typeof wx && wx.getSystemInfoSync) {
18 | wx.navigateTo({
19 | url: '../log/index?id=1',
20 | })
21 | } else {
22 | location.href = 'log.html'
23 | }
24 | }
25 |
26 | export default Counter
27 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import Counter from './components/counter'
4 |
5 | export default function createApp() {
6 | const container = document.createElement('div')
7 | container.id = 'app'
8 | document.body.appendChild(container)
9 |
10 | ReactDOM.render(, container)
11 | }
12 |
13 | ;('undefined' != typeof wx && wx.getSystemInfoSync) || createApp()
14 |
--------------------------------------------------------------------------------
/src/log.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | export default function createApp() {
5 | const container = document.createElement('div')
6 | container.id = 'app'
7 | document.body.appendChild(container)
8 |
9 | ReactDOM.render(我是log页面
, container)
10 | }
11 |
12 | ;('undefined' != typeof wx && wx.getSystemInfoSync) || createApp()
13 |
--------------------------------------------------------------------------------