前言
最近和朋友聊天,发现我朋友调试前端页面的时候,都是上传svn或者git到测试服务器上调试,这样一来效率非常差,并且在多人的时候会频繁更新测试环境,然后我问我朋友,为什么不本地开发?然后我朋友说因为后端是java,所以本地要搭建java环境那些,很麻烦,也不会。
其实前端搭建本地开发环境非常简单,而且半小时就搞掂,下面来给大家分享一下一套半小时就能搭建的本地环境的方式,以及思路。
一些本地开发的问题
作为一名前端,在开发中经常需要将写好的代码在浏览器中展现才能知道我们编写的组件、样式等是否如我们所期望的样子展示。所以我们需要经常去刷新页面进行查看。
在现在2019,React和Vue使用率持续上升,但是React和Vue都有一个很重要的点,就是需要依赖数据进行渲染。当然我们可以mock数据,来模拟请求获取的数据。但是随着页面的复杂度,就不能简单的去mock数据那么简单了。
举个例子:
- 如果当前的html需要依赖一些后端的模板变量进行服务器渲染再到浏览器获取,那么本地自己mock数据就不行了,因为后端可能是java,php等等其他语言,作为一名前端不一定电脑都配有这些语言的运行环境。
- 如果当前页面是一个运行很久的页面,当中的数据因为时间的问题,mock的数据和真正页面所需要的数据有一定的差异。那么mock数据和测试环境接口的数据维护起来的成本就很大了。
初步了解一个测试环境的流程
一个测试环境可以大致分为上图中的几步
- 请求url
- 接受服务器的html(可能经过后端的模板引擎渲染)
- 渲染html过程中请求静态资源
- 静态资源在测试环境下一般都不会上传CDN(土豪随意)
- 执行静态文件(css和js)
- js文件需要ajax请求测试数据进行渲染
- 发起ajax请求,获取测试数据
- 渲染组件
那么我们分解出来的几个步骤后就可以开始我们的本地服务的搭建了。
搭建一(技术选型)
一般我们构建页面都是需要使用webpack的,那么我们就可以利用webpack提供给我们的devServer这个参数进行配置了,如果不懂可以直接看webpack的文档就可以了,非常简单。
代码语言:javascript复制// 使用ip这个node的第三方库
const ip = require( 'ip' );
devServer = {
host: ip.address().toString(),
allowedHosts: [
'localhost',
ip.address(),
'0.0.0.0',
'127.0.0.1',
],
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
},
disableHostCheck: true,
port: 9527,
setup: router // 新版可以用before
};
关键是setup/before这部分了,这里将是如何获取模板和接口的关键了。
搭建二(利用服务器为我们本地开发提供模板引擎的调用)
之前有说,如果我们的后端是php、java、python等等的后端语言,我们往往不一定在电脑中具备那么的语言运行环境。就算你都会搭建,你也要浪费很多时间,当然可以使用docker来一键部署,另外数据库的权限不一定提供给你,哪怕的测试环境的。就算提供给你,你确定你的电脑在跑着webpack的情况下,还能有很充足的内存支持你开启一些后端服务?
既然这样何苦难为你的电脑呢,我们采取一个更加简单的方式。首先我们需要和后端做一些约定,来实现我在本地能使用测试环境的服务器来实现模板引擎的渲染。
- 举例我们现在要将一个http://m.baidu.com/index.php的这个路径下的页面,通过本地开发的方式让测试服务器为我们提供模板引擎的使用功能。
- 在浏览器下,实际上浏览器是使用GET的方式去请求http://m.baidu.com/index.php这个接口的,那么现在我们来一些约定。
- 如果http://m.baidu.com/index.php通过POST的方式请求,那么将进入前端本地开发模式
- 后端获取模板并不通过服务器的文件I/O来读取文件内容传入模板引擎,而是通过一个POST的body上的一个指定字段,例如这个字段为_dev_templete_string='你的模板字符串',并且再验证是否带有_is_dev=true字段。
- 当判断_is_dev=true并且_dev_templete_string不为空,那么将_dev_templete_string字段中的字符串放入模板引擎的函数中,并且渲染完成后,从接口中返回。
- 当完成第二步的时候,基本你的模板就可以通过测试服务器来为你工作了。接着你只需要在webpack的setup/before字段中配置好路由即可。
路由配置示例:
代码语言:javascript复制const bodyParser = require( 'body-parser' );
function router( app, Server ) {
app.use( bodyParser.urlencoded( { extended: false } ) );
// 首页模板
app.get( //index.php/, function ( request, response ) {
const fileContent = readFile( 'home' );
// 获取服务器渲染后的html文件字符串
getHomeHtml( request, response, fileContent, userLog.cookie, ( html ) => {
response.send( html );
} );
} );
}
如何获取本地模板?
- 你的模板文件在webpack的编译当中(html-webpack-plugin插件),通过webpack的路由获取文件
- 你的文件不在webpack的编译当中,直接用Node读取html文件
获取webpack内存中的模板
代码语言:javascript复制async function readHtml(name, Server, pageType) {
console.log(`http://localhost:9527/html/${name}.html`)
const res = await axios({
url: `http://localhost:9527/html/${name}.html`,
method: 'get',
responseType: 'text'
});
return res.data;
}
每个人的配置或许会有点差异,实际情况根据自己配置的路径来获取
搭建三(静态资源)
静态资源其实交给webpack处理就可以了,通过配置publicPath来确认输出出来的路径,又webpack启动的服务来获取就可以,不懂的可以去看webpack的文档。
搭建四(接口)
因为我们启动webpack的构建,在不借用任何修改host或者自己启动dns服务等操作的情况下,一般我们通过前3步搭建出来的页面,在加载完js之后,请求接口都会出现跨域的问题。因为如果我们不配置host,那么我们一般是用ip加上端口来访问我们webpack启动的服务的,这个时候就形成了一个跨域问题,如果你修改host,那么也会有端口跨域问题,所以我们就可以利用webpack提供给我们的devServer功能,做一个简单的反向代理的功能。
利用setup/before中配置路由的方式配置一个你的ajax路径的反向代理规则
代码语言:javascript复制app.post(//home/getData.php/, function (request, response) {
let reqData = request.body;
ajaxRequest(reqData, request, response, (result) => {
response.json(result);
});
});
ajaxRequest
代码语言:javascript复制const querystring = require( 'querystring' );
function getOptions( postData, request, hostname ) {
return {
hostname: '你的测试域名',
port: 80,
path: request.originalUrl,
method: request.method,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength( postData ),
'Cookie': ''
}
};
}
function ajaxRequest( data, request, response, callback, hostname ) {
const postData = querystring.stringify( data );
let req = http.request( getOptions( postData, request, hostname ), ( res ) => {
let data = '';
res.setEncoding( 'utf8' );
res.on( 'data', ( chunk ) => {
data = chunk;
} );
res.on( 'end', () => {
let parsedBody = data;
if ( res.headers['content-type'] ) {
let contentType = res.headers['content-type']
if ( ~contentType.search(/^|/json/i) ) {
try {
parsedBody = JSON.parse(data)
} catch (err) {
parsedBody = data
}
}
}
callback instanceof Function && callback(parsedBody);
} )
} );
req.on( 'error', ( e ) => {
if (/((.*))$/.test(data)) {
data = data.match(/((.*))$/)[1];
}
console.error( '出错了,' e.message );
} );
req.write( postData );
req.end();
}
简单来说就是匹配你页面中请求的路径,因为host和端口不一样导致跨域,所以用node这层来做一个反向代理,设定好host和端口,来帮你从node这一层去模拟浏览器发出的请求,欺骗服务以为你是浏览器发送过来的(其实就像爬虫一样)。从而实现跨域的请求方式,来解决我本地开发中的ajax跨域问题。
到这里其实基本的本地服务环境就已经搭建起来了,当然其实还可以扩展出欺骗浏览器登录的方式,既然我们都可以实现了反向代理了,那么我们就可以这么做来完成登录模式下的本地开发了。
- 和后端协商好一个借口给你来登录,或者你找到你们系统中的漏洞可以通过node来请求登录接口。
- 一般后端判断登录的条件都是以在cookie中写入一些值,来判断用户是否登录,一般是tokenID,那么我们就可以利用node帮我们来请求这个接口,并且在接口返回的时候获取接口的响应头中的set-cookies字段以字符串的形式存入一个txt文件当中。
- 之前我们所做的模板和接口的反向代理那里,我们可以做一些修改,我们判断一下本地是否存在这个cookie的文件,如果存在那么就将cookie中的内容读取出来,并且写入请求头的cookie上,带到后端去,那么就可以欺骗服务器以为我们已经登录了。
login
代码语言:javascript复制function createCookieStr( cookies ) {
let str = '';
for ( let i = 0; i < cookies.length; i ) {
str = `${cookies[i].split( ' ' )[0]} `;
}
return str;
}
function login( user, pwd ) {
return new Promise( ( resolve, reject ) => {
let req = http.request( {
hostname: 'xxx.xxx.com',
port: 80,
path: `http://xxx.xxx.com/login.php?user=${user}&pwd=${pwd}`,
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}, ( res ) => {
res.on( 'end', () => {
let cookies = res.headers['set-cookie'];
cookie = createCookieStr( cookies );
if ( cookie != '' ) {
console.log( '登录成功' );
} else {
console.log( '登录失败' );
}
resolve( cookie );
} )
} );
req.on( 'error', ( e ) => {
console.error( '出错了,' e.message );
} );
req.write( '' );
req.end();
} );
}
//创建登录记录log
function creatUserLog(data) {
let dirPath = path.join( root, '/.user_log' );
if ( !fs.existsSync( dirPath ) ) {
fs.mkdirSync( dirPath );
}
fs.writeFileSync(
path.join( root, '/.user_log/userLog.json' ),
JSON.stringify( data, null, 4 )
);
}
login( user, pwd )
.then( cookie => {
creatUserLog( {
cookie: cookie,
id: user
});
})
在请求中我们可以在代码上这样添加
代码语言:javascript复制let userJson = {};
// 通过判断环境变量或者判断文件是否存在
if ( NODE_LOGIN == 'true' ) {
userJson =require( '../.user_log/userLog.json' );
}
function getOptions( postData, request, hostname ) {
const cookie = userJson.cookie || request.headers.cookie;
return {
hostname: '你的测试域名',
port: 80,
path: request.originalUrl,
method: request.method,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength( postData ),
'Cookie': ''
}
};
}
这样就可以实现欺骗服务的你已经登录了。
总结
- 本地开发的方式有很多种,有的公司可能已经提供了一套完善的机制,丰富的docker环境或者服务器开发等一系列牛逼的方式,例如BAT。但是很多中小型公司可能并不具备这样的条件,所以我们可以善用webpack提供给我们的功能来实现本地开发。
- 以上的方式也只是我在公司项目中的做法,可能需要你根据自己公司的情况做一些调整,但是关键是思路,并不是具体的实现,所以明白中间的思想你就可以半小时就搭建一套公司的前端本地开发方案了。
- 相比自己搭建环境,使用webpack去做反向代理的方式其实会更加简单,快捷并且对于你用来开发的电脑的负担也会相对来说小一点,能有更多内存来提供更流畅的开发体验。
- 一般这一套方案都是跟着git或者svn的仓库一起提交的,理论上每个同事基本都是可以复用的,无需重复搭建环境,直接跑起来开发,更加方便。
- 如果像我自己再公司的项目那样,我们就是利用这样的方案,让整个电商的项目可以在webpack的devServer中完成的从首页到下单的本地开发,非常方便的开发那种需要跨几个页面维度的需求。(通过webpack中配置路由规则)
以上就是我本篇文章的分享,如果你也有很方便的本地开发方案,欢迎留言或者链接贴一下给我互相学习一下。