├── style.css ├── .babelrc ├── .gitignore ├── lib ├── bar.js ├── foo.js.map ├── foo.js ├── MyComponent.js.map └── MyComponent.js ├── src ├── bar.js ├── foo.es6 └── MyComponent.jsx ├── example ├── index.html └── src │ └── index.jsx ├── index.js ├── .editorconfig ├── webpack.config.js ├── package.json ├── LICENSE └── README.md /style.css: -------------------------------------------------------------------------------- 1 | .my-component pre { 2 | background: #cccccc; 3 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "react" 6 | ] 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | # Created by .ignore support plugin (hsz.mobi) 3 | /npm-debug.log 4 | -------------------------------------------------------------------------------- /lib/bar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yan on 16-1-20. 3 | */ 4 | module.exports = function bar() { 5 | 6 | } -------------------------------------------------------------------------------- /src/bar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yan on 16-1-20. 3 | */ 4 | module.exports = function bar() { 5 | 6 | } -------------------------------------------------------------------------------- /src/foo.es6: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yan on 16-1-20. 3 | */ 4 | const foo = ()=> { 5 | 6 | } 7 | export default foo; -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yan on 16-1-20. 3 | */ 4 | module.exports = require('./lib/MyComponent'); 5 | exports.default = require('./lib/MyComponent'); 6 | exports.bar = require('./lib/bar'); 7 | exports.foo = require('./lib/foo'); -------------------------------------------------------------------------------- /lib/foo.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/foo.es6"],"names":[],"mappings":";;;;;;;;AAGA,IAAM,MAAM,SAAN,GAAM,GAAK,EAAL;kBAGG","file":"foo.js","sourcesContent":["/**\n * Created by yan on 16-1-20.\n */\nconst foo = ()=> {\n\n}\nexport default foo;"]} -------------------------------------------------------------------------------- /lib/foo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | /** 7 | * Created by yan on 16-1-20. 8 | */ 9 | var foo = function foo() {}; 10 | exports.default = foo; 11 | //# sourceMappingURL=foo.js.map -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://editorconfig.org/#example-file 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [Makefile] 7 | indent_style = tab 8 | 9 | [*.{js,md,jsx}] 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 2 -------------------------------------------------------------------------------- /src/MyComponent.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yan on 16-1-20. 3 | */ 4 | import React from 'react'; 5 | 6 | const MyComponent = props=> { 7 | return
8 | props: 9 |
{JSON.stringify(props, null, 2)}
10 |
11 | } 12 | 13 | export default MyComponent; -------------------------------------------------------------------------------- /example/src/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yan on 16-1-20. 3 | */ 4 | import React from 'react'; 5 | import MyComponent from '../../lib/MyComponent'; 6 | import 'style!css!../../style.css'; 7 | import {render} from 'react-dom'; 8 | 9 | var element = document.createElement("div"); 10 | document.body.appendChild(element); 11 | render(, element); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by yan on 16-1-20. 3 | */ 4 | var path = require('path'); 5 | 6 | module.exports = { 7 | entry: path.join(__dirname, 'example', 'src', 'index.jsx'), 8 | output: { 9 | filename: 'bundle.js' 10 | }, 11 | module: { 12 | loaders: [{ 13 | test: /\.jsx$/, 14 | loader: 'babel', 15 | include: [ 16 | path.join(__dirname, 'example') 17 | ] 18 | }] 19 | }, 20 | devServer: { 21 | contentBase: path.join(__dirname, 'example') 22 | } 23 | } -------------------------------------------------------------------------------- /lib/MyComponent.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/MyComponent.jsx"],"names":[],"mappings":";;;;;;;;;;;;AAKA,IAAM,cAAc,SAAd,WAAc,QAAQ;AAC1B,SAAO;;MAAK,WAAU,cAAV,EAAL;;IAEL;;;MAAM,KAAK,SAAL,CAAe,KAAf,EAAsB,IAAtB,EAA4B,CAA5B,CAAN;KAFK;GAAP,CAD0B;CAAR;;;;kBAOL","file":"MyComponent.js","sourcesContent":["/**\n * Created by yan on 16-1-20.\n */\nimport React from 'react';\n\nconst MyComponent = props=> {\n return
\n props:\n
{JSON.stringify(props, null, 2)}
\n
\n}\n\nexport default MyComponent;"]} -------------------------------------------------------------------------------- /lib/MyComponent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require("react"); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | var MyComponent = function MyComponent(props) { 14 | return _react2.default.createElement( 15 | "div", 16 | { className: "my-component" }, 17 | "props:", 18 | _react2.default.createElement( 19 | "pre", 20 | null, 21 | JSON.stringify(props, null, 2) 22 | ) 23 | ); 24 | }; /** 25 | * Created by yan on 16-1-20. 26 | */ 27 | 28 | exports.default = MyComponent; 29 | //# sourceMappingURL=MyComponent.js.map -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-component-example", 3 | "version": "0.0.0", 4 | "description": "Tutorial on how to write React component in ES6 and publishing", 5 | "main": "index.js", 6 | "style": "style.css", 7 | "scripts": { 8 | "start": "webpack-dev-server", 9 | "dev": "rimraf lib && babel src --watch --copy-files --source-maps --extensions .es6,.es,.jsx --out-dir lib", 10 | "compile": "rimraf lib && babel src --copy-files --source-maps --extensions .es6,.es,.jsx --out-dir lib" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "react-component" 15 | ], 16 | "author": "wyvernnot", 17 | "license": "MIT", 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "babel-cli": "^6.4.5", 21 | "babel-loader": "^6.2.1", 22 | "babel-preset-es2015": "^6.3.13", 23 | "babel-preset-react": "^6.3.13", 24 | "babel-preset-stage-0": "^6.3.13", 25 | "css-loader": "^0.23.1", 26 | "react": "^0.14.6", 27 | "react-dom": "^0.14.6", 28 | "rimraf": "^2.5.0", 29 | "style-loader": "^0.13.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 龙天 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-component-example 2 | > 如何使用 ES6 编写一个 React 模块, 并且编译后发布到 NPM. 3 | 4 | ## 目录 5 | 6 | - [前言](#前言) 7 | - [源代码](#源代码) 8 | - [编译](#编译) 9 | - [Babel](#babel) 10 | - [命令详解](#命令详解) 11 | - [.babelrc文件](#babelrc文件) 12 | - [样例代码](#样例代码) 13 | - [发布](#发布) 14 | - [使用](#使用) 15 | - [使用源码](#使用源码) 16 | - [样式](#样式) 17 | - [关于](#关于) 18 | 19 | ## 前言 20 | 21 | 前端开发果真是发展迅猛,刚享受到由模块化,组件化和单元测试带来的种种好处,又得迅速拥抱 Grunt, Gulp, Browserify, Webpack 这类自动化工具的变革。 22 | 除了工具和生态圈,JavaScript 本身也在飞速发展着。ES2015(ES6) ,ES2016(ES7) ... 照这样的节奏,几乎是一年一个标准。标准多了,为解决兼容性的问题, 23 | 竟也派生出了 `源代码` 和 `编译` 的概念。前端开发者通过语法糖、转化器、Polyfill 等,可以享受到标准乃至尚未定稿草案里的规范的便利,大幅提升开发效率。 24 | 25 | 如果你在使用 React, 那么肯定已经撸了好多自己的组件, 并尝试着共享出来。在 OneAPM 前端开发过程中, 我们也曾遇到了一些组件共享的问题: 26 | 27 | 例如: 28 | 29 | * 是通过 git 直接发布还是通过 NPM 发布 ? 30 | * 发布的是 ES5 的代码还是 ES6 的代码 ? 31 | * 如何解决 Babel5 和 Babel6 的冲突 ? 32 | 33 | 这篇文章会通过编写一个叫做 MyComponet 的组件来演示发布一个 React 组件需要注意的地方, 并不涉及单元测试和代码规范等。 34 | 至于这个模块本身,它的功能特别简单, 就是显示模块自身的的属性。 35 | 36 | ## 源代码 37 | 38 | 我们来编写 `MyComponent` 组件,放到项目的 `src` 目录下。 39 | 40 | **src/MyComponent.jsx** 41 | 42 | ```js 43 | import React from 'react'; 44 | 45 | const MyComponent = props=> { 46 | return
47 | props: 48 |
{JSON.stringify(props, null, 2)}
49 |
50 | } 51 | 52 | export default MyComponent; 53 | ``` 54 | 55 | 关于各种文件放在哪里, 这里是我推荐的一些约定: 56 | 57 | * src 下用于存放源代码 58 | * lib 是编译后的代码,这个目录只读 59 | * 所有包含 ES6 语法的文件名统一后缀为 .es6 60 | * 所有包含 JSX 语法的文件后统一缀名为 .jsx 61 | 62 | 假设源代码里还有另外两个文件 `foo.es6` 和 `bar.js`,简化起见都丢到 `src` 的根目录下。 63 | 64 | ## 编译 65 | 66 | ### Babel 67 | 68 | 为了把 ES6 代码编译成 ES5,需要安装 Babel,这个工具可以说野心极大,一次编译可以让 JavaScript 运行在所有地方。(听起来是不是有点 Java 的作风) 69 | 70 | 目前最常用的是 Babel5 版本,但是 Babel6 版本的设计更为精巧,已经非常推荐更新。也正是由于 Babel 有两个版本,所以开发过程中很有可能遇到这样的情况, 71 | 模块 A 的开发依赖于 Babel5 版本,而模块 B 依赖于 Babel6 版本。 72 | 73 | *解决这个问题最好的做法就是把 A 和 B 拆开,独立开发和发布。并且在发布到 NPM 的时候发布是的编译后的,也就是 ES5 版本的代码。* 74 | 75 | 所以如果你的机器上的 `babel` 是全局安装的,是时候卸载它了,因为它的版本不是 5 就是 6 ,会导致一些不可预见的问题。 76 | 77 | `npm uninstall babel-cli --global` 78 | 79 | 正确的安装方式是把 babel-cli 作为 development 依赖 80 | 81 | `npm install babel-cli --save-dev` 82 | 83 | 使用的时候并不是直接调用全局的 `babel` 而是调用依赖里的 `babel` 可执行文件 84 | 85 | `./node_modules/.bin/babel` 86 | 87 | 如果按照前文的约定来组织代码,`src` 目录结构看起来是这样的 88 | 89 | ``` 90 | src 91 | ├── bar.js 92 | ├── foo.es6 93 | └── MyComponent.jsx 94 | ``` 95 | 96 | 模块所有的代码都在一个目录下,这样编译过程就简单多了,两条命令就可以完成 97 | 98 | ```sh 99 | ./node_modules/.bin/rimraf lib 100 | ./node_modules/.bin/babel src --copy-files --source-maps --extensions .es6,.es,.jsx --out-dir lib 101 | ``` 102 | 103 | 输出目录的结构 104 | 105 | ``` 106 | lib 107 | ├── bar.js 108 | ├── foo.js 109 | ├── foo.js.map 110 | ├── MyComponent.js 111 | └── MyComponent.js.map 112 | ``` 113 | 114 | ### 命令详解 115 | 116 | 具体解释一下各个命令的作用: 117 | 118 | 第一条命令 `./node_modules/.bin/rimraf lib` 119 | 120 | **作用** 编译前清空之前的 lib 目录,这是一个好习惯,可以杜绝对 lib 下的文件的任何手动更改。 121 | 122 | 第二条命令 123 | 124 | `./node_modules/.bin/babel src --out-dir lib --source-maps --extensions .es6,.es,.jsx --copy-files ` 125 | 126 | **作用** 遍历 src 目录下的文件,如果后缀名是 .es/.es6/.jsx 中的一种,就编译成 ES5,否则就直接拷贝到输出目录 lib 下 127 | 128 | 参数详解: 129 | 130 | `--out-dir lib` 指定输出目录为 lib 131 | 132 | `--extensions .es6,.es,.jsx` 指定需要编译的文件类型 133 | 134 | `--copy-files` 对于不需要编译的文件直接拷贝 135 | 136 | `--source-maps` 生成 souce-map 文件 137 | 138 | ### .babelrc 文件 139 | 140 | 编译过程中还隐含了一个步骤就是加载 `.babelrc` 文件里的配置,该文件内容如下 141 | 142 | ```json 143 | { 144 | "presets": [ 145 | "es2015", 146 | "stage-0", 147 | "react" 148 | ] 149 | } 150 | ``` 151 | 152 | 这是因为 Babel6 采用了插件化的设计,做到了灵活配置:如果要转换 JSX 语法文件,就加上 React 的 preset,同时项目依赖里要添加 153 | `babel-preset-react` 154 | 155 | ```sh 156 | npm install babel-preset-react --save-dev 157 | ``` 158 | 159 | ## 样例代码 160 | 161 | 开发和调试 React 模块目前最好用的打包工具还是 Webpack,在项目跟目录下,新建一个 example 目录: 162 | 163 | example/index.html 164 | 165 | ```html 166 | 167 | 168 | 169 | 170 | Example 171 | 172 | 173 | 174 | 175 | ``` 176 | 177 | example/src/index.jsx 178 | 179 | ```js 180 | import React from 'react'; 181 | import MyComponent,{foo,bar} from '../../'; 182 | import {render} from 'react-dom'; 183 | 184 | var element = document.createElement("div"); 185 | document.body.appendChild(element); 186 | render(, element); 187 | ``` 188 | 189 | webpack.config.js 190 | 191 | ```js 192 | var path = require('path'); 193 | 194 | module.exports = { 195 | entry: path.join(__dirname, 'example', 'src', 'index.jsx'), 196 | output: { 197 | filename: 'bundle.js' 198 | }, 199 | module: { 200 | loaders: [{ 201 | test: /\.jsx$/, 202 | loader: 'babel', 203 | include: [ 204 | path.join(__dirname, 'example') 205 | ] 206 | }] 207 | }, 208 | devServer: { 209 | contentBase: path.join(__dirname, 'example') 210 | } 211 | } 212 | ``` 213 | 214 | **运行样例代码** 215 | 216 | ```sh 217 | ./node_modules/.bin/webpack-dev-server 218 | ``` 219 | 220 | ## 发布 221 | 222 | 发布前,还有一件事就是为你的模块添加一个入口文件 `index.js`,全部指向编译后的代码 223 | 224 | ``` 225 | module.exports = require('./lib/MyComponent'); 226 | exports.default = require('./lib/MyComponent'); 227 | exports.bar = require('./lib/bar'); 228 | exports.foo = require('./lib/foo'); 229 | ``` 230 | 231 | 接下来就可以发布到 NPM 了。 232 | 233 | ```sh 234 | npm publish 235 | ``` 236 | 237 | ## 使用 238 | 239 | 别的开发者如果想使用你新发布的模块的时候可以直接通过 NPM 安装 240 | 241 | ```sh 242 | npm install react-component-example --save-dev 243 | ``` 244 | 245 | 然后在父级项目的代码里导入模块 246 | 247 | ```js 248 | import MyComponent,{foo,bat} from 'react-component-example' 249 | ``` 250 | 251 | 此时导入的直接是 ES5 代码,跳过了组件的编译过程从而避免了出现组件 Babel 版本和父级项目 Babel 版本不一致的问题,并且速度更快,是不是很棒! 252 | 253 | ### 使用源码 254 | 255 | 假设你的模块包含很多组件,开发者可能只想用其中的一个或某几个,这时可以这样导入: 256 | 257 | ``` 258 | import MyComponent from 'react-component-example/src/MyComponent.jsx' 259 | ``` 260 | 261 | 这种情况下,导入的是 ES6 代码,并且会被加入父级项目的编译过程。此外,父级项目在编译这个文件的时候会读取组件的 .babelrc 配置文件。 262 | 263 | ### 样式 264 | 265 | 推荐每个模块在发布的时候把样式文件 style.css 放在根目录下,如果使用的是预处理工具 LESS 或者 SASS, 同样把处理前后的样式文件一起发布。 266 | 267 | ## 关于 268 | 269 | **本文使用的 babel 版本** 270 | 271 | `./node_modules/.bin/babel --version` 6.4.5 (babel-core 6.4.5) 272 | 273 | **LICENSE** 274 | 275 | MIT --------------------------------------------------------------------------------