用生命谱写代码的赞歌

0%

babel+eslint+webpack2 搭建 react 开发环境

搭建 React 开发环境

文章介绍了如何使用 babel+eslint+webpack2 搭建 react 开发环境,详情访问我自己搭建的 React 的 github 地址

npm包(生产依赖–save)

  • react
  • react-dom

文件结构

  • myReact
    • components
      • Hello.jsx
    • images
      • demo.jpg
    • js
      • Routers.jsx
    • styles
      • base.scss
      • hello.scss
    • index.jsx
    • index.html
    • .babelrc
    • .eslintignore
    • .eslintrc.js
    • package.json
    • webpack.develop.config.js

入口文件 index.jsx

1
2
3
4
5
6
7
8
9
import React from 'react'  // 此处必须引用React, 因为使用了jsx语法, 否则报错 'React' must be in scope when using JSX react/react-in-jsx-scope'
import ReactDom from 'react-dom'

import Hello from './components/Hello.jsx' // 引入自定义Hello组件
// 绑定到html模板指定节点
ReactDom.render(
<Hello/>,
document.getElementById('app')
)

html 模板

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React初探</title>
</head>
<body>
<div id="app"></div>
<!-- 指定webpack打包后的js文件, 对应webpack配置文件的output属性 -->
<script src="app.bundle.js"></script>
</body>
</html>

Hello组件 Hello.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'

import '../styles/hello.scss' // 导入css文件
// 性能检测:运行程序,在操作之前先运行Perf.start()开启检测,然后进行若干操作,运行Perf.stop()停止检测
// 然后在运行Perf.printWasted()即可打印出浪费性能的组件列表
// 如果每次操作多浪费几毫秒、几十毫秒,没必要深究,如果浪费过多影响了用户体验,就必须去优化它
import Perf from 'react-addons-perf'
if (__DEV__) {
window.Perf = Perf
}

export default class Hello extends React.Component {
constructor(props) {
super(props)
// React最基本优化方式,组件每次更新之前都要过一遍这个函数,返回true则更新,返回false则不更新
// 默认情况下,这个函数会一直返回true,也就是说,如果有一些无效的改动触发了这个函数,也会导致无效的更新
// 最好每个函数都写上这个函数
this.shouldComponentUpdate = () => PureRenderMixin.shouldComponentUpdate
this.state = {

}
}
render () {
return (
<div>
<p>Hello, everyone</p>
<img/>
</div>
)
}
}

css 文件

  • base.scss
1
$fontsize: 100px;
  • hello.scss
1
2
3
4
5
@import 'base.scss';  // 不能忘记写分号, 否则报错 --> Invalid CSS after "p": expected selector or at-rule, was "{"

p {
font-size: $fontsize;
}

路由配置文件(Routers.jsx)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import React from 'react'
import {Router, Route, Redirect, browserHistory, IndexRoute} from 'react-router'

import AppContainer from '../containers/AppContainer.jsx'
import HomeContainer from '../containers/HomeContainer.jsx'
// 以下组件采用动态按需加载 getComponent
// import MovieContainer from '../containers/MovieContainer.jsx'
// import AboutContainer from '../containers/AboutContainer.jsx'
// import MovieListContainer from '../containers/MovieListContainer.jsx'
// import MovieDetailContainer from '../containers/MovieDetailContainer.jsx'
// import MovieSearchContainer from '../containers/MovieSearchContainer.jsx'

// 路由配置信息IndexRoute表示首次渲染的组件
export default class Routers extends React.Component {
constructor(props) {
super(props)
this.state = {

}
}
render() {
return (
<Router history={browserHistory}>
<Route path='/' component={AppContainer}>
<IndexRoute component={HomeContainer}/>
<Route path='home' component={HomeContainer}/>
<Route
path='movie'
getComponent={
(nextState, callback) => {
require.ensure([], require => {
callback(null, require('../containers/MovieContainer.jsx').default)
}, 'movie')
}
}
onEnter={() => console.log('进入了movie路由')}
onLeave={() => console.log('离开了movie路由')}
>
<IndexRoute
getComponent={
(nextState, callback) => {
require.ensure([], require => {
callback(null, require('../containers/MovieListContainer.jsx').default)
}, 'movieList')
}
}
/>
{/* 绝对路由带有'/',不带'/'的路由基于父组件的路径 */}
<Route path='/movieList/:movieType'
getComponent={
(nextState, callback) => {
require.ensure([], require => {
callback(null, require('../containers/MovieListContainer.jsx').default)
}, 'movieList')
}
}
/>
<Route path='/movieDetail/:id'
getComponent={
(nextState, callback) => {
require.ensure([], require => {
callback(null, require('../containers/MovieDetailContainer.jsx').default)
}, 'movieDetail')
}
}
/>
<Route path='/movieSearch/:keyword'
getComponent={
(nextState, callback) => {
require.ensure([], require => {
callback(null, require('../containers/MovieSearchContainer.jsx').default)
}, 'movieSearch')
}
}
/>
{/* 路由重定向 */}
<Redirect from='movieList' to='/movieList'/>
<Redirect from='movieDetail' to='/movieDetail'/>
<Redirect from='movieSearch' to='/movieSearch'/>
</Route>
<Route path='about'
getComponent={
(nextState, callback) => {
require.ensure([], require => {
callback(null, require('../containers/AboutContainer.jsx').default)
}, 'about')
}
}
/>
</Route>
</Router>
)
}
}

