如何让webpack-dev-server允许react-router的入口点?

120
我正在创建一个应用程序,该应用程序在开发过程中使用webpack-dev-server和react-router。
似乎webpack-dev-server是在假定你只有一个公共入口点的情况下构建的(即“ / ”),而react-router允许使用无限数量的入口点。
我想要利用webpack-dev-server带来的好处,尤其是它对提高生产力非常有用的热重载功能,但我仍然希望能够加载在react-router中设置的路由。
如何实现它们一起工作?是否可以以某种方式在webpack-dev-server前面运行express服务器以允许这样做?

我这里有一个非常粗糙的版本,但它很脆弱,只允许匹配简单的路由:https://github.com/natew/react-base(请参见make-webpack-config)和(app/routes.js)。 - Nathan Wienert
9个回答

107

要使此功能正常工作,您应将WebpackDevServerhistoryApiFallback设置为true。以下是一个小例子(根据需要进行调整):

var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');

var config = require('./webpack.config');


var port = 4000;
var ip = '0.0.0.0';
new WebpackDevServer(webpack(config), {
    publicPath: config.output.publicPath,
    historyApiFallback: true,
}).listen(port, ip, function (err) {
    if(err) {
        return console.log(err);
    }

    console.log('Listening at ' + ip + ':' + port);
});

你会错过index.html顶部的状态栏,但这个方法非常有效 :) - swennemen
8
这应该是可接受的答案。来自webpack dev server文档的信息:如果你正在使用HTML5历史API,你可能需要提供你的index.html文件来代替404响应,可以通过设置historyApiFallback: true来实现。如果我正确理解了问题,这将解决问题。 - Sebastian
1
@smnbbrv 没问题。它实际上在底层使用 connect-history-api-fallback,如果你想要传递一个具有中间件特定选项的对象,而不仅仅是 true,那么可以这样做。 - Juho Vepsäläinen
@DonnyP webpack-dev-middleware不会与connect-history-api-fallback一起使用,因此您必须将其单独连接到设置中。 - Juho Vepsäläinen
1
如果您正在使用CLI,则可以使用以下命令:webpack-dev-server --history-api-fallback - Levi
显示剩余3条评论

69

我设置了一个代理来实现这个目的:

你有一个正则表达式web服务器,它可以在任何路由上提供index.html文件,但是如果它是资源路由,则请求将被代理到web-dev-server。

你的React热更新入口仍将直接指向webpack dev server,因此热重载仍然有效。

假设你在8081上运行webpack-dev-server,代理在8080上。你的server.js文件将如下所示:

"use strict";
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./make-webpack-config')('dev');

var express = require('express');
var proxy = require('proxy-middleware');
var url = require('url');

## --------your proxy----------------------
var app = express();
## proxy the request for static assets
app.use('/assets', proxy(url.parse('http://localhost:8081/assets')));

app.get('/*', function(req, res) {
    res.sendFile(__dirname + '/index.html');
});


# -----your-webpack-dev-server------------------
var server = new WebpackDevServer(webpack(config), {
    contentBase: __dirname,
    hot: true,
    quiet: false,
    noInfo: false,
    publicPath: "/assets/",

    stats: { colors: true }
});

## run the two servers
server.listen(8081, "localhost", function() {});
app.listen(8080);

现在您可以按以下方式在webpack配置文件中创建入口点:

 entry: [
     './src/main.js',
     'webpack/hot/dev-server',
     'webpack-dev-server/client?http://localhost:8081'
 ]

注意热更新的直接调用是8081。

同时确保将绝对URL传递给output.publicPath选项:

 output: {
     publicPath: "http://localhost:8081/assets/",
     // ...
 }

1
有一个问题,可能不太相关,所以如果需要的话我可以开一个新的问题。但是我注意到现在webpack开发服务器的控制台输出没有流式传输。以前,你可以看它编译并看到百分比上升,现在只是在编译之后直接输出。 - Nathan Wienert
干得好。这正是应该做的方式。我添加了一个关于 output.publicPath 选项的注释,它也应该是绝对 URL。 - Tobias K.
谢谢,你的资产里有什么?在我的情况下,bundle.js目录被称为“dist”。除非还有其他东西需要放在那里,否则我会保持它不变?我的应用程序在一个名为“app”的文件夹中 - 它们应该放在同一个地方吗? - Dominic
7
使用内置的Webpack代理可能更容易。这样,您不会干扰服务器本身,使服务器保持“纯净”。相反,您只需要对Webpack配置进行少量(3-5行)的添加即可。有了它,您仅修改用于开发目的的开发脚本,将生产代码(server.js)保持原样(不像您的版本),我认为这是正确的方法。 - jalooc
4
这个答案虽然有点过时,但仍然是正确的。现在有更直接的方法可用,搜索 historyApiFallback - Eugene Kulabuhov
显示剩余7条评论

