react使用cdn加载第三方库
背景
最近在做网页的性能优化,其中一项优化就是使用CDN优化第三方库可以减小应用的打包体积,从而提高加载速度。
react使用cdn加载第三方库实现步骤
-
选择要从CDN加载的第三方库:找到React应用中引用的第三方库,如React、ReactDOM、lodash等,确定哪些库适合从CDN加载,有些库有许多依赖文件,因此需要确保所有这些文件都可以通过 CDN 访问,一些大型库使用 CDN 的方式可能会影响到应用的首次加载速度。
-
找到第三方库的CDN链接:许多流行的第三方库都有公共CDN链接。可以从cdnjs、jsDelivr或Google Hosted Libraries等公共CDN中找到对应的链接。
-
修改HTML模板:在React应用的HTML模板中,如
public/index.html
,将第三方库的CDN链接添加到<head>
部分。例如,如果要从CDN加载React和ReactDOM库,可以这样添加:
<head>
...
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
...
</head>
- 配置Webpack外部依赖:接下来,需要在Webpack配置中将这些从CDN加载的库设置为外部依赖,以防止它们被打包到你的应用代码中。在webpack.config.js中添加以下配置:
module.exports = {
...
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
...
}
上面的配置表示React和ReactDOM将从全局对象中引用,而不是被打包到应用中。对于其他库,可以类似地添加相应的配置。
- 重新打包并运行React应用,在浏览器中访问并确保应用正常工作。使用浏览器的开发者工具查看网络请求,确认第三方库是否从CDN加载。
上面是一个简单实现的例子,现在要基于我的react项目来做实现,我的项目使用了react-app-rewired
和customize-cra
,所以写法有点不一样。
使用react-app-rewired
和customize-cra
实现cdn加载第三方库配置
- 在
config-overrides.js
中配置CDN并使用customize-cra
:
const { override, addWebpackPlugin } = require('customize-cra');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const cdnConfig = {
react: 'https://unpkg.com/react@17/umd/react.production.min.js',
reactDOM: 'https://unpkg.com/react-dom@17/umd/react-dom.production.min.js'
};
function setExternals() {
return (config) => {
config.externals = {
'react': 'React',
'react-dom': 'ReactDOM'
};
return config;
}
}
function updateHtmlWebpackPlugin() {
return (config) => {
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
...config.plugins.find(plugin => plugin.constructor.name === 'HtmlWebpackPlugin').options,
cdnConfig
});
config.plugins = config.plugins.map(plugin =>
plugin.constructor.name === 'HtmlWebpackPlugin' ? HtmlWebpackPluginConfig : plugin
);
return config;
}
}
module.exports = override(setExternals(), updateHtmlWebpackPlugin());
- 修改
public/index.html
,添加CDN链接:
<head>
...
<% if (htmlWebpackPlugin.options.cdnConfig) { %>
<script crossorigin src="<%= htmlWebpackPlugin.options.cdnConfig.react %>"></script>
<script crossorigin src="<%= htmlWebpackPlugin.options.cdnConfig.reactDOM %>"></script>
<% } %>
...
</head>
-
运行
npm start
或npm run build
,查看浏览器的开发者工具,确保React和ReactDOM从CDN加载。 -
然后我想在public/index.html文件里面实现动态加载,不需要每次都手动去新增cdn链接,所以对
public/index.html
文件进行一些改造:
<head>
...
<% if (htmlWebpackPlugin.options.cdnConfig) { %>
<% for (var key in htmlWebpackPlugin.options.cdnConfig) { %>
<script crossorigin src="<%= htmlWebpackPlugin.options.cdnConfig[key] %>"> </script>
<% } %>
<% } %>
...
</head>
然后问题来了!!这里有个坑!!
部署上去后控制台报错ReactDOM is undefined
,而且是偶发性的,有时候打开又是正常的。
排查问题后发现:可能是由于React和ReactDOM库的加载顺序问题导致的。在之前的 cdnConfig
中,对象的属性是无序的,所以 index.html
文件中的循环可能会在加载ReactDOM之前就加载React,这会导致报错。
所以通过将 cdnConfig
改为一个数组来解决这个问题,数组中的元素是一个包含 name
和 src
属性的对象,这样就可以控制加载的顺序了。
config-overrides.js
const cdnConfig = [
{name: 'react', src: 'https://unpkg.com/react@17/umd/react.production.min.js'},
{name: 'reactDOM', src: 'https://unpkg.com/react-dom@17/umd/react-dom.production.min.js'},
];
function setExternals() {
return (config) => {
config.externals = {
'react': 'React',
'react-dom': 'ReactDOM',
};
return config;
}
}
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
...config.plugins.find(plugin => plugin.constructor.name === 'HtmlWebpackPlugin').options,
cdnConfig
});
然后在 public/index.html
文件中,通过循环来加载这些库:
<head>
...
<% if (htmlWebpackPlugin.options.cdnConfig) { %>
<% htmlWebpackPlugin.options.cdnConfig.forEach(function(lib) { %>
<script crossorigin src="<%= lib.src %>"></script>
<% }) %>
<% } %>
...
</head>
好了,应该就搞定了。ヾ(✿゚▽゚)ノ
区分环境处理
哦吼,不对,还不行,我们的cdn资源是放在对象存储系统里面来管理,所以需要外部路径变为内部路径,再处理一下,区分一下生产开发环境。
config-overrides.js
// src 替换成内部资源路径
const cdnConfig = [
{name: 'react', src: '/react.production.min.js'},
{name: 'reactDOM', src: '/react-dom.production.min.js'},
];
// 判断是否是生产环境
function setExternals() {
return (config) => {
if (process.env.NODE_ENV === "production") {
config.externals = {
"react": "React",
"react-dom": "ReactDOM",
"lodash": "_",
"axios": "axios",
"echarts": "echarts"
};
}
return config;
};
}
function updateHtmlWebpackPlugin() {
return (config) => {
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
...config.plugins.find(plugin => plugin.constructor.name ===
"HtmlWebpackPlugin").options,
cdnConfig: process.env.NODE_ENV === "production"
? cdnConfig
: undefined
});
config.plugins = config.plugins.map(plugin =>
plugin.constructor.name === "HtmlWebpackPlugin"
? HtmlWebpackPluginConfig
: plugin
);
return config;
};
}
public/index.html
<head>
...
<% if (process.env.NODE_ENV === 'production' && htmlWebpackPlugin.options.cdnConfig) { %>
<% htmlWebpackPlugin.options.cdnConfig.forEach(function(lib) { %>
<script crossorigin src="<%= lib.src %>"></script>
<% }) %>
<% } %>
...
</head>
好了好了,这下应该就齐全了!!✿✿ヽ(°▽°)ノ✿