react使用cdn加载第三方库

react使用cdn加载第三方库

背景

最近在做网页的性能优化,其中一项优化就是使用CDN优化第三方库可以减小应用的打包体积,从而提高加载速度。

react使用cdn加载第三方库实现步骤

  1. 选择要从CDN加载的第三方库:找到React应用中引用的第三方库,如React、ReactDOM、lodash等,确定哪些库适合从CDN加载,有些库有许多依赖文件,因此需要确保所有这些文件都可以通过 CDN 访问,一些大型库使用 CDN 的方式可能会影响到应用的首次加载速度。

  2. 找到第三方库的CDN链接:许多流行的第三方库都有公共CDN链接。可以从cdnjs、jsDelivr或Google Hosted Libraries等公共CDN中找到对应的链接。

  3. 修改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>
  1. 配置Webpack外部依赖:接下来,需要在Webpack配置中将这些从CDN加载的库设置为外部依赖,以防止它们被打包到你的应用代码中。在webpack.config.js中添加以下配置:
module.exports = {
  ...
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  },
  ...
}

上面的配置表示React和ReactDOM将从全局对象中引用,而不是被打包到应用中。对于其他库,可以类似地添加相应的配置。

  1. 重新打包并运行React应用,在浏览器中访问并确保应用正常工作。使用浏览器的开发者工具查看网络请求,确认第三方库是否从CDN加载。

上面是一个简单实现的例子,现在要基于我的react项目来做实现,我的项目使用了react-app-rewiredcustomize-cra,所以写法有点不一样。

使用react-app-rewiredcustomize-cra实现cdn加载第三方库配置

  1. 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());

  1. 修改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>

  1. 运行npm startnpm run build,查看浏览器的开发者工具,确保React和ReactDOM从CDN加载。

  2. 然后我想在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 改为一个数组来解决这个问题,数组中的元素是一个包含 namesrc 属性的对象,这样就可以控制加载的顺序了。

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>

好了好了,这下应该就齐全了!!✿✿ヽ(°▽°)ノ✿