webpack-dev-server自定义路由
前言
由于种种原因公司的项目没有使用webpack-dev-server,而是自己写了个koa服务来负责静态资源的分发,这样就导致了开发环境下没法使用HMR,每次修改后都需要手动刷新浏览器,这样肯定十分的不爽啊,于是我就开始了探索webpack之路。
从Multiple Configurations到Single Configuration
该项目是多入口(即多应用)项目,之前使用的Multiple Configurations,这也就是为什么不使用webpack-dev-server大致原因,因为Multiple Configurations无法使多个应用跑在一个devServer下。在经过一天多努力下终于从Multiple Configurations改成了Single Configuration,虽然回想起来好像很简单,主要是更改entry配置,但是还是有许多盘根错节的细节需要调试。但不管怎样,终于能够将多个应用跑在同一个devServer下了。
新的问题
但随之而来的是新的问题,由于改成了Single Configuration,并且由devServer提供开发服务,就导致了路由也随之变更,请看下面代码(实际上配置是由脚本动态生成的,并且也是在脚本中手动运行webpack的,但这里为了简化就直接上配置以助快速理解):
module.exports = {
entry: {
app1: 'src/apps/app1',
app2: 'src/apps/app2',
// ...
},
output: {
path: isDev ? config.dev.dist : config.prod.dist,
filename: '[name]/js/[name].[contenthash].js',
chunkFilename: '[name]/js/[name].[contenthash].js',
},
devServer: {
port: 8080,
host: 'localhost',
hot: true,
},
// ...
}
在这种配置下,必须通过/app1
/app2
的形式去访问具体的应用,而原来的配置是这样的:/index?app=1
,通过query的appId来决定渲染哪个应用。其实路由的改变本来也无所谓,但是由于客户那边不接受路由的变更(之前有一次变更就迎来的一片抱怨),因此组内人员一致认为生产环境不能变更路由。而生产环境和开发环境不一致也是无法接受的,这会影响到其他同事维护的代码。就这样,我又踏上了研究webpack-dev-server之路。
路由注册
要解决这个问题,那么第一步就是要在devServer的基础注册路由/index
,于是我开始仔细的阅读webpack文档,终于在DevServer中找到了这样一个配置:setupMiddlewares
,这是文档链接。通过这个配置我们可以轻松的在devServer的基础上注册自己需要的路由,并返回想要返回的内容,代码如下:
module.exports = {
// ...
devServer: {
// ...
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined');
}
devServer.app.get('/index', (_, res) => {
res.send('this is an app');
});
}
}
}
运行项目然后访问/index
,发现页面成功的显示了this is an app
。这就说明我们的路由注册成功了。
返回应用页面
路由注册成功了,但是我们的页面不能只显示this is an app
啊,所以必须要把应用的页面文件返回给浏览器,但是DevServer打包后的文件是存在内存中的,我们要怎么拿到文件的内容呢?我查阅了整个webpack文档都没找到相关的内容(也许是年纪大了,眼睛不好使)。这就使我非常苦恼了,在翻遍了webpack文档和搜索相关内容无果后,我决定去查看一下webpack-dev-server源码,当我打开/lib/Server.js
文件后发现有3000多行代码,瞬间想看的心减半,但是我心想就差这一步了不能就这样无疾而终啊。于是我开始咬牙去看源码(虽然看不懂),经过漫长的反复的上上下下的看,终于找到了(this.middleware).context.outputFileSystem
这样一行代码,直觉告诉我这个东西也许有用,于是我打开的我的代码打印了这个对象,发现它和nodejs
中的fs模块几乎一样,我意识到这是webpack为了便于在内存中存取文件而实现的一套文件系统工具。于是我尝试着打印webpack输出的某个文件:
const cwd = process.cwd()
module.exports = {
// ...
devServer: {
// ...
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined');
}
devServer.app.get('/index', (_, res) => {
const fileName = join(cwd, 'dist/app1/index.html')
console.log(devServer.middleware.context.outputFileSystem.readFileSync(fileName, 'utf8'))
res.send('this is an app');
});
}
}
}
再次运行webpack并打开浏览器访问/index
,果然在控制台输出了app1的页面内容,完美!!!我只需要将页面返回给浏览器不就实现了我的需求嘛!!!
根据appId返回对应的页面
经过上面的测试我们已经确定了获取内存中文件的方法,那么我们只需要从query
获取appId,然后根据appId返回具体的应用页面就完全实现了我们的需求,代码如下:
const cwd = process.cwd()
module.exports = {
// ...
devServer: {
// ...
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined');
}
const fs = devServer.middleware.context.outputFileSystem
devServer.app.get('/index', (req, res) => {
const appId = req.query.app
const appName = getAppNameById(appId)
if (appName) {
const fileName = join(cwd, `dist/${appName}/index.html`)
const html = fs.readFileSync(fileName, 'utf8')
res.send(html);
}
});
}
}
}
至此,所有的问题都已引刃而解!