webpack解决跨域的几种方法

作为前端攻城狮,经常要面临的问题就是跨域,不论是调自己mock服务的数据,还是真实前后台联调。因为接口服务与前端工程通常都是独立的工程,而前端有很多协议需要遵循同源策略(后端则不需要)。解决跨域的方法有很多,可以在服务端配置,也可以在前端解决。作为前端开发者,我更偏向在前端把跨域解决掉,别问我为什么,我就是喜欢这种不依赖后端的感觉[傲娇脸]。

PS:我使用webpack构建工程,我的方法都是基于webpack的大前提下。如果你是用的其他构建工具,或者是没有使用构建工具的工程环境,我的方法可能不能适用。

不能操控服务端代码的情况

当你不能操控服务端代码时(比如前后端联调),你可以通过webpack配置代理。

1. 通过 http-proxy 代理

在webpack配置文件 webpack.config.js 中添加 devServer 配置。然后配置它的 proxy 属性,webpack-dev-server 在起服务后,会把匹配的本地请求转到 proxy 里配置的服务器上去请求,通过服务端的转发,实现跨域。

  • 有点抽象,举个栗子:

    我的前端工程服务端口8000。
    我要联调的服务端口3000,域名‘champyin.com’。

    我在前端这么发请求:xhr.open(‘GET, ‘/api/students’, true);
    那么我其实发的请求完整url为 http://localhost:8000/api/students,
    直接发肯定是报错的,因为我的本地是没有这个接口的。

    现在我在webpack这么配置代理,给/api配置一个映射

    1
    2
    3
    4
    5
    devServer: {
    proxy: {
    '/api': 'http://champyin.com:3000'
    }
    }

    npm run dev 重启下前端工程,webpack-dev-server 在遇到’/api/students’这种以/api开头的请求的时候,它不再往本地发了,而是向对应的http://champyin.com:3000发请求。这是一种后端的请求转发,而后端没有跨域问题。这个时候,虽然在浏览器查看网络请求的时候,会看到前端发的请求地址是http://localhost:8000/api/students,但其实它的背后真正获取响应的请求是http://champyin.com:3000/api/students

  • 灵活的proxy配置
    很多时候,后端的接口不一定都有一个统一的前缀,这个时候,如果还按照上面的方法,那就要对每个不同的接口名配置一个映射,而且后台一旦增加接口,这里也要跟着加,每个映射的值还都是一样的,又麻烦又冗余。其实,webpack已经考虑了这一点,每一个映射规则的value可以是一个对象,并提供 pathRewrite 参数来重置请求的上下文。

    复用一下上面的例子,只不过服务端稍有不同,服务端提供的接口没有统一的/api前缀,而是直接的接口名:/students/classes/grades

    这个时候,proxy 可以这么配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    devServer: {
    proxy: {
    '/api': {
    target: 'http://champyin.com:3000',
    pathRewrite: {
    '/api': ''
    }
    }
    }
    }

    这样我们的前端请求依然保留/api前缀不用变,而在转发之前,这个前缀会自动被重置去掉。

    现在我在前端依然这么发请求:xhr.open(‘GET, ‘/api/students’, true);
    即我发的请求完整url为 http://localhost:8000/api/students,
    而经过代理后的最终url为 http://champyin.com:3000/students

    这样,就不用因为后台接口不规范,而影响我们的前端代码的质量了。

    当然,这个只是在联调模式可以这么做,真正生产环境的时候,还是要后台统一规范,或者部署一个中间层做这种代理转发。


可以操控服务端代码的情况

当你可以控制服务端代码时(比如自己mock数据),你还可以通过以下的方法避免跨域(变相地解决跨域)

2. 在dev-server内mock数据

webpack-dev-server内部其实是自己起了一个express来做服务。
webpack的devServer配置提供了一个before方法,在启动服务之前,这个方法会被执行,我们可以把我们mock数据的逻辑写在这里面。

1
2
3
4
5
6
7
devServer: {
before(app) {
app.get('/api/sdutents', (req, res) => {
res.json({name: 'champyin', score: 100})
})
}
}

这个before方法会传一个参数供我们使用,这个参数就是webpack-dev-server内部起的express对象。

重启前端工程时,我们的mock服务也就启动了。这时,我们的mock接口跟前端其实在同一服务也就是webpack-dev-server的express服务下,也就不存在跨域了。

2. 在服务端启动webpack

假设后端我们的express mock接口代码长这样:

1
2
3
4
5
6
7
8
const express = require('express');
let app = express();
app.get('/api/students', (req, res) => {
res.json({name: 'champyin', score: 100})
})
app.listen(3000, () => {
console.log('server is on 3000');
});

现在我们要把webpack构建放到后端来起。在后端起webpack需要用到叫做webpack-dev-middleware的中间件,整体逻辑大致是:
获取webpack模块 -> 获取webpack配置文件 -> 将配置文件传给webpack执行,获得compiler实例 -> 把compiler实例传给webpack-dev-middleware中间件,然后整个交给express作为express中间件执行 -> done!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bin/www.js file
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');

let webpackConf = require('../webpack.config.js');
let compiler = webpack(webpackConf);

let app = express();
app.use(webpackDevMiddleWare(compiler));

app.listen(3000, () => {
console.log('server is on 3000');
});

现在启动前端工程就不是npm run dev了,而是node bin/www.js

这时,我们的前端就跟后端复用了同一个服务,自然也就不存在跨域了。

其实这相当于在改后端了,不过这个后端是前端可以控制的,所以也算是前端的扩展了。另外,如果可以改后端的话,还可以直接在express里面通过设置请求头来实现跨域,不过这个就跟webpack没有关系了,就不在这里细讲了。

总结

第一种方法是我们提倡的跨域配置,配置代理。首选推荐。
第二和第三种方法其实是在你能控制后端工程的情况下,把前后端工程合并成一个工程了,区别是第二种方法相当于把后端接口移到前端工程来起,第三种方法是把前端工程构建移到后端工程来起。避免了跨域,也算是变相地解决跨域的方案吧,在某些轻量的工程里可以快速搭建调试环境。