高性能网站建设指南

当我们在浏览器地址栏输入网址 ,回车,回车这一瞬间到看到页面到底发生了什么呢?

域名解析 –> 发起TCP的3次握手 –> 建立TCP连接后发起http请求 –> 服务器响应http请求,浏览器得到html代码 –> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) –> 浏览器对页面进行渲染呈现给用户

规则一:减少http请求

雪碧图:CSS Sprites,若干小图标拼合成一张图后布局(background-position: -260px -90px;)

雪碧图减少了浏览器的请求次数,但有自己的问题,高清屏会失真,不方便变化。

图标字体,可解决这些问题。

@font-face {
    font-family: "Bitstream Vera Serif Bold";
    src: url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf");
}
  • font-family:设置文本的字体名称。之后可以在定义字体的字体栈中使用这个名称。
  • src:设置自定义字体的相对路径或者绝对路径。 

图标字体优势

  1. 轻量级:一个图标字体要比一系列的图像要小。一旦字体加载了,图标就会马上渲染出来,不需要下载一个个图像。这样可以减少HTTP的请求数量,而且和HTML5的离线存储配合,可以对性能做出优化。

  2. 灵活性:不调字体可以像页面中的文字一样,通过font-size属性来对其进行大小的设置,而且还可以添加各种文字效果,如color、hover、filter、text-shadow、transform等效果。灵活的简直不像话!

  3. 兼容性:图标字体支持现代浏览器,甚至是低版本的IE浏览器,所以可以放心的使用它。

  4. 相比于位图放大图片会出现失真、缩小又会浪费掉像素点,图标字体不会出现这种情况

小图片转base64,可以节省一个http请求

规则三:添加Expires头,缓存图片、样式表、脚本

<<===

Expires头和Cache-Control结合使用

Expires:Wed, 16 Oct 2024 05:42:03 GMT

Cache-Control:max-age=315360000

规则四:压缩组件

===>>Accept-Encoding: gzip, deflate

服务器压缩响应:

<<===Content-Encoding: gzip

规则五:将样式表放在顶部,使用link标签将样式表放在head中

“CSS at the Top”

样式表放如在底部会导致 无样式闪烁FOUC、浏览器行为 白屏。放在顶部,就可以避免这些问题。

规则六:将脚本放在底部,script标签放在底部

脚本放在顶部对Web页面影响:会阻塞后面内容的呈现。阻塞后面组件的下载

规则十一:避免重定向,重定向会使页面变慢

301 moved permancently

302 moved Temporatily

303 see other

304 not modified

规则十三:配置Etag

===>GET

<===Etag: '10c24bc-4ab-457e1c1f'

第二次

===>If-None-Match: '10c24bc-4ab-457e1c1f'

<===304 Not Modified

其他:

使用CDN(Content Delivery Network,内容分发网络)

 

webpack性能优化

参考:https://github.com/wisestcoder/blog/issues/2

1、前言

随着前端的发展,在一个前端项目中,框架和构建工具已经成了编配,而webpack显然已经成了最火热的构架工具之一。React,Vue,angularjs2等诸多知名项目也都选用其作为官方构建工具,极受业内追捧,随者工程开发的复杂程度和代码规模不断地增加,webpack暴露出来的各种性能问题也愈发明显,极大的影响着开发过程中的体验。
2810595339-585b9b07eef9a_articlex

本文旨在分析 webpack 的性能问题,并提供不同的解决方案。

2、性能问题源自何处

  1. 项目体积过大,有时只是一个小改动,但热更新的全量构建导致编译时间出奇的长。
  2. 多个模块之间共用基础资源存在重复打包,代码复用率不高。
  3. 一些具有公共特性的代码没有提取成通用组件。
  4. 一些代码库被打包在项目中,导致项目编译时间太长;而且不利于做缓存。
  5. 图片等静态资源没有走cdn
  6. 单页面项目过大,导致首次加载时间太长

在此我们介绍一款 wepback 的可视化资源分析工具:webpack-visualizer,这款工具可以在webpack构建的时候会自动帮你计算出各个模块在你的项目工程中的依赖与分布情况,方便做更精确的资源依赖和引用的分析。

