通过上一篇《小白学react之由FOUC引发的一次webpack变革》我们学习了webpack的一些高级用法,比如如何生成哈希文件,如何将开发和生产配置分离,如何解决FOUC问题,以及如何运用html格式的模版来自动生成我们的index.html。
但是在尝试将我们的应用部署到我的一个阿里云服务器上的时候却出现了问题。整个问题主要就是因为前端路由和访问路径不一致导致的,往下我就描述下应该如何解决。
背景是这样的,我有个服务器已经在跑一个网站(假如就是我的www.techgogogo.com),默认监听的就是8080和80端口,网站根部路比如就在/www下面。
为了不破坏我原来的网站的访问,我将构建好的文件夹“build“直接丢到/www下面,所以现在的文件结构就是/www/build,index.html文件的路径就是/www/build/index.html。
然后我在浏览器访问:http://techgogogo.com/build,发现访问不了,报错:
Warning: [react-router] Location “/build/” did not match any routes
我们查看下我们在上几篇文章中建立的路由文件src/RootRouters.jsx:
/**
* Created by apple on 9/27/16.
*/
import React, {PropTypes, Component} from 'react';
//import {Router, Route, IndexRoute, withRouter} from 'react-router';
var Locations = require('./components/Locations.jsx');
import { Router, Route,IndexRedirect,IndexRoute, browserHistory } from 'react-router'
import Home from './components/Home.jsx'
import About from './components/About.jsx'
class RootRouters extends Component {
render() {
const { history } = this.props;
return (
"/" component={Home} >
"/about" component={About} />
"/locations" component={Locations} />
);
}
}
export default RootRouters;
可以看到我们的路由是基于”/”,那么结合刚才的报错信息,问题原因就很明显了。我们用”/build“来访问,路由当然就找不到了,因为”/”下面我们只有”/about”和”/locations”。
既然我们路由没有基于”/build”来做,那么我首先想到的就是将路由文件的”/”改成”/build”,这样构建出来的肯定就没有问题了。
但这样改的话本机通过webpack-de-server调试的时候就显得很不灵活,每次都需要在浏览器上多输入个build才能访问。
那么有没有不需要改动路由的方法呢?
这时我们就需要用到html的标签了。我们可以在index.html中通过设置个基地址,将其指定成”/build/”,那么我们的路由中的”/”其实在访问时就会自动加上”/build/”的基地址来进行路由了。
<head>
<meta charset="utf-8">
<title>小白学Reacttitle>
<base href="/build/" />
<link href="app.69113dacee12b4eec16c.css" rel="stylesheet">
head>
既然这个方式可行,那么我们将这个配置放到index.html的生成模版template.html里面就好了。
但是这里的可扩展性还是很弱,不够灵活:
- 如果我们有分生产服务器和staging服务器,两个服务器上的部署路径是不一样的,比如一个是build文件夹下面,一个是static文件夹下面。那么我我们这种方案就处理不了
- 每次改动我们都需要改到我们的源码,这也是我觉得不应该的地方。这种东西应该抽取出来作为可配置化的参数传入,改的应该是配置文件,而不是我们的应用的源码。
根据这两点需求,我找到了EJS。EJS说白了就是一门可以让我们可以通过平白的javascript来动态生成我们需要的html文件的模版语言。详情可以查看github上的项目介绍。
我们可以看到它常用的标签其实就只有两个:
我们往下的EJS解决方案中其实也只用到了这两个标签而已。
我们前几篇用的html-webpack-plugin的模版是HTML格式的template.html,但是可定制性不强。其实html-webpack-plugin默认的模版文件格式本身就是EJS格式。
通过html-webpack-plugin这个webpack插件,我们可以自动将webpack生成的css,js,favicon等文件注入到指定的EJS模版文件里面,然后再通过EJS里面的代码逻辑来按需生成对应的index.html文件。
我们在EJS模版文件中主要是通过以下这两种方式来使用webpack传进来的文件和配置项。
- htmlWebpackPlugin.files: 通过这个方式我们可以在EJS中获取到webpack传过来的js和css等文件,可用的选项示例如下:
"htmlWebpackPlugin": {
"files": {
"css": [ "main.css" ],
"js": [ "assets/head_bundle.js", "assets/main_bundle.js"],
"chunks": {
"head": {
"entry": "assets/head_bundle.js",
"css": [ "main.css" ]
},
"main": {
"entry": "assets/main_bundle.js",
"css": []
},
}
}
}
根据以上的知识点,我们现在就可以在webpack.config.js进行编码以加入EJS模版文件的支持了。
我们这里先假定我们的EJS模版文件叫做index.ejs。首先,我们先定义好我们需要传到EJS模版的选项参数列表:
const htmlPluginDefine = {
template: 'index.ejs',
title: '小白学React',
inject: false,
baseHref: '/'
appMountId: 'ReactApp',
};
ReactDOM.render(
<RootRouters history={browserHistory} />,
document.getElementById('ReactApp')
);
往下我们就可以通过htmp-webpack-plugin来操作如何将我们的设置项传入到EJS模版文件了:
if(TARGET === 'build' || TARGET === 'prod') {
…
plugins: [
…
new HtmlWebpackPlugin(Object.assign(htmlPluginDefine, {
baseHref:'/build/',
filename: 'index.html',
})),
...
]
});
}
我们这里会改写上面htmlPluginDefine定义的baseHref,让我们的react router能正确处理访问:http://localhost:8080/build/ 的情况;然后制定我们最终的输出html文件名为index.html。
那往下就到我们的EJS模版文件的编码了。
有了我们上面的知识铺垫,相信大家理解下面这段代码并不难:
<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title || 'Webpack App'%>title>
<% if (htmlWebpackPlugin.options.baseHref) { %>
<base href="<%= htmlWebpackPlugin.options.baseHref %>" />
<% } %>
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
<% } %>
head>
<body>
<% if (htmlWebpackPlugin.options.appMountId) { %>
<div id="<%= htmlWebpackPlugin.options.appMountId%>">div>
<% } %>
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>">script>
<% } %>
body>
html>
可以看到我们这里操作的主要就是前面提到的htmlWebpackPlugin.options和htmlWebpackPlugin.files这两个对象。
我们对着上面的代码,参考最终生成的index.html文件,基本上就一目了然了:
<head>
<meta charset="utf-8">
<title>小白学Reacttitle>
<base href="/build/" />
<link href="app.69113dacee12b4eec16c.css" rel="stylesheet">
head>
<body>
<div id="ReactApp">div>
<script src="app.69113dacee12b4eec16c.js">script>
body>
html>
为了模拟我们在阿里云服务器上的访问路径,我们需要修改一下我们上几编文章中编写的Express调试服务器,将服务器的http根目录改成我们项目顶层目录。这样我们就可以通过http://localhost:8080/build的方式进行访问了。
将server.js的以下这一行:
app.use(express.static(path.join(__dirname, 'build')))
修改成:
app.use(express.static(path.join(__dirname, './')))
然后启动server.js:
node server.js
最后就可以通过浏览器访问http://localhost:8080/build了 。 同时我们按照文章开始的时候描述的方式部署到服务器也可以正常进行访问了。
除了解决了上面的部署问题之外,我发现我们最后打出来的bundle有点过大。所以,期间顺便通过在webpack的生产配置下运用插件将文件进行了压缩:
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
git clone https://github.com/kzlathander/alt-tutorial-webpack.git
cd alt-tutorial-webpack
npm install
npm start
同时
本文由天地会珠海分舵编写,转载需授权,喜欢点个赞,吐槽请评论,进一步交流请关注公众号techgogogo或者直接联系本人微信zhubaitian1
《未完待续》