本文同步自我的博客http://joeray61.com
本文是一个简单的webpack入门教程,希望能够帮助webpack初学者快速上手。如有错误,敬请斧正。
本文所有的demo都可以在webpack-demo里找到,git clone
之后需要执行npm install
安装所有依赖包。
简介
webpack
是一个模块打包工具,出自德国开发者Tobias Koppers
之手,处理速度很快。在webpack
看来,一切资源都可以是一个模块,不仅限于js文件。正是由于这些特点,使得webpack
可以替代Grunt
和Gulp
,成为了目前业内最火的前端构建工具。
安装
webpack可以安装到全局,也可以作为项目的依赖工具安装到项目中
// 全局安装
$ npm install -g webpack
$ npm install -g webpack-dev-server
如果报没有权限的话,在命令前面加上sudo
就可以了
// 局部安装
$ npm install --save-dev webpack
$ npm install --save-dev webpack-dev-server
可以看到,我们在安装webpack
的同时,也安装了webpack-dev-server
。那么webpack-dev-server
是什么呢?它是一个轻量级的基于express
的node.js
服务器,用来服务资源文件。不是必须的,但是建议一起安装。
命令行
$ webpack main.js bundle.js
以上是直接使用webpack
命令行来进行打包的命令,把main.js
构建成bundle.js
,具体示例可以查看demo1,在文件夹中执行上方的命令即可。
webpack
有一些参数是我们应该要知道的
webpack
: 构建一次开发版本webpack -p
: 构建一次产品版本,与开发版本的区别是会对文件进行压缩webpack -d
: 添加source map
webpack --watch
: 监听文件的改动,持续增量构建webpack --colors
: 让命令行的输出更好看一点(实际使用zsh
的命令行时发现并没有区别)
配置文件
webpack
还有很多强大的功能,这些都可以在命令行使用,但是如果都写在命令行,既不方便使用,也不能直观地反映各个配置项,这时候,就需要配置文件登场了。
webpack
默认使用的配置文件名是webpack.config.js
,也可以通过--config
参数在命令行指定另一个命名的配置文件。配置文件其实也是一个模块,所有的构建信息都放在module.exports
中。下面我们就来讲一讲各个重要的配置项。
以下各个示例请在各demo文件夹中执行
webpack-dev-server
进行查看
入口文件
入口文件的配置项名称是entry
,代表了webpack
给整个项目进行打包的一个主入口,可以搭配output
属性指定的输出文件来使用
// webpack.config.js
module.exports = {
entry: 'main.js',
output: {
filename: 'bundle.js'
}
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo2</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
// main.js
document.write('<h1>Hello Webpack!</h1>');
示例见demo2
多入口文件
在多页的应用中,我们需要有多个入口文件,这在webpack
中也是可以支持的。
// webpack.config.js
module.exports = {
entry: {
profile: './profile.js',
feed: './feed.js'
},
output: {
filename: '[name].bundle.js'
}
};
// feed.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo3-Feed</title>
</head>
<body>
<script src="feed.bundle.js"></script>
</body>
</html>
// feed.js
document.write('<h1>Hello Feed!</h1>');
// profile.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo3-Profile</title>
</head>
<body>
<script src="profile.bundle.js"></script>
</body>
</html>
// profile.js
document.write('<h1>Hello Profile!</h1>');
示例见demo3,在demo3文件夹中启动webpack-dev-server
,访问localhost:8080/profile.html
和localhost:8080/feed.html
查看效果。
Loaders
Loaders
顾名思义就是加载器,用于加载各种格式的文件,例如React
使用的JSX
,ES6
和ES7
,图片、CSS
、JSON
文件等,可谓是webpack
中最核心的功能之一。官方文档中列出了所有可用的loaders。
loaders
配置在module.exports
中的module
字段下(说起来有点绕,待会儿看代码就懂了),是一个数组,代表loader
的集合,每一个loader
有几个配置项:
test
: 正则表达式,用于匹配文件的路径,一般都直接用来匹配文件后缀,在复杂的场景下,可以针对同一类型的不同文件做不同处理loader
:loader
的名称,实际使用时可以省略-loader
的后缀(例如css-loader
,可以写成css
)include
: 必须包含的文件路径exclude
: 不需要处理的文件路径query
:loader
的额外设置选项
下面我介绍几个常用的loader。
CSS Loader
先安装style-loader
和css-loader
。其中,style-loader
用来向 HTML 页面中插入<style>
,css-loader
用来读取 css 文件。
$ npm install --save-dev style-loader css-loader
然后在webpack.config.js
中配置loader
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.css$/,
loader: 'style!css'
}
]
}
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo4</title>
</head>
<body>
<h1>Hello World!</h1>
<script src="bundle.js"></script>
</body>
</html>
// a.css
h1 {color: red;}
// main.js
require('./a.css');
示例见demo4,可以看到,多个loader
之间用!
连接。在本地用webpack-dev-server
启动服务,访问localhost:8080
可以看到h1
中的字体颜色如a.css
定义的那样显示红色
这个 demo 中的 css 是对全局生效的,如果需要只对当前模块生效,就要用到我们下面介绍的css module
。
CSS Module
近几年,模块化这个概念在前端被不断地被提及。模块化使得代码被分割成一个个更小的、独立的、可维护性更高的独立单元。相比于JS
,CSS
的模块化发展的相对慢一点。最近出现了一个叫做css modules
的技术,在css模块中,所有的类名和动画名默认只对向前模块生效。webpack
对css module
提供了不错的支持,只需要在css-loader
后面加上?module
即可使用。如果要对全局生效,可以使用:global()
,把选择器作为参数传入。
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.css$/,
loader: 'style!css?module'
}
]
}
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo5</title>
</head>
<body>
<div class="foo">Foo</div>
<div class="bar">Bar</div>
<script src="bundle.js"></script>
</body>
</html>
// a.css
.foo {color: red;}
:global(.bar) {
color: green;
}
// main.js
var style = require('./a.css');
document.write('<div class=' + style.foo + '>Module - Foo</div>');
document.write('<div class="bar">Module - Bar</div>');
需要注意的是,在main.js
中使用时,必须用变量style.foo
,直接写成class="foo"
是不会生效的,因为a.css
中的.foo
的类名被编译了,所以导出的style.foo
将不等于foo
。
示例代码见demo5。在 demo5 的目录下执行webpack-dev-server
,在浏览器中打开localhost:8080
,可以看到对应的效果。
Url Loader
先安装url-loader
和file-loader
$ npm install --save-dev url-loader file-loader
注意,
url-loader
对file-loader
有依赖,但是安装url-loader
时不会自动安装file-loader
,这里我暂时没有花时间去了解原因,先都手动安装吧。如果不安装file-loader
,超过limit
大小的文件将无法加载。
url-loader
是一个文件加载器,我们可以给他设定一个limit
,用来限定文件大小,文件小于这个大小时会被转换成一个Data Url
,反之转换成普通url表示资源路径。我们常用url-loader
来加载图片。
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.(png|jpg)$/,
loader: 'url-loader?limit=8192'
}
]
}
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo6</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
// main.js
var img1 = new Image();
img1.src = require('./webpack.png');
document.body.appendChild(img1);
var img2 = new Image();
img2.src = require('./wade.jpg');
document.body.appendChild(img2);
示例代码见demo6。在demo6目录下运行webpack-dev-server
,在浏览器中访问localhost:8080
,审查元素可以看到,8k以下的图片使用了Data Url
,8k以上的图片使用的是资源地址。
Babel Loader
现如今,ES6
、React
可谓是红透半边天,说起ES6
、React
,就不得不提Babel
了。Babel
是一个 Javascript 编译工具,可以让你现在就能使用目前还未被浏览器完全支持的下一代JS(ES6/ES7),或是基于 JS 进行扩展的 JSX 等。
我们先安装需要的包
$ npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom
其中,babel-core
是babel
的核心功能,babel-loader
是webpack
使用的babel
加载器,babel-preset-es2015
和babel-preset-react
是babel
用来解析ES6
和JSX
的包,react
和react-dom
是开发react
的依赖包。
// webpack.config.js
module.exports = {
entry: './main.jsx',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.js[x]?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
}
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo7</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
// main.jsx
import React from 'react';
import {render} from 'react-dom';
render(
<h1>Hello world!</h1>,
document.querySelector('#root')
);
可以看到,在这个配置中,我们把babel-loader
的配置写在query
字段中。其实也可以直接写在loader
字段中,loader: 'babel-loader?presets[]=es2015&presets[]=react'
,但是这样就不是太清晰了,建议配置比较长时拿出来放到query
字段中去。
示例代码见demo7,在demo7目录下执行webpack-dev-server
,在浏览器中访问localhost:8080
查看效果。
Plugins
plugins
是webpack
中另一个非常重要且又用的配置,它与loaders
的区别在于,loader
是用来加载文件的,会逐个文件处理,而plugins
是作为webpack
功能的补充,整个构建过程都在持续发挥作用。下面我来给大家介绍几个常用的插件。
UglifyJS Plugin
这个插件估计很多人看名字就猜出来了,是用于压缩 JS 代码的。它是webpack
的内置插件,不需要安装就可以使用。
直接上代码吧:
// webpack.config.js
var webpack = require('webpack');
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
plugins: [
new UglifyJsPlugin()
]
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo8</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
// main.js
var name = 'JoeRay61';
var age = 24;
document.write('My name is ' + name + ', I\'m ' + age + ' years old.');
示例代码见demo8。在demo8目录下执行webpack-dev-server
,在浏览器中访问localhost:8080
,打开开发这工具,可以看到bundle.js的代码被压缩成了一行,变量名也被替换了,说明插件生效了。
!function(r){function e(t){if(o[t])return o[t].exports;var n=o[t]={exports:{},id:t,loaded:!1};return r[t].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var o={};return e.m=r,e.c=o,e.p="",e(0)}([function(r,e){var o="JoeRay61",t=24;document.write("My name is "+o+", I'm "+t+" years old.")}]);
Feature Flags
想象这样一种场景,我们希望在开发环境中在代码中输出一些调试信息,那么如何在webpack
中实现呢。这时候就需要用上Feature Flags
。它是借由webpack
的自定义插件机制实现的全局环境变量,我们可以在代码中判这些全局变量的值来实现定制化的功能。
// webpack.config.js
var webpack = require('webpack');
var flags = new webpack.DefinePlugin({
__PROD__: JSON.stringify(JSON.parse(process.env.PROD || 'false'))
});
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
plugins: [flags]
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo9</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
// main.js
document.write('<h1>Hello world!</h1>');
if (!__PROD__) {
document.write('<p>it is dev version</p>');
}
示例代码见demo9。进入demo9目录,分别执行PROD=1 webpack-dev-server
和webpack-dev-server
,在浏览器中访问localhost:8080
,可以看到区别。
Hot Module Replacement
HMR
是webpack
中一个激动人心的功能,中文直译叫模块热替换
,是指配合webpack-dev-server
的服务器,在你修改了项目的模块之后,不需要手动刷新页面,即可以看到更新后的效果,有效地提升了开发效率。
配合webpack-dev-server
,我们有2种方式可以启用该功能:
-
在命令中中
webpack-dev-server
命令后面追加--hot
和--inline
参数--hot
: 添加HMR插件,将服务器切换到 hot 模式--inline
: 将webpack-dev-server
的 runtime 加入到打包后的文件中--hot --inline
: 当这两个参数并存时,会额外添加一个webpack/hot/dev-server
的entry
-
配置
webpack.config.js
添加
new webpack.HotModuleReplacementPlugin()
插件添加
webpack/hot/dev-server
和webpack-dev-server/client?http://localhost:8080
这2个entry
针对第2中方式我们来测试一下:
// webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: [
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
'./main.js'
],
output: {
filename: 'bundle.js'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo10</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
// main.js
document.write('<h1>Hello world!</h1>');
注意,测试过程中我发现,如果原来是全局安装的
webpack-dev-server
,必须要在项目中局部安装webpack-dev-server
,否则启动服务器时会报错
安装命令$ npm install --save-dev webpack-dev-server
示例代码见demo10。在demo10目录下,执行webpack-dev-server
,在浏览器中查看localhost:8080
,发现输出Hello world!
,这时候不要关闭服务器,直接修改main.js
的代码,保存后查看浏览器效果,发现虽然没有手动刷新页面,但是效果已经出来了。
Commons Chunk Plugin
当几个不同的脚本有公共的部分时,我们可以把公共部分抽出来放到一个单独的文件中。
// webpack.config.js
var webpack = require('webpack');
module.exports = {
entry: {
index: './index.js',
main: './main.js'
},
output: {
filename: '[name].bundle.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('common.js')
]
};
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo11</title>
</head>
<body>
<script src="common.js"></script>
<script src="index.bundle.js"></script>
<script src="main.bundle.js"></script>
</body>
</html>
// index.js
var data = require('./data');
document.write('<p>foo is ' + data.foo + '</p>');
// main.js
var data = require('./data');
document.write('<p>bar is ' + data.bar + '</p>');
// data.js
var data = {
foo: 123,
bar: 456
};
module.exports = data;
具体示例见demo11。在demo11目录下执行webpack-dev-server
,在浏览器中访问localhost:8080
,打开开发者工具,发现data.js
被抽出单独放到了common.js
中。
总结
本文是我自己学习webpack
的一个笔记,我把它记录下来,希望能给其他的webpack
初学者提供一点帮助,谢谢观看,欢迎交流!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。