webpack2终极优化-阿里云开发者社区

开发者社区> 行者武松> 正文

webpack2终极优化

简介:
+关注继续查看

webpack2终极优化


webpack是当下最流行的js打包工具,这得益于网页应用日益复杂和js模块化的流行。webpack2增加了一些新特性也正式发布了一段时间,是时候告诉大家如何用webpack2优化你的构建让它构建出更小的文件尺寸和更好的开发体验。

优化输出

打包结果更小可以让网页打开速度更快以及简约宽带。可以通过这以下几点做到

压缩css

css-loader 在webpack2里默认是没有开启压缩的,最后生成的css文件里有很多空格和tab,通过配置

css-loader?minimize参数可以开启压缩输出最小的css。css的压缩实际是是通过cssnano实现的。

tree-shaking

tree-shaking 是指借助es6 import export 语法静态性的特点来删掉export但是没有import过的东西。要让tree-shaking工作需要注意以下几点:

  • 配置babel让它在编译转化es6代码时不把import export转换为cmd的module.export,配置如下:

  1. "presets": [ 
  2.  
  3.     [ 
  4.  
  5.       "es2015"
  6.  
  7.       { 
  8.  
  9.         "modules"false 
  10.  
  11.       } 
  12.  
  13.     ] 
  14.  
  15. ]  
  • 大多数分布到npm的库里的代码都是es5的,但是也有部分库(redux,react-router等等)开始支持tree-shaking。这些库发布到npm里的代码即包含es5的又包含全采用了es6 import export 语法的代码。

拿redux库来说,npm下载到的目录结构如下:


  1. ├── es 
  2.  
  3. │   └── utils 
  4.  
  5. ├── lib 
  6.  
  7. │   └── utils  

其中lib目录里是编译出的es5代码,es目录里是编译出的采用import export 语法的es5代码,在redux的package.json文件里有这两个配置:


  1. main": "lib/index.js", 
  2.  
  3. "jsnext:main""es/index.js",  

这是指这个库的入口文件的位置,所以要让webpack去读取es目录下的代码需要使用jsnext:main字段配置的入口,要做到这点webpack需要这样配置:


  1. module.exports = { 
  2.  
  3. resolve: { 
  4.  
  5.             mainFields: ['jsnext:main','main'], 
  6.  
  7.         } 
  8.  
  9. };  

这会让webpack先使用jsnext:main字段,在没有时使用main字段。这样就可以优化支持tree-shaking的库。

优化 UglifyJsPlugin

webpack --optimize-minimize 选项会开启 UglifyJsPlugin来压缩输出的js,但是默认的UglifyJsPlugin配置并没有把代码压缩到最小输出的js里还是有注释和空格,需要覆盖默认的配置:


  1. new UglifyJsPlugin({ 
  2.  
  3.     // 最紧凑的输出 
  4.  
  5.     beautify: false
  6.  
  7.     // 删除所有的注释 
  8.  
  9.     comments: false
  10.  
  11.     compress: { 
  12.  
  13.       // 在UglifyJs删除没有用到的代码时不输出警告   
  14.  
  15.       warnings: false
  16.  
  17.       // 删除所有的 `console` 语句 
  18.  
  19.       // 还可以兼容ie浏览器 
  20.  
  21.       drop_console: true
  22.  
  23.       // 内嵌定义了但是只用到一次的变量 
  24.  
  25.       collapse_vars: true
  26.  
  27.       // 提取出出现多次但是没有定义成变量去引用的静态值 
  28.  
  29.       reduce_vars: true
  30.  
  31.     } 
  32.  
  33. })  

定义环境变量 NODE_ENV=production

很多库里(比如react)有部分代码是这样的:


  1. if(process.env.NODE_ENV !== 'production'){ 
  2.  
  3. // 不是生产环境才需要用到的代码,比如控制台里看到的警告     
  4.  
  5. }  

在环境变量 NODE_ENV 等于 production 的时候UglifyJs会认为if语句里的是死代码在压缩代码时删掉。

使用 CommonsChunkPlugin 抽取公共代码

