本篇主要是讲一些全家桶的优化与完善,基础功能上一篇已经讲得差很少了。直接开始: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。