什么是webpack
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用
与gulp/grunt的区别
Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack可以替代Gulp/Grunt类的工具。
- Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,这个工具之后可以自动替你完成这些任务。
- Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个浏览器可识别的JavaScript文件。
安装
初始化npm项目,创建package.json文件
npm init
安装webpack
//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack
一个简单的例子
在项目中创建两个文件夹和三个文件:
- app文件夹:用来存放原始数据和我们将写的JavaScript模块
- public文件夹:用来存放准备给浏览器读取的数据(包括使用webpack生成的打包后的js文件以及一个index.html文件)
- index.html:放在public文件夹中
- Greeter.js:放在app文件夹中
- main.js:放在app文件夹中
index.html文件只有最基础的html代码,它唯一的目的就是加载打包后的js文件(bundle.js)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
<script src="bundle.js"></script>
</body>
</html>
Greeter.js只包括一个用来返回包含问候信息的html元素的函数
// Greeter.js
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
main.js用来把Greeter模块返回的节点插入页面
var greeter = require('./Greeter.js');
document.getElementById('root').appendChild(greeter());
准备工作至此完成。开始正式使用webpack。
第一种使用方法是在命令行中使用webpack
webpack {entry file/入口文件} {destination for bundled file/存放bundle.js的地方}
webpack app/main.js public/bundle.js
// 如果没有全局安装webpack,在终端使用命令时需要额外指定webpack在node_modules中的地址
// node_modules/.bin/webpack app/main.js public/bundle.js
执行完成后webpack就打包生成了bundle.js文件
Hash: c2083bed710216439706
Version: webpack 2.4.1
Time: 57ms
Asset Size Chunks Chunk Names
bundle.js 3.04 kB 0 [emitted] main
[0] ./app/Greeter.js 192 bytes {0} [built]
[1] ./app/main.js 141 bytes {0} [built]
webpack还可以通过配置文件来使用。
通过配置文件使用webpack
Webpack拥有很多其它的比较高级的功能(比如loaders和plugins),这些功能其实都可以通过命令行模式实现,但是这样不太方便且容易出错的,一个更好的办法是定义一个配置文件,这个配置文件其实也是一个简单的JavaScript模块,可以把所有的与构建相关的信息放在里面。
还是继续上面的例子来说明如何写这个配置文件,在当前练习文件夹的根目录下新建一个名为webpack.config.js的文件,并在其中进行最最简单的配置,如下所示,它包含入口文件路径和存放打包后文件的地方的路径。
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
}
}
注:“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。
现在如果你需要打包文件只需要在终端里你运行webpack命令就可以了,这条命令会自动参考webpack.config.js文件中的配置选项打包你的项目。
更快捷的执行webpack任务
npm可以引导任务执行,对其进行配置后可以使用简单的npm start命令来代替这些繁琐的命令。
在package.json中对npm的脚本部分进行相关设置即可,设置方法如下。
在其中的scripts中增加对应命令
{
"name": "webpack-sample-project",
"version": "1.0.0",
"description": "Sample webpack project",
"scripts": {
"start": "webpack" //配置的地方就是这里啦,相当于把npm的start命令指向webpack命令
},
"author": "zhang",
"license": "ISC",
"devDependencies": {
"webpack": "^1.12.9"
}
}
npm的start是一个特殊的脚本名称,它的特殊性表现在,在命令行中使用npm start就可以执行相关命令,如果对应的此脚本名称不是start,想要在命令行中运行时,需要这样用npm run {script name}如npm run build
现在就可以直接在命令行中输入下面的指令来运行webpack打包任务了。
npm start
生成Source Maps(使调试更容易)
开发总是离不开调试,如果可以更加方便的调试当然就能提高开发效率,不过打包后的文件有时候你是不容易找到出错了的地方对应的源代码的位置的,Source Maps就是来帮我们解决这个问题的。
通过简单的配置后,Webpack在打包时可以为我们生成的source maps,这为我们提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试。
在webpack的配置文件中配置source maps,需要配置devtool,它有以下四种不同的配置选项,各具优缺点,描述如下:
上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的构建速度的后果就是对打包后的文件的的执行有一定影响。
在学习阶段以及在小到中性的项目上,eval-source-map是一个很好的选项,不过记得只在开发阶段使用它,继续上面的例子,进行如下配置
module.exports = {
devtool: 'eval-source-map',//配置生成Source Maps,选择合适的选项
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
}
}
- 开发环境推荐:
cheap-module-eval-source-map
- 生产环境推荐:
cheap-module-source-map
使用webpack构建本地服务器
Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现浏览器监测代码修改,并自动刷新修改后的结果,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖
npm install --save-dev webpack-dev-server
devserver作为webpack配置选项中的一项,具有以下配置选项
继续把这些命令加到webpack的配置文件中,现在的配置文件如下所示
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
devServer: {
contentBase: "./public",//本地服务器所加载的页面所在的目录
colors: true,//终端中输出结果为彩色
historyApiFallback: true,//不跳转
inline: true//实时刷新
}
}
Loaders
Loaders是webpack中最让人激动人心的功能之一了。通过使用不同的loader,webpack通过调用外部的脚本或工具可以对各种各样的格式的文件进行处理,比如说分析JSON文件并把它转换为JavaScript文件,或者说把下一代的JS文件(ES6,ES7)转换为现代浏览器可以识别的JS文件。或者说对React的开发而言,合适的Loaders可以把React的JSX文件转换为JS文件。
Loaders需要单独安装并且需要在webpack.config.js下的modules关键字下进行配置,Loaders的配置选项包括以下几方面:
- test:一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
- loader:loader的名称(必须)
- include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
- query:为loaders提供额外的设置选项(可选)
//安装可以装换JSON的loader
npm install --save-dev json-loader
webpack的配置文件
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
module: {//在配置文件里添加JSON loader
loaders: [
{
test: /\.json$/,
loader: "json-loader"
}
]
},
devServer: {
contentBase: "./public",
colors: true,
historyApiFallback: true,
inline: true
}
}
babel
Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,不过webpack把它们整合在一起使用,但是对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-preset-es2015包和解析JSX的babel-preset-react包)。
我们先来一次性安装这些依赖包
// npm一次性安装多个依赖模块,模块之间用空格隔开
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react
在webpack中配置Babel的方法如下
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.json$/,
loader: "json-loader"
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',//在webpack的module部分的loaders里进行配置即可
query: {
presets: ['es2015','react']
}
}
]
},
devServer: {
contentBase: "./public",
colors: true,
historyApiFallback: true,
inline: true
}
}
现在你的webpack的配置已经允许你使用ES6以及JSX的语法了。继续用上面的例子进行测试,不过这次我们会使用React,记得先安装 React 和 React-DOM
npm install --save react react-dom
使用ES6的语法,更新Greeter.js并返回一个React组件
//Greeter,js
import React, {Component} from 'react'
import config from './config.json';
class Greeter extends Component{
render() {
return (
<div>
{config.greetText}
</div>
);
}
}
export default Greeter
使用ES6的模块定义和渲染Greeter模块
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
render(<Greeter />, document.getElementById('root'));
Babel的配置选项
Babel其实可以完全在webpack.config.js中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 “.babelrc” 的配置文件中。webpack会自动调用.babelrc里的babel配置选项:
// webpack.config.js
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.json$/,
loader: "json-loader
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
devServer: {...} // Omitted for brevity
}
//.babelrc
{
"presets": ["react", "es2015"]
}
CSS module
在过去的一些年里,JavaScript通过一些新的语言特性,更好的工具以及更好的实践方法(比如说模块化)发展得非常迅速。模块使得开发者把复杂的代码转化为小的,干净的,依赖声明明确的单元,且基于优化工具,依赖管理和加载管理可以自动完成。
不过前端的另外一部分,CSS发展就相对慢一些,大多的样式表却依旧是巨大且充满了全局类名,这使得维护和修改都非常困难和复杂。
最近有一个叫做 CSS modules 的技术就意在把JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块。Webpack从一开始就对CSS模块化提供了支持,在CSS loader中进行配置后,你所需要做的一切就是把”modules“传递都所需要的地方,然后就可以直接把CSS的类名传递到组件的代码中,且这样做只对当前组件有效,不必担心在不同的模块中具有相同的类名可能会造成的问题。具体的代码如下
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {...},
module: {
loaders: [
{
test: /\.json$/,
loader: "json-loader"
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
loader: 'style!css?modules-loader'//跟前面相比就在后面加上了?modules
}
]
},
devServer: {...}
}
创建一个Greeter.css文件
.root {
background-color: #eee;
padding: 10px;
border: 3px solid #ccc;
}
导入.root到Greeter.js中
import React, {Component} from 'react';
import config from './config.json';
import styles from './Greeter.css';//导入
class Greeter extends Component{
render() {
return (
<div className={styles.root}>//添加类名
{config.greetText}
</div>
);
}
}
export default Greeter
放心使用把,相同的类名也不会造成不同组件之间的污染。
参考的这篇文章很好,所以需要在实践中多读几遍。全文在此。
context
context 是 webpack 编译时的基础目录,入口起点(entry)会相对于此目录查找。
若不配置,默认值为当前目录,webpack设置 context 默认值代码:
this.set("context", process.cwd());
process.cwd()即webpack运行所在的目录(等同package.json所在目录)。
context 应该配置为绝对路径,假如入口起点为src/main.js,则可以配置:
{
context: path.resolve('./src'),
entry: './main.js'
}
此时 entry 不能再配置为’./src/main.js’,因为 webpack 会相对于 context 配置的 src 目录区查找入口起点(main.js),而 main.js 就在此目录下,所以应当将 entry 配置为当前目录(./)。
context 有什么实际作用?官方文档的解释是使得你的配置独立于工程目录 「This makes your configuration independent from CWD (current working directory)」。怎么理解?举个例子,在分离开发生产配置文件时候,一般把 webpack.config 放到 build 文件夹下,此时 entry 却不用考虑相对于 build 目录来配置,仍然要相对于 context 来配置,这也就是所谓的独立于工程目录。
需要注意的是,output 的配置项和 context 没有关系,但是有些插件的配置项和 context 有关,后面会说明。