轉載自Setting up your React /ES6 Development environment with Webpack, Express and Babel
在2016年,你需要嘗試學習一下react, 而首先,你需要將React和React DOM這兩個庫加入到你的頁面中
加入你已經在頁面中加入了這兩個庫,接下來我該怎么使用React呢?
多說幾句,你得在你的項目中加入Babel你才可以使用React
在我第一次接觸React的時候,我做了一個小玩意來練習在頁面上創建并渲染組件。雖然我非常喜歡react動態的頁面和Redux的單狀態項目,可我使用了太多的時間來配置配置文件以及安裝開發依賴,浪費了大量的編碼時間
React項目初始化搭建非常痛苦是因為React不是一個面向一整個項目的庫。根據Facebook的文檔,it’s a view library for building user interfaces
這是一個用于建設用戶界面的庫,可隨著React環境的慢慢成熟,當使用React開發的時候大量的開發者會普遍使用一些類似于Webpack或者Babel的工具來處理這個問題,可是這些工具經常變更,所以,每當你第一次初始化React開發環境的時候,會令人非常煩惱
為了幫你解決這個困擾,我已經把把自己的項目初始化放到了這個gist上,其中包含了,我的webpack在開發環境和生產環境的配置和express服務器的代碼。我會向你演示如何熱更新模塊,這個功能可以在你的瀏覽器在你修改代碼后自動更新界面,不需要自己手動刷新這個界面,這一整個項目也在Github上面可以找到
你可以直接使用我的配置然后開始使用React,可是我還是想嘮叨一遍這些文件都在做一些什么事情,這樣的話當出現了一些bug后,你不會在要解決一些問題的時候手足無措。當我剛開始搭建一個React項目的時候,有大量的關于使用es5語法和一些廢棄的模塊搭建的Webpack/React項目的教程。如果你是一個非常酷的選手,正在使用ES6編寫React的話,我希望這篇文章能夠幫到你
/ 注意:在我寫了這篇博客之后,我開發了一個Create-React-App的項目,這個項目是一個我自己做的用以快速創建一個React的Helloworld項目的工具,它分裝了webpack和Babel復雜的使用。
但是配置Webpack和Babel是你開發React必須要掌握的技能,這個教程會很好的幫助你理解他們 /
1、開始一個React項目需要準備些什么
技術上,你只需要兩個庫:React和React-DOM(這兩個庫讓你能夠把React組件渲染到瀏覽器上)
但是如果你要馬上使用React,你必須要學會如何編寫JSX
什么是JSX?JSX是一個讓你用HTML寫法寫組建的Javascript語法,使用了JSX,你的React代碼就會變得非常美觀,讓其他的開發者能清楚的之后你的代碼想要實現什么,比如這就是JSX:
const Component = () => (<div><h1>I am a component></h1></div>);
可惜的是,現在的瀏覽器的Javascript解釋器并不能理解JSX,他遇到JSX會拋出異常,所以你需要自己轉換成通用JS語法:
var Component = function Component() {
return React.createElement(
"div",
null,
React.createElement(
"h1",
null,
"I am a component>"
)
);
};
更多
沒有人愿意像這樣寫代碼,這么寫會顯得代碼非常冗余,而且這有點讓人弄不懂這個代碼到底在做些什么,可是瀏覽器只能解釋通用版本的JS代碼,所以我們在開發時候應該怎么辦
進入Babel。Babel是一個Javascript的編譯器,無論你使用什么你喜歡的(JSX還是ES6)語法,他都會幫你轉換成瀏覽器可以理解的語言
我使用JSX和ES6來編寫javascript并使用Babel來將他們轉換成使用ES5規范的另一個文件中,這樣瀏覽器就能夠理解這些代碼了
好,那么現在我們有React,React-DOM還有Babel,我們還需要什么呢
當我們編寫React的時候,我們需要在把大量不同的組件寫到不同的文件中,這些文件都會導入各自的依賴。如果我們將這些文件都通過sprite標簽各自導入的話,會消耗大量時間。

這就是我們為什么使用Webpack的原因,這是一個可以將我們需要的所有文件和庫依賴整合成一個文件能夠直接加入到頁面中的打包工具
Webpack還有一個非常方便的插件叫做webpack-dev-server,這個插件通過一個輕量級的Express服務器使得你可以很方便的看到你修改的地方在瀏覽器上的改變,很吊!
所以在我們開始編寫代碼之前,我們需要:
1、React
2、React-DOM
3、Babel
4、Webpack
安裝依賴
那么,我們要怎么準備這些東西呢

