什么是 webpack
Webpack 是德国开发者 Tobias Koppers 开发的一个强力的模块打包器。 所谓包(bundle)就是一个 JavaScript 文件,它把一堆资源(assets)合并在一起,以便它们可以在同一个文件请求中发回给客户端。 包中可以包含 JavaScript、CSS 样式、HTML 以及很多其它类型的文件。
为什么要使用 webpack
- 与 react 一类模块化开发的框架搭配着用比较好。
- 属于配置型的构建工具,容易上手,160 行代码可大致实现 gulp 400 行才能实现的功能。
- webpack 使用内存来对构建内容进行缓存,构建过程会比较快。
webpack 的特点
能够实现多种不同的前端模块系统
前端页面越来越复杂、代码量变大,我们需要使用一些模块系统来组织代码、把代码分割成不同的模块来使用。
目前前端模块系统主要分为以下几个标准:
- Script标签形式
- CommonJS
- AMD和一些变种实现
- CMD
- ES6模块
以上方式有各自的优缺点,这里不做赘述。
webpack 作为一个智能解析器,可以处理几乎任何第三方的库,无论他们的模块形式是 commonjs,amd 还是普通的 js 文件,甚至加载依赖的时候可以动态表达式:
import React, { PropTypes } from ‘react‘ require( ‘es6-promise‘ ).polyfill() require( "./templates/" + name + ".jade" ) var $ = require( ‘jQuery‘ ) |
分块打包
资源请求是个老生常谈的优化问题,有两个极端的方向来处理资源:
- 将所有文件都打包在一个请求里
- 每个模块都发起一个请求
webpack 打包前端资源(模块)时能够实现代码分割,按需加载,如在单页面应用里,将每个 page 的资源单独打包,按页面加载。这种方式被称为 code splitting(具体实现方式查看:https://webpack.github.io/docs/code-splitting.html。
处理所有资源
目前模块系统只能处理 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-map、cheap-source-map、cheap-module-source-map、cheap-module-eval-source-map、hidden-source-map,也可以自己组合,如cheap-module-eval-source-map。
// webpack.config.js module.exports = {
devtool: ‘cheap-module-eval-source-map‘ }; |
据说cheap-module-eval-source-map绝大多数情况下都会是最好的选择,这也是下版本 webpack 的默认选项。
具体使用参考官网devtool部分
entry 和 output
entry 用来定义入口文件,可以是个字符串或数组或者对象。
当 entry 是个字符串的时候:
module.exports = {
entry: ‘./main.js‘ }; |
当 entry 是个数组的时候,里面同样包含入口 js 文件,另外一个参数可以是用来配置 webpack 提供的一个静态资源服务器,webpack-dev-server。webpack-dev-server 会监控项目中每一个文件的变化,实时的进行构建,并且自动刷新页面:
module.exports = {
entry: [
‘webpack/hot/only-dev-server‘ ,
‘./main.js‘
] }; |
当 entry 是个对象的时候,我们可以将不同的文件构建成不同的文件,按需使用:
module.exports = {
entry: {
a: ‘./a.js‘ ,
b: ‘./b.js‘
} }; |
output 用于定义构建后的文件的输出,是个对象。其中包含path和filename:
module.exports = {
output: {
path: ‘./build‘ ,
filename: ‘bundle.js‘
} }; |
如果有多个入口文件分开打包,可以通过[name]来命名打包的输出文件。
module.exports = {
entry: {
a: "./a" ,
b: "./b" ,
c: [ "./c" , "./d" ]
},
output: {
path: path.join(__dirname, "dist" ),
filename: "[name].entry.js"
} }; |
具体使用参考官网
entry以及output部分
module
module 主要是用来配置加载器(Loaders),包括loaders、preLoaders、postLoaders、noParse。
webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换,本文第六章将会介绍一些常用的 Loaders。
loaders、preLoaders、postLoaders的配置选项包括以下几方面:
- test: 一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
- loader: loader的名称(必须)
- include/exclude: 手动添加必须处理的文件/文件夹,或屏蔽不需要处理的文件/文件夹(可选)
- query: 为loaders提供额外的设置选项(可选)
module.exports = {
module: {
preLoaders: [
{test: /\.js$/, loader: "eslint-loader" , exclude: /node_modules/} //打包前使用eslint检测js
],
loaders: [
{test: /\.less$/, loader: ‘style-loader!css-loader!less-loader‘ }, //处理css文件,把less编译成css
{test: /\.(png|jpg)$/, loader: ‘url-loader?limit=8192‘ } // 处理图片,大小低于8k的文件编译为base64
]
} }; |
noParse是 webpack 的一个很有用的配置项,如果你确定一个模块中没有其它新的依赖就可以配置这项,webpack 将不再扫描这个文件中的依赖。
module: {
noParse: [/moment- with -locales/] } |
具体使用参考官网module部分
官网也收集了常用的 Loaders 列表:list-of-loaders
resolve
resolve 用来配置文件路径的指向。可以定义文件跟模块的默认路径及后缀等,节省 webpack 搜索文件的时间、优化引用模块时的体验。常用的包括alias、extensions、root、modulesDirectories属性:
- alias:是个对象,把资源路径重定向到另一个路径,比如require(‘React’)默认引用的是 /node_modules/react.js,我们可以定义用react.min.js替代
//webpack.config.js
...
resolve: {
alias: {
‘someutil‘
: path.join(__dirname,
‘./src/util/someutil‘
),
‘react-dom‘
: path.join(nodeModulesPath,
‘react-dom/dist/react-dom.min‘
)
}
}
...
//app.js
//配置resolve.alias前
import react-dom form
‘react-dom‘
//此时默认载入‘/node_modules/react-dom/dist/react-dom.js‘
import someutil form
‘../../src/util/someutil‘
//需要定位该文件相对位置
//配置resolve.alias后
import react-dom form
‘react-dom‘
//此时载入压缩后的‘react-dom.min.js‘
import someutil form
‘someutil‘
//不需要再写相对位置,因为已在resolve.alias中定义
- extensions:是个数组,定义资源的默认后缀,比如定义后引用a.js、b.json、c.css等资源可以不用写后缀名直接写a、b、c
//webpack.config.js
...
resolve: {
extensions: [
‘‘
,
‘.js‘
,
‘.jsx‘
,
‘.json‘
,
‘.css‘
]
}
...
//app.js
//配置resolve.extensions前
import a form
‘a.js‘
import b form
‘b.json‘
import c form
‘c.css‘
//配置resolve.extensions后
import a form
‘a‘
import b form
‘b‘
import c form
‘c‘
- root:是个数组,通过绝对路径的方式来定义查找模块的文件夹。可以是一个数组,主要是用来增加模块的搜寻位置使用的。root 的值必须是绝对路径,使用path.resolve设置。
//webpack.config.js
var
path = require(
‘path‘
);
resolve: {
root: [
path.resolve(
‘./app/modules‘
),
path.resolve(
‘./vendor/modules‘
)
]
}
//这样设置后,会增加搜索app/modules和vendor/modules下所有node_modules里面的模块。
- modulesDirectories:是个数组,是用来设置搜索的目录名的,默认值:[“web_modules”, “node_modules”]。如果把值设置成[“mydir”], webpack会查询“./mydir”, “../mydir”, “../../mydir”等。
//webpack.config.js
...
resolve: {
modulesDirectories: [
‘node_modules‘
,
‘./src/component‘
]
}
...
//app.js
//配置resolve.modulesDirectories前
import a form
‘../../src/component/a‘
import b form
‘../../src/component/b‘
//配置resolve.modulesDirectories后
import a form
‘a‘
import b form
‘b‘
具体使用参考官网resolve部分
plugins
插件,比 loader 更强大,能使用更多 webpack 的 api。webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。本文第七章将会介绍一些常用的插件。
var HtmlWebpackPlugin = require( ‘html-webpack-plugin‘ ); module.exports = {
plugins: {
new webpack.optimize.OccurenceOrderPlugin(), //为组件分配ID
new webpack.HotModuleReplacementPlugin(), //热加载插件
new webpack.optimize.UglifyJsPlugin(), //压缩混淆js
new webpack.NoErrorsPlugin(), //跳过编译时出错的代码并记录
new ExtractTextPlugin( ‘[name]-[hash:5].min.css‘ ) //将css单独打包并重命名
} } |
具体使用参考官网内置 plugins 列表list-of-plugins部分,这里有所有内置插件的详细使用说明。
externals
当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中。例如 React、jQuery 等文件我们想使用 CDN 的引用,不想把他们打包进输出文件。就可以通过配置 externals 参数来配置:
module.exports = {
externals: {
jQuery: true
} }; |
然后在页面里引入< script src="//cdn/jquery.min.js">
这样 jQuery 就不用打包了,直接指向 windows.jQuery 就好
externals部分
其他
除以上 6 个常用配置属性外,webpack 的配置属性还有context
、cache
、profile
等等,个人觉得并不常用,各位可以前往官网configuration详细查看。
常用 Loaders 介绍
babel-loader
babel-loader 用来编译下一代 JavaScript,比如 ES6、React 等,编译 ES6、React 的话还需要安装 babel-preset-es2015、babel-preset-react。
eslint-loader
配合eslint用来规范纠错 js,你可能同时还会需要babel-eslint、eslint-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 的话:
require( ‘imports?jQuery=jquery!bootstrap/dist/js/bootstrap‘ ); |
imports-loader 会在 bootstrap 的源码前面,注入如下代码:
/* IMPORTS FROM imports-loader */ var jQuery = require( "jquery" ); |
exports-loader
用于向一个模块中提供导出模块功能。功能与imports-loader类似,但他是在文件的最后添加一行。
举个栗子,比如file.js中没有调用export导出模块,或者没有define定义模块,因此无法模块化调用它。可以使用 exports-loader:
require( "exports?file,parse=helpers.parse!./file.js" ); |
他会在./file.js文件的最后添加如下代码:
/* EXPORTS FROM exports-loader */ exports[ "file" ] = (file); exports[ "parse" ] = (helpers.parse); |
expose-loader
这个 loader 是将某个对象暴露成一个全局变量。
举个栗子,比如把jQuery对象暴露成全局变量。这样,那些bootstrap.js之类的文件就都能访问这个变量了。
module: {
loaders: [
{ test: require.resolve( "jquery" ), loader: "expose?$!expose?jQuery" },
] } |
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
定义一个共用的插件入口。
new webpack.ProvidePlugin({
$: "jquery" ,
jQuery: "jquery" }) |
这样就可直接在文件中使用$,无需再require(‘jQuery’)。
UglifyJsPlugin
js 代码压缩混淆 。
DedupePlugin