React Router v4 嵌套路由不适用于 webpack-dev-server [英] React Router v4 nested routes not work with webpack-dev-server

查看:52
本文介绍了React Router v4 嵌套路由不适用于 webpack-dev-server的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试像这样为我的 React 应用程序设置嵌套路由

  • / -> 主页
  • /about -> 关于页面
  • /protected -> 受保护的默认页面
  • /protected/page1 -> 受保护的页面 1

它在代码和盒子中工作正常(https://codesandbox.io/s/react-router-nested-route-utqy7) React 16.8.1 React Router 4.3.1

但是当我用 webpack-dev-server (3.7.1) 设置同样的东西时,它只能到达 / 而无法到达其他路由.

我的文件结构就像

├── package.json├── src│ ├── index.jsx│ └── index.html├── webpack│ ├── path.js│ ├── webpack.common.js│ └── webpack.dev.js└── webpack.config.js

paths.js

const path = require('path');模块.出口 = {outputPath: path.resolve(__dirname, '../', 'build'),entryPath: path.resolve(__dirname, '../', 'src/index.jsx'),templatePath: path.resolve(__dirname, '../', 'src/index.html'),};

webpack.common.js

const webpack = require('webpack');const convert = require('koa-connect');const history = require('connect-history-api-fallback');const HtmlWebpackPlugin = require('html-webpack-plugin');const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');const commonPaths = require('./paths');模块.出口 = {条目:commonPaths.entryPath,模块: {规则: [{测试:/\.(js|jsx)$/,loader: 'babel-loader',排除:/(节点模块)/,},],},服务: {添加:应用程序=>{app.use(convert(history()));},内容:commonPaths.entryPath,开发:{publicPath: commonPaths.outputPath,},打开:真实,},解决: {模块:['src','node_modules'],扩展名:['*', '.js', '.jsx', '.css', '.scss'],},插件: [新的 webpack.ProgressPlugin(),新的 HtmlWebpackPlugin({模板:commonPaths.templatePath,}),新的 ScriptExtHtmlWebpackPlugin({默认属性:'异步',}),],};

webpack.dev.js

const webpack = require('webpack');const commonPaths = require('./paths');模块.出口 = {模式:'发展',输出: {文件名:'[名称].js',路径:commonPaths.outputPath,chunkFilename: '[name].js',},模块: {规则: [{测试:/\.(css|scss)$/,用: ['样式加载器',{加载器:'css-加载器',},'sass-loader',],},],},开发服务器:{contentBase: commonPaths.outputPath,压缩:真的,热:真的,},插件:[新的 webpack.HotModuleReplacementPlugin()],};

webpack.config.js

const webpackMerge = require('webpack-merge');const common = require('./webpack/webpack.common');const devConfig = require(`./webpack/webpack.dev.js`);module.exports = webpackMerge(common, devConfig);

index.jsx

从react"导入React;从react-dom"导入{渲染};从react-router-dom"导入 { BrowserRouter, Route };const 主页 = () =>(<div><h1>主页</h1>

);const AboutPage = () =>(<div><h1>关于</h1>

);const 受保护 = () =>(<div><h1>受保护的默认页面</h1>

);const ProtectedPage1 = () =>(<div><h1>ProtectedPage1</h1>

);使成为(<浏览器路由器><div><Route path="/" component={Homepage} 精确/><Route path="/about" component={AboutPage}/><路线路径=/受保护"render={({ 匹配:{ url } }) =>(<div><Route path={`${url}/`} component={Protected} 精确/><路由路径={`${url}/page1`} component={ProtectedPage1}/>

)}/>

</BrowserRouter>,document.getElementById('app'));

我认为我的配置中有些路径不正确,我就是想不通哪里错了.

解决方案

我终于找到了 webpack-dev-server 无法提供嵌套路由的原因.

作为单页应用程序,当您访问 React 应用程序的 /somepath 时,它实际上回退到 / 并将路径名传递给 react 路由器.React 路由器将通过使用浏览器的历史 API 将您导航到 /somepath.

webpack-dev-server,由于某种未知原因,默认情况下不会启用这种回退到历史 API"行为.

所以,我们需要在webpack配置的devServer中添加historyApiFallback: true,.

现在,所有顶级路由,比如 /somepath 都应该可以工作,但是对于嵌套路由,比如 /somepath/morepath,这还不够.

使用默认的 webpack-dev-server 设置,编译的 html 模板将指向捆绑的 js,如 <script type="text/javascript" src="main.js"></script>.注意 src="main.js",它假定 main.jsindex.html 位于同一路径下.该假设对于顶级路径 /somepath 是正确的,但对于嵌套路由 /somepath/morepath,此假设将导致 html 文件访问 main.js 就像 /somepath/main.js.

