想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!php
這篇文檔 是呂小明老師結合以往的項目經驗 加上本身自己對react webpack redux理解寫下的總結文檔,總共耗時一週總結下來的,但願能對讀者可以有收穫, 我是在這基礎多些加工!css
1.版本說明html
2.目錄結構前端
3.初始化項目vue
4.webpackhtml5
5.reactnode
7.引入babelwebpack
12.如何理解entry point(bundle),chunk,module
14.模塊熱替換(Hot Module Replacement)
因爲構建相關例如webpack,babel等更新的較快,因此本教程如下面各類模塊的版本號爲主,切勿輕易修改或更新版本。
"dependencies": { "babel-core": "^6.26.3", "babel-eslint": "^8.2.3", "babel-loader": "^7.1.4", "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "babel-preset-stage-3": "^6.24.1", "css-loader": "^0.28.11", "eslint": "^4.19.1", "eslint-loader": "^2.0.0", "eslint-plugin-react": "^7.9.1", "file-loader": "^1.1.11", "history": "^4.7.2", "html-webpack-plugin": "^3.2.0", "react": "^16.4.0", "react-dom": "^16.4.0", "react-hot-loader": "^4.0.0", "react-redux": "^5.0.7", "react-router-dom": "^4.3.1", "react-router-redux": "^5.0.0-alpha.9", "redux": "^4.0.0", "sass-loader": "^7.0.3", "style-loader": "^0.21.0", "url-loader": "^1.0.1", "webpack": "^4.12.0", "webpack-cli": "^3.0.3", "webpack-dev-server": "^3.1.1" }
開發和發佈版本的配置文件是分開的,多入口頁面的目錄結構。
react-family/ | |──dist/ * 發佈版本構建輸出路徑 | |──dev/ * 調試版本構建輸出路徑 | |──src/ * 工具函數 | | | |—— component/ * 各頁面公用組件 | | | |—— page/ * 頁面代碼 | | |—— index/ * 頁面代碼 | | | |—— Main/ * 組件代碼 | | | | |—— Main.jsx * 組件jsx | | | | |—— Main.scss * 組件css | | | | | |—— detail/ * 頁面代碼 | | | |—— static/ * 靜態文件js,css | | |──webpack.config.build.js * 發佈版本使用的webpack配置文件 |──webpack.config.dev.js * 調試版本使用的webpack配置文件 |──.eslint * eslint配置文件 |__.babelrc * babel配置文件
1.建立文件夾
mkdir react-family-bucket
2.初始化npm
cd react-family-bucket npm init
若是有特殊須要,能夠填入本身的配置,一路回車下來,會生成一個package.json,裏面是你項目的基本信息,後面的npm依賴安裝也會配置在這裏。
1.安裝webpack
npm install webpack@4.12.0 --save or npm install webpack@4.12.0 --g
--save
是將當前webpack安裝到react-family-bucket下的/node_modules。
--g
是將當前webpack安裝到全局下面,能夠在node的安裝目錄下找到全局的/node_modules。
2.配置webopack配置文件
touch webpack.config.dev.js
3.新建一個app.js
touch app.js
寫入基本的webpack配置,能夠參考這裏:
const path = require('path'); const srcRoot = './src'; module.exports = { // 輸入配置 entry: [ './app.js' ],, // 輸出配置 output: { path: path.resolve(__dirname, './dev'), filename: 'bundle.min.js' }, };
3.執行webpack命令
若是是全局安裝:
webpack --config webpack.config.dev.js
若是是當前目錄安裝:
./node_modules/.bin/webpack --config webpack.config.dev.js
爲了方便咱們使用,能夠在package.json中 scripts
添加執行命令:
"scripts": { "dev": "./node_modules/.bin/webpack --config webpack.config.dev.js", },
執行npm run dev命令以後,會發現須要安裝webpack-cli,(webpack4以後須要安裝這個)
npm install webpack-cli --save
安裝後,執行 npm run dev 會發現控制檯有個警告 WARNING in configuration
,去除WARNING in configuration 警告,在webpack.config.dev.js 增長一個配置便可:
... mode: 'development' ...
成功以後會在dev下面生成bundle.min.js表明正常。
若是想要動態監聽文件變化須要在命令後面添加 --watch。
1.安裝react
npm install react react-dom --save
2.建立page目錄和index頁面文件:
mkdir src mkdir page cd page
3.建立index
mkdir index cd index & touch index.js & touch index.html
index.js
import ReactDom from 'react-dom'; import Main from './Main/Main.jsx'; ReactDom.render(<Main />, document.getElementById('root'));
index.html
<!DOCTYPE html> <html> <head> <title>index</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> </head> <body> <div id="root"></div> </body> </html>
4.建立Main組件
import React from 'react'; class Main extends React.Component { constructor(props) { super(props); } render() { return (<div>Main</div>); } } export default Main;
5.修改webpack配置入口文件
entry: [ path.resolve(srcRoot,'./page/index/index.js') ],
1.處理樣式文件須要這些loader:
npm install css-loader sass-loader style-loader file-loader --save
配置:
module: { // 加載器配置 rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'], include: path.resolve(srcRoot)}, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], include: path.resolve(srcRoot)} ] },
2.url-loader 處理處理靜態文件
npm install url-loader --save
配置:
module: { // 加載器配置 rules: [ { test: /\.(png|jpg|jpeg)$/, use: 'url-loader?limit=8192&name=images/[name].[hash].[ext]', include: path.resolve(srcRoot)} ] },
limit:
表示超過多少就使用base64來代替,單位是byte
name:
能夠設置圖片的路徑,名稱和是否使用hash 具體參考這裏
bebel是用來解析es6語法或者是es7語法分解析器,讓開發者可以使用新的es語法,同時支持jsx,vue等多種框架。
1.安裝babel
npm install babel-core babel-loader --save
配置:
module: { // 加載器配置 rules: [ { test: /\.(js|jsx)$/, use: [{loader:'babel-loader'}] ,include: path.resolve(srcRoot)}, ] },
2.babel配置文件:.babelrc
touch .babelrc
配置:
{ "presets": [ "es2015", "react", "stage-0" ], "plugins": [] }
babel支持自定義的預設(presets)或插件(plugins),只有配置了這兩個才能讓babel生效,單獨的安裝babel是無心義的。
presets
:表明babel支持那種語法(就是你用那種語法寫),優先級是從下往上,state-0|1|2|..表明有不少沒有列入標準的語法回已state-x表示,參考這裏
plugins
:表明babel解析的時候使用哪些插件,做用和presets相似,優先級是從上往下。
依次安裝:
npm install babel-preset-es2015 babel-preset-react babel-preset-stage-0 --save
3.babel-polyfill是什麼?
咱們以前使用的babel,babel-loader 默認只轉換新的 JavaScript 語法,而不轉換新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局對象,以及一些定義在全局對象上的方法(好比 Object.assign)都不會轉譯。若是想使用這些新的對象和方法,必須使用 babel-polyfill,爲當前環境提供一個墊片。
npm install --save babel-polyfill
4.transform-runtime 有什麼區別?
當使用babel-polyfill時有一些問題:
這時就須要transform-runtime來幫咱們有選擇性的引入:
npm install --save babel-plugin-transform-runtime
配置文件:
{ "plugins": [ ["transform-runtime", { "helpers": false, "polyfill": false, "regenerator": true, "moduleName": "babel-runtime" }] ] }
記得咱們以前新建的index.html麼 咱們執行構建命令以後並無將index.html打包到dev目錄下 咱們須要HtmlWebpackPlugin來將咱們output的js和html結合起來:
npm install html-webpack-plugin --save
配置:
const HtmlWebpackPlugin = require('html-webpack-plugin'); ... plugins: [ new HtmlWebpackPlugin({ filename: path.resolve(devPath, 'index.html'), template: path.resolve(srcRoot, './page/index/index.html'), }) ]
filename:
能夠設置html輸出的路徑和文件名template:
能夠設置已哪一個html文件爲模版
更多參數配置能夠參考這裏
1.安裝redux
npm install redux react-redux --save
1.新建reducers,actions目錄和文件
|—— index/ |—— Main/ * 組件代碼 | |—— Main.jsx * 組件jsx | |—— Main.scss * 組件css | |—— actions/ | |—— actionTypes.js * action常量 | |—— todoAction.js * action | |—— reducers/ | |—— todoReducer.js * reducer | |—— store.js | |—— index.js
2.修改代碼,引入redux,這裏以一個redux todo爲demo例子:
index.js
import ReactDom from 'react-dom'; import React from 'react'; import Main from './Main/Main.jsx'; import store from './store.js'; import { Provider } from 'react-redux'; ReactDom.render( <Provider store={store}> <Main /> </Provider> , document.getElementById('root'));
store.js
import { createStore } from 'redux'; import todoReducer from './reducers/todoReducer.js'; const store = createStore(todoReducer); export default store;
tabReducer.js
import { ADD_TODO } from '../actions/actionTypes.js'; const initialState = { todoList: [] }; const addTodo = (state, action) => { return { ...state, todoList: state.todoList.concat(action.obj) } } const todoReducer = (state = initialState, action) => { switch(action.type) { case ADD_TODO: return addTodo(state, action); default: return state; } }; export default todoReducer;
Main.jsx
import React from 'react'; import { connect } from 'react-redux'; import { addTodo } from '../actions/todoAction.js'; class Main extends React.Component { onClick(){ let text = this.refs.input; this.props.dispatch(addTodo({ text: text.value })) } render() { return ( <div> <input ref="input" type="text"></input> <button onClick={()=>this.onClick()}>提交</button> <ul> {this.props.todoList.map((item, index)=>{ return <li key={index}>{item.text}</li> })} </ul> </div> ); } } export default connect( state => ({ todoList: state.todoList }) )(Main);
todoAction.js
import { ADD_TODO } from './actionTypes.js'; export const addTodo = (obj) => { return { type: ADD_TODO, obj: obj }; };
webpack-dev-server是一個小型的Node.js Express服務器,它使用webpack-dev-middleware來服務於webpack的包。
1.安裝
npm install webpack-dev-server --save
修改在package.json中添加的執行命令:
"scripts": { "dev": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js", },
2.配置webpack配置文件:
devServer: { "contentBase": devPath, "compress": true, },
contentBase
: 表示server文件的根目錄compress
: 表示開啓gzip
更多的配置文檔參考這裏
3.devtool功能:
具體來講添加了devtool: 'inline-source-map'以後,利用source-map你在chrome控制檯看到的source源碼都是真正的源碼,未壓縮,未編譯前的代碼,沒有添加,你看到的代碼是真實的壓縮過,編譯過的代碼,更多devtool的配置能夠參考這裏。
在以前的配置中,都是基於單入口頁面配置的,entry和output只有一個文件,可是實際項目不少狀況下是多頁面的,在配置多頁面時,有2中方法能夠選擇:
1.在entry入口配置時,傳入對象而不是單獨數組,output時利用[name]關鍵字來區分輸出文件例如:
entry: { index: [path.resolve(srcRoot,'./page/index/index1.js'),path.resolve(srcRoot,'./page/index/index2.js')], detail: path.resolve(srcRoot,'./page/detail/detail.js'), home: path.resolve(srcRoot,'./page/home/home.js'), }, output: { path: path.resolve(__dirname, './dev'), filename: '[name].min.js' },
2.經過node動態遍歷須要entry point的目錄,來動態生成entry:
const pageDir = path.resolve(srcRoot, 'page'); function getEntry() { let entryMap = {}; fs.readdirSync(pageDir).forEach((pathname)=>{ let fullPathName = path.resolve(pageDir, pathname); let stat = fs.statSync(fullPathName); let fileName = path.resolve(fullPathName, 'index.js'); if (stat.isDirectory() && fs.existsSync(fileName)) { entryMap[pathname] = fileName; } }); return entryMap; } { ... entry: getEntry() ... }
本demo採用的是第二中寫法,可以更加靈活。
在webpack中,如何理解entry point(bundle),chunk,module?
根據圖上的表述,我這裏簡單說一下便於理解的結論:
理解這些概念對於後續使用webpack插件有很大的幫助。
以前咱們配置HtmlWebpackPlugin時,一樣採用的是但頁面的配置,這裏咱們將進行多頁面改造,entryMap是上一步獲得的entry:
function htmlAarray(entryMap) { let htmlAarray = []; Object.keys(entryMap).forEach(function(key){ let fullPathName = path.resolve(pageDir, key); let fileName = path.resolve(fullPathName, key + '.html') if (fs.existsSync(fileName)) { htmlAarray.push(new HtmlWebpackPlugin({ chunks: key, // 注意這裏的key就是chunk filename: key + '.html', template: fileName, inlineSource: '.(js|css)' })) } }); return htmlAarray; }
修改plugin配置:plugins: [ ... ].concat(htmlMap)
模塊熱替換 (Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它容許在運行時更新各類模塊,而無需進行徹底刷新,很高大上有木有!
下面說一下配置方法,它須要結合devServer使用:
devServer: { hot: true // 開啓HMR },
開啓plugin:
const webpack = require('webpack'); plugins: [ new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin(), ].concat(htmlMap)
結合React一塊兒使用:
1.安裝react-hot-loader
npm install react-hot-loader --save
並新建一個Container.jsx:
import React from 'react'; import Main from './Main.jsx'; import { hot } from 'react-hot-loader' class Container extends React.Component { render() { return <Main /> } } export default hot(module)(Container);
結合redux:若是項目沒有使用redux,能夠無需配置後面2步
2.修改store.js新增下面代碼,爲了讓reducer也能實時熱替換
if (module.hot) { module.hot.accept('./reducers/todoReducer.js', () => { const nextRootReducer = require('./reducers/todoReducer.js').default; store.replaceReducer(nextRootReducer); }); }
3.修改index.js
import ReactDom from 'react-dom'; import React from 'react'; import Container from './Main/Container.jsx'; import store from './store.js'; import { Provider } from 'react-redux'; ReactDom.render( <Provider store={store}> <Container /> </Provider> , document.getElementById('root'));
當控制檯看到[WDS] Hot Module Replacement enabled.表明開啓成功
ESLint 是衆多 Javascript Linter 中的其中一種,其餘比較常見的還有 JSLint 跟 JSHint,之因此用 ESLint 是由於他能夠自由選擇要使用哪些規則,也有不少現成的 plugin 可使用,另外他對 ES6 還有 JSX 的支持程度跟其餘 linter 相比之下也是最高的。
1.安裝ESLint
npm install eslint eslint-loader babel-eslint --save
其中eslint-loader是將webpack和eslint結合起來在webpack的配置文件中新增一個eslint-loader種,修改以下:
{ test: /\.(js|jsx)$/, use: [{loader:'babel-loader'},{loader:'eslint-loader'}] ,include: path.resolve(srcRoot)},
2.新建.eslintrc配置文件,將parser配置成babel-eslint
{ "extends": ["eslint:recommended"], "parser": "babel-eslint", "globals": { }, "rules": { } }
3.安裝eslint-plugin-react:
npm install eslint-plugin-react --save
修改.eslintrc配置文件,增長rules,更多rules配置能夠參考這裏
{ "extends": ["eslint:recommended","plugin:react/recommended"], "parser": "babel-eslint", "globals": { "window": true, "document": true, "module": true, "require": true }, "rules": { "react/prop-types" : "off", "no-console" : "off" } }
react-router強大指出在於方便代碼管理,結合redux使用更增強大,同時支持web,native更多參考這裏
1.安裝react-router-dom
npm install react-router-dom --save
2.若是項目中用了redux,能夠安裝react-router-redux
npm install react-router-redux@next history --save
3.修改代碼:
index.js
import ReactDom from 'react-dom'; import React from 'react'; import Container from './Main/Container.jsx'; import { store, history } from './store.js'; import { Provider } from 'react-redux'; import createHistory from 'history/createHashHistory'; import { ConnectedRouter } from 'react-router-redux'; const history = createHistory(); ReactDom.render( <Provider store={store}> <ConnectedRouter history={history}> <Container /> </ConnectedRouter> </Provider> , document.getElementById('root'));
結合history,react-router一共有3中不一樣的router:
更多配置能夠參考這裏
4.若是想要在代碼邏輯中獲取當前的route路徑須要引入router-reducer:
新建main.js:
import { combineReducers } from 'redux'; import { routerReducer } from "react-router-redux"; import todoReducer from './todoReducer.js'; const reducers = combineReducers({ todoReducer, router: routerReducer }); export default reducers;
修改store.js:
import { createStore } from 'redux'; import mainReducer from './reducers/main.js'; const store = createStore(mainReducer); export default store;
而後就能夠在this.props.router裏面獲取單相關的路徑信息
5.若是須要本身經過action來觸發router的跳轉,須要引入routerMiddleware:
import { createStore,applyMiddleware } from 'redux'; import { routerMiddleware } from "react-router-redux"; const middleware = routerMiddleware(history); const store = createStore(mainReducer,applyMiddleware(middleware));
6.使用Route和Link和withRouter:
先說說都是幹嗎的:
<Route exact path="/" component={Div1}></Route> <Route path="/2" component={Div2}></Route>
export default withRouter(connect( state => ({ todoList: state.todoReducer.todoList }) )(Main));
若是你在使用hash時遇到Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack錯誤,能夠將push改成replace即:
<NavLink replace={true} to="/2" activeClassName="selected" >切換到2號</NavLink>
7.設置初始化路由:
BrowserRouter和HashRouter:
const history = createHistory(); history.push('2');
MemoryRouter:
const history = createMemoryHistory({ initialEntries: ['/2'] });
redux-thunk 是一個比較流行的 redux 異步 action 中間件,好比 action 中有 setTimeout 或者經過 fetch通用遠程 API 這些場景,那麼久應該使用 redux-thunk 了。redux-thunk 幫助你統一了異步和同步 action 的調用方式,把異步過程放在 action 級別解決,對 component 沒有影響。
1.安裝redux-thunk:
npm install redux-thunk --save
2.修改store.js:
import { createStore,applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import mainReducer from './reducers/main'; ... const store = createStore(mainReducer, applyMiddleware(thunk)); ... export default store;
3.在action.js使用redux-thunk:
export const getData = (obj) => (dispatch, getState) => { setTimeout(()=>{ dispatch({ type: GET_DATA, obj: obj }); },1000); };
axios 是一個基於Promise 用於瀏覽器和 nodejs 的 HTTP 客戶端:
1.安裝axios:
npm install axios --save
2.在action中使用axios:
import axios from 'axios'; export const getData = (obj) => (dispatch, getState) => { axios.get('/json/comments.json').then((resp)=>{ dispatch({ type: GET_DATA, obj: resp }); }); };
Javascript的回調地獄,相信不少人都知道,尤爲是在node端,近些年比較流行的是Promise的解決方案,可是隨着 Node 7 的發佈,編程終級解決方案的 async/await應聲而出。
function resolveAfter2Seconds() { return new Promise(resolve => { setTimeout(() => { resolve('resolved'); }, 2000); }); } async function asyncCall() { var result = await resolveAfter2Seconds(); } asyncCall();
async/await的用途是簡化使用 promises 異步調用的操做,並對一組 Promises執行某些操做。await前提是方法返回的是一個Promise對象,正如Promises相似於結構化回調,async/await相似於組合生成器和 promises。
1.async/await須要安裝babel-plugin-transform-async-to-generator。
npm install babel-plugin-transform-async-to-generator --save
2.在.babelrc中增長配置:
"plugins": [ "transform-async-to-generator" ]
這樣作僅僅是將async轉換generator,若是你當前的瀏覽器不支持generator,你將會收到一個Uncaught ReferenceError: regeneratorRuntime is not defined的錯誤,你須要:
3.安裝babel-plugin-transform-runtime:
npm install babel-plugin-transform-async-to-generator --save
4.修改.babelrc中的配置(能夠去掉以前配置的transform-async-to-generator):
"plugins": [ "transform-runtime" ]
5.若是不想引入全部的polyfill(參考上面對babel的解釋),能夠增長配置:
"plugins": [ "transform-runtime", { "polyfill": false, "regenerator": true, } ]
6.結合axios使用
import axios from 'axios'; export const getData = (obj) => async (dispatch, getState) => { let resp = axios.get('/json/comments.json'); dispatch({ type: GET_DATA, obj: resp }); };
1.對於webpack1,2以前,你可使用require.ensure來控制一個組件的懶加載:
require.ensure([], _require => { let Component = _require('./Component.jsx'); },'lazyname')
2.在webpack4中,官方已經再也不推薦使用require.ensure來使用懶加載功能Dynamic Imports,取而代之的是ES6的import()方法:
import( /* webpackChunkName: "my-chunk-name" */ /* webpackMode: "lazy" */ 'module' );
不小小看註釋裏的代碼,webpack在打包時會動態識別這裏的代碼來作相關的配置,例如chunk name等等。
3.Prefetching/Preloading modules:
webpack 4.6.0+支持了Prefetching/Preloading的寫法:
import(/* webpackPreload: true */ 'ChartingLibrary');
3.結合React-Router使用:
react-loadable對上述的功能作了封裝,豐富了一些功能,結合React-Router起來使用更加方便。
npm install react-loadable --save
在react-router裏使用:
function Loading() { return <div>Loading...</div>; } let Div2 = Loadable({ loader: () => import('./Div2'), loading: Loading, }); <Route path="/2" component={Div2}></Route>
CommonsChunkPlugin 插件,是一個可選的用於創建一個獨立文件(又稱做 chunk)的功能,這個文件包括多個入口 chunk 的公共模塊。經過將公共模塊拆出來,最終合成的文件可以在最開始的時候加載一次,便存起來到緩存中供後續使用。
1.在webpack4以前的用法:
new webpack.optimize.CommonsChunkPlugin({ name: 'common', chunks: ['page1','page2'], minChunks: 3 })
更多的參數配置,能夠參考這裏
2.在webpack4以後的用法:
module.exports = { //... optimization: { splitChunks: { chunks: 'async', minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } } };
你的點贊是我持續分享好東西的動力,歡迎點贊!
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
https://github.com/qq44924588...
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。