本篇主要是讲一些全家桶的优化与完善,基础功能上一篇已经讲得差不多了。直接开始:
当javaScript抛出异常时,我们会很想知道它发生在哪个文件的哪一行。但是webpack 总是将文件输出为一个或多个bundle,我们对错误的追踪很不方便。Source maps试图解决这一个问题,我们只需要改变一下配置项即可。
在webpack.dev.config.js中加入:
devtool:"inline-source-map"
先进行一个测试,打开src/Pages/UserInfo/UserInfo.js
import imgSrc from '../../../public/image/react15.png'
...
<h2>个人资料</h2>
<img src={imgSrc}/>
运行后,页面报错
出现这个错误是因为打包后的文件找不到我们之前写好的相对路径。对此,我们可以用如下方式解决。
首先我们要安装两个依赖:
我们打包后,页面统一生成bundle.js,当我们进入Home页面时,因为加载的文件过多会导致页面慢。我们想要达到跳转到对应页面时按需加载文件的效果,就需要用到bundle-loader。
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 效果如图
按需加载文件的进阶优化则是文件缓存。缓存我们要解决以下两个问题:
由于我们在dist/index.html中引用的还是bundle.js,所以我们要改成每次编译后自动插入到index.html中,可以用到HtmlWebpackPlugin。
可以发现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,更高效的加载时间等。
虽然文件名不同了,但是改变代码重新打包会发现app.hash.js和vendor.chunkhash.js一样都更新了名字,这不就和没拆分是一样的吗?
别着急,看官网介绍
注意mainfest与vendor的顺序不能错哦
js 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来压缩打包后生成的文件。
const UglifyJSPlugin=require('uglifyjs-webpack-plugin')
module.exports={
plugins:[
...
new UglifyJSPlugin()
]
}
运行npm run build有没有发现打包的文件小了好多
每次打包dist都会多好多文件混合在里面,我们应该清掉之前打包的文件,只留下当前打包后的文件。我们用到clean-webpack-plugin
const CleanWebpackPlugin=require('clean-webpack-plugin');
...
plugins:[
new CleanWebpackPlugin(['dist'])
]
现在试试打包一下,每次是不是都是直接覆盖整个文件。虽然api文件也被清掉了,但是没关系,那只是用来测试的。
当我们打包后,静态文件没办法定位到静态服务器,我们需要在webpack.config.js中配置
output:{
...
publicPath:'/'
}
如果我要要将打包到js的css内容抽出来单独成css文件,我们可以使用extract-text-webpack-plugin.
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文件啦
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的中间件来处理的。为了弄清楚,我们自己来写一个。
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。