Webpack打包进阶

说在前面

因为使用了React直出,页面各项性能指标令人悦目。本篇将深刻探讨目前PC部落所采用webpack打包优化策略,以及探讨PC部落并未使用的 webpack Code Splitting 代码分包、异步模块加载特性。看看它们又是如何对PC部落的性能起到进一步的催化做用。javascript

为何要使用webpack

若是你曾经使用过 Broserify, RequireJS 或相似的打包工具,并注重:代码分包、异步加载、静态资源打包(图片/CSS)。那么 webpack 就是帮你构建项目的利器!简单一句话:在webpack中,全部资源都被看成是模块,js能够引用 css , css 中能够嵌入图片 dataUrl。php

webpack特性

对应不一样文件类型的资源,webpack有对应的模块 loader ,好比对于 less, 使用的是 less-loader,你能够在这里找到 全部loader. webpack 具备requireJS 和 browserify 的功能,但仍有本身的新特性: 一、对 CommonJS、AMD、ES6的语法作了兼容; 二、对js、css、图片等资源文件都支持打包; 三、串联式模块加载器以及插件机制让其具备更好的灵活性和拓展性,例如对 coffeeScript、ES6的支持; 四、有独立的配置文件 webpack.config.js; 五、能够将代码切割成不一样 chunk,实现按需加载,下降了初始化时间; 六、支持 SourceUrls 和 SourceMaps,易于调试; 七、具备强大的 Plugin 接口,大可能是内部插件,使用起来比较灵活; 八、webpack 使用异步 IO 并具备多级缓存,使得 webpack 在增量编译上更快!css

为何混用Grunt和webpack

自React诞生以来,耳熟能详的是 React+webpack 开发大法,并且在大多数 React 网络教程中也不多说起同时采用了 Grunt 联合构建项目。html

Grunt 能够对整个项目文件作复制、删除、合并、压缩等等。而Webpack 的优点在于对静态文件(js/jsx/coffeeScript/css/less/sass/iamges)按不一样模块加载(包括按需加载)——这正是咱们对webpack感兴趣的地方,各个模块组建化(能够将一个组建的图片、样式、脚本、页面放在同一个文件夹中)。因此,在项目中两者分工不一样,各司其职。java

注:使用 gulp 替换 grunt 固然也是没有问题。node

webpack配置

webpack 有多种配置方式,因为PC部落中静态资源文件较多,使用配置文件进行打包会方便不少。react

一般状况下,若是咱们只使用 webpack 构建项目,那么配置 webpack.config.js 便可。因为在PC部落中使用了 grunt,并在 grunt 组合任务中调用 webpack 任务,所以须要在 grunt 的任务配置中添加 webpack.js(使用了load-grunt-config插件) 进行配置。android