27

对于其他可能仍在寻找答案的人,我提供了一个简单的代理绕过方法,可以轻松地实现此操作,并且配置将放入webpack.config.js中。

我确定使用正则表达式测试本地内容有更加优雅的方式,但这适合我的需求。

devServer: {
  proxy: { 
    '/**': {  //catch all requests
      target: '/index.html',  //default target
      secure: false,
      bypass: function(req, res, opt){
        //your custom code to check for any exceptions
        //console.log('bypass check', {req: req, res:res, opt: opt});
        if(req.path.indexOf('/img/') !== -1 || req.path.indexOf('/public/') !== -1){
          return '/'
        }

        if (req.headers.accept.indexOf('html') !== -1) {
          return '/index.html';
        }
      }
    }
  }
} 

感谢您的代码!这个和只是将“historyApiFallback:true”添加到同一个devServer对象有什么区别吗?(除了能够进一步自定义之外,显而易见的)。只是好奇。 - Zachary Raineri

15

如果你使用CLI运行webpack-dev-server,你可以通过webpack.config.js配置devServer对象,并使用historyApiFallback选项

module.exports = {
  entry: "index.js",
  output: {
    filename: "bundle.js"
  },
  devServer: {
    historyApiFallback: true
  }
}

每次出现404错误时,都会将其重定向到index.html。

提示:如果使用publicPath,则还需要将其传递给devServer:

module.exports = {
  entry: "index.js",
  output: {
    filename: "bundle.js",
    publicPath: "admin/dashboard"
  },
  devServer: {
    historyApiFallback: {
      index: "admin/dashboard"
    }
  }
}

您可以通过查看输出的前几行(带有“404s will fallback to: 路径”部分)来验证是否设置正确。

enter image description here


12

对于更近期的回答,当前版本的webpack(4.1.1),您可以在webpack.config.js中像这样设置:

const webpack = require('webpack');

module.exports = {
    entry: [
      'react-hot-loader/patch',
      './src/index.js'
    ],
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ['babel-loader']
            },
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: ['style-loader','css-loader']
            }
        ]
    },
    resolve: {
      extensions: ['*', '.js', '.jsx']  
    },
    output: {
      path: __dirname + '/dist',
      publicPath: '/',
      filename: 'bundle.js'
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
      contentBase: './dist',
      hot: true,
      historyApiFallback: true
    }
  };

重要的部分是 historyApiFallback: true。无需运行自定义服务器,只需使用cli:

"scripts": {
    "start": "webpack-dev-server --config ./webpack.config.js --mode development"
  },

2

针对运行同构应用程序(即服务器端渲染React组件)的情况,我想补充一下答案。

在这种情况下,您可能还希望在更改React组件之一时自动重新加载服务器。您可以使用piping包来完成这个操作。您只需安装它并在server.js的开头添加require("piping")({hook: true})即可。就是这样。当您更改任何组件时,服务器将重新启动。

然而,这会引起另一个问题 - 如果您从与express服务器相同的进程中运行webpack服务器(如上面接受的答案),则webpack服务器也将重新启动,并且每次都会重新编译您的捆绑包。为避免这种情况,您应该在不同的进程中运行主服务器和webpack服务器,以便piping只重新启动您的express服务器并且不会触及webpack。 您可以使用concurrently包来完成此操作。您可以在react-isomorphic-starterkit中找到此示例。在package.json中,他有:

"scripts": {
    ...
    "watch": "node ./node_modules/concurrently/src/main.js --kill-others 'npm run watch-client' 'npm run start'"
  },

同时运行两个服务器,但是在不同的进程中。


这是否意味着某些文件被重复监视了?比如共享的同构/通用文件? - David Sinclair

1

historyApiFallback也可以是一个对象而不是布尔值,其中包含路由信息。

historyApiFallback: navData && {
  rewrites: [
      { from: /route-1-regex/, to: 'route-1-example.html' }
  ]
}

1

-1
这对我有用:只需先添加webpack中间件,然后再添加app.get('*'... index.html解析器,因此express将首先检查请求是否与webpack提供的路由之一匹配(例如:/dist/bundle.js/__webpack_hmr_),如果不是,则会移动到带有*解析器的index.html
即:
app.use(require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
}))
app.use(require('webpack-hot-middleware')(compiler))
app.get('*', function(req, res) {
  sendSomeHtml(res)
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接