大家入门react想必都是从这行命令开始的吧!
npx create-react-app my-app
这个命令会生成一个工程骨架,小伙伴就可以愉快的板砖了。搬久了发现工程越来越大,一打包好几兆,严重影响加载速度。这时候就需要多入口打包,将工程分为几个小的SPA。比如登录时一个SPA,内容管理是一个SPA,产品管理是一个SPA,每个SPA加载共同的JS,和自己的JS,无关的不加载。我们就需要配置webpack来改造工程,结果发现项目里根本没有webpack.config.js。
React团队就像你妈一样为你做好了饭,你吃就好了。把webpack进行了封装,把你当傻瓜。
在package.json你会发现有个react-scripts包,打开源码就可以找到相见恨晚的webpack.config.js。但是我们不能直接修改这个文件,可以使用以下两种方法来对webpack进行配置。
第一种方式 eject
执行命令
yarn eject
把封装的配置脚本释放到当前工程里,在config目录下就有webpack.config.js了。此过程是不可逆的,你也将失去react-scripts未来升级能给你带来的好处。
第二种方式 craco
感谢社区提供第二种方案,让你的代码还可以继续保持纯洁的肉体。首先安装一下@craco/craco 这个包。
yarn add @craco/craco
项目根目录下新建craco.config.js,在这个文件中覆盖默认的webpack.config即可。
craco.config.js如何配置请参阅文档:https://www.npmjs.com/package/@craco/craco
我们通过第二种方式实现多入口改造。改造思路:
- 保留react-create-app默认的入口,入口名为main,js为src/index.js,使用模板public/index.html生成index.html
- 为src/entries/下的每个子目录,创建一个同名的入口,入口js为src/entries/{entry-name}/index.js, 使用模板public/{entry-name}.html生成{entry-name}.html
按照这个思路我们需要把webpack config的entry项改为多入口,配置插件HtmlWebpackPlugin为每个entry生成对应的HTML。
其它要点:
- output.filename需要修改为按入口名生成bundle.js
- optimization.runtimeChunk需要修改为single 这样不同的入口会共用同一个runtime.js而不是每个生成一个
废话不多说,直接放码! - 默认webpack runtime代码会内嵌到HTML,可以在.env文件中设置INLINE_RUNTIME_CHUNK=false来禁用,或者通过代码删除InlineChunkHtmlPlugin
- devServerConfig.historyApiFallback中disableDotRule设置为true,要不然开发服务器会把/xxx.html的请求重定向到/xxx上,无法打开入口html
const path = require('path');
const fs = require('fs');
const CracoLessPlugin = require('craco-less');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
function configureWebpack(webpackConfig, {env, paths}) {
const isEnvDevelopment = env === 'development';
const isEnvProduction = env === 'production';
//配置HtmlWebpackPlugin用来产生一个独立的HTML
function mkHtmlWebpackPlugin(chunks, filename, template) {
return new HtmlWebpackPlugin({
inject: true,
template: template || paths.appHtml,
chunks,
filename,
...isEnvProduction ? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
}
} : undefined
});
}
//遍历src/entries为所有子目录创建一个webpack入口,并配置对应的HtmlWebpackPlugin
const entriesDir = path.join(paths.appSrc, 'entries');
const fileNames = fs.readdirSync(entriesDir);
const entries = {};
const htmlWebpackPlugins = [];
fileNames.forEach(fileName => {
const filePath = path.join(entriesDir, fileName);
const file = fs.statSync(filePath);
if(file.isDirectory()){
entries[fileName] = path.join(filePath, "index.js");
let template = path.join(paths.appPublic, fileName + ".html");
if (!fs.existsSync(template))
template = undefined;
htmlWebpackPlugins.push(mkHtmlWebpackPlugin([fileName], fileName + ".html", template));
}
});
//main为create-react-app默认创建的入口,保留下来。这样既可以实现原始的单入口,又可以实现多入口
webpackConfig.entry = {
main: webpackConfig.entry,
...entries
};
//覆盖默认的plugins配置
const defaultHtmlWebpackPluginIndex = webpackConfig.plugins.findIndex(plugin => plugin instanceof HtmlWebpackPlugin);
webpackConfig.plugins.splice(defaultHtmlWebpackPluginIndex, 1, mkHtmlWebpackPlugin(["main"], "index.html"), ...htmlWebpackPlugins);
//create-react-app默认用的是一个固定文件名,不适合多入口!改为按入口名生成输出文件名
if (isEnvDevelopment)
webpackConfig.output.filename = 'static/js/[name].bundle.js';
//共用runtime bundle
webpackConfig.optimization.runtimeChunk = "single";
// react-scripts默认在生产环境会将runtime chunk内嵌到html中
// 禁用该行为,使用独立的js
// 也可以在根目录新建.env文件,设置INLINE_RUNTIME_CHUNK=false来禁用
// 不过配置入口太多了,不方便管理,直接这里用代码禁用好了
const inlineChunkHtmlPluginIndex = webpackConfig.plugins.findIndex(plugin => plugin instanceof InlineChunkHtmlPlugin);
if (inlineChunkHtmlPluginIndex >= 0)
webpackConfig.plugins.slice(inlineChunkHtmlPluginIndex, 1);
return webpackConfig;
}
function configureDevServer(devServerConfig, { env, paths, proxy, allowedHost }) {
devServerConfig.historyApiFallback = {
disableDotRule: true, //禁用,否则当访问/xxx.html时服务器会自动去掉.html重写url为/xxx
index: paths.publicUrlOrPath,
verbose: true,
};
return devServerConfig;
}
module.exports = {
devServer: configureDevServer,
webpack: {
configure: configureWebpack,
}
};
通过上述代码即可完成多入口改造。具体实践代码可以参考
https://github.com/ikeyit/ikeyit-management-web
假设你有入口/src/entries/your-loved-mp4/index.js,访问http://localhost:3000/your-loved-mp4.html即可打开该入口。
执行编译
yarn build
在build文件夹中可以看到生成了好多js,webpack会把共用和不共用的代码分到不同的包里。
执行命令分析Bundle Size
yarn analyze
(完)
欢迎关注作者的github项目,学习微服务:
一个支持多店铺的电商系统,基于Spring Boot的微服务构架
https://github.com/ikeyit/ikeyit-services
一个基于React的电商管理后台
https://github.com/ikeyit/ikeyit-management-web