blogsome icon indicating copy to clipboard operation
blogsome copied to clipboard

React多页面-项目配置

Open hwen opened this issue 6 years ago • 0 comments

React多页面-项目配置

目录

  • 前言
  • 改造CRA
  • 多入口
  • 路由重写
  • htmlPlugin配置
  • react-loadable配置
  • bundle分析

前言

多页面项目准确来说是个多个SPA,每个SPA都可以看作是单独的应用。

有时候可以将一个项目拆分成多页面,每个页面当作一个模块,每个模块的功能相对独立,这样打包时就可以分开打包,从而优化项目的加载速度。因为如果项目太大,将整个项目都打包成一个SPA,就会显得非常臃肿。首次加载速度也会很慢。

还有一种场景,比方说移动端的活动页面,每次活动之间,比较互相独立,所以也非常适合做成多页面,每次活动都当成一个SPA来开发。同时多次活动页面开发下来,可以积累很多通用的组件。也为未来接入运营系统打下根基。

选择从CRA(create-react-app)开始,是因为CRA是很多接触react开发的人的新手村,也是很多同学搭建react项目的选择。从NPM下载量来看,每个月都有4w~6w的下载,可见覆盖率非常高。

我们通过扩展CRA的配置来达到多页面的需求。

以搭建一个活动项目来模板,下面会从各个方面来探讨。因为我会将内容成三大类,而且有清晰的目录。所以即使有些地方可能暂时没用到,或者没能理解清楚,也可以以后再来查看。

  • 项目地址
  • React多页面-移动端方案探讨 - todo
  • React多页面-项目配置
  • React多页面-Nginx 服务器配置 - todo

改造CRA

需要对CRA进行自定义配置,执行npm run eject命令即可。执行eject后,会将webpack配置暴露出来,从而进行改造。

本文不会从零开始,将所有细节都覆盖。而旨在将每个关键点都进行说明,授人与渔而非鱼。可以翻到GitHub项目的源码来对照查看。

多入口

打包的时候需要根据不同的页面生成不同的bundle,其中一个关键点在于webpack entry的配置。每个页面都有新的入口文件。

目录结构如下,在modules目录下,每个页面都新建一个单独的文件夹。

同时希望我们的配置尽量自动化,每次创建新的文件夹(新的页面)时,自动生成对应入口配置。

要达到这样的目的,只需要读取modules文件夹,然后遍历其中的文件名即可,规定每个模块使用文件名作为模块名。

// 遍历 modules 下的文件夹
function pageDirs() {
  const dir = path.resolve(appPaths.appSrc, 'modules');
  if (fs.existsSync(dir)) {
    const files = fs.readdirSync(dir);
    return files;
  } else {
    ilog(dir + ' Not Found! ');
    return null;
  }
}

// modules 下第一层的文件夹名
const entryModules = pageDirs();

获取到文件夹列表后,就可以动态生成入口文件配置。这里需要对本地环境跟线上环境作区分,本地环境需要额外配置devServer, hotReload.

// generate entrys 生成入口节点
function genEntry() {
  if (!entryModules) throw new Error('Do not find any page in src/modules folder');
  return entryModules.reduce((entry, page) => {
    entry[page] = [];
    if (isDev) {
      entry[page].push(require.resolve('react-dev-utils/webpackHotDevClient'));
    }
    // 遍历生成entry
    entry[page].push(path.resolve(appPaths.appSrc, `modules/${page}/index.js`));
    return entry;
  }, {});
}

路由重写

前面已经规定了,用文件夹名作为模块名。也就是${page}名。

我们希望每个模块页面用/${page}来进行区分,每个模块路由的basename也设置为/${page}。比方说现在有两个页面A,B。A页面(SPA)下有路由,/about,B页面有另外一个路由/about,内容是不同的。访问时就用/A/about/B/about进行区分。

所以我们需要将所有/A开头的路径重定向到打包好的A.html文件。

另外,为了方便服务器配置静态资源,也将/h5-event-static路径全部重定向到静态资源目录。

/**
 * 生成路由重写配置。用于 webpack-dev-server。
 * 生产环境也需要配置相应的路由规则(例如Nginx,Apache等)
 */
function genRewrites() {
  return entryModules.reduce(
    (rewrites, page) => {
      // modules rewrite
      rewrites.push({
        from: new RegExp(`^/${page}`),
        to: `/${page}.html`,
      });
      return rewrites;
    },
    [
      {
        from: new RegExp(`^/h5-event-static/(.*)$`),
        to: ctx => `/${ctx.match && ctx.match[1]}`,
      },
    ]
  );
}

htmlPlugin配置

htmlWebpackPlugin 的配置就没什么特别的,跟普通的配置一样,只需要对prod的打包进行压缩就好。

// 生成 htmlwebpackplugin 配置
function genHtmlPlugin() {
  const HtmlPlugins = entryModules.reduce((htmlPlugins, page) => {
    if (isDev) {
      htmlPlugins.push(
        new HtmlWebpackPlugin({
          inject: true,
          template: appPaths.appHtml,
          chunks: [`${page}`],
          filename: `${page}.html`,
          NODE_ENV: /(production|prod)/.test(process.env.NODE_ENV) ? 'prod' : 'non-prod',
        })
      );
    } else {
      htmlPlugins.push(
        new HtmlWebpackPlugin({
          inject: true,
          template: appPaths.appHtml,
          chunks: [`${page}`],
          filename: `${page}.html`,
          NODE_ENV: /(production|prod)/.test(process.env.NODE_ENV) ? 'prod' : 'non-prod',
          minify: {
            removeComments: true,
            collapseWhitespace: true,
            removeRedundantAttributes: true,
            useShortDoctype: true,
            removeEmptyAttributes: true,
            removeStyleLinkTypeAttributes: true,
            keepClosingSlash: true,
            minifyJS: true,
            minifyCSS: true,
            minifyURLs: true,
          },
        })
      );
    }

    return htmlPlugins;
  }, []);
  if (isDev) HtmlPlugins.push(new HtmlWebpackPluginPublicPath({ publicPath: '/h5-event-static' }));
  return HtmlPlugins;
}

react-loadable配置

请参考移【React多页面-移动端方案探讨】中的 react-loadable 配置

bundle analysis

全部配置好后,我们就可以跑一下bundle分析来,查看配置的效果。同时可以根据bundle分析来进行相应的优化。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin;

const merge = require('webpack-merge');
const prod = require('./webpack.config.prod');

module.exports = merge(prod, {
  plugins: [new BundleAnalyzerPlugin()],
});

想查看具体效果,可以克隆项目到本地,然后运行npm run analyze即可。

ana

hwen avatar Nov 09 '18 03:11 hwen