blogsome
blogsome copied to clipboard
React多页面-项目配置
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
即可。