3、解决方案

我们主要针对不同的性能问题提供不同的解决方案。

3.1 合理去除对一些代码库的构建

image

从上图中我们不难发现大多数的工程项目中,依赖库的体积永远是大头,通常体积可以占据整个工程项目的7-9成,而且在每次开发过程中也会重新读取和编译对应的依赖资源,这其实是很大的的资源开销浪费,而且对编译结果影响微乎其微,毕竟在实际业务开发中,我们很少会去主动修改第三方库中的源码,所以我们需要通过一些方案来抽离代码库。

1. 使用 externals 配置来提取常用库

externals的官方定义是:防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
例如,从 CDN 引入 react ,而不是把它打包:

index.html

...
<script src="https://cdn.bootcss.com/react/15.6.1/react.js"></script>
...

webpack.config.js

externals: {
  react: 'React'
}

这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:

import react from 'react';

简单来说 external 就是把我们的依赖资源声明为一个外部依赖,然后通过 script 外链脚本引入。这也是我们早期页面开发中资源引入的一种翻版,只是通过配置后可以告知 webapck 遇到此类变量名时就可以不用解析和编译至模块的内部文件中,而改用从外部变量中读取,这样能极大的提升编译速度,同时也能更好的利用CDN来实现缓存。

2. 利用 DllPlugin 和 DllReferencePlugin 预编译资源模块

我们的项目依赖中通常会引用大量的npm包,而这些包在正常的开发过程中并不会进行修改,但是在每一次构建过程中却需要反复的将其解析,如何来规避此类损耗呢?这两个插件就是干这个用的。

简单来说 DllPlugin 的作用是预先编译一些模块,而 DllReferencePlugin 则是把这些预先编译好的模块引用起来。这边需要注意的是 DllPlugin必须要在 DllReferencePlugin 执行前先执行一次, dll 这个概念应该也是借鉴了windows程序开发中的 dll 文件的设计理念。

相较于 externals ,DllPlugin 的主要是:

  • 由于 externals 的配置项需要对每个依赖库进行逐个定制,所以每次引入新的代码库的时候都需要手动修改外链的引入,并且在CDN上配置该代码库的资源,过程比较繁琐,而通过 dllPlugin 则能完全通过配置读取,减少维护的成本。
  • DllPlugin 会将多个代码库抽离成一个js资源,可以减少一些 script 标签。

(1) 配置 dllPlugin 对应资源表并编译文件

dll.config.js

const webpack = require('webpack');
const path = require('path');

const vendors = [
    'react',
    'react-dom',
    'react-router'
];

module.exports = {
    output: {
        path: __dirname + '/dist',
        filename: '[name].js',
        library: '[name]',
    },
    entry: {
        lib: vendors,
    },
    plugins: [
        new webpack.DllPlugin({
            path: path.join(__dirname, 'dll', 'manifest.json'),
            name: '[name]',
            context: __dirname,
        }),
    ],
};

然后执行命令:

NODE_ENV=development webpack --config  webpack.dll.lib.js

结果会生成一个 manifest.json 文件和一个 lib.js 文件。

  • manifest.json 记录了webpack中的预编译信息,这样等于提前拿到了依赖库中的chunk信息,在实际开发过程中就无需要进行重复编译。
  • lib.js 就是将配置的代码库编译后生成的文件。

(2) dllPlugin的静态资源引入

生成了 manifest.json 文件和 lib.js 文件之后,我们还要在我们的配置文件中配置 manifest.json,让 webpack 能够不自动编译这些代码库,配置如下:

plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./dll/manifest.json'),
    })
]

注意:如果你有依赖代码库相同的项目,也可以使用同一份 manifest.json 和 lib.js 文件,只需在配置中将manifest.json引入,在 script 标签中引入 lib.js 即可。

3.2 多入口项目合理提取出公共代码

当项目的入口很多,但是入口文件存在一些公共代码,对所有依赖的chunk进行公共部分的提取的必要性就会发挥出来。

  1. 默认会把所有入口节点的公共代码提取出来, 生成一个common.js
