├── 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
--------------------------------------------------------------------------------