部落格地址:地址
Github專案地址: 地址
建立專案
$ mkdir example
$ cd example
$ npm init -y複製程式碼
建立目錄結構
├── config // webpack配置檔案目錄
├── package.json
├── server // 開啟本地node服務檔案,包括熱更新等
└── src // 原始檔目錄複製程式碼
安裝Webpack
$ npm i --save-dev webpack複製程式碼
編寫Webpack配置檔案
在config目錄下新建檔案:
$ cd config
$ touch webpack.config.dev.js
$ touch webpack.config.pro.js複製程式碼
我們先來寫開發環境下的 webpack.config.dev.js
const path = require('path')
const webpack = require('webpack')
// 先定義一些路徑
// 配置資料夾路徑
const CONFIG_PATH = path.resolve(__dirname)
// 原始碼資料夾路徑
const APP_PATH = path.resolve(CONFIG_PATH, '../src')
// 應用入口檔案
const APP_FILE = path.resolve(APP_PATH, 'index.js')
// 打包目錄資料夾路徑
const BUILD_PATH = path.resolve(ROOT_PATH, '../dist')
module.exports = {
// 入口
entry: APP_FILE,
// 輸出
output: {
// 告訴Webpack結果儲存在哪裡
path: BUILD_PATH,
// 打包後的檔名
filename: 'bundle.js',
//模板、樣式、指令碼、圖片等資源對應的server上的路徑
publicPath: "/assets/",
}
}複製程式碼
一個最簡單的webpack配置檔案已完成
安裝Babel
我們使用ES6來編寫程式碼,所以需要安裝ES6的babel-preset
$ npm i --save-dev babel-cli babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0複製程式碼
同時修改webpack配置檔案,在module.exports
增加:
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}]
}複製程式碼
在根目錄新增babelrc檔案
cd /path/to/example
touch .babelrc複製程式碼
修改.babelrc
{
"env": {
"development": {
"presets" : ["es2015", "stage-0", "react"],
},
"production": {
"presets" : [["es2015", { "modules": false, "loose": true }], "stage-0", "react"],]
}
}
}複製程式碼
安裝其他Loader
css-loader
style-loader
postcss-loader
less-loader
file-loader
url-loader
autoprefixer
npm i --save-dev css-loader style-loader file-loader less less-loader postcss-loader url-loader autoprefixer複製程式碼
同時修改webpack配置檔案,修改module.exports
下的module.rules
:
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require('autoprefixer')()
]
}
}
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require('autoprefixer')()
]
}
},
'less-loader'
]
},
{
test: /\.(eot|woff|ttf|woff2|svg|gif)(\?|$)/,
loader: 'file-loader?name=[hash].[ext]'
},
{
test: /\.(png|jpg)$/,
loader: 'url-loader?limit=1200&name=[hash].[ext]'
}
]複製程式碼
本地node服務
在server下新建server.js和index.html:
$ cd server
$ touch server.js
$ touch index.html複製程式碼
安裝server服務依賴
$ npm i --save express複製程式碼
修改server.js檔案
const app = new (require('express'))()
// 本地預覽程式碼的埠
const port = 3003
app.get('*', function(req, res) {
res.sendFile(__dirname + '/index.html')
})
app.listen(port, function(error) {
if (error) {
/*eslint no-console: 0*/
console.error(error)
} else {
/*eslint no-console: 0*/
console.info("==> ? Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port)
}
})複製程式碼
最簡單的本地預覽服務已經完成
熱更新
我們使用react-hot-loader
來做熱更新,webpack-dev-middleware
和webpack-hot-middlewaref
來實時重新整理頁面
安裝依賴
$ npm i --save-dev react-hot-loader webpack-dev-middleware webpack-hot-middleware複製程式碼
編寫index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, viewport-fit=contain"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body ontouchstart="">
<div id="app"></div>
<script src="/assets/bundle.js"></script>
</body>
</html>
複製程式碼
修改server.js
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const config = require('../config/webpack.config.dev.js')
const app = new (require('express'))()
// 本地預覽程式碼的埠
const port = 3003
const compiler = webpack(config)
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: config.output.publicPath,
stats: {
colors: true
}
}))
app.use(webpackHotMiddleware(compiler))
app.get('*', function(req, res) {
res.sendFile(__dirname + '/index.html')
})
app.listen(port, function(error) {
if (error) {
/*eslint no-console: 0*/
console.error(error)
} else {
/*eslint no-console: 0*/
console.info("==> ? Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port)
}
})複製程式碼
修改webpack.config.dev.js的module.exports
的entry
entry: [
'react-hot-loader/patch',
// 這裡reload=true的意思是,如果碰到不能hot reload的情況,就整頁重新整理。
'webpack-hot-middleware/client?reload=true',
APP_FILE
],複製程式碼
在module.exports新增:
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development') //定義編譯環境
}
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],複製程式碼
在babelrc檔案中新增preset
"plugins" : ["react-hot-loader/babel"]複製程式碼
.babelrc
最終程式碼:
{
"env": {
"development": {
"presets" : ["es2015", "stage-0", "react"],
"plugins" : ["react-hot-loader/babel"]
},
"production": {
"presets" : [["es2015", { "modules": false, "loose": true }], "stage-0", "react"],
}
}
}
複製程式碼
webpack.config.dev.js
最終程式碼:
/**
* @author Chan Zewail
* ###### Thu Jan 25 19:28:40 CST 2018
*/
const path = require('path')
const webpack = require('webpack')
// 先定義一些路徑
// 配置資料夾路徑
const CONFIG_PATH = path.resolve(__dirname)
// 原始碼資料夾路徑
const APP_PATH = path.resolve(CONFIG_PATH, '../src')
// 應用入口檔案
const APP_FILE = path.resolve(APP_PATH, 'index.js')
// 打包目錄資料夾路徑
const BUILD_PATH = path.resolve(ROOT_PATH, '../dist')
module.exports = {
entry: [
'react-hot-loader/patch',
// 這裡reload=true的意思是,如果碰到不能hot reload的情況,就整頁重新整理。
'webpack-hot-middleware/client?reload=true',
APP_FILE
],
output: {
// 告訴Webpack結果儲存在哪裡
path: BUILD_PATH,
// 打包後的檔名
filename: 'bundle.js',
//模板、樣式、指令碼、圖片等資源對應的server上的路徑
publicPath: "/assets/",
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require('autoprefixer')()
]
}
}
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: (loader) => [
require('autoprefixer')()
]
}
},
'less-loader'
]
},
{
test: /\.(eot|woff|ttf|woff2|svg|gif)(\?|$)/,
loader: 'file-loader?name=[hash].[ext]'
},
{
test: /\.(png|jpg)$/,
loader: 'url-loader?limit=1200&name=[hash].[ext]'
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development') //定義編譯環境
}
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
resolve: {
//字尾名自動補全
extensions: ['.js', '.jsx', '.less', '.scss', '.css'],
// 根路徑別名
alias: {
'@': `${APP_PATH}/`,
},
modules: [
'node_modules',
'src',
]
}
}
複製程式碼
npm指令碼命令
安裝一些工具依賴
npm i --save-dev copyfiles clean-webpack-plugin複製程式碼
在package.json中新增:
"scripts": {
"dev": "node server/server.js",
"start": "npm run dev",
"copy": "copyfiles -f ./server/index.html ./dist",
"build": "npm run copy && webpack --config config/webpack.config.pro.js"
},複製程式碼
開始編寫專案程式碼
安裝react
npm i --save react react-dom複製程式碼
入口檔案
$ cd src
# 入口檔案
$ touch index.js
# 應用檔案
$ touch App.js複製程式碼
編寫index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
// 應用檔案
import App from './App'
ReactDOM.render(
<AppContainer>
<App/>
</AppContainer>
, document.getElementById('app'))
// 熱更新
if (module.hot) {
module.hot.accept('./App', () => {
const NextApp = require('./App').default
ReactDOM.render(
<AppContainer>
<NextApp/>
</AppContainer>,
document.getElementById('app')
)
})
}複製程式碼
全家桶
react
react-dom
react-router
redux
redux-saga
redux-persist
history
redux-logger
react-router-redux
npm i --save react-router redux redux saga reux-persist history redux-logger react-router-redux複製程式碼
編寫configureStore生成store,
$ cd src
$ mkdir store
$ touch index.js複製程式碼
/**
* @author Chan Zewail
* ###### Thu Jan 25 19:28:40 CST 2018
*/
import { createStore, applyMiddleware, compose } from 'redux'
import { persistStore } from 'redux-persist'
import reducers from '@/reducers'
import createHistory from 'history/createHashHistory'
import { routerMiddleware } from 'react-router-redux'
import createSagaMiddleware from 'redux-saga'
import sagas from '@/sagas'
import logger from 'redux-logger'
// 建立history
export const history = createHistory()
//建立saga中介軟體
const sagaMiddleware = createSagaMiddleware()
// 需要呼叫的中介軟體
const middleWares = [
sagaMiddleware,
routerMiddleware(history),
logger
]
// 生成store
const store = createStore(reducers, undefined, compose(
applyMiddleware(...middleWares),
))
// 將store資料儲存到快取
const persistor = persistStore(store, null)
// 生成最終的store函式
export default function configureStore(){
// 執行saga
sagaMiddleware.run(sagas)
return { persistor, store }
}
export function getPersistor() {
return persistor
}
複製程式碼
編寫reducer
$ cd src
$ mkdir reducers
$ touch index.js複製程式碼
/**
* @author Chan Zewail
* ###### Thu Jan 25 19:28:40 CST 2018
*/
import { routerReducer } from 'react-router-redux'
import { persistCombineReducers, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
// import xxxReducer from './xxx'
// redux-persist 配置
export const config = {
key: 'root',
storage,
debug: true,
// 將對應reducer儲存到本地快取的白名單
whitelist: [
'routing',
]
}
// 合併reducer
export default persistCombineReducers(config, {
routing: routerReducer, // 預設引入路由reducer
// xxx: xxxReducer,
})
複製程式碼
編寫saga
$ cd src
$ mkdir sagas
$ touch index.js複製程式碼
/**
* @author Chan Zewail
* ###### Thu Jan 25 19:28:40 CST 2018
*/
import { fork, all } from 'redux-saga/effects'
// import xxxSagaFlow from './xxxFlow'
// 根saga
export default function* rootSaga () {
yield all([
// fork(xxxSagaFlow)
])
}
複製程式碼
App.js
/**
* @author Chan Zewail
* ###### Thu Jan 25 19:28:40 CST 2018
*/
import React from 'react'
import { Provider } from 'react-redux'
import { history } from '@/store'
import { ConnectedRouter } from 'react-router-redux'
import { PersistGate } from 'redux-persist/es/integration/react'
import { Redirect } from 'react-router-dom'
// persister 快取恢復前呼叫的方法
const onBeforeLift = () => {
// console.log('before action')
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
const { persistor, store } = this.props.stores
// 主頁面
return (
<Provider store={store}>
<PersistGate loading={<div/>} onBeforeLift={onBeforeLift} persistor={persistor}>
<ConnectedRouter history={history}>
// switch and route
</ConnectedRouter>
</PersistGate>
</Provider>
)
}
}
export default App
複製程式碼
修改 index.js並新增獲取store方法,然後傳遞到App的props(熱更新store)
index.js
/**
* @author Chan Zewail
* ###### Thu Jan 25 19:28:40 CST 2018
*/
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import configureStore from '@/store'
// 主頁面
import App from './App'
export const stores = configureStore()
ReactDOM.render(
<AppContainer>
<App stores={stores}/>
</AppContainer>
, document.getElementById('app'))
// 熱更新
if (module.hot) {
module.hot.accept('./App', () => {
const NextApp = require('./App').default
ReactDOM.render(
<AppContainer>
<NextApp stores={stores}/>
</AppContainer>,
document.getElementById('app')
)
})
}
複製程式碼
最後
最基礎的react全家桶已搭建完畢,其他功能可逐漸加入並個性化定製
最終程式碼:github.com/czewail/zew…
使用問題可在Issues提出
歡迎Start