new webpack.optimize.CommonsChunkPlugin('common.js')
  1. 有选择的提取公共代码
new webpack.optimize.CommonsChunkPlugin('common.js',['entry1','entry2']);
  1. 指定模块必须被入口chunk 共享的数目
new webpack.optimize.CommonsChunkPlugin({
  name: "commons",
  minChunks: 3
  filename: "commons.js"
})
  1. 抽取enry中的一些lib抽取到vendors中
entry = {
    vendors: ['fetch', 'loadash']
};
new webpack.optimize.CommonsChunkPlugin({
    name: "vendors",
    minChunks: Infinity
});

3.3 单页面应用合理分割代码、按需加载

现在很多项目都采用单页面开发,特别是一些移动端的网站;但是当网站规模越来越大的时候,首先出现的问题是 Javascript 文件变得巨大,这导致首页渲染的时间让人难以忍受。实际上程序应当只加载当前渲染页所需的 JavaScript,也就是大家说的“代码分拆" — 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。
通过代码分割,我们得到的效果如下:

分割之前的页面

01

分割之后的效果

02

可以很清楚的看到,我们将一个大的js文件拆分成了若干个chunk文件。

我们项目的结构如下:

page
├── home
│   ├── home.js
│   ├── home.scss
├── guide
│   ├── guide.js
│   ├── guide.scss
└── more
│   ├── more.js
│   └── more.scss
└── app.js

按需加载之后,我们需要对Route进行改造,我们将component方法替换成getComponent,让路由去动态的加载组件。
app.js是项目入口,配置如下:

const rootRoute = {
  indexRoute: {
    getComponent(nextState, cb) {
      require.ensure([], (require) => {
        cb(null, require('./home'))
      })
    }
  },
  getComponent(nextState, cb)  {
    require.ensure([], (require) => {
      cb(null, require('./index'))
    })
  },
  path: '/',
  childRoutes: [
    require('./guide'),
    require('./more')
  ]
}

render((
  <Router
    history={hashHistory}
    routes={rootRoute}
  />
), document.getElementById('app'))

此处有四个属性:

path

将匹配的路由,也就是以前的 path。

getComponent

对应于以前的 component 属性,但是这个方法是异步的,也就是当路由匹配时,才会调用这个方法。
这里面有个 require.ensure 方法

require.ensure(dependencies, callback, chunkName)

这是 webpack 提供的方法,这也是按需加载的核心方法。第一个参数是依赖,第二个是回调函数,第三个就是上面提到的 chunkName,用来指定这个 chunk file 的 name。

如果需要返回多个子组件,则使用 getComponents 方法,将多个组件作为一个对象的属性通过 cb 返回出去即可。这个在官方示例也有,但是我们这里并不需要,而且根组件是不能返回多个子组件的,所以使用 getComponent

indexRoute

indexRoute用来显示默认路由,不需要进行按需加载。

childRoutes

这里面放置的就是子路由的配置,这里的子路由都应该是按需加载的。

我们还需要在子路由中进行配置。

home.js

module.exports = require('./home');

由于home是默认的路由,所以不需要进行按需加载

guide.js

module.exports = {  
  path: '/guide',
  getComponent(nextState, cb) {
    require.ensure([], (require) => {
      cb(null, require('./guide'))
    })
  }
}

more.js

module.exports = {  
  path: '/more',
  getComponent(nextState, cb) {
    require.ensure([], (require) => {
      cb(null, require('./more'))
    })
  }
}

项目经过webpack打包之后,会生成包含子路由的chunk文件,并且在路由切换的时候进行按需加载。

3.4 加快代码压缩速度

UglifyJsPlugin 凭借基于node开发,压缩比例高,使用方便等诸多优点已经成为了js压缩工具中的首选,但是我们在webpack的构建中观察发现,当webpack build进度走到80%前后时,会发生很长一段时间的停滞,经测试对比发现这一过程正是 UglifyJsPlugin 在对我们的 output 中的 bunlde 部分进行压缩耗时过长导致,针对这块我们推荐使用webpack-uglify-parallel来提升压缩速度。

