本篇主要是講一些全家桶的優化與完善,基礎功能上一篇已經講得差很少了。直接開始:css
當javaScript拋出異常時,咱們會很想知道它發生在哪一個文件的哪一行。可是webpack 老是將文件輸出爲一個或多個bundle,咱們對錯誤的追蹤很不方便。Source maps試圖解決這一個問題,咱們只須要改變一下配置項便可。
在webpack.dev.config.js中加入:html
devtool:"inline-source-map"
npm install --save-dev less-loader less css-loader style-loader
module:{ rules:[ { test:/\.js$/, use:['babel-loader?cacheDirectory=true'], include:path.join(__dirname,'src') },{ test:/\.less$/, use:[ 'style-loader', {loader:'css-loader',options:{importLoaders:1}}, 'less-loader' ] } ] },
測試下java
cd src/pages/Home touch Home.less
打開 Home.lessreact
.wrap{ width:300px; height:300px; background:red; & .content{ width:200px; height:200px; margin:auto; background:yellow; } }
在Home.js中引入,並添加classwebpack
import './Home.less' ... render(){ return( <div> <h1>當前共點擊次數爲:{this.state.count}</h1> <button onClick={()=> this._test()}>點擊我!</button> <div className="wrap"> <div className="content"></div> </div> </div> ) }
由於添加了新的依賴,咱們從新跑一次npm run start,效果如圖
ios
先進行一個測試,打開src/Pages/UserInfo/UserInfo.jsgit
import imgSrc from '../../../public/image/react15.png' ... <h2>我的資料</h2> <img src={imgSrc}/>
運行後,頁面報錯
github
出現這個錯誤是由於打包後的文件找不到咱們以前寫好的相對路徑。對此,咱們能夠用以下方式解決。
首先咱們要安裝兩個依賴:web
npm install --save-dev url-loader file-loader
在webpack.dev.config.js增長配置shell
module:{ rules:[ ... { test:/\.(png|jpg|gif)$/, use:[{ loader:'url-loader', options:{ // 設置爲小於8K的大小 limit:8192 } }] } ] }
配置成功後,咱們從新運行npm run start(由於新加了依賴要從新跑一次服務),看下效果(PS:盜用大冪冪的照片^_^)
咱們打包後,頁面統一輩子成bundle.js,當咱們進入Home頁面時,由於加載的文件過多會致使頁面慢。咱們想要達到跳轉到對應頁面時按需加載文件的效果,就須要用到bundle-loader。
npm install bundle-loader --save
cd src/router touch Bundle.js
打開Bundle.js,根據示例
import React,{Component} from 'react' class Bundle extends Component{ state={ mod:null }; componentWillMount(){ this.load(this.props) } componentWillReceiveProps(nextProps){ if(nextProps.load !== this.props.load){ this.load(nextProps) } } load(props){ this.setState({ mod:null }); props.load((mod)=>{ this.setState({ mod:mod.default ? mod.default : mod }) }) } render(){ return this.props.children(this.state.mod) } } export default Bundle;
import React from 'react'; import {BrowserRouter as Router,Route,Switch,Link} from 'react-router-dom'; import Home from 'bundle-loader?lazy&name=home!pages/Home/Home'; import About from 'bundle-loader?lazy&name=page1!pages/About/About'; import Counter from 'bundle-loader?lazy&name=counter!pages/Counter/Counter'; import UserInfo from 'bundle-loader?lazy&name=userInfo!pages/UserInfo/UserInfo'; const Loading = function(){ return <div>Loading...</div> }; const createComponent = (component) => (props) => ( <Bundle load={component}> { (Componet) => Component ? <Component {...props} /> : <Loading/> } </Bundle> ); const getRouter=()=>( <Router> <div> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> <li><Link to="counter">Counter</Link></li> <li><Link to="userinfo">UserInfo</Link></li> </ul> <Switch> <Route exact path="/" component={createComponent(Home)}/> <Route path="/about" component={createComponent(About)}/> <Route path="/counter" component={createComponent(Counter)}/> <Route path="/userinfo" component={createComponent(UserInfo)}/> </Switch> </div> </Router> ); export default getRouter;
output:{ path:path.join(__dirname,'./dist'), filename:'bundle.js', chunkFilename:'[name].js' }
運行npm run start 效果如圖
按需加載文件的進階優化則是文件緩存。緩存咱們要解決如下兩個問題:
output:{ path:path.join(__dirname,'./dist'), filename:'[name].[hash].js', chunkFilename:'[name].[chunkhash].js' }
咱們能夠看到編譯後的文件名已經變了
因爲咱們在dist/index.html中引用的仍是bundle.js,因此咱們要改爲每次編譯後自動插入到index.html中,能夠用到HtmlWebpackPlugin。
npm install html-webpack-plugin --save-dev
cd src touch index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="app"></div> </body> </html>
var HtmlWebpackPlugin=require('html-webpack-plugin'); ... plugins:[new HtmlWebpackPlugin({ filename:'index.html', template:path.join(__dirname,'src/index.html') })],
此時刪掉以前的dist/index.html,運行npm run start訪問正常。
咱們打包生成的文件js文件中,都包含了react,redux,react-router這樣的代碼。然而這些依賴代碼咱們在不少文件都引用了,而不須要它自動更新。因此咱們能夠把這些公共代碼提取出來。
咱們根據教程配置。
var webpack=require('webpack'); module.exports={ entry:{ app:[ 'react-hot-loader/patch', path.join(__dirname,'src/index.js') ], vendor:['react','react-router-dom','redux','react-dom','react-redux'] }, plugins:[ ... new webpack.optimize.CommonsChunkPlugin({ name:'vendor' }) ] }
從新運行,打包文件以下
能夠發現app.[hash].js和vendor.[hash].js生成的hash是同樣的。也就意味着若是代碼有改動app.[hash].js與vendor.[hash].js都會同時改變。而後vendor裏的內容咱們不但願它更新。根據文檔,我要在webpack裏還要配置
應用到咱們項目應該
output:{ path:path.join(__dirname,'./dist'), filename:'[name].[chunkhash].js', chunkFilename:'[name].[chunkhash].js' }
再次運行,發現報錯,webpack-dev-server --hot 不兼容chunkhash
解決這個問題,咱們要先區分生產環境與開發環境的區別。因此,上面的問題先留一下,咱們先來構建生產環境的配置。
生產環境與開發環境的區別每每體如今目標差別大。開發環境咱們要配置的東西不少,要求實時加裁,熱更新模塊等。但生產環境要求較小,更關注小的bundle,更輕量的Source map,更高效的加載時間等。
touch webpack.config.js
var path=require('path'); var HtmlWebpackPlugin=require('html-webpack-plugin'); var webpack=require('webpack'); module.exports={ // 入口文件指向src/index.js entry:{ app:[ 'react-hot-loader/patch', path.join(__dirname,'src/index.js') ], vendor:['react','react-router-dom','redux','react-dom','react-redux'] }, //打包後的文件到當前目錄下的dist文件夾,名爲bundle.js output:{ path:path.join(__dirname,'./dist'), filename:'[name].[chunkhash].js', chunkFilename:'[name].[chunkhash].js' }, module:{ rules:[ { test:/\.js$/, use:['babel-loader?cacheDirectory=true'], include:path.join(__dirname,'src') },{ test:/\.less$/, use:[ 'style-loader', {loader:'css-loader',options:{importLoaders:1}}, { loader: 'less-loader', options: { strictMath: true, noIeCompat: true } } ] }, { test:/\.(png|jpg|gif)$/, use:[{ loader:'url-loader', options:{ limit:8192 } }] } ] }, plugins:[ new HtmlWebpackPlugin({ filename:'index.html', template:path.join(__dirname,'src/index.html') }), new webpack.optimize.CommonsChunkPlugin({ name:'vendor' }) ], devtool:"cheap-module-source-map", resolve:{ alias:{ pages:path.join(__dirname,'src/pages'), components:path.join(__dirname,'src/components'), router:path.join(__dirname,'src/router'), actions:path.join(__dirname,'src/redux/actions'), reducers:path.join(__dirname,'src/redux/reducers'), // redux:path.join(__dirname,'src/redux') 與模塊重名 } } };
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config webpack.config.js", "start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot" },
運行一次打包命令 npm run build,文件名支持了chunkhash.
雖然文件名不一樣了,可是改變代碼從新打包會發現app.[hash].js和vendor.[chunkhash].js同樣都更新了名字,這不就和沒拆分是同樣的嗎?
彆着急,看官網介紹
注意mainfest與vendor的順序不能錯哦
plugins:[ new HtmlWebpackPlugin({ filename:'index.html', template:path.join(__dirname,'src/index.html') }), new webpack.HashedModuleIdsPlugin(), new webpack.optimize.CommonsChunkPlugin({ name:'vendor' }), new webpack.optimize.CommonsChunkPlugin({ name:'mainfest' }) ]
當咱們構建了基礎的生產環境配置後,咱們能夠增長指定環境配置,根據process.env.NODE_ENV環境變量關聯,讓library中應該引用哪些內容。例如,當不處於生產環境中時,library可能會添加額外的日誌log和test。當使用 process.env.NODE_ENV === 'production' 時,一些 library 可能針對具體用戶的環境進行代碼優化,從而刪除或添加一些重要代碼。
module.exports={ plugins:[ ... new webpack.DefinePlugin({ 'process.env':{ 'NODE_ENV':JSON.stringify('production') } }) ] }
webpack使用UglifyJSPlugin來壓縮打包後生成的文件。
npm install uglifyjs-webpack-plugin --save-dev
const UglifyJSPlugin=require('uglifyjs-webpack-plugin') module.exports={ plugins:[ ... new UglifyJSPlugin() ] }
運行npm run build有沒有發現打包的文件小了好多
每次打包dist都會多好多文件混合在裏面,咱們應該清掉以前打包的文件,只留下當前打包後的文件。咱們用到clean-webpack-plugin
npm install clean-webpack-plugin --save-dev
const CleanWebpackPlugin=require('clean-webpack-plugin'); ... plugins:[ new CleanWebpackPlugin(['dist']) ]
如今試試打包一下,每次是否是都是直接覆蓋整個文件。雖然api文件也被清掉了,可是不要緊,那只是用來測試的。
當咱們打包後,靜態文件沒辦法定位到靜態服務器,咱們須要在webpack.config.js中配置
output:{ ... publicPath:'/' }
若是我要要將打包到js的css內容抽出來單獨成css文件,咱們可使用extract-text-webpack-plugin.
npm install extract-text-webpack-plugin --save-dev
const ExtractTextPlugin=require("extract-text-webpack-plugin"); module.exports={ module:{ rules:[ ... { test:/\.(css|less)$/, use:ExtractTextPlugin.extract({ fallback:"style-loader", use:"css-loader" }) } ] }, plugins:[ ... new ExtractTextPlugin({ filename:'[name].[contenthash:5].css', allChunks:true }) ] }
咱們能夠增長一些css文件引用,來測試下。因爲咱們以前的示例是用less來寫的樣式,那麼咱們加上less的配置,使之生成獨立文件。
修改剛剛的配置項:
module.exports={ module:{ rules:[ ... { test:/\.(css|less)$/, use:ExtractTextPlugin.extract({ fallback:"style-loader", use:["css-loader","less-loader"] }) } ] }, }
從新打包,就能看到被生成的css文件啦
npm install --save axios
export const GET_USERINFO_REQUEST="userInfo/GET_USERINFO_REQUEST"; export const GET_USERINFO_SUCCESS="userInfo/GET_USERINFO_SUCCESS"; export const GET_USERINFO_FAIL="userInfo/GET_USERINFO_FAIL"; export function getUserInfo(){ return{ types:[GET_USERINFO_REQUEST,GET_USERINFO_SUCCESS,GET_USERINFO_FAIL], promise:client => client.get('/api/userInfo.json') } }
其中dispath(getUserInfo())後,是經過redux的中間件來處理的。爲了弄清楚,咱們本身來寫一個。
cd src/redux mkdir middleware && cd middleware touch promiseMiddleware.js
import axios from 'axios'; export default store => next =>action =>{ const {dispatch,getState}=store; // 若是dispatch傳來的是一個function,則跳過 if(typeof action === 'function'){ action(dispatch,getState); return ; } // 解析action const { promise, types, afterSuccess, ...rest }=action; // 若是不是異步請求則直接跳轉下一步 if(!action.promise){ return next(action); } // 解析types const [REQUEST,SUCCESS,FAILURE]=types; // 發送action next({ ...rest, type:REQUEST }); // 成功 const onFulfilled = result=>{ next({ ...rest, result, type:SUCCESS }); if(afterSuccess){ afterSuccess(dispatch,getState,result); } }; // 失敗 const onRejected=error=>{ next({ ...rest, error, type:FAILURE }); }; return promise(axios).then(onFulfilled,onRejected).catch(error=>{ console.error('MIDDLEWARE ERROR:',error); onRejected(error) }) }
import {createStore,applyMiddleware} from 'redux'; import combineReducers from './reducers.js'; // import thunkMiddleware from 'redux-thunk'; // let store = createStore(combineReducers,applyMiddleware(thunkMiddleware)); import promiseMiddleware from './middleware/promiseMiddleware'; let store = createStore(combineReducers,applyMiddleware(promiseMiddleware)); export default store;
export default function reducer(state=initState,action){ switch(action.type){ ... case GET_USERINFO_SUCCESS: return{ ...state, isLoading:false, userInfo:action.result.data, errMsg:'' } } }
咱們重啓npm run start ,訪問userInfo接口是否是成功啦!
好啦,先寫到這吧,若是還有細節完善會在源碼上更新。源碼地址,歡迎star和issues。