前端记录 记录分享前端知识

webpack 使用总结

前言

本文是对近半年使用 webpack 的一个总结。webpack 作为 配置型的工具,虽然配置项很多、功能强大,但是用起来并不复杂。文内主要内容来自网络摘抄以及翻译官网文档,相对比较全面。
目前 webpack 已经更新到 2.x 版本,不过 1.x 与 2.x 两个大版本之间的区别并不大。本文主要是总结对 1.x 版本的使用。
本文的 配套 DEMO 请访问 https://github.com/tumars/boilerplate-webpack-react-es6-cssModule 查看。

本文日后还会更新修改,如若转载请附上原文地址:http://www.ferecord.com/webpack-summary.html,以便溯源。

目录

什么是 webpack

Webpack 是德国开发者 Tobias Koppers 开发的一个强力的模块打包器。 所谓包(bundle)就是一个 JavaScript 文件,它把一堆资源(assets)合并在一起,以便它们可以在同一个文件请求中发回给客户端。 包中可以包含 JavaScript、CSS 样式、HTML 以及很多其它类型的文件。

为什么要使用 webpack

  • 与 react 一类模块化开发的框架搭配着用比较好。
  • 属于配置型的构建工具,容易上手,160 行代码可大致实现 gulp 400 行才能实现的功能。
  • webpack 使用内存来对构建内容进行缓存,构建过程会比较快。

webpack 的特点

1. 能够实现多种不同的前端模块系统
前端页面越来越复杂、代码量变大,我们需要使用一些模块系统来组织代码、把代码分割成不同的模块来使用。
目前前端模块系统主要分为以下几个标准:

  • Script标签形式
  • CommonJS
  • AMD和一些变种实现
  • CMD
  • ES6模块

以上方式有各自的优缺点,这里不做赘述。
webpack 作为一个智能解析器,可以处理几乎任何第三方的库,无论他们的模块形式是 commonjs,amd 还是普通的 js 文件,甚至加载依赖的时候可以动态表达式:

2. 分块打包
资源请求是个老生常谈的优化问题,有两个极端的方向来处理资源:

  • 将所有文件都打包在一个请求里
  • 每个模块都发起一个请求