webpack-uglify-parallel 的实现原理是采用了多核并行压缩的方式来提升我们的压缩速度。

使用配置也非常简单,只需要将我们原来webpack中自带的 UglifyJsPlugin 配置:

new webpack.optimize.UglifyJsPlugin({
   exclude:/\.min\.js$/
   mangle:true,
   compress: { warnings: false },
   output: { comments: false }
})

修改成如下代码即可:

const os = require('os');
const UglifyJsParallelPlugin = require('webpack-uglify-parallel');
new UglifyJsParallelPlugin({
   workers: os.cpus().length,
   mangle: true,
   compressor: {
      warnings: false,
      drop_console: true,
      drop_debugger: true
   }
})

3.5 让loader多进程地去处理文件

happypack 的原理是让loader可以多进程去处理文件,原理如图示:

68747470733a2f2f73692e6765696c6963646e2e636f6d2f687a5f696d675f303864623030303030313561333536326361363830613032363836305f3830305f3438365f756e61646a7573742e706e67

此外,happypack同时还利用缓存来使得rebuild 更快

var HappyPack = require('happypack'),
  os = require('os'),
  happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

modules: {
	loaders: [
	  {
        test: /\.js|jsx$/,
        loader: 'HappyPack/loader?id=jsHappy',
        exclude: /node_modules/
      }
	]
}

plugins: [
    new HappyPack({
      id: 'jsHappy',
      cache: true,
      threadPool: happyThreadPool,
      loaders: [{
        path: 'babel',
        query: {
          cacheDirectory: '.webpack_cache',
          presets: [
            'es2015',
            'react'
          ]
        }
      }]
    }),
    //如果有单独提取css文件的话
    new HappyPack({
      id: 'lessHappy',
      loaders: ['style','css','less']
    })
  ]

4、结尾

性能优化无小事,追求快没有止境,在前端工程日益庞大复杂的今天,针对实际项目,持续改进构建工具的性能,对项目开发效率的提升和工具深度理解都是极其有益的。

5、参考文章

webpack中文官方博客

react-router 按需加载

webpack中文站

happypack

yumu脚手架

 


 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
高性能网站建设指南 “如果实现了Steve这些建议中的20%,你的站点就能出现戏剧性的变化。有了这本书和YSlow扩展,实在是没有理由再构建出运行速度缓慢的网站了。”         ——Joe Hewitt,Firebu9调试器和Mozilla的DOM解释器的开发者   “Steve Souders完成了一项神奇的工作,他提出了一系列简明的、可操作的,并且注重实效的开发步骤,使Web性能世界发生了翻天覆地的改变。”         ——Eric Lawrence,微软Fiddler Web Debugger开发者   想让你的网站显示得更快?本书提供了14种规则,可以使用户在请求页面时减少20%~25%的响应时间。作为Chief Performance Yahoo!,作者Steve Souders收集了在优化网络时访问最多的页面积累下来的最佳实践。即使网站已经进行了高度的优化——如Yahoo!Search和Yahoo!首页——我们也能从这些出奇简单的性能规则中获益。   《高性能网站建设指南》一书介绍了如何针对网站中的Ajax、CSS、JavaScript、Flash和图片进行性能优化。每个性能规则都提供了示例,在本书的配套网站上可以找到代码片段。这些规则包括:   •减少HTTP请求     •使用外部JavaScript和CSS   •使用内容发布网络   •减少DNS查找   •添力Expires头     •精简JavaScript   •压缩组件       •避免重定向   •将样式表放在顶部 •移除重复脚本   •将脚本放在底部   •配置ETag   •避免CSS表达式   •使Ajax可缓存   如果你希望构建高流量页面,并且改善用户访问网站的体验,那么本书是你不可或缺之物。   Steve Souders,Chief Performance Yahoo!,为Yahoo!的其他产品团队开发了性能分析工具,并致力于推广这些最佳实践和工具。在加盟Yahoo!之前,Stevee曾就职于多家中小型公司,其中有两家还是他协办的——Helix Systems和CoolSync。他是斯坦福大学管理科学与工程硕士。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值