【umi】 从umi讨论区学到一些性能优化

485 阅读2分钟

11884 得到一些启示

image.png

优化

1、 关于国际化优化

国际化是常见的需求, json文件可能会很大,采用动态加载的方式就会从umi.js的文件分出来,让umi.js的体积看起来没有那么大。

原先我们使用也是umi的国际化,后面因为需要从后端拉取数据,改成了i18next方案

2、图表或者编辑器, 组件手动懒加载出去?但是umi已经按页拆包了

3、 代码分割

-   类型:`{ jsStrategy: 'bigVendors' | 'depPerChunk' | 'granularChunks'; jsStrategyOptions: {} }`
-   默认值:`null`

本示例依赖安装

image.png

  • bigVendors 是大 vendors 方案,会将 async chunk 里的 node_modules 下的文件打包到一起,可以避免重复。同时缺点是,1)单文件的尺寸过大,2)毫无缓存效率可言。

image.png

image.png

源码 packages/preset-umi/src/features/codeSplitting/codeSplitting.ts

  // 大 vendors 方案
    if (jsStrategy === 'bigVendors') {
      memo.optimization.splitChunks({
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            name: 'vendors',
            chunks: 'async',
            ...jsStrategyOptions,
          },
        },
      });
    }
  • depPerChunk 和 bigVendors 类似,不同的是把依赖按 package name + version 进行拆分,算是解了 bigVendors 的尺寸和缓存效率问题。但同时带来的潜在问题是,可能导致请求较多。我的理解是,对于非大型项目来说其实还好,因为,1)单个页面的请求不会包含非常多的依赖,2)基于 HTTP/2,几十个请求不算问题。但是,对于大型项目或巨型项目来说,需要考虑更合适的方案。

image.png

是不是只有直接依赖呀

   // 按 package name + version 进行拆分
    if (jsStrategy === 'depPerChunk') {
      memo.optimization.splitChunks({
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            chunks: 'async',
            name(module: any) {
              // e.g. node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es
              const path = module.context.replace(/.pnpm[\\/]/, '');
              const match = path.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/);
              if (!match) return 'npm.unknown';
              const packageName = match[1];
              return `npm.${packageName
                .replace(/@/g, '_at_')
                .replace(/\+/g, '_')}`;
            },
          },
        },
      });
    }
  • granularChunks 在 bigVendors 和 depPerChunk 之间取了中间值,同时又能在缓存效率上有更好的利用。无特殊场景,建议用 granularChunks 策略。

image.png

    // 结合了大vendor 和 按 package name + version 进行拆分两种方案, 果然复杂些
    if (jsStrategy === 'granularChunks') {
      const FRAMEWORK_BUNDLES = [
        'react-dom',
        'react',
        // 'core-js',
        // 'regenerator-runtime',
        'history',
        'react-router',
        'react-router-dom',
        'scheduler',
        // TODO
        // add renderer-react
      ];
      memo.optimization.splitChunks({
        cacheGroups: {
          default: false,
          defaultVendors: false,
          framework: {
            name: 'framework',
            chunks: 'all',
            test: new RegExp(
              `[\\\\/]node_modules[\\\\/](${FRAMEWORK_BUNDLES.join(
                `|`,
              )})[\\\\/]`,
            ),
            priority: 40,
            enforce: true,
          },
          lib: {
            test(module: any) {
              return (
                !isModuleCSS(module) &&
                module.size() > 160000 &&
                /node_modules[/\\]/.test(module.identifier())
              );
            },
            name(module: any) {
              const rawRequest =
                module.rawRequest &&
                module.rawRequest.replace(/^@(\w+)[/\\]/, '$1-');
              if (rawRequest) {
                return `${
                  // when `require()` a package with relative path,
                  // need remove leading `.` and `/`, otherwise will not found `.js` file
                  // e.g. require('../../lib/codemirror')
                  rawRequest.replace(/\./g, '_').replace(/\//g, '-')
                }-lib`;
              }

              const identifier = module.identifier();
              const trimmedIdentifier = /(?:^|[/\\])node_modules[/\\](.*)/.exec(
                identifier,
              );
              const processedIdentifier =
                trimmedIdentifier &&
                trimmedIdentifier[1].replace(/^@(\w+)[/\\]/, '$1-');

              return `${processedIdentifier || identifier}-lib`;
            },
            priority: 30,
            minChunks: 1,
            reuseExistingChunk: true,
            chunks: 'async',
          },
          shared: {
            name(_module: any, chunks: any) {
              const cryptoName = crypto
                .createHash('sha1')
                .update(
                  chunks.reduce((acc: any, chunk: any) => {
                    return acc + chunk.name;
                  }, ''),
                )
                .digest('base64')
                // replace `+=/` that may be escaped in the url
                // https://github.com/umijs/umi/issues/9845
                .replace(/\//g, '')
                .replace(/\+/g, '-')
                .replace(/=/g, '_');
              return `shared-${cryptoName}`;
            },
            priority: 10,
            minChunks: 2,
            reuseExistingChunk: true,
            chunks: 'async',
          },
        },
      });
    }

4、 把固定依赖变成 umd external 加载

好奇这种优化方案用的人多不?因为大家会担心节点不稳定,容易挂,反正说是这么说,没看过用过

externals: isProd ? {
   react: 'window.React',
   'react-dom': 'window.ReactDOM'
 } : {},
 scripts: isProd ? ['...', '...'] : []

上文说的编辑器组件也可以的吧

总结

其实关于优化还有很多,代码分割和splitchunks是常见的减少包体积的手段,还有比如ssr、图片webp等等这些,umi自己也说过,如果是to c,建议用next 框架,umi 是面向中后台的框架

参考