babel

npm包(开发依赖–save-dev)

  • babel-core
  • babel-loader
  • babel-preset-es2015
  • babel-preset-react
  • babel-preset-stage-0

.babelrc

  • 配置方式一
1
2
3
4
5
6
7
8
9
10
11
{
"presets": [["es2015"], ["stage-0"], ["react"]]
}
// 或者
{
"presets": ["es2015", "stage-0", "react"]
}
// babel-preset-es2015 被推荐使用 babel-reset-env,即:
{
"presets": ["env", "stage-0", "react"]
}
  • babel-preset-env 详细配置
1
2
3
4
5
6
7
8
9
10
11
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}],
["stage-0"],
["react"]
]
}
  • 配置方式二
    • react-transform支持组件任意组装
    • react-transform-hmr支持组件热更新(自由拔插)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"presets": ["env", "stage-0", "react"],
"plugins": ["transform-runtime"],
"env": {
"dev": {
"plugins": [[
"react-transform",
{
"transforms": [{
"transform": "react-transform-hmr",
"imports": ["react"],
"locals": ["module"]
}]
}
]]
}
}
}
  • 配置方式三
    • transform-runtime 以便用在发布 library(库)和 tool(工具函数)中,减少编译重复,创建沙箱环境,避免污染全局环境
    • babel-runtime 转换器插件 (transform-runtime) 一般只用在开发时,而里面的实际垫片 (babel-runtime itself) 的代码在你部署或发布库时都需要。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"presets": [
"env",
"stage-0",
"react"
],
"plugins": [
[
"transform-runtime",
{
"helpers": false,
"polyfill": false,
"regenerator": true,
"moduleName": "babel-runtime"
}
],
[
"import",
{
"libraryName": "antd",
"style": "css"
}
]
]
}

eslint

npm包(–save-dev)

  • babel-eslint
  • eslint
  • eslint-config-standard
  • eslint-friendly-formatter
  • eslint-loader
  • eslint-plugin-html
  • eslint-plugin-import
  • eslint-plugin-node
  • eslint-plugin-promise
  • eslint-plugin-react
  • eslint-plugin-standard

.eslintignore

1
2
node_modules
dist

.eslintrc.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
module.exports = {
root: true, // eslint找到这个标识后,不会再去父文件夹中找eslint的配置文件
parser: 'babel-eslint', //使用babel-eslint来作为eslint的解析器
parserOptions: { // 设置解析器选项
sourceType: 'module', // 表明自己的代码是ECMAScript模块
ecmaVersion: 6, // 可选3 5(默认) 6 7
ecmaFeatures: {
jsx: true // 支持jsx语法eslint检测
}
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: [
'standard', // 继承eslint-config-standard里面提供的lint规则
'plugin:react/recommended' // 继承eslint-config-react里面提供的lint规则
]
// required to lint *.vue files
plugins: [ // 使用的插件eslint-plugin-html. 写配置文件的时候,可以省略eslint-plugin-
'html'
],
// 启用额外的规则或者覆盖基础配置中的规则的默认选项
rules: { // 0表示关闭检测,1表示警告,2表示禁止使用(报错)
// allow paren-less arrow functions
// 箭头函数括号规则(0表示关闭)
'arrow-parens': 0,
// allow async-await
// 是否允许generator函数
'generator-star-spacing': 0,
// http://eslint.org/docs/rules/comma-dangle
// 对象或数组元素结尾是否省略逗号
'comma-dangle': ['error', 'only-multiline'],
// 分号检测
'semi': 0
},
globals: { // 声明在代码中自定义的全局变量
'CONFIG': true
},
env: { // 定义预定义的全局变量,比如browser: true,这样你在代码中可以放心使用宿主环境给你提供的全局变量。
browser: true, // browser global variables.
node: true, // Node.js global variables and Node.js scoping.
worker: true, // web workers global variables.
mocha: true, // adds all of the Mocha testing global variables.
phantomjs: true, // PhantomJS global variables.
serviceworker: true // Service Worker global variables.
}
}

webpack2

package.json

  • 开发环境: npm start
    • 设置运行环境是dev还是production,windows开发环境下,dev后面不要留空格,否则 set 命令把空格带进环境变量里去 (windows环境)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
{
"name": "my_react",
"version": "1.0.0",
"description": "",
"main": "index.jsx",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "set NODE_ENV=dev&& webpack-dev-server --progress --color",
"dev": "webpack-dev-server --config webpack.develop.config.js --devtool eval --progress --color --hot --content-base src",
"build": "rm -rf ./build && set NODE_ENV=production&& webpack --config ./webpack.production.js --progress --colors"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.25.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.1",
"babel-plugin-react-transform": "^2.0.2",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"css-loader": "^0.28.4",
"eslint": "^4.2.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.9.0",
"eslint-plugin-html": "^3.1.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.1.0",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-standard": "^3.0.1",
"file-loader": "^0.11.2",
"node-sass": "^4.5.3",
"open-browser-webpack-plugin": "0.0.5",
"react-addons-perf": "^15.4.2",
"react-transform-hmr": "^1.0.4",
"sass-loader": "^6.0.6",
"style-loader": "^0.18.2",
"url-loader": "^0.5.9",
"webpack": "^3.2.0",
"webpack-dev-server": "^2.5.1"
},
"dependencies": {
"react": "^15.6.1",
"react-addons-perf": "^15.4.2",
"react-addons-pure-render-mixin": "^15.6.0",
"react-dom": "^15.6.1",
"react-router": "^3.0.5"
}
}

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var OpenBrowserPlugin = require('open-browser-webpack-plugin')