當然是npm install了
在你的項目的根目錄,運行下面的命令來安裝React和React-DOM
npm install --save react
npm install --save react-dom
運行下面的命令來安裝開發依賴,這些依賴對你最后的應用是沒有用的,但是在你的開發編譯過程中有用
npm install --save-dev babel-core
npm install --save-dev babel-cli
npm install --save-dev babel-loader
npm install --save-dev babel-preset-es2015
npm install --save-dev babel-preset-react
npm install --save-dev react-hot-loader
npm install --save-dev webpack
npm install --save-dev webpack-dev-middleware
npm install --save-dev webpack-hot-middleware
我們使用的Babel被分為核心庫(babel-core),命令行工具(babel-cli)和一個能夠讓babel和webpack協調使用的插件babel-loader。除此之外,我們還安裝了babel-preset-react和babel-preset-es2015插件,這兩個插件涵蓋了把ES6和React代碼編譯成通用javascript的規則
我現在不想深入react-hot-loader和webpack middleware模塊,但我們會在將來的熱更新模塊章節中提及到他
當你完成這些操作后,你的package.json應該像這樣:
{
"name": "how_far_hr",
"version": "1.1.0",
"engines": {
"node": "5.12.0"
},
"description": "A small toy app showing HR progress with React and ES6",
"author": "Jon Deng <jondeng.com>",
"license": "MIT",
"private": false,
"scripts": {
"start": "npm build && node dist/app.js",
"dev-start": "node dist/app.js",
"build": "webpack --config ./webpack.deployment.config.js --progress --colors"
},
"dependencies": {
"bootstrap": "^4.0.0-alpha.2",
"express": "^4.14.0",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"redux": "^3.6.0",
"underscore": "^1.8.3"
},
"devDependencies": {
"babel-cli": "^6.6.5",
"babel-core": "^6.17.0",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.16.0",
"babel-preset-react": "^6.16.0",
"react-hot-loader": "^3.0.0-beta.5",
"webpack": "^1.13.2",
"webpack-dev-middleware": "^1.8.3",
"webpack-hot-middleware": "^2.12.2"
}
}
如果已經做好了的話,這是我的項目目錄,我把我所有的源文件都放在了app下的目錄,當這些文件被編譯打包的時候,webpack會把這些文件都輸出到dist下,在根目錄下含有一個package.json文件和生產環境還有開發環境的webpack配置文件

