diff --git a/.editorconfig b/.editorconfig index fb49a93..9e77100 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ root = true charset = utf-8 end_of_line = lf indent_style = space -indent_size = 4 +indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9318c57..0000000 --- a/.eslintrc +++ /dev/null @@ -1,29 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true - }, - "plugins": [ - "react", - "babel" - ], - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true, - "experimentalObjectRestSpread":true - } - }, - "rules": { - "camelcase": 2, - "curly": 2, - "eqeqeq": 2, - "brace-style": [2, "1tbs"], - "quotes": [2, "single"], - "semi": [2, "always"], - "space-infix-ops": 2, - "no-param-reassign": 0, - "prefer-spread": 2 - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..2912627 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,71 @@ +'use strict'; + +const OFF = 0; +const WARNING = 1; +const ERROR = 2; + +module.exports = { + 'extends': 'airbnb', + 'env': { + 'browser': true, + 'node': true, + 'es6': true, + 'mocha': true + }, + globals: { + assert: true + }, + 'parser': 'babel-eslint', + 'plugins': [ + 'react', + 'babel' + ], + 'parserOptions': { + 'ecmaVersion': 6, + 'sourceType': 'module', + 'ecmaFeatures': { + 'jsx': true, + 'experimentalObjectRestSpread': true + } + }, + 'rules': { + 'indent': [ERROR, 2, { 'SwitchCase': 1 }], //规定代码的缩进方式:2个空格 + 'camelcase': ERROR, //强制驼峰法命名 + 'curly': ERROR, //必须使用 if(){} 中的{} + 'eqeqeq': ERROR, //必须使用全等 + 'brace-style': [ERROR, '1tbs'], //大括号风格 + 'quotes': [ERROR, 'single'], //引号类型 + 'semi': [ERROR, 'always'], //语句强制分号结尾 + 'space-infix-ops': ERROR, //中缀操作符周围要不要有空格 + 'no-param-reassign': OFF, //不允许对函数的形参进行赋值 + 'prefer-spread': ERROR, //首选展开运算 + 'comma-dangle': OFF, //不允许或强制在对象字面量或者数组属性的结尾使用逗号 + 'padded-blocks': OFF, //规定代码块前后是否要加空行 + 'prefer-const': OFF, + 'no-multi-spaces': ERROR, + 'no-var': OFF, + 'one-var': OFF, + 'class-methods-use-this': WARNING, + 'no-unused-expressions': [ERROR, { allowShortCircuit: true }], + /** + * https://github.com/airbnb/javascript/tree/master/react + */ + 'react/prefer-es6-class': [WARNING, 'always'], //使用 class extends React.Component + 'react/jsx-pascal-case': ERROR, //骆驼式命名 + 'react/jsx-closing-bracket-location': ERROR, //JSX语法缩进/格式 + 'react/jsx-curly-spacing': ERROR, //JSX {} 引用括号里两边加空格 + 'react/jsx-boolean-value': [OFF, 'always'], //如果属性值为 true, 可以直接省略 + 'jsx-quotes': [ERROR, 'prefer-double'], //JSX属性值总是使用双引号(") + 'react/no-string-refs': ERROR, //Refs里使用回调函数 + 'react/jsx-wrap-multilines': ERROR, //多行的JSX标签写在 ()里 + 'react/self-closing-comp': ERROR, //没有子元素的标签来说总是自己关闭标签 + 'react/jsx-no-bind': ERROR, //当在 render() 里使用事件处理方法时,提前在构造函数里把 this 绑定上去 + 'react/no-is-mounted': ERROR, //不要再使用 isMounted + 'react/prop-types': [ERROR, { ignore: ['children', 'className', 'style'] }], + 'jsx-a11y/href-no-hash': OFF, + 'jsx-a11y/label-has-for': OFF, + 'react/jsx-filename-extension': OFF, + 'react/prefer-stateless-function': OFF, + 'react/require-default-props':OFF + } +}; diff --git a/docs/gh-pages.js b/docs/gh-pages.js new file mode 100644 index 0000000..8fa1d74 --- /dev/null +++ b/docs/gh-pages.js @@ -0,0 +1,6 @@ +var ghpages = require('gh-pages'); +var path = require('path'); + +ghpages.publish(path.join(__dirname, '../assets'), function(err) { + console.log(err); +}); diff --git a/docs/index.js b/docs/index.js index 8c8843f..7ebb528 100644 --- a/docs/index.js +++ b/docs/index.js @@ -1,60 +1,60 @@ -import React from 'react'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; -import ECharts, {dispatchAction} from '../src'; import { Markdown } from 'markdownloader'; +import ECharts from '../src'; import './style.less'; import './highlight.less'; - import baseBarOptions from './data/bar-base'; -import mapOptions from './data/map.js'; +import mapOptions from './data/map'; import chinaJson from './data/china.json'; const events = { - click:function(params){ - console.log(params); - } + click(params) { + console.log(params); + } }; -ECharts.registerMap('china',chinaJson); +ECharts.registerMap('china', chinaJson); -const App = React.createClass({ +class App extends Component { - render() { - let styles = Object.assign({ - width: '100%', - height: '400px' - }, this.props.style); + render() { + let styles = Object.assign({ + width: '100%', + height: '400px' + }, this.props.style); - return ( -
-
+ return ( +
+
-
-

RSuite ECharts

-

ECharts for React

- +
+

RSuite ECharts

+

ECharts for React

+ -

Map

- +

Map

+ - - {require('./README.md') } - -
-
-
- ); - } -}); + + {require('./README.md')} + +
+
+
+ ); + } +} const rootElement = document.getElementById('app'); ReactDOM.render(, - rootElement + rootElement ); diff --git a/package.json b/package.json index 63805f0..b67290e 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,18 @@ "description": "ECharts for React", "main": "lib/index.js", "scripts": { - "build": "rm -rf lib && babel src --out-dir lib", - "dev": "NODE_ENV=development webpack-dev-server --hot --inline --progress --colors --port 3100", - "docs": "NODE_ENV=production webpack --progress -colors ", - "cp:docs": "cp -rf ./assets/* ../rsuite.github.io/rsuite-echarts/" + "dev": "NODE_ENV=development webpack-dev-server --inline --progress --colors --port 3000 --host 0.0.0.0 --devtool source-map ", + "docs": "rm -rf assets && NODE_ENV=production webpack ", + "build": "rm -rf lib && babel src --out-dir lib && cp -R src/less lib", + "publish-docs": "node docs/gh-pages.js" }, "dependencies": { "echarts": "^3.1.10", "element-resize-event": "^2.0.5" }, "peerDependencies": { - "react": ">=0.14.0" + "react": "^0.14.9 || >=15.3.0", + "react-dom": ">=0.14.0" }, "author": "simonguo.2009@gmail.com", "license": "MIT", @@ -22,33 +23,39 @@ "type": "git", "url": "git@github.com:rsuite/rsuite-echarts.git" }, + "homepage": "https://github.com/rsuite/rsuite-echarts", "devDependencies": { - "react": "^0.14.2", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "prop-types": "^15.5.10", "babel-core": "^6.7.6", "babel-loader": "^6.2.4", + "babel-eslint": "^6.1.2", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.3.13", "babel-preset-stage-0": "^6.5.0", "babel-standalone": "^6.7.7", "css-loader": "^0.23.1", "es5-shim": "^4.1.14", - "eslint": "^2.8.0", + "eslint": "^3.19.0", + "eslint-config-airbnb": "^15.0.1", "eslint-plugin-babel": "^3.2.0", - "eslint-plugin-react": "^5.0.1", - "extract-text-webpack-plugin": "^1.0.1", + "eslint-plugin-jsx-a11y": "^5.0.1", + "eslint-plugin-import": "^2.6.1", + "eslint-plugin-react": "^7.0.1", + "extract-text-webpack-plugin": "^2.0.0", "highlight.js": "^9.5.0", "html-loader": "^0.4.3", "html-webpack-plugin": "^2.22.0", "json-loader": "^0.5.4", "less": "^2.7.1", "less-loader": "^2.2.3", - "markdown-loader": "^0.1.7", + "markdown-loader": "^2.0.0", + "markdownloader": "^1.0.5", "marked": "^0.3.5", - "react-dom": "^0.14.2", "react-hot-loader": "^1.3.0", "style-loader": "^0.13.1", - "webpack": "^1.13.1", - "webpack-dev-server": "^1.14.1", - "markdownloader": "1.0.5" + "webpack": "^3.0.0", + "webpack-dev-server": "^2.2.0" } } diff --git a/src/ECharts.js b/src/ECharts.js index 7c0301a..00db757 100644 --- a/src/ECharts.js +++ b/src/ECharts.js @@ -1,103 +1,116 @@ -import React from 'react'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import echarts from 'echarts'; import onResize from 'element-resize-event'; -const ECharts = React.createClass({ - propTypes: { - style: React.PropTypes.object, - theme: React.PropTypes.oneOfType([ - React.PropTypes.string, - React.PropTypes.object - ]), - group: React.PropTypes.string, - option: React.PropTypes.object.isRequired, - //是否不跟之前设置的option进行合并,默认为false,即合并 - notMerge: React.PropTypes.bool, - //在设置完option后是否不立即刷新画布,默认为false,即立即刷新 - notRefreshImmediately: React.PropTypes.bool, - onEvents: React.PropTypes.object - }, - getDefaultProps() { - return { - notMerge: true, - notRefreshImmediately: false, - onEvents: {}, - style: {}, - theme: {} - }; - }, - componentDidMount() { - this.init(); - }, - componentDidUpdate() { - this.renderEcharts(); - }, - componentWillUnmount() { - this.dispose(); - }, - init() { - this.chart = echarts.init(this.refs.container, this.props.theme); - this.renderEcharts(); - this.initEvents(); - }, - initEvents() { - let onEvents = this.props.onEvents; - for (let eventName in onEvents) { - this.chart.on(eventName, onEvents[eventName]); - } - - onResize(this.refs.container, () => { - this.chart.resize(); - }); - }, - dispose() { - if (this.chart) { - this.chart.dispose(); - this.chart = null; - } - }, - renderEcharts() { - - let { option, notMerge, notRefreshImmediately} = this.props; - this.chart.showLoading(); - this.chart.setOption(option, notMerge, notRefreshImmediately); - this.chart.hideLoading(); - - }, - render() { - let { - id, - option, - style, - className, - ...props, - } = this.props; +const propTypes = { + style: PropTypes.object, + theme: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object + ]), + group: PropTypes.string, + option: PropTypes.object.isRequired, + // 是否不跟之前设置的option进行合并,默认为false,即合并 + notMerge: PropTypes.bool, + // 在设置完option后是否不立即刷新画布,默认为false,即立即刷新 + notRefreshImmediately: PropTypes.bool, + onEvents: PropTypes.object +}; + +const defaultProps = { + notMerge: true, + notRefreshImmediately: false, + onEvents: {}, + style: {}, + theme: {} +}; + +class ECharts extends Component { + + componentDidMount() { + this.init(); + } - let styles = Object.assign({ - width: '100%', - height: '100%' - }, style); - - return ( -
-
- ); + componentDidUpdate() { + this.renderEcharts(); + } + + componentWillUnmount() { + this.dispose(); + } + + init() { + this.chart = echarts.init(this.container, this.props.theme); + this.renderEcharts(); + this.initEvents(); + } + + initEvents() { + let onEvents = this.props.onEvents; + for (let eventName in onEvents) { + this.chart.on(eventName, onEvents[eventName]); } -}); + + onResize(this.container, () => { + this.chart.resize(); + }); + } + + dispose() { + if (this.chart) { + this.chart.dispose(); + this.chart = null; + } + } + + renderEcharts() { + + let { option, notMerge, notRefreshImmediately } = this.props; + this.chart.showLoading(); + this.chart.setOption(option, notMerge, notRefreshImmediately); + this.chart.hideLoading(); + + } + + render() { + let { + id, + option, + style, + className, + ...props, + } = this.props; + + let styles = Object.assign({ + width: '100%', + height: '100%' + }, style); + + return ( +
{ + this.container = ref; + }} + style={styles} + {...props} + /> + ); + } +} + +ECharts.propTypes = propTypes; +ECharts.defaultProps = defaultProps; const APIS = ['getMap', 'connect', 'disConnect', 'getInstanceByDom', 'registerMap', 'registerTheme']; APIS.forEach((api) => { - ECharts[api] = echarts[api]; + ECharts[api] = echarts[api]; }); ECharts.dispatchAction = function (echartsId, payload) { - const chartInstance = echarts.getInstanceByDom(document.getElementById(echartsId)); - return chartInstance.dispatchAction(payload); + const chartInstance = echarts.getInstanceByDom(document.getElementById(echartsId)); + return chartInstance.dispatchAction(payload); }; diff --git a/src/index.js b/src/index.js index 60c7e00..45e4715 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,4 @@ import ECharts from './ECharts'; -import themes from './themes'; export default ECharts; diff --git a/src/themes/pagurian.js b/src/themes/pagurian.js index 93329a5..ebd3718 100644 --- a/src/themes/pagurian.js +++ b/src/themes/pagurian.js @@ -1,6 +1,6 @@ import echarts from 'echarts'; echarts.registerTheme('pagurian', { - backgroundColor: '#f5f5f5', - color: ['#fe8463', '#9bca63', '#fad860', '#60c0dd', '#0084c6', '#d7504b', '#c6e579', '#26c0c0', '#f0805a', '#f4e001', '#b5c334'] + backgroundColor: '#f5f5f5', + color: ['#fe8463', '#9bca63', '#fad860', '#60c0dd', '#0084c6', '#d7504b', '#c6e579', '#26c0c0', '#f0805a', '#f4e001', '#b5c334'] }); diff --git a/webpack.config.js b/webpack.config.js index d969163..1093adb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,68 +4,103 @@ const HtmlwebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const markdownLoader = require('markdownloader').renderer; +const { NODE_ENV } = process.env; +const extractLess = new ExtractTextPlugin({ + filename: '[name].[contenthash].css', + disable: NODE_ENV === 'development' +}); -const output = { - path: path.resolve(__dirname, 'assets'), - filename: 'bundle.js' -}; - -const entry=[ - path.join(__dirname, 'docs/index'), -] - -const jsloaders=[ - 'babel?babelrc' +const docsPath = NODE_ENV === 'development' ? './assets' : './'; +const plugins = [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin(), + new webpack.DefinePlugin({ + 'NODE_ENV': JSON.stringify(NODE_ENV) + }), + extractLess, + new HtmlwebpackPlugin({ + title: 'RSUITE Echarts', + filename: 'index.html', + template: 'docs/index.html', + inject: true, + hash: true, + path: docsPath + }) ]; -if (process.env.NODE_ENV === 'development') { - output.publicPath = '/assets/'; - entry.push('webpack/hot/dev-server'); - jsloaders.push('react-hot'); +if (process.env.NODE_ENV === 'production') { + plugins.push(new webpack.optimize.UglifyJsPlugin()); + plugins.push(new webpack.BannerPlugin({ banner: `Last update: ${new Date().toString()}` })); } - -const config = { - entry, - output, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) - }), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false - } - }), - new ExtractTextPlugin('[name].css'), - new HtmlwebpackPlugin({ - title: 'RSuite-ECharts', - filename: 'index.html', - template: 'docs/index.html', - inject: true, - hash: true - }), - ], - module: { - loaders: [ - { - test: /\.js$/, - loaders: jsloaders, - exclude: /node_modules/ - }, { - test: /\.less$/, - loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') - }, { - test: /\.md$/, - loader: 'html!markdown' - }, { - test: /\.json$/, - loader: 'json-loader' - } - ] +const common = { + entry: path.resolve(__dirname, 'src/'), + devServer: { + hot: true, + contentBase: path.resolve(__dirname, ''), + publicPath: '/' + }, + output: { + path: path.resolve(__dirname, 'assets'), + filename: 'bundle.js', + publicPath: './' + }, + plugins, + module: { + rules: [{ + test: /\.jsx?$/, + use: [ + 'babel-loader' + ], + exclude: /node_modules/ }, - markdownLoader -}; + { + test: /\.less$/, + loader: extractLess.extract({ + // use style-loader in development + fallback: 'style-loader', + use: [ + 'css-loader', + 'less-loader' + ], + }) + }, + { + test: /\.css$/, + loader: ExtractTextPlugin.extract({ + // use style-loader in development + fallback: 'style-loader', + use: [ + 'css-loader', + ], + }) + }, + { + test: /\.md$/, + use: [{ + loader: 'html-loader' + }, { + loader: 'markdown-loader', + options: { + pedantic: true, + renderer: markdownLoader.renderer + } + } + ] + }, { + test: /\.(woff|woff2|eot|ttf|svg)($|\?)/, + use: [{ + loader: 'url-loader?limit=1&hash=sha512&digest=hex&size=16&name=resources/[hash].[ext]' + }] + }] + } +}; -module.exports = config; +module.exports = (env = {}) => { + return Object.assign({}, common, { + entry: [ + path.resolve(__dirname, 'docs/index') + ] + }); +};