CommonsChunkPlugin可以提取出多个代码块都依赖的模块形成一个单独的模块。要发挥CommonsChunkPlugin的作用还需要浏览器缓存机制的配合。在应用有多个页面的场景下提取出所有页面公共的代码减少单个页面的代码,在不同页面之间切换时所有页面公共的代码之前被加载过而不必重新加载。这个方法可以非常有效的提升应用性能。

在生产环境按照文件内容md5打hash

webpack编译在生产环境出来的js、css、图片、字体这些文件应该放到CDN上,再根据文件内容的md5命名文件,利用缓存机制用户只需要加载一次,第二次加载时就直接访问缓存。如果你之后有修改就会为对应的文件生产新的md5值。做到以上你需要这样配置:


  1.  
  2.   output: { 
  3.  
  4.     publicPath: CND_URL, 
  5.  
  6.     filename: '[name]_[chunkhash].js'
  7.  
  8.   }, 
  9.  
  10. }  

知道以上原理后我们还可以进一步优化:利用CommonsChunkPlugin提取出使用页面都依赖的基础运行环境。比如对于最常见的react体系你可以抽出基础库react react-dom redux react-redux到一个单独的文件而不是和其它文件放在一起打包为一个文件,这样做的好处是只要你不升级他们的版本这个文件永远不会被刷新。如果你把这些基础库和业务代码打包在一个文件里每次改动业务代码都会导致浏览器重复下载这些包含基础库的代码。以上的配置为:


  1. // vender.js 文件抽离基础库到单独的一个文件里防止跟随业务代码被刷新 
  2.  
  3. // 所有页面都依赖的第三方库 
  4.  
  5. // react基础 
  6.  
  7. import 'react'
  8.  
  9. import 'react-dom'
  10.  
  11. import 'react-redux'
  12.  
  13. // redux基础 
  14.  
  15. import 'redux'
  16.  
  17. import 'redux-thunk';  

  1. // webpack配置 
  2.  
  3.  
  4.   entry: { 
  5.  
  6.     vendor: './path/to/vendor.js'
  7.  
  8.   }, 
  9.  
  10. }  

DedupePlugin 和 OccurrenceOrderPlugin

在webpack1里经常会使用 DedupePlugin 插件来消除重复的模块以及使用 OccurrenceOrderPlugin 插件让被依赖次数更高的模块靠前分到更小的id 来达到输出更少的代码,在webpack2里这些已经这两个插件已经被移除了因为这些功能已经被内置了。

除了压缩文本代码外还可以:

  • 用imagemin-webpack-plugin 压缩图片
  • 用webpack-spritesmith 合并雪碧图
  • 对于支持es6的js运行环境使用babili

以上优化点只需要在构建用于生产环境代码的时候才使用,在开发环境时最好关闭因为它们很耗时。

优化开发体验

优化开发体验主要从更快的构建和更方便的功能入手。

更快的构建

缩小文件搜索范围

webpack的resolve.modules配置模块库(通常是指node_modules)所在的位置,在js里出现import 'redux'这样不是相对也不是绝对路径的写法时会去node_modules目录下找。但是默认的配置会采用向上递归搜索的方式去寻找node_modules,但通常项目目录里只有一个node_modules在项目根目录,为了减少搜索我们直接写明node_modules的全路径:


  1. module.exports = { 
  2.  
  3.     resolve: { 
  4.  
  5.         modules: [path.resolve(__dirname, 'node_modules')] 
  6.  
  7.     } 
  8.  
  9. };  

除此之外webpack配置loader时也可以缩小文件搜索范围。

  • loader的test正则表达式也应该尽可能的简单,比如在你的项目里只有.js文件时就不要把test写成/\.jsx?$/
  • loader使用include命中只需要处理的文件,比如babel-loader的这两个配置:

只对项目目录下src目录里的代码进行babel编译


  1.  
  2.     test: /\.js$/, 
  3.  
  4.     loader: 'babel-loader'
  5.  
  6.     include: path.resolve(__dirname, 'src'
  7.  
  8. }  

项目目录下的所有js都会进行babel编译,包括庞大的node_modules下的js


  1.  
  2.     test: /\.js$/, 
  3.  
  4.     loader: 'babel-loader' 
  5.  
  6. }  

开启 babel-loader 缓存