所以,我们最终要寻找一种方法,当它访问捆绑的js时,为html文件指定某个位置.而且,这是publicPath 的工作.将 publicPath: '/', 添加到 webpack 配置的输出块.它会告诉 html 总是从 / 文件夹访问 main.js 并且编译的 html 将是 <script type="text/javascript" src="/main.js"></script>.这正是我们正在寻找的.

I try to setup nested routes for my react app like this

It works fine in codesandbox (https://codesandbox.io/s/react-router-nested-route-utqy7) React 16.8.1 React Router 4.3.1

But when I set the same thing up with webpack-dev-server (3.7.1), it can only reach / and can't reach to the rest routes.

My file structure is like

├── package.json
├── src
│   ├── index.jsx
│   └── index.html
├── webpack
│   ├── paths.js
│   ├── webpack.common.js
│   └── webpack.dev.js
└── webpack.config.js

paths.js

const path = require('path');

module.exports = {
  outputPath: path.resolve(__dirname, '../', 'build'),
  entryPath: path.resolve(__dirname, '../', 'src/index.jsx'),
  templatePath: path.resolve(__dirname, '../', 'src/index.html'),
};

webpack.common.js

const webpack = require('webpack');
const convert = require('koa-connect');
const history = require('connect-history-api-fallback');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const commonPaths = require('./paths');

module.exports = {
  entry: commonPaths.entryPath,
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        exclude: /(node_modules)/,
      },
    ],
  },
  serve: {
    add: app => {
      app.use(convert(history()));
    },
    content: commonPaths.entryPath,
    dev: {
      publicPath: commonPaths.outputPath,
    },
    open: true,
  },
  resolve: {
    modules: ['src', 'node_modules'],
    extensions: ['*', '.js', '.jsx', '.css', '.scss'],
  },
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({
      template: commonPaths.templatePath,
    }),
    new ScriptExtHtmlWebpackPlugin({
      defaultAttribute: 'async',
    }),
  ],
};

webpack.dev.js

const webpack = require('webpack');
const commonPaths = require('./paths');

module.exports = {
  mode: 'development',
  output: {
    filename: '[name].js',
    path: commonPaths.outputPath,
    chunkFilename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
          },
          'sass-loader',
        ],
      },
    ],
  },
  devServer: {
    contentBase: commonPaths.outputPath,
    compress: true,
    hot: true,
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
};

webpack.config.js

const webpackMerge = require('webpack-merge');
const common = require('./webpack/webpack.common');

const devConfig = require(`./webpack/webpack.dev.js`);
module.exports = webpackMerge(common, devConfig);

index.jsx

import React from "react";
import { render } from "react-dom";
import { BrowserRouter, Route } from "react-router-dom";

const Homepage = () => (
  <div>
    <h1>Home Page</h1>
  </div>
);
const AboutPage = () => (
  <div>
    <h1>About</h1>
  </div>
);
const Protected = () => (
  <div>
    <h1>Protected default page</h1>
  </div>
);
const ProtectedPage1 = () => (
  <div>
    <h1>ProtectedPage1</h1>
  </div>
);

render(
  <BrowserRouter>
    <div>
      <Route path="/" component={Homepage} exact />
      <Route path="/about" component={AboutPage} />

      <Route
        path="/protected"
        render={({ match: { url } }) => (
          <div>
            <Route path={`${url}/`} component={Protected} exact />
            <Route path={`${url}/page1`} component={ProtectedPage1} />
          </div>
        )}
      />
    </div>
  </BrowserRouter>,
  document.getElementById('app')
);

I think some paths are incorrect in my config, I just can't figure out where is wrong.

解决方案

I finally figured out the reason that webpack-dev-server couldn't serve nested routes.

As a single page application, when you visit /somepath of your react app, it actually fallback to the / and pass the pathname to react router. React router will navigate you to /somepath by the using browser's history API.

webpack-dev-server, for some unknown reason, doesn't enable this "fallback to history API" behaviour by default.

So, we need to add historyApiFallback: true, to the devServer of webpack config.

Now, all top level routes, like /somepath should work, but for nested routes, like /somepath/morepath, it's not enough.

With default webpack-dev-server setting, the compiled html template will point to the bundled js like <script type="text/javascript" src="main.js"></script>. Pay attention to the src="main.js" which assumes the main.js is under the same path as the index.html. The assumption is true for top level path /somepath but for nested routes, /somepath/morepath, this assumption will lead html file to access main.js like /somepath/main.js.

So, we end up with looking for a way to specify a certain place for html file when it's going to access the bundled js. And, it's the job of publicPath. Adding publicPath: '/', to the output block of webpack config. It will tell html to always access main.js from / folder and the compiled html will be <script type="text/javascript" src="/main.js"></script>. That's exactly what we're looking for.

这篇关于React Router v4 嵌套路由不适用于 webpack-dev-server的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