配置总览
var taskConfig = { dev: { entry: { // 入口文件,考虑到多页面资源缓存,咱们打成多个包 "index": path.resolve(config.srcPath, "pages/index/index.jsx"), "detail": path.resolve(config.srcPath, "pages/detail/detail.jsx"), ... }, resolve: { // 请求重定向,显示指出依赖查找路径 alias: { img: path.resolve(config.srcPath + 'img'), comps: path.resolve(config.srcPath + 'pages/components') ... } }, output: { // 输出文件 path: config.devPath + '/js', // 文件绝对路径 filename: "[name].min.js", // 输出文件名 publicPath: "http://s.url.cn/qqun/xiaoqu/buluo/p/js/", // 公共访问路径,替换CDN chunkFilename: "[name].chunk.min.js" // 异步加载时须要被打包的文件名 }, module: { // 各种文件 loader noParse: [], // 忽略解析的文件 preLoaders: [{ // 预加载的模块 test: /\.jsx$/, exclude: /node_modules/, loader: 'jsxhint-loader' }], loaders: [{ // 各式加载器 test: /\.jsx$/, loader: 'jsx-loader', include: path.resolve(config.srcPath) }, { test: /\.less$/, // 使用“!”链式loader,从右向左依次执行 loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader"), include: path.resolve(config.srcPath) }, { test: /\.(jpe?g|png|gif|svg)$/i, // inline base64url for <=1500 images loader: 'url-loader?limit=1500&name=images/[name].[hash].[ext]', include: path.resolve(config.srcPath) }] }, externals: { // 指定采用外部 CDN 依赖的资源,不被webpack打包 "react": "React", "react-dom": "ReactDOM" }, plugins: [ ... // 公共模块独立打包配置 new CommonsChunkPlugin("common", "common.min.js", ["index", "detail", "barindex", "search"]), // 独立打包css文件之外链形式加载 new ExtractTextPlugin("../css/[name].min.css") ], watch: true, keepalive: true, lessLoader: { lessPlugins: [ new LessPluginAutoPrefix() ] } }, 

webpack打包优化

一、请求重定向

resolve.alias 是webpack 的一个配置项,它的做用是把用户的一个请求重定向到另外一个路径。 好比:webpack

resolve: {  // 显示指出依赖查找路径 alias: { comps: 'src/pages/components' } } 

这样咱们在要打包的脚本中的使用 require('comps/Loading.jsx'); 其实就等价于require('src/pages/components/Loading.jsx')。这犹如《高性能javascript》中给查询压力较大的对象给了一个别名,经过使用别名能够将本例减小几乎一半的时间。ios

二、忽略对已知文件的解析

module.noParse,若是你肯定一个模块中没有其它新的依赖,就能够配置这项,webpack 将再也不扫描这个文件中的依赖。 好比咱们在入口文件 entry.js 中检测到对资源src/pages/components/ueditor.min.js资源的请求,若是咱们配置:

module: { noParse: [/ueditor/] } 

noParse规则中的/ueditor/一条生效,因此 webpack 直接把依赖打包进了 entry.js。增长这样的配置会让 webpack 编译时间更短。

三、使用公用CDN

考虑到web上有不少的公用 CDN 服务,那么咱们能够将 react 从 bundle 中分离出来,进而不会被 webpack 打包, 做为外部依赖引用 CDN 。 方法是使用 externals 声明一个外部依赖。 如:

module:{ externals: { // 方式一:申明为外部依赖并指定别名 "react": "React", "react-dom": "ReactDOM" // 方式二:true 为外部依赖,false 则不是 a: false, // a is not external b: true // b is external }, } 

并在 HTML 代码中加上一行

<script src="//cdn.bootcss.com/react/0.14.2/react.js"> <script src="//cdn.bootcss.com/react/0.14.2/react-dom.js"> 

这样咱们在js中引入React = require('react') , webpack 就不会把 react 打包进来而直接引用CDN,这样作可让 webpack 编译时间缩减一大半!

系列插件

CommonsChunkPlugin

开发中须要将多个页面的公用模块独立打包,从而能够利用浏览器缓存机制来提升页面加载效率,减小页面初次加载时间,只有当某功能被用到时才去动态加载。这就要使用到 webpack 中的 CommonsChunkPlugin 插件。

使用:
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { ... /* * @param 1 将公共模块提取,生成名为 common 的chunk * @param 2 最终生成的公共模块的 js 文件名 * @param 3 公共模块提取的资源列表 */ new CommonsChunkPlugin("common", "common.min.js", ["index", "detail", "barindex", "search"]) } 

ExtractTextPlugin

webpack 中编写js文件时,能够经过 require 的方式引入其余静态资源,可经过loader对文件自动解析并打包文件。 一般咱们会将 js 文件打包合并,css 文件在页面header中嵌入 style 的方式载入页面。但在开发过程当中咱们并不想将样式打包在脚本中(最好能够独立生成css文件,之外链形式加载)。 ExtractTextPlugin 插件能够帮咱们达到这样的效果。

安装:

npm install extract-text-webpack-plugin –save-dev

使用:
var ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { ... plugins: [ new ExtractTextPlugin("../css/[name].min.css") ] } 

这样配置就能够将 js 中的 css 文件提取,并以指定的文件名来进行加载。

LessPluginAutoPrefix

顾名思义,就是autoPrefix插件,用来补全CSS的厂商前缀(-webkit-, -moz-, -o-);

使用:
var LessPluginAutoPrefix = require('less-plugin-autoprefix'); var taskConfig = { dev: { ... lessLoader: { lessPlugins: [ new LessPluginAutoPrefix() ] } } 

Code Splitting

对于一个大型的web app,咱们把全部的 js 文件合成一个显然是很是低效的,由于有些 js 模块并非咱们当前页面所须要的(这会大大增长页面首屏渲染时间)。Webpack 就是这样一种神器,为您提供优质的代码分包服务,今后“妈妈不再用担忧页面按需加载的问题了”!

方式一:require

require(dependencies, callback) 听从 AMD 规范定义的异步方法。使用该方法时,全部的依赖被异步加载并从左至右当即执行,依赖都被执行后,执行callback

方式二:require.ensure

require.ensure(dependencies, callback) 听从 CommonJS 规范,在须要的时候才下载依赖的模块。当全部的依赖都被加载完毕,便执行 callback(注:require做为callback的参数)。 细心的同窗可能还记得 output 配置中有

output: { ... chunkFilename: "[name].chunk.min.js" } 

chunk 究竟是什么? chunk 又是怎么生成的呢? 为了实现部分资源的异步加载,有些资源是不打包到入口文件里面的。因而咱们使用 require.ensure 做为代码分割的标识。require.ensure 会建立一个 chunk ,且能够指定该 chunk 的名称(注:若是这个chunk已经存在了,则将本次依赖的模块合并到已经存在的chunk中),最后这个 chunk 在 webpack 构建时会单独生成一个文件。 好比咱们要根据当前运行平台,加载两个不一样的UI组建,那么:

var platform = Util.getPlatform(); if( platform === "ios"){ require.ensure(['./components/dialog'], function(require){ ... }, 'popup'); // 最后一个参数是 chunk 名 } if( platform === "android"){ require.ensure(["./components/toast"], function(require){ ... }, 'popup'); } 

经过webpack打包以后,会生成一个 popup.chunk.min.js 文件。在不一样的运行平台上,咱们会发现 popup.chuck.min.js 文件的内容是相同的(由于咱们配置的 chunk 名都是 popup)。 若是咱们想让按需加载的模块再次拆分红 dialog 和 toast,两个文件,仅仅须要将 require.ensure 中配置的chunk 名改不一样,便可在代码被执行时加载单一文件。

注意点:

一、require :加载模块,并当即执行; 二、require.ensure:仅仅加载模块,但不会执行; 三、不用在 html 中显示调用生成的 chunk 文件,按需加载时会自动调用; 四、不用担忧第三方库被反复打包的问题,由于咱们已经使用 CommonsChunkPlugin 对公共部分进行提取。

相关文章
相关标签/搜索