babel编译过程很耗时,好在babel-loader提供缓存编译结果选项,在重启webpack时不需要创新编译而是复用缓存结果减少编译流程。babel-loader缓存机制默认是关闭的,打开的配置如下:


  1. module.exports = { 
  2.  
  3.     module: { 
  4.  
  5.          loaders: [{ 
  6.  
  7.                 test: /\.js$/, 
  8.  
  9.                 loader: 'babel-loader?cacheDirectory'
  10.  
  11.          }] 
  12.  
  13.   } 
  14.  
  15. };  

使用 alias

resolve.alias 配置路径映射。

发布到npm的库大多数都包含两个目录,一个是放着cmd模块化的lib目录,一个是把所有文件合成一个文件的dist目录,多数的入口文件是指向lib里面下的。

默认情况下webpack会去读lib目录下的入口文件再去递归加载其它依赖的文件这个过程很耗时,alias配置可以让webpack直接使用dist目录的整体文件减少文件递归解析。配置如下:


  1. module.exports = { 
  2.  
  3.   resolve: { 
  4.  
  5.     alias: { 
  6.  
  7.       'moment''moment/min/moment.min.js'
  8.  
  9.       'react''react/dist/react.js'
  10.  
  11.       'react-dom''react-dom/dist/react-dom.js' 
  12.  
  13.     } 
  14.  
  15.   } 
  16.  
  17. };  

使用 noParse

module.noParse 配置哪些文件可以脱离webpack的解析。

有些库是自成一体不依赖其他库的没有使用模块化的,比如jquey、momentjs、chart.js,要使用它们必须整体全部引入。

webpack是模块化打包工具完全没有必要去解析这些文件的依赖,因为它们都不依赖其它文件体积也很庞大,要忽略它们配置如下:


  1. module.exports = { 
  2.  
  3.   module: { 
  4.  
  5.     noParse: /node_modules\/(jquey|moment|chart\.js)/ 
  6.  
  7.   } 
  8.  
  9. };  

除此以外还有很多可以加速的方法:

  • 使用happypack多进程并行构建
  • 使用DllPlugin复用模块

更方便的功能

模块热替换

模块热替换是指在开发的过程中修改代码后不用刷新页面直接把变化的模块替换到老模块让页面呈现出最新的效果。

