Skip to content

静态资源优化

字体

体积优化

字体包往往包含多个字符,但项目中只需要用到部分字体,通过插件取出需要的字体

安装字蛛

shell
npm i -g font-spider-plus
html
<div>
  哈哈
</div>

<style>
  @font-face {
    font-family: 'font';
    src: url('./fonts/TencentSans-W7.ttf') format('truetype');
  }
  div  {
    font-family:font;
  }
</style>

执行生成

js
fsp local index.html

加载时优化

在字体加载时,字是不会被显示出来的,带来糟糕的用户体验

  1. 在加载前使用默认字体显示
css
@font-face {
    font-display: swap; 
}
  1. 使用 base64 内连
js
@font-face {     
    font-family: 'TencentSansW7';     
    src: url('data:application/x-font-woff;charset=utf-8;base64,d09G…') format('woff2'); 
}

图片

SVG

使用 SVGO 压缩 svg

shell
brew install svgo
svgo *.svg

webp

减少图片体积

  1. 使用智图压缩 png 图片,并生成 webp 文件‘
  2. 使用 imagemin-webp-webpack-plugin 将图片压缩为 webp
js
const ImageminWebpWebpackPlugin= require("imagemin-webp-webpack-plugin");

module.exports = {
	plugins: [new ImageminWebpWebpackPlugin(
		{
			config: [{
				test: /\.(jpe?g|png)/,
				options: {
					quality:  75
				}
			}],
			overrideExtension: true, // 覆盖原文件
			detailedLogs: false, // 日志
			silent: false, // 日志
			strict: true // 严格匹配
		}
	)]
};

判断 webp 是否可用

less
.useWepb(@url) {
  background-image: url(@url);
  .webp & {
     background-image: url('@{url}.webp');
  }
}

// 使用时
.header{
  .useWepb('../image/header.jpg');
}

gif

使用 video 代替 gif,可以减少资源体积

  1. 使用 ffmpeg 将 gif 压缩成 mp4、webm
shell
ffmpeg -i my-animation.gif -b:v 0 -crf 25 -f mp4 -vcodec libx264 -pix_fmt yuv420p my-animation.mp4
shell
ffmpeg -i my-animation.gif -c vp9 -b:v 0 -crf 41 my-animation.webm
  1. 使用 video 标签代替,自动播放、循环、静音、内嵌
html
<video autoplay loop muted playsinline>
  <source src="my-animation.webm" type="video/webm">
  <source src="my-animation.mp4" type="video/mp4">
</video>

CSS

谨慎使用 @import,会导致 css 串行加载

JS

Tree Shaking

对于无副作用,且导出了但没有使用的,会被去除,不引入打包文件中

副作用:导入时会执行代码,无不只是 export 模块

js
// demo.js
export function a() {
  return 'a'
}

export function b() {
  return 'b'
}

// main.js
import { a } from "demo.js"
// b 不会被打包

生产环境 会自动开启

@Babel/plugin-transform-runtime

以下代码会被编译成

js
class Person{}
js
function _instanceof(left, right) {
  if (right != null && typeof Symbol !== "undefined" &&   right[Symbol.hasInstance]) {
    return !!right[Symbol.hasInstance](left);
  }
  else {
    return left instanceof right;
  }
}
function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); }
}
var Person = function Person() {
  _classCallCheck(this, Person);
};

可以看到 _instanceof_classCallCheck 都是 Babel 内置的 helpers 函数。如果每个 class 编译结果都在代码中植入这些 helpers 具体内容,对产出代码体积就会有明显恶化影响

启用插件后

json
{
  "plugins": [
    "@babel/plugin-transform-runtime",
  ]
}
js
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var Person = function Person() {   
    (0, _classCallCheck2.default)(this, Person); 
};

公用函数会被单独打包到一个模块里

babel 按需引入

json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage", // 按需引入
      },
    ]
  ],
}

代码切割

http2 普及后,更倾向于多文件,单个文件更小,提高缓存命中率,减少总体积

  1. 对于多 entry 的应用可以再抽取出公共的方法,通过 minChunks 来设定,比如超过两个引用就抽取到 commons 中。
  2. initial 的模块按照项目实际情况手动划分为几个部分(组件库,框架依赖React,Vue等),因为考虑到他们更新频率比较低,最大化缓存命中率
  3. async 的模块设定一个合适的 minSize 即可,使得文件大小和个数最优
js
splitChunks: {
  cacheGroups: {
    lib: {
      // 项目基本框架
      chunks: 'initial',
      test: /(react|react-dom|react-dom-router)/,
      name: 'lib',
    },
    components: {
      // 组件库
      chunks: 'initial',
      test: /(antd)/,
      name: 'components',
      reuseExistingChunk: true,
    },
    commons: {
      // 其他同步加载公共包
      chunks: 'initial',
      minChunks: 2,
      name: 'commons',
    },
    'async-commons': {
      // 异步加载公共包、组件等
      chunks: 'async',
      minSize: 30000,
      reuseExistingChunk: true,
    },
  },
}