React - 多页面配置(creat-react-app)

2018-11-09 admin

说明

本文对 React 多页面应用配置的探讨是基于使用 creat-react-app 构建的项目,并不是 webpack 多页面配置教程。

在使用 React 进行开发的过程中,我们通常会使用 creat-react-app 脚手架命令来搭建项目,避免要自己配置 webpack,提高我们的开发效率。但是使用 creat-react-app 搭建的项目是单页面应用,如果我们是做中后台管理页面或SPA,这样是满足要求的,但如果项目有多入口的需求,就需要我们进行一些配置方面的修改。

一般现在有两种方式将脚手架搭建的项目修改为多入口编译:

  1. 使用 react-app-rewired 修改脚手架配置,在项目中安装 react-app-rewired 后,可以通过创建一个 config-overrides.js 文件来对 webpack 配置进行扩展。
  2. 执行 npm eject 命令,弹出配置文件,进行自定义配置。(这个方案需要对 webpack 有一定的了解)

这里对第 2 种方法给出具体配置方案。

具体方案

准备工作

  1. 使用 create-react-app 命令创建一个单页应用,并且创建成功之后执行 npm run eject 弹出配置文件,eject 之后文件组织结构如下:

    my-app
    ├── config
    │   ├── jest
    │   ├── env.js
    │   ├── paths.js
    │   ├── webpack.config.dev.js
    │   ├── webpack.config.prod.js
    │   └── webpackDevServer.config.js
    ├── node_modules
    ├── public
    ├── scripts
    │   ├── build.js
    │   ├── start.js
    │   └── test.js
    ├── package.json
    ├── README.md
    └── src
    

    其中 config 和 scripts 是我们需要重点关注的,我们的修改也主要集中在这两个文件夹中的文件。

  2. 了解相关工具模块,我们修改的配置文件都是在 node 环境中执行的,其中涉及到一些 node 模块和 npm 上的一些包,比如 path 路径模块globby (增强版的 glob)等。这些模块和库可以帮助我们处理文件路径方面的问题,在开始之前,可以先粗略了解一下它们的用法。

  3. 了解 webpack 入口(entry)概念HtmlWebpackPlugin 插件,这有助于理解接下来的第 4、5点。实际上,如果要进行多页面的配置,应该已经对于这些有了一定的了解了。

  4. 在开始进行多入口配置之前,需要先明确项目的文件组织结构,这关系到我们如何准确获取所有的入口文件,一般有以下几种做法:

    • 定义一个入口文件名称的数组, 遍历这个数组以获取所有的入口文件。例如:APP_ENTRY=["index","admin"],这样每增加一个入口就要相应的在这个数组里增加一个元素。

      my-app
      └── src
          ├── index
          │   ├── index.js
          │   ├── index.less
          │   ├── components
          │   └── ...
          └── admin
              ├── index.js
              ├── index.less
              ├── components
              └── ...
      
    • 在 my-app/public/ 下为所有入口文件新建对应的 .html 文件,通过获取 public 下所有的 .html 文件,确定所有的入口文件。同样的,每增加一个入口就需要在 public 下增加相应的 html 文件。

      my-app
      ├── public
      │   ├── index.html
      │   └── admin.html
      └── src
          ├── index
          │   ├── index.js
          │   ├── index.less
          │   ├── components
          │   └── ...
          └── admin
              ├── index.js
              ├── index.less
              ├── components
              └── ...
      
    • 通过遍历 src 下所有的 index.js 文件,确定所有的入口文件,即每个页面所在的子文件夹下都有唯一的一个 index.js 文件,只要遵从这种文件组织规则,就可以不用每次新增入口的时候再去修改配置了(当然,一般来说项目变动不大的情况下配置文件完成之后几乎不用修改)。

      my-app
      ├── public
      │   ├── index.html
      └── src
          ├── index
          │   ├── index.js
          │   ├── index.less
          │   ├── components
          │   └── ...
          └── admin
              ├── index.js
              ├── index.less
              ├── components
              └── ...
      

      当然,文件组织结构还可以有很多其他的方式,只需要确定其中一种文件组织规则,然后按照这个规则去修改和定义配置文件即可,主要就是确定入口文件、指定入口文件对应的 html 模板文件、指定输出文件等。 但是,文件组织规则应该项目内保持统一,我们在做项目时需要加一些限制,否则没有哪种配置文件可以完全只能的匹配所有的需求。

      “如果你愿意限制做事方式的灵活度,你几乎总会发现可以做得更好。” ——John Carmark

  5. 我们以上面第 4 点中的最后一种文件组织结构进行接下来的配置编写,我们的项目目录如下:

            my-app
            ├── public
            │   ├── index.html // 作为所有页面的 html 模板文件
            └── src
                ├── index // 最终的页面为 index.html
                │   ├── index.js
                │   ├── index.less
                │   ├── components
                │   └── ...
                └── admin // 最终的页面为 admin.html
                    ├── index.js
                    ├── index.less
                    ├── components
                    └── ...
    
    

    获取所有入口文件的方法:

    const globby = require('globby');
    const entriesPath = globby.sync([resolveApp('src') + '/*/index.js']);
    
    