webpack-dev-server内置模块热替换,配置起来也很方便,下面以react应用为例,步骤如下:

  • 在启动webpack-dev-server的时候带上--hot参数开启模块热替换,在开启--hot后针对css的变化是会自动热替换的,但是js涉及到复杂的逻辑还需要进一步配置。
  • 配置页面入口文件

  1. import App from './app'
  2.  
  3.   
  4.  
  5. function run(){ 
  6.  
  7. render(<App/>,document.getElementById('app')); 
  8.  
  9.  
  10. run(); 
  11.  
  12.   
  13.  
  14. // 只在开发模式下配置模块热替换 
  15.  
  16. if (process.env.NODE_ENV !== 'production') { 
  17.  
  18.   module.hot.accept('./app', run); 
  19.  
  20. }  

当./app发生变化或者当./app依赖的文件发生变化时会把./app编译成一个模块去替换老的,替换完毕后重新执行run函数渲染出最新的效果。

自动生成html

webpack只做了资源打包的工作还缺少把这些加载到html里运行的功能,在庞大的app里手写html去加载这些资源是很繁琐易错的,我们需要自动正确的加载打包出的资源。

webpack原生不支持这个功能于是我做了一个插件 web-webpack-plugin

具体使用点开链接看详细文档,使用大概如下:

demo(https://github.com/gwuhaolin/web-webpack-plugin/tree/master/demo/out-html)

webpack配置


  1. module.exports = { 
  2.  
  3.     entry: { 
  4.  
  5.         A: './a'
  6.  
  7.         B: './b'
  8.  
  9.     }, 
  10.  
  11.     plugins: [ 
  12.  
  13.         new WebPlugin({ 
  14.  
  15.             // 输出的html文件名称,必填,注意不要重名,重名会覆盖相互文件。 
  16.  
  17.             filename: 'index.html'
  18.  
  19.             // 该html文件依赖的entry,必须是一个数组。依赖的资源的注入顺序按照数组的顺序。 
  20.  
  21.             requires: ['A''B'], 
  22.  
  23.         }), 
  24.  
  25.     ] 
  26.  
  27. };  

将会输出一个index.html文件,这个文件将会自动引入 entry A 和 B 生成的js文件,

输出的html:


  1. <html> 
  2.  
  3. <head> 
  4.  
  5.     <meta charset="UTF-8"
  6.  
  7. </head> 
  8.  
  9. </body> 
  10.  
  11. <script src="A.js"></script> 
  12.  
  13. <script src="B.js"></script> 
  14.  
  15. </body> 
  16.  
  17. </html>  

输出的目录结构


  1. ├── A.js 
  2.  
  3. ├── B.js 
  4.  
  5. └── index.html  

管理多页面

虽然webpack适用于单页应用,但复杂的系统经常是由多个单页应用组成,每个页面一个功能模块。webpack给出了js打包方案但缺少管理多个页面的功能。 web-webpack-plugin的AutoWebPlugin会自动的为你的系统里每个单页应用生成一个html入口页,这个入口会自动的注入当前单页应用依赖的资源,使用它你只需如下几行代码:


  1. plugins: [ 
  2.  
  3.     // ./src/pages/ 代表存放所有页面的根目录,这个目录下的每一个目录被看着是一个单页应用 
  4.  
  5.     // 会为里面的每一个目录生成一个html入口 
  6.  
  7.     new AutoWebPlugin('./src/pages/', { 
  8.  
  9.       //使用单页应用的html模版文件,这里你可以自定义配置 
  10.  
  11.       template: './src/assets/template.html'
  12.  
  13.     }), 
  14.  
  15.   ],  

查看web-webpack-plugin的文档了解更多

分析输出结果

如果你对当前的配置输出或者构建速度不满意,webpack有一个工具叫做webpack analyze 以可视化的方式直观的分析构建,来进一步优化构建结果和速度。要使用它你需要在执行webpack的时候带上--json --profile2个参数,这代表让webpack把构建结果以json输出并带上构建性能信息,使用如下:


  1. webpack --json --profile > stats.json 

会生产一个stats.json文件,再打开webpack analyze 上传这个文件开始分析。

最后附上这篇文章所讲到的webpack整体的配置,分为开发环境的webpack.config.js和生产环境的webpack-dist.config.js


作者:佚名

来源:51CTO

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
艾伟_转载:VS 2010 和 .NET 4.0 系列之《代码优化的Web开发Profile》篇
本系列文章导航 VS 2010 和 .NET 4.0 系列之《ASP.NET 4 中的SEO改进 》篇 VS 2010 和 .NET 4.0 系列之《干净的Web.Config文件 》篇 VS 2010 和 .
913 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10056 0
ASP.NET Web开发 Echarts图表空数据优化
      1、问题提出       在Web开发中,使用Echarts百度图表控件显示折线图、饼图等时,如果从数据库取出的是空数据,默认显示的是动态气泡图,看起来很凌乱,用户体验不好,那么我...
951 0
cetos sysctl.conf优化参考之一
简单的说明上面的参数的含义: net.ipv4.tcp_syncookies = 1 #表示开启SYN Cookies。
791 0
前端优化系列 - 基于UC内核的极致Web体验
Web页面的体验,特别是性能体验,一直饱受诟病。在和Native比较时,我们往往避其锋芒(性能),宣扬Web的跨平台,快速迭代,容易推广,开发成本低等等特性。但是,Web的体验真的很差吗?一些页面实践表明,深度优化的Web体验完全可以媲美Native。
3709 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13869 0
轻松实现 Web 性能优化
分数和指标有助于提高 Web 的速度,但它们只是手段,而不是目标本身。
3371 0
提高性能:用RequireJS优化Wijmo Web页面
上周Wijmo 2014 V2版本刚刚发布(下载地址),  有网友下载后发现仅仅使用了40个Widgets的一小部分,还需要加载全部的jquery.wijmo-pro.all.3.20142.45.min.js包? 即对仅使用部分的Widget,有无办法优化而提高网络性能呢。
896 0
+关注
行者武松
杀人者,打虎武松也。
17142
文章
2569
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载