module.exports = {
context: __dirname + '/src',
entry: {
app: './index.jsx'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
enforce: 'pre', // 在babel-loader对源码进行编译前进行lint的检查
include: /src/, // src文件夹下的文件需要被lint
use: [{
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter')
}
}]
// exclude: /node_modules/ 可以不用定义这个字段的属性值,eslint会自动忽略node_modules和bower_
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: [
'babel-loader',
'eslint-loader'
]
},

{
test: /\.css$/,
loaders: [
'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
loaders: [
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 3000
}
}
]
},
{
test: /\.(eot|woff|ttf|woff2|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 5000
}
}
]
}
]
},
resolve:{
extensions:['.js', '.jsx']
},
plugins: [
// html模板插件
new HtmlWebpackPlugin({
template: __dirname + '/src/index.html' // 不使用 path.resolve() 方法,字符串路径第一位需要加 斜杠/
}),
// 热加载插件
new webpack.HotModuleReplacementPlugin(),
// 打开浏览器
new OpenBrowserPlugin({
url: 'http://localhost:8080'
}),
// 可在业务js代码中使用__DEV__判断是否是dev模式(dev模式下可以提示错误、测试报告等,production模式不提示)
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true), // 测试全局变量
__DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false')) // package.json中set NODE_ENV=dev&&...,dev后面不能留空格,否则set命令会把空格带进环境变量中去
})
],
devServer: {
historyApiFallback: true, //不跳转,在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html
inline: true, // 实时刷新
hot: true //是否使用热加载插件
}
}

webpack.production.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
var pkg = require('./package.json')
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = {
entry: {
app: path.resolve(__dirname, 'src/index.jsx'),
// 将 第三方依赖(node_modules中的) 单独打包
vendor: Object.keys(pkg.dependencies)
},
output: {
path: __dirname + "/build",
filename: "js/[name].[chunkhash:8].js"
},
resolve:{
extensions:['.js', '.jsx']
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{loader: 'style-loader'},
{loader: 'css-loader'},
{
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require('autoprefixer')() // 调用autoprefixer插件,例如display: flex
]
}
}
]
},
{
test: /\.less$/,
exclude: /node_modules/,
use: [
{loader: 'style-loader'},
{loader: 'css-loader'},
{
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require('autoprefixer')()
]
}
},
{loader: 'less-loader'}
]
},
{
test: /\.(png|gif|jpg|jpeg|bmp)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 5000,
name: 'images/[name].[chunkhash:8].[ext]'
}
}
]
},
{
test:/\.(png|woff|woff2|svg|ttf|eot)($|\?)/i,
use: [
{
loader: 'url-loader',
options: {
limit: 5000,
name: 'fonts/[name].[chunkhash:8].[ext]'
}
}
]
}
]
},
plugins: [
// webpack 内置的 banner-plugin
new webpack.BannerPlugin("Copyright by hushiking@github.com."),

// html 模板插件
new HtmlWebpackPlugin({
template: __dirname + '/src/index.html'
}),

// 定义为生产环境,编译 React 时压缩到最小
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify(process.env.NODE_ENV) // package.json中set NODE_ENV=production&&...,production后面不能留空格,否则set命令会把空格带进环境变量中去
}
}),

// 为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
// new webpack.optimize.OccurenceOrderPlugin(),

new webpack.optimize.UglifyJsPlugin({
compress: {
//supresses warnings, usually from module minification
warnings: false
}
}),

// 分离CSS和JS文件
new ExtractTextPlugin('/css/[name].[chunkhash:8].css'),

// 提供公共代码
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: '/js/[name].[chunkhash:8].js'
}),

// 可在业务 js 代码中使用 __DEV__ 判断是否是dev模式(dev模式下可以提示错误、测试报告等, production模式不提示)
new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse((process.env.NODE_ENV == 'dev') || 'false'))
})
]
}