修改 Paths.js 文件

由于入口文件路径在修改开发环境配置和修改生环境配置中都会用到,我们将入口文件路径的获取放在 Paths.js 文件中进行获取和导出,这样开发环境和生产环境就都可以使用了。

修改 /config/paths.js 文件:

// 引入 globby 模块
const globby = require('globby');
// 入口文件路径
const entriesPath = globby.sync([resolveApp('src') + '//*/index.js']);
// 在导出对象中添加 entriesPath
module.exports = {
  dotenv: resolveApp('.env'),
  appPath: resolveApp('.'),
  appBuild: resolveApp('build'),
  ...
  entriesPath,
};

修改开发环境配置

修改 /config/webpack.config.dev.js 文件

// 获取指定路径下的入口文件
function getEntries(){
  const entries = {};
  const files = paths.entriesPath;
  files.forEach(filePath => {
    let tmp = filePath.split('/');
    let name = tmp[tmp.length - 2];
    entries[name] = [
      require.resolve('./polyfills'),
      require.resolve('react-dev-utils/webpackHotDevClient'),
      filePath,
    ];
  });
  return entries;
}

// 入口文件对象
const entries = getEntries();

// 配置 HtmlWebpackPlugin 插件, 指定入口生成对应的 html 文件,有多少个页面就需要 new 多少个 HtmllWebpackPlugin
// webpack配置多入口后,只是编译出多个入口的JS,同时入口的HTML文件由HtmlWebpackPlugin生成,也需做配置。
// chunks,指明哪些 webpack入口的JS会被注入到这个HTML页面。如果不配置,则将所有entry的JS文件都注入HTML。
// filename,指明生成的HTML路径,如果不配置就是build/index.html,admin配置了新的filename,避免与第一个入口的index.html相互覆盖。
const htmlPlugin = Object.keys(entries).map(item => {
  return new HtmlWebpackPlugin({
    inject: true,
    template: paths.appHtml,
    filename: item + '.html',
    chunks: [item],
  });
});
  • 修改 module.exports 中的 entry、output 和 plugins:
module.exports = {
  ...
  // 入口
  entry: entries,
  // 出口
  output: {
    pathinfo: true,
    // 指定不同的页面模块文件名
    filename: 'static/js/[name].js',
    chunkFilename: 'static/js/[name].chunk.js',
    publicPath: publicPath,
    devtoolModuleFilenameTemplate: info =>
      path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
  },
  ...
  plugins: [
    ...
    // 替换 HtmlWebpackPlugin 插件配置
    // new HtmlWebpackPlugin({
    //   inject: true,
    //   template: paths.appHtml,
    // }),
    ...htmlPlugin,
  ],
};

修改生产环境配置

修改 /config/webpack.config.prod.js 文件 生产环境和开发环境的修改基本相同,只是入口对象和 HtmlWebpackPlugin 插件配置稍有不同(JS、css文件是否压缩等)。

// 获取指定路径下的入口文件
function getEntries(){
  const entries = {};
  const files = paths.entriesPath;
  files.forEach(filePath => {
    let tmp = filePath.split('/');
    let name = tmp[tmp.length - 2];
    entries[name] = [
      require.resolve('./polyfills'),
      filePath,
    ];
  });
  return entries;
}

// 入口文件对象
const entries = getEntries();

// 配置 HtmlWebpackPlugin 插件, 指定入口文件生成对应的 html 文件
const htmlPlugin = Object.keys(entries).map(item => {
  return new HtmlWebpackPlugin({
    inject: true,
    template: paths.appHtml,
    filename: item + '.html',
    chunks: [item],
    minify: {
      removeComments: true,
      collapseWhitespace: true,
      removeRedundantAttributes: true,
      useShortDoctype: true,
      removeEmptyAttributes: true,
      removeStyleLinkTypeAttributes: true,
      keepClosingSlash: true,
      minifyJS: true,
      minifyCSS: true,
      minifyURLs: true,
    },
  });
});
  • 修改 module.exports 中的 entry 和 plugins:
module.exports = {
  ...
  // 入口
  entry: entries,
  ...

  plugins: [
    ...
    // 替换 HtmlWebpackPlugin 插件配置
    // new HtmlWebpackPlugin({
    //   inject: true,
    //   template: paths.appHtml,
    //   minify: {
    //     removeComments: true,
    //     collapseWhitespace: true,
    //     removeRedundantAttributes: true,
    //     useShortDoctype: true,
    //     removeEmptyAttributes: true,
    //     removeStyleLinkTypeAttributes: true,
    //     keepClosingSlash: true,
    //     minifyJS: true,
    //     minifyCSS: true,
    //     minifyURLs: true,
    //   },
    // }),
    ...htmlPlugin,
  ],
};

修改 webpackDevServer 配置

上述配置完成后,理论上就可以打包出多入口的版本;但使用 npm start 启动后,发现无论输入 /index.html 还是 /admin.html,显示的都是 index.html。输入不存在的 /xxxx.html,也显示为 index.html 的内容。这个现象与 devServer.historyApiFallback 有关:

当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。

这个问题只在开发环境中会出现,生产环境不用考虑,解决方法如下: 修改 /config/webpackDevServer.config.js 文件

// 在开发环境中如果要通过地址访问不同的页面, 需要增加以下配置
const files = paths.entriesPath;
const rewrites = files.map(v => {
  const fileParse = path.parse(v);
  return {
    from: new RegExp(`^\/${fileParse.base}`),
    to: `/build/${fileParse.base}`,
  };
});
historyApiFallback: {
  disableDotRule: true,
  rewrites: rewrites,
},

错误排查

这里对可能出现的错误进行记录和罗列:

原文链接:https://segmentfault.com/a/1190000016960824

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-44978.html

文章标题:React - 多页面配置(creat-react-app)

相关文章
三步搞定vue在vscode的环境配置问题
1. vscode基础开发插件 vscode-icons 图标美化 Debugger for Chrome 调试 Beautify 代码格式化 Prettier 代码格式化 ESLint 代码规范 JavaScript (ES6) cod...
2017-12-25
jQuery给多个不同元素添加class样式的方法
本文实例讲述了jQuery给多个不同元素添加class样式的方法。分享给大家供大家参考。具体分析如下: jQuery可以通过addClass()方法给多个不同的html元素同时添加相同的class <!DOCTYPE html>...
2017-03-22
Webpack(含 4)配置详解——从 0 配置一套开发模板
前言 源代码 熟悉 webpack 与 webpack4 配置。 webpack4 相对于 3 的最主要的区别是所谓的零配置,但是为了满足我们的项目需求还是要自己进行配置,不过我们可以使用一些 webpack 的预设值。同时 webpack...
2018-05-02
js使用split函数按照多个字符对字符串进行分割的方法
本文实例讲述了js使用split函数按照多个字符对字符串进行分割的方法。分享给大家供大家参考。具体分析如下: js中的split()函数可以对字符串按照指定的符号进行分割,但是如果字符串中存在多个分割符号,js的split()函数是否还可以...
2017-03-21
详解使用vue-router进行页面切换时滚动条位置与滚动监听事件
按照正常的产品逻辑,我们在进行页面切换时滚动条应该是在页面顶部的,可是。。。在使用vue-router进行页面切换时,发现滚动条所处的位置被自动记录了下来,且在另一个组件内定义的滚动监听事件仍会运行,着实吃了一大惊。。。 说说我的破解方法:...
2017-03-13
JavaScript中的call方法和apply方法使用对比
方法定义 call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象。 说明: call 方法可以用来代替另一个对象调用一个方法。c...
2017-03-29
[翻译]基于Webpack4使用懒加载分离打包React代码
原文地址:https://engineering.innovid.com/code-splitting-using-lazy-loading-with-react-redux-typescript-and-webpack-4-3ec601...
2018-03-11
IE7浏览器窗口大小改变事件执行多次bug及IE6/IE7/IE8下resize问题
本文主要通过代码示例给大家介绍IE7浏览器窗口大小改变事件执行多次bug及IE6/IE7/IE8下resize问题;分步介绍,先给大家介绍IE7浏览器窗口大小改变事件执行多次bug,具体问题分析及解决方案请看下文。 var resizeTi...
2017-03-29
angular+ionic 的app上拉加载更新数据实现方法
第一步,首先需要在<ion-content>标签里面加入标签<ion-infinite-scroll ng-if="hasmore" on-infinite="loadMore()"...
2017-03-07
bootstrap实现的自适应页面简单应用示例
本文实例讲述了bootstrap实现的自适应页面简单应用。分享给大家供大家参考,具体如下: <!DOCTYPE html> <html> <head> <meta charset='u...
2017-03-14
回到顶部