webpack 打包前端资源(模块)时能够实现代码分割,按需加载,如在单页面应用里,将每个 page 的资源单独打包,按页面加载。这种方式被称为 code splitting(具体实现方式查看:https://webpack.github.io/docs/code-splitting.html

3. 处理所有资源
目前模块系统只能处理 javascript,而我们还有很多其他静态资源需要处理,比如:

  • 预编译的 js 文件(coffeescript 或 jsx 或 es6 -> javascript)
  • css 文件 (less -> css -> autoprefixer)
  • 图片 (压缩、转 base64)
  • 模板 (jade、各种template)
  • 等等

配置 webpack 后,这些都很容易处理。

webpack 与 、requirejs 等工具的区别

gulp、grunt 是自动化任务构建工具
webpack 是模块化解决方案

gulp 是通过一系列插件将原本复杂繁琐的任务(Task)自动化,并不能将你的 css 等非 js 资源模块化,它与 webpack 之间有一定的功能重合,比如打包、压缩混淆、图片转 base64 等等。但它们的目的跟要解决的问题是不一样的。

webpack 本质上是类似 browserify、seajs 、requirejs 的JS模块化的方案。其中 seajs / require 是一种类型,browserify / webpack 是另一种类型。

seajs / require : 是一种在线”编译” 模块的方案,相当于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module 这些东西。也就实现了模块化。

browserify / webpack : 是一种预编译模块的方案,相比于上面 ,这个方案更加智能。这里以webpack为例。首先,它是预编译的,不需要在浏览器中加载解释器。另外,你在本地直接写JS,不管是 AMD / CMD / ES6 风格的模块化,它都能认识,并且编译成浏览器认识的JS。

webpack 的配置

首先看一个简单的 webpack 配置图。

webpack.config.js:

Webpack的配置主要为了这几个项目:

devtool

devtool 属性可以配置调试代码的方式,有多种调试方式。devtool 一般只在开发时使用,生产环境下应将值设为 false。常用的值为以下两个:

  • eval
    可以设断点调试,不显示列信息,每个js模块代码用eval()执行,并且在生成的每个模块代码尾部加上注释,不会生成.map文件。
  • source-map
    可以设断点调试,不显示列信息,生成相应的.Map文件,并在合并后的代码尾部加上注释//# sourceMappingURL=**.js.map ,可以看到模块代码并没有被eval()包裹,此种模式并没有将调试信息放入D打包后的代码中,保持了打包后代码的简洁性.

其他还有eval-source-mapcheap-source-mapcheap-module-source-mapcheap-module-eval-source-maphidden-source-map,也可以自己组合,如cheap-module-eval-source-map

据说cheap-module-eval-source-map绝大多数情况下都会是最好的选择,这也是下版本 webpack 的默认选项。

具体使用参考官网 devtool 部分

entry 和 output

entry 用来定义入口文件,可以是个字符串或数组或者对象。
当 entry 是个字符串的时候:

当 entry 是个数组的时候,里面同样包含入口 js 文件,另外一个参数可以是用来配置 webpack 提供的一个静态资源服务器,webpack-dev-server。webpack-dev-server 会监控项目中每一个文件的变化,实时的进行构建,并且自动刷新页面:

当 entry 是个对象的时候,我们可以将不同的文件构建成不同的文件,按需使用:

output 用于定义构建后的文件的输出,是个对象。其中包含pathfilename

如果有多个入口文件分开打包,可以通过[name]来命名打包的输出文件。

具体使用参考官网 entry 以及 output 部分

module

module 主要是用来配置加载器(Loaders),包括loaderspreLoaderspostLoadersnoParse
webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换,本文第六章将会介绍一些常用的 Loaders。

loaderspreLoaderspostLoaders的配置选项包括以下几方面:

  • test: 一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
  • loader: loader的名称(必须)
  • include/exclude: 手动添加必须处理的文件/文件夹,或屏蔽不需要处理的文件/文件夹(可选)
  • query: 为loaders提供额外的设置选项(可选)

noParse是 webpack 的一个很有用的配置项,如果你确定一个模块中没有其它新的依赖就可以配置这项,webpack 将不再扫描这个文件中的依赖。

具体使用参考官网 module 部分
官网也收集了常用的 Loaders 列表:list-of-loaders

resolve

resolve 用来配置文件路径的指向。可以定义文件跟模块的默认路径及后缀等,节省 webpack 搜索文件的时间、优化引用模块时的体验。常用的包括aliasextensionsrootmodulesDirectories属性:

  • alias:是个对象,把资源路径重定向到另一个路径,比如require('React')默认引用的是 /node_modules/react.js,我们可以定义用react.min.js替代

 

  • extensions:是个数组,定义资源的默认后缀,比如定义后引用a.js、b.json、c.css等资源可以不用写后缀名直接写a、b、c

 

  • root:是个数组,通过绝对路径的方式来定义查找模块的文件夹。可以是一个数组,主要是用来增加模块的搜寻位置使用的。root 的值必须是绝对路径,使用path.resolve设置。

  • modulesDirectories:是个数组,是用来设置搜索的目录名的,默认值:["web_modules", "node_modules"]。如果把值设置成["mydir"], webpack会查询“./mydir”, “../mydir”, “../../mydir”等。

 

具体使用参考官网 resolve 部分

plugins

插件,比 loader 更强大,能使用更多 webpack 的 api。webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。本文第七章将会介绍一些常用的插件。

具体使用参考官网内置 plugins 列表 list-of-plugins 部分,这里有所有内置插件的详细使用说明。

externals

当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中。例如 React、jQuery 等文件我们想使用 CDN 的引用,不想把他们打包进输出文件。就可以通过配置 externals 参数来配置:

然后在页面里引入<script src="//cdn/jquery.min.js"></script>
这样 jQuery 就不用打包了,直接指向 windows.jQuery 就好

具体使用参考官网 externals 部分

其他

除以上 6 个常用配置属性外,webpack 的配置属性还有contextcacheprofile等等,个人觉得并不常用,各位可以前往官网 configuration 详细查看。

常用 Loaders 介绍

babel-loader

babel-loader 用来编译下一代 JavaScript,比如 ES6、React 等,编译 ES6、React 的话还需要安装 babel-preset-es2015、babel-preset-react。

eslint-loader

配合 eslint 用来规范纠错 js,你可能同时还会需要 babel-eslinteslint-plugin-react

style-loader、css-loader、less-loader、sass-loader

处理 css、less、sass 文件

postcss-loader

用 postcss 来处理 CSS,最被常用的是其 autoprefixer 插件

resolve-url-loader

用来解析 css 里的 url() 声明里的文件的相对路径,使用 css-loader 时一般也必须要同时使用 resolve-url-loader

file-loader

用来处理文件名及路径,比如给文件添加 hash,返回文件相对路径等。

url-loader

与上面的 file-loader 类似,但是当文件小于设定的 limit 时可以处理成 Data Url(base 64)。

raw-loader

把文件内容作为字符串返回。例如var fileContent = require('raw!./file.txt'),这里把./file.txt的内容作为字符串返回。

imports-loader

用于向一个模块的作用域内注入变量。
举个栗子,比如我们需要使用bootstrap.js,这个文件虽然依赖jQuery但其内部没有require('jquery'),这导致文件内的jQuery对象不可识别,所以模块化调用bootstrap.js时就会报错jQuery is not defined`。使用 imports-loader 的话:

imports-loader 会在 bootstrap 的源码前面,注入如下代码:

exports-loader

用于向一个模块中提供导出模块功能。功能与imports-loader类似,但他是在文件的最后添加一行。
举个栗子,比如file.js中没有调用export导出模块,或者没有define定义模块,因此无法模块化调用它。可以使用 exports-loader:

他会在./file.js文件的最后添加如下代码:

expose-loader

这个 loader 是将某个对象暴露成一个全局变量。
举个栗子,比如把jQuery对象暴露成全局变量。这样,那些bootstrap.js之类的文件就都能访问这个变量了。

react-hot-loader

React 的网页热加载刷新 loader。同时需要配合使用 webpack-dev-server

常用插件介绍

具体的使用方法请在官网list-of-plugins查询,本文只简单介绍几个常用插件。

CommonsChunkPlugin

插件用法比较多,常用的是把一些不经常更改的公共组件合并压缩成一个 common 文件。有些类库如utils, bootstrap之类的可能被多个页面共享,最好是可以合并成一个js,而非每个js单独去引用。这样能够节省一些空间。

似乎只有在多入口文件时才能把公共文件提取出来,但一般模块化都建议单入口文件,所以个人觉得这个插件对我并没有什么卵用,有多入口需求的同学可以使用。

extract-text-webpack-plugin

第三方插件,将 CSS 打包成独立文件,而非内联。

HotModuleReplacementPlugin

代码热替换。

HtmlWebpackPlugin

生成 HTML 文件,配合 ExtractTextPlugin 可以加入打包后的 js 和 css。

NoErrorsPlugin

报错但不退出 webpack 进程。

OccurenceOrderPlugin

通过计算模块出现次数来分配模块。通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID。这个经常被使用可以较快地获得模块。这使得模块可以预读,建议这样可以减少总文件大小。

ProvidePlugin

定义一个共用的插件入口。

这样就可直接在文件中使用$,无需再require('jQuery')

UglifyJsPlugin

js 代码压缩混淆 。

DedupePlugin

检测完全相同(以及几乎完全相同)的文件,并把它们从输出中移除。

DefinePlugin

主要用来定义全局的环境变量,以便我们在自己的程序中引用它。

webpack-visualizer-plugin

一个第三方插件,可以生成一个文件查看项目引用的所有模块的占比。

常用优化手段及技巧

区分开发及生产环境

前端开发环境通常分为两种:

  • 开发环境: 需要日志输出,sourcemap ,错误报告等等
  • 生产环境:需要做代码压缩,对文件名进行 hash 处理等等

为了区分我们可以创建两个文件分别进行不同环境下的配置:

  • webpack.config.dev.js // 开发环境
  • webpack.config.prod.js // 生产环境

同时 webpack 还提供了 DefinePlugin 插件来设置全局环境变量,后面会根据设置的不同环境变量决定是否打包压缩,还是启动dev server 或者是 prod server

判断当前环境是否是生产环境

使用代码热替换

使用代码热替换在开发的时候无需刷新页面即可看到更新,只在开发环境的配置文件使用,具体配置如下。

webpack/hot/dev-server加入到 webpack 配置文件的 entry 项:

new webpack.HotModuleReplacementPlugin()加入到 webpack 配置文件的 plugins 项:

在项目根目录新建个server.js文件,将server部分分离到一个单独的 :

在 package.json 中定义启动监听热加载:

现在你可以通过运行npm start启动服务器。

编译 ES6、React

这里主要是借助 Babel 进行编译。
安装依赖:

在项目根目录新建个.babelrc文件:

配置 webpack 配置文件的 module 项:

更多请访问 babel-loaderbabel-preset-react-hmre

使用 ESLint 检查规范 js 代码

ESLint 是个代码错误与风格检测工具,可以辅助编码规范执行,有效控制代码质量。
ESLint 主要有以下特点:

  • 默认规则包含所有 JSLint、JSHint 中存在的规则,易迁移;
  • 规则可配置性高:可设置「警告」、「错误」两个 error 等级,或者直接禁用;
  • 包含代码风格检测的规则(可以丢掉 JSCS 了);
  • 支持插件扩展、自定义规则。

安装依赖:

项目根目录新建.eslintrc文件,用来配置 ESLint 规则,以下为常见规则:

在项目根目录新建.eslintignore 文件告诉 ESLint 去忽略特定的文件和目录:

在Sublime中安装插件:
SublimeLinter
SublimeLinter-contrib-eslint

更多请访问 eslintbabel-eslinteslint-plugin-reaceslint-loaderESLint 使用入门Lint Like It’s 2015

压缩代码

使用 webpack 内置的 UglifyJsPlugin 即可:

Code Spliiting,分割代码,按需加载

对于大型的web 应用而言,把所有的代码放到一个文件的做法效率很差,特别是在加载了一些只有在特定环境下才会使用到的阻塞的代码的时候。Webpack有个功能会把你的代码分离成Chunk,后者可以按需加载。这个功能就是 Code Spliiting
Code Spliting的具体做法就是一个分离点,在分离点中依赖的模块会被打包到一起,可以异步加载。一个分离点会产生一个打包文件。

本文这里主要是基于 React 与 React-Router 的 Code Spliiting。开篇说了 webpack 目前已经更新到 2.x 版本, 1.x 与 2.x 的 Code Spliiting 方法略有不同,这里分开来讲。

webpack 1.x 的例子:

webpack 2.x 的例子:

 

使用 DllPlugin 和 DllReferencePlugin 分割代码

通过 DllPlugin 和 DllReferencePlugin,webpack 引入了另外一种代码分割的方案。我们可以将常用的库文件打包到 dll 包中,然后在 webpack 配置中引用。业务代码的可以像往常一样使用 require 引入依赖模块,比如 require(‘react’), webpack 打包业务代码时会首先查找该模块是否已经包含在 dll 中了,只有 dll 中没有该模块时,webpack 才将其打包到业务 chunk 中。

首先我们使用 DllPlugin 将常用的库打包在一起:

该配置会产生两个文件,模块库文件:vender.[chunkhash].js 和模块映射文件:vender-menifest.json。其中 vender-menifest.json 标明了模块路径和模块 ID(由 webpack 产生)的映射关系,其文件内容如下:

然后在业务代码的 webpack 配置文件中使用 DllReferencePlugin 插件引用:

需要注意的是:dll包的代码是不会执行的,需要在业务代码中通过require显示引入。

编译 less/sass、自动添加浏览器前缀、将 css 单独打包

我用的是 less,所以这里以 less 为例。
安装编译 less 的依赖:

安装处理 css 内 url() 声明路径的依赖:

安装 autoprefixer 依赖:

配置 webpack 配置文件的 module、postcss项:

生产环境下我们可能会需要把 css 单独打包出来,这时需要用到 ExtractTextPlugin 插件 :

 

压缩图片、将图片转为 base64

图片处理常见的loader有以下三种:

  • file-loader: 默认情况下会根据图片生成对应的 MD5 hash 的文件格式
  • url-loader: url-loader类似于file-loader,但是url-loader可以根据自定义文件大小或者转化为 base64 格式的 dataUrl,或者单独作为文件,也可以自定义对应的 hash 文件名
  • image-webpack-loader: 提供压缩图片的功能

安装依赖:

 

给文件添加 hash 缓存、自动生成页面

在 output 项给生成文件添加 hash:

文件名带上 hash 值后,这个值在每次编译的时候都会发生变化,都需要在 html 文件里手动修改引用的文件名,这种重复工作很琐碎且容易出错,这里我们可以使用 html-webpack-plugin 来帮我们自动处理这件事情:

 

使用 Fetch

我个人的项目里 Fetch 已经完全替代 Ajax 了,使用 Fetch 为了兼容旧浏览器需要使用一些腻子脚本,我们可以将腻子脚本暴露到全局。这里主要使用前文中提到的import-loader、exports-loader。
安装依赖:

配置 webpack 配置文件的 plugins 项:

这样就可以直接在开发文件中使用 Fetch。

总结

再次说明下本文的 配套 DEMO 请访问 https://github.com/tumars/boilerplate-webpack-react-es6-cssModule 查看。
本文还会继续更新修改,欢迎各位交流指教。

部分参考引用链接

  1. webpack 官网
  2. webpack github 主页
  3. webpack 简介_简书
  4. 使用基于 Webpack 的工具创建 Angular 应用
  5. Webpack 中文指南_gitbook
  6. Webpack、Browserify和Gulp三者之间到底是怎样的关系?_知乎
  7. webpack使用优化(基本篇)
  8. 详解前端模块化工具-webpack
  9. 入门 Webpack,看这篇就够了
  10. webpack的几个常用loader
  11. [React项目总结] 基于 webpack 搭建前端工程基础篇
  12. webpack代码分割技巧

3 条评论

欢迎留言