webpack-dev-server自定义路由

560 阅读4分钟

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);
                }
            });
        }
    }
}

至此,所有的问题都已引刃而解!