3、為開發環境配置Webpack
為了使得webpack在開發環境上運作,我們需要對React進行配置,這可以在我們每次保存我的時候轉義并打包并可以利用webpack-dev-server
直接在瀏覽器上顯示修改。
以下是我的開發環境的webpack.config.js文件
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './app/index.js',
output: {
path: __dirname,
filename: 'bundle.js',
publicPath: '/app/assets/'
},
module: {
loaders: [
{
test: /.jsx?$/,
loader: 'babel-loader',
include: path.join(__dirname, 'app'),
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}
]
},
};
第一行和第二行,我使用了require語法導入了path和webpack模塊
在第五行,我指明了這次打包的文件的入口,最簡單的找到入口文件的方法就是去找那些調用React-DOM來渲染你的React組建到頁面DOM的文件
在第6行到第9行,我申明了我會把我所有的腳本文件打包到一個叫做bundle.js
的文件,并申明了公開文件目錄為/app/assets
最后,我在12~22行,我在每次webpack打包文件的時候都讓webpack運行babel-loader。babel-loader會將用ES6和JSX編寫的文件轉移成瀏覽器可以識別的通用javascript文件,在第14行到16行,我申明了一個正則表達式來讓webpack轉義那些在app/
目錄下后綴名為.jsx
的文件并屏蔽了node_modules
下的文件,在第16行,我設置了Babel使用RS6和React的規則去轉義文件
現在,你想要啟動你的應用并動態編輯的話,你只要在命令行執行以下的操作:
webpack-dev-server --progress --colors
我喜歡使用progress和colors命令來讓命令行的輸出可讀性更高
也不是很難嘛,對不對~
4、為生產環境配置Webpack
const path = require('path');
const webpack = require('webpack');
module.exports = {
devtool: 'source-map',
entry: [
'./app/index.js'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/dist/'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
minimize: true,
compress: {
warnings: false
}
})
],
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
include: path.join(__dirname, 'app'),
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}]
},
};
你會發現生產環境的webpack配置回合開發環境的非常像
主要的區別在于我們添加了一個叫做UglifyJSPlugin
的插件,這個插件可以混淆你的代碼
你也可以根據你自己的需要添加更多的webpack插件
5、創建一個Express服務器
當然,webpack-dev-server在開發的時候非常方便,但是你最好別把它用在開發環境上,如果你使用Express服務器的話你可以使用更多你想要的功能,如果你想要熱更新功能,我們會在下個章節中提及
在下面的文件中,我編寫了一個工廠方法來新建一個express服務器對象,我們會把這個方法在應用入口文件中導入并創建一個express服務器
const path = require('path')
const express = require('express')
module.exports = {
app: function () {
const app = express();
const indexPath = path.join(__dirname, 'indexDep.html');
const publicPath = express.static(path.join(__dirname, '../dist'));
app.use('/dist', publicPath);
app.get('/', function (_, res) { res.sendFile(indexPath) });
return app;
}
}
在第10行,我為express對象指出/dist
目錄來滿足靜態文件比如css,圖片,javascript文件的直接獲取的需求。因為我們webpack配置文件中,我們的打包的文件會導出到/dist
目錄下
在第11行,我告訴express當訪問根目錄的時候返回indexDep.html頁面,這個文件中含有一個腳本標簽來導入我們在dist下打包后的文件,使得React可以渲染到頁面DOM上
在下一個章節,我們會使用這個工廠方法,來創建一個express服務器對象
6、添加動態模塊動態模塊更新
開發React最炫酷的事情就是有很多開發功能上的優點,比如動態模塊更新,這個功能可以讓我們當組件代碼被修改的時候讓這些組件自動在頁面更新,而不是重新渲染一邊這個頁面。使用動態模塊更新,你會有一個非常好的動態響應編輯的體驗。因為在你編輯這個組件的時候他們會持續重新渲染,所以你會馬上看到自己的修改
我發現使用動態模塊更新最簡單的方法就是在express服務器上面使用一個webpack中間件
在繼續下去之前,我想要花一點時間來了解一下中間件,在express中,中間件是像管道一樣的方法,接收數據流,輸出處理過的數據流
因為響應數據是數據流,我們可以在這些數據流入客戶端之前利用這些中間件方法來修改這些數據
所以我們使用webpack-dev-middleware來將react代碼轉義成瀏覽器可讀的通用js,并將這些文件打包到一個可以被客戶端讀取的js文件中
另外我們還將使用webpack-hot-middleware,這個中間件可以檢測到文件的修改并通知客戶端重新渲染組件
根據下面的文檔,可以看出webpack-hot-middleware是怎么工作的
The middleware installs itself as a webpack plugin, and listens for compiler events.
每個連接的客戶端會建立一個服務器可推送的連接,服務器可以給連接的客戶端推送重新編譯事件的消息
當客戶端獲取到這個消息的時候,他會檢查代碼是否更新了,如果本地的代碼不是最新,就會觸發重新讀取代碼的事件
下面是app.js
文件,這個文件被用做我們服務器的入口。
這個入口是服務器啟動所調用的第一個文件,也是連接所有應用文件的一個文件,這個入口文件在node應用中經常被叫做app.js或者index.js,為了更好地理解,這個解答會更有幫助
常規的,我們的入口文件會創建一個新的express服務器對象來服務器我們的webpack,并監聽一個指定的端口,這個服務器會提供一個index.html文件,這個html文件會通過一個腳本標簽引入react組件,另外我們的入口文件會在開發環境的時候使用中間件來允許我們使用動態模塊更新
const Server = require('./server.js')
const port = (process.env.PORT || 8080)
const app = Server.app()
if (process.env.NODE_ENV !== 'production') {
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const config = require('../webpack.deployment.config.js')
const compiler = webpack(config)
app.use(webpackHotMiddleware(compiler))
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPathdist
}))
}
app.listen(port)
console.log(`Listening at http://localhost:${port}`)
第一行,我們導入了工廠方法并創建了express服務器對象
第五行,我檢查是否是在開發環境
第12~16行,我們在express服務器中添加了webpack中間件
現在我們有了一個開發服務器,我們現在只需要在命令行打入下面命令就能本地啟動應用了
node dist/app.js
與其每次都打著重復的命令,讓我們把我們的運行腳本寫入到package.json文件中吧,這可以幫助我們在像heroku這樣的服務器上開發,這讓我們可以使用start腳本來部署應用
{
"name": "how_far_hr",
"version": "1.1.0",
"engines": {
"node": "5.12.0"
},
"description": "A small toy app showing HR progress with React and ES6",
"author": "Jon Deng <jondeng.com>",
"license": "MIT",
"private": false,
"scripts": {
"start": "npm build && node dist/app.js",
"dev-start": "node dist/app.js",
"build": "webpack --config ./webpack.deployment.config.js --progress --colors"
},
"dependencies": {
"bootstrap": "^4.0.0-alpha.2",
"express": "^4.14.0",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"redux": "^3.6.0",
"underscore": "^1.8.3"
},
"devDependencies": {
"babel-cli": "^6.6.5",
"babel-core": "^6.17.0",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.16.0",
"babel-preset-react": "^6.16.0",
"react-hot-loader": "^3.0.0-beta.5",
"webpack": "^1.13.2",
"webpack-dev-middleware": "^1.8.3",
"webpack-hot-middleware": "^2.12.2"
}
}
后面沒啥了,就醬