文档章节

webpack从零开始

曾建凯
 曾建凯
发布于 2016/05/18 23:38
字数 4266
阅读 5634
收藏 225

webpack是一个基于node.js编写的资源整合打包器(官方原称:MODULE BUNDLER),通过指定入口文件,他能将该入口文件中引用的所有前端资源都合并打包,并最终输出到你指定的输出目录。webpack进阶比gulp要复杂得多,本文只以从零开始上手使用为指导。

本文所示代码,可通过git@osc的这个项目获取到:http://git.oschina.net/janpoem/webpack-tutorial

本文中可能在一些细节上表述得不够清晰,完全按照文章的顺序去阅读和执行,可能会碰到一些问题(也是webpack有太多的细节),所以建议先获取到源代码,先执行个npm install,让他慢慢跑,然后一边看, 一边参考着代码来阅读,能更好的帮助理解文章说到的一些东西。

并且如果有碰到本文中执行不下去,出问题的,欢迎提出严厉的批评和指责!

简单的开始

从零开始

以下部分如果你已经了解node.js和npm,请忽略跳过,直接进入《webpack安装》。

首先,你要确保你的电脑安装了node.js,点击这里找到适合你系统的版本下载并安装。并确保你的系统内能正确执行以下命令:

npm -v
node -v

node.js是什么?

Node.js是一个Javascript运行环境(runtime)。实际上它是对Google V8引擎进行了封装。V8引 擎执行Javascript的速度非常快,性能非常好。Node.js对一些特殊用例进行了优化,提供了替代的API,使得V8在非浏览器环境下运行得更好。

npm是什么?

NPM的全称是Node Package Manager ,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。

webpack安装

如果你对webpack的基本安装和使用已经十分了解,并已经安装,可以跳过本章节,并直接进入《webpack进阶》章节。

接下来你需要在全局环境内安装webpack和webpack-dev-server,当然后者并不是必须的,但还是强烈推荐你安装。

npm install webpack webpack-dev-server -g

初次使用

随便建立一个测试的目录,比如:webpack-first,用命令行进入,或者webstorm/phpstorm/atom等打开这个目录。

在目录中添加一个文件:webpack.config.js

module.exports = {
	entry: [
		"./main.js"
	],
	output: {
		path: './output',
		filename: 'app.js'
	}
};

在这个文件中,我们声明了一个入口文件,为当前目录下的main.js,并且输出目录的基础路径为当前目录下的output,输出的文件名为app.js

接着,我们往下添加test.js和main.js文件,main.js如上所述,为项目的入口文件,test.js为需要引入在main.js中的模块。

test.js

module.exports = [
	'a', 'b', 'c'
];

main.js

// 引入test.js文件,并将其输出的内容(module.exports)赋值到test变量上
var test = require('./test');
// 在浏览器的console中输出test变量的内容
console.log(test);

进入命令行模式(如果是phpstorm/webstorm可以打开他的Terminal工具),输入指令:webpack,会看到他执行的结果:

该命令执行完,webpack会在你的项目内添加一个output的目录,打开这个目录,你会看到根据你的webpack.config.js配置,他生成了一个app.js文件。

这里特别说明一下,如果你仅仅只是需要用webpack来打包最原始的js文件,是不需要在这个项目内安装webpack和webpack-dev-server的,他会使用你全局(就是刚才npm install -g的)安装的版本,这点比gulp好多了。

使用webpack-dev-server

webpack-dev-server,是webpack提供的一个插件,他提供了一个http服务器环境,给你实时预览打包合并的结果。

特别提前说明的是,使用webpack-dev-server指令,你必须指定--content-base指令,--content-base指令,用于指定http服务器的document root目录。

在刚才的项目根目录中,执行以下命令:webpack-dev-server --content-base ./public,我们以当前目录下的public目录作为http服务器的根目录:

特别说明:在webpack.config.js中指定的output目录和--content-base并没有必然的关系,output指定的是你输出的路径。

在项目中添加public目录,并添加一个用于测试的文件:public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>webpack first sample</title>
</head>
<body>
<!--引入output中的app.js文件-->
<script type="text/javascript" src="app.js"></script>
</body>
</html>

访问http://localhost:8080/,并打开你的浏览器调试工具,你将能看到在main.js中输出的信息。

除此之外,你还可以访问:http://localhost:8080/webpack-dev-server/,这里提供了一个基于iframe的方式加载index.html的页面,并且当你每次修改完js文件时,他都会自动刷新这个页面以实时预览。

webpack进阶

上述可以作为一个基本的上手说明,这里要说的,就是一些比较实际——和项目实际关联的内容。

要使用进阶功能,就需要在项目内安装webpack和webpack-dev-server了,如果你还没执行npm init,可以先执行一次npm init,他会需要你回答一些问题,并生成一个与当前项目相关的package.json文件。package.json文件,能有效的管理你的node.js项目的版本,已经依赖库,执行脚本,等信息,尤其当你将项目提交到代码仓库,别人也需要执行你的项目时,通过package.json文件,能够很好的克服不同的系统环境下,缺失的依赖类库。

npm init执行以后,请执行下面的命令:

npm install webpack webpack-dev-server --save-dev

这个指令的含义是,在当前项目内安装最新版本的webpack和webpack-dev-server。

--save-dev参数,用于告诉npm这两个类库的信息需要写入到package.json中,并作为开发依赖库。npm还有一个--save指令,与--save-dev类似,但他关联的是直接依赖。--save-dev和--save,决定了别人在引用你的项目时,你项目依赖库是否也安装在对方的node_modules目录中,这个详细就不在这里展开了。

执行完这两个命令,打开你的package.json文件,你会看到如下内容:

npm命令需要访问国外的npm库,如果你感觉你当前的网络环境出国外不是那么稳定,可以考虑使用cnpm,点击这里看教程,这里就不再介绍了。

webpack-dev-server命令行参数

在webpack-dev-server包含了一些比较常用的参数,可以大大提高我们的开发效率,这里介绍几个最常用的:

--port <端口号>: 指定http服务的端口号

--host <主机名>: 指定http服务的主机名,这在局域网内使用实时调试非常有用。

--compress: 启用gzip压缩

--inline: 将webpack-dev-server的运行时文件合并打包到输出的文件中

--hot: 使用HotModuleReplacementPlugin插件(已经整合在webpack中,无需npm安装),并将http服务器切换到hot模式,其实所谓hot模式,就是当你更改了某个被引用的文件,会hot replace,并重新合并到输出文件中。

一般来说,--hot --inline会合并使用,这个方式会合并将webpack/hot/dev-server打包到输出文件中。这个webpack/hot/dev-server,实际上就是这个http服务器的hot replace的核心逻辑,而这个东西,其实在你就是你当前项目的node_modules/webpack/hot/dev-server.js文件。打开node_modules/webpack/hot/目录,实际上你会看到这里还有其他的几种模式,比如only-dev-server等。事实上你是可以显式的将这些文件配置在你的webpack.config.js文件中里的,比如:

module.exports = {
	entry: [
		"webpack/hot/only-dev-server",
		"./main.js"
	],
	output: {
		path: './output',
		filename: 'app.js'
	}
};

当你指定了only-dev-server以后,执行:webpack-dev-server --content-base ./public --hot时,注意输出的变化:

而如果我把webpack.config.js中的webpack/hot/only-dev-server注释掉,再执行:webpack-dev-server --content-base ./public --hot --inline

这是官方文档非常隐晦的地方,没有明确说明的地方。

时空门:webpack-dev-server官方说明

而webpack本身比较常用的参数有:

--devtool : 调试工具的模式,eval是将你的css和js代码变为eval的方式合并打包。

--config : 指定配置文件

--progress: 在命令行终端输出编译合并的过程信息

--colors: 在命令行终端中显示带颜色的信息

更多更详细的信息,可以看这里,webpack CLI模式的说明

webpack还提供了一个通过你自己书写代码来启动http服务的功能,这个应该说是最高阶的内容了,本文就不讨论了,有兴趣的可以去官方文档看:webpack-dev-server,这篇文档的下半部分,就是完全由你自己实现一个http服务的简单教程。详细的可以去看webpack node.js API

除此之外,这篇文章是你使用webpack以前,必须阅读的:webpack config配置参数说明,每个项目有自身的特殊性,本文只是希望能将一些基本常用的东西能做一个涵盖,所以完全靠本文的内容,肯定无法解决每个项目实际碰到的问题,所以熟读这个配置文档,应该是最起码的要求。

webpack loaders

在实用层面的第二个问题就是各种loaders,这里分两个部分,一个是js部分,包括如js近亲系的es6、jsx、babel tranplier、coffee等,第二部分为CSS以及其近亲,如less、stylus,同时包括其他前端的静态资源的引用问题。

这里是webpack loaders的清单

加载JS部分

基于webpack打包,你可以比以往过去更加放心大胆的使用各种奇葩,前所未见的各种脚本语言——当然这里的前提是你能自己处理好他们的转译问题。而目前babel已经提供了大多数常见的语言、css、html模板的loaders,不过根据我实际经验,webpack主要的优势还是在于处理js的合并打包。

以下就以es6和jsx为例(使用babel-loader):

npm install babel-loader babel-preset-es2015 babel-preset-react --save-dev

如果你需要使用到babel的一些插件,也需要通过npm来进行安装,关于babel如何使用的问题,可以参考我写的这篇文章:《Babel指南 - 基本环境搭建》。

npm install babel-plugin-transform-class-properties babel-plugin-transform-es2015-block-scoping babel-plugin-transform-es2015-computed-properties --save-dev

在webpack.config.js中,我们修改如下配置:

module.exports = {
	entry: [
		"./main.js"
	],
	output: {
		path: './output',
		filename: 'app.js'
	},
	module: {
		loaders: [
			{
				test: /\.(es6|jsx)?$/,
				exclude: /(node_modules|bower_components)/,
				loader: 'babel', // 'babel-loader' is also a legal name to reference
				query: {
					presets: ['es2015', 'react'],
					plugins: [
						"transform-es2015-block-scoping",
						"transform-class-properties",
						"transform-es2015-computed-properties"
					]
				}
			}
		]
	}
};

其实也很简单,通过这样,你就可以随意的使用es6和jsx了,比如我们添加一个例子hello_world.jsx:

注意,要使用到react,你还是需要先安装:

npm install react react-dom --save-dev

hello_world.jsx代码如下

var React = require('react');

class HelloWorld extends React.Component {

	constructor(props) {
		super(props);
		this.state = {
			url: '',
			value: 'http://tool.oschina.net/'
		};
	}

	getUrl() {
		let url = this.state.value;
		if (/^\/\//.test(url)) {
			url = 'http:' + url;
		}
		else if (!/^https?\:\/\//i.test(url)) {
			url = 'http://' + url;
		}
		return url;
	}

	changeUrl(value) {
		this.setState({value: value});
	}

	goUrl(value, event) {
		if (event && event.preventDefault)
			event.preventDefault();
		this.setState({url: value});
	}

	renderFrame() {
		if (this.state.url) {
			return
		}
	}

	componentDidMount() {
		this.goUrl(this.getUrl());
	}

	render() {
		return <div>
			<h1>Hello world!!</h1>
			<form onSubmit={(e) => this.goUrl(this.getUrl(), e)}>
				<input type="text" value={this.state.value} onChange={(e) => this.changeUrl(e.target.value)}/>
				<button>Hello world</button>
			</form>
			{this.renderFrame()}
		</div>;
	}
}

module.exports = HelloWorld;

接着修改main.js:

var test = require('./test');
var ReactDOM = require('react-dom');
var React = require('react');
var HelloWorld = require('./hello_world.jsx');

var doc = document, body = doc.body;

body.onload = function() {
	var el = doc.createElement('div');
	el.style.opacity = 0;
	el.style.marginTop = '-100px';
	el.style.transitionProperty = 'opacity margin-top';
	el.style.transitionDuration = '800ms';
	el.style.transitionTimingFunction = 'cubic-bezier(0.65,-0.1, 0.24, 1.47)';
	body.appendChild(el);
	ReactDOM.render(React.createElement(HelloWorld), el);
	setTimeout(function() {
		el.style.opacity = 1;
		el.style.marginTop = 0;
	}, 1);
};

访问http://localhost:8080/就会看到:

加载CSS部分

要在webpack中引入css文件,需要简单的做一个梳理。

JS中引入CSS文件

通过css-loader,是可以将一个css文件用require函数,在代码中被引用,并且返回这个css中样式定义的文字内容。当然首先你必须安装css-loader:

npm install css-loader --save-dev

然后我们就可以在js中引入css

var css = require('css!./style.css')

但这样,只是将css的内容引入,并没有加载到页面上,所以我们需要修改main.js:

var test = require('./test');
var ReactDOM = require('react-dom');
var React = require('react');
var HelloWorld = require('./hello_world.jsx');
var css = require('css!./style.css');

var doc = document, body = doc.body;

body.onload = function() {
	// 在head中把样式加载
	var style = doc.createElement('style');
	style.innerText = css.toString();
	doc.head.appendChild(style);

	var el = doc.createElement('div');
	el.style.opacity = 0;
	el.style.marginTop = '-100px';
	el.style.transitionProperty = 'opacity margin-top';
	el.style.transitionDuration = '800ms';
	el.style.transitionTimingFunction = 'cubic-bezier(0.65,-0.1, 0.24, 1.47)';
	body.appendChild(el);
	ReactDOM.render(React.createElement(HelloWorld), el);
	setTimeout(function() {
		el.style.opacity = 1;
		el.style.marginTop = 0;
	}, 1);
};

这样,我们会看到http://localhost:8080/已经加载到style.css的样式了。关于css-loader的详细用法,可以去css-loader的github看看。

注意,目前我们还没去修改webpack.config.js。

style-loader

style-loader:可以将一个已经输出的内容变为一个style的DOM标签输出。安装:

npm install style-loader --save-dev

这时候去掉刚才在body.onload中增加的在head加入输出css的代码,修改main.js如下:

var test = require('./test');
var ReactDOM = require('react-dom');
var React = require('react');
var HelloWorld = require('./hello_world.jsx');
var css = require('style!css!./style.css');

var doc = document, body = doc.body;

body.onload = function() {
	var el = doc.createElement('div');
	el.style.opacity = 0;
	el.style.marginTop = '-100px';
	el.style.transitionProperty = 'opacity margin-top';
	el.style.transitionDuration = '800ms';
	el.style.transitionTimingFunction = 'cubic-bezier(0.65,-0.1, 0.24, 1.47)';
	body.appendChild(el);
	ReactDOM.render(React.createElement(HelloWorld), el);
	setTimeout(function() {
		el.style.opacity = 1;
		el.style.marginTop = 0;
	}, 1);
};

注意:style!css!./style.css。这时候得到的效果,和刚才是一样的。

为什么要不厌其烦的将css和style的loader拆开两个章节来介绍呢,因为所有less、stylus实际上都是最终都是基于这个机制在运作的。

加载图片、字体、svg等资源

如果需要在样式中加载图片,那么就需要url-loader,而如字体,则需要使用file-loader,还是安装:

npm install url-loader file-loader --save-dev

注意,这里就是webpack和gulp最大的区别,webpack中,只要在你的源码中存在被引用的资源,你都需要说明这些资源需要被如何加载。事实上,webpack还有如base64-loader,你也完全可以将一个图片作为base64-loader来处理。当然这里我们就以url-loader处理。

使用不同的loader,将决定了合并打包后的处理方式,如果使用base64-loader,他当然会将图片的内容打包成base64编码合并在js中。而url-loader,则会在输出的目录生成对应的文件(只有本地文件,会输出到output目录下)。

修改webpack.config.js:

module.exports = {
	entry: [
		"./main.js"
	],
	output: {
		path: './output',
		filename: 'app.js'
	},
	module: {
		loaders: [
			{
				test: /\.(es6|jsx)?$/,
				exclude: /(node_modules|bower_components)/,
				loader: 'babel', // 'babel-loader' is also a legal name to reference
				query: {
					presets: ['es2015', 'react'],
					plugins: [
						"transform-es2015-block-scoping",
						"transform-class-properties",
						"transform-es2015-computed-properties"
					]
				}
			},
			{
				test: /\.(png|jpg|jpeg|gif|(woff|woff2)?(\?v=[0-9]\.[0-9]\.[0-9])?)$/,
				loader: 'url-loader?limit=1000'
			},
			{
				test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
				loader: 'file'
			}
		]
	}
};

然后重启webpack-dev-server,你就能看到图片、svg等资源已经能正确的加载。

关于合并打包,我们最后会说到。

加载CSS近亲

这里以stylus为例,其他less、scss等的也都是同理的。

npm install stylus stylus-loader --save-dev

同样的,你只要使用require即可在页面引入styl文件:

require('style!css!stylus!./hello.styl');

如果你觉得使用style!css!stylus!的方式加载文件,过于怪异,那么你可以修改webpack.config.js文件,增加两个loader:

{
	test: /\.styl$/,
	loader: "style!css!stylus"
},
{
	test: /\.css$/,
	loader: "style!css"
}

这样,你就可以使用顺眼一点的方式来加载:

require('./style.css');
require('./hello.styl');

最终打包

到此,我们已经完成了全部的编码。接下来就要将内容打包输出了,这时候你只要在项目的根目录下执行一下webpack即可。

这时候我们会看到,在output目录下,他输出了一个app.js和svg文件。而本例子中的两个css文件,他全部合并到打包到了app.js文件中。

如果你希望将css文件从js文件中分离出来,需要一个额外的插件:

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

这个插件可以指定拦截特定加载器输出的文本内容,并最终合并输出到你指定的文件上去,但要特别注意,使用这个插件后,webpack-dev-server自动输出样式调试就会因为这个插件而失效,所以建议构建的时候,使用单独的配置文件,比如添加一个webpack.build.js,使用指令:

webpack --config webpack.build.js

webpack.build.js内容如下:

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
	entry: [
		"./main.js"
	],
	output: {
		path: './output',
		filename: 'app.js'
	},
	module: {
		loaders: [
			{
				test: /\.(es6|jsx)?$/,
				exclude: /(node_modules|bower_components)/,
				loader: 'babel',
				query: {
					presets: ['es2015', 'react'],
					plugins: [
						"transform-es2015-block-scoping",
						"transform-class-properties",
						"transform-es2015-computed-properties"
					]
				}
			},
			{
				test: /\.(png|jpg|jpeg|gif|(woff|woff2)?(\?v=[0-9]\.[0-9]\.[0-9])?)$/,
				loader: 'url-loader?limit=1000'
			},
			{
				test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
				loader: 'file'
			},
			{
				test: /\.styl$/,
				// loader: "style!css!stylus"
				loader: ExtractTextPlugin.extract('style', 'css!stylus')
			},
			{
				test: /\.css$/,
				// loader: "style!css"
				loader: ExtractTextPlugin.extract('style', 'css')
			}
		]
	},
	plugins: [
		new ExtractTextPlugin('app.css')
	]
};

这次就能将css文件分离出来了。

webpack还有很多可调节、可优化的配置,但是碍于篇幅,在这里就不再详细展开了。可能有一些细节的地方,本文没有介绍的很清楚,也请多多见谅。

本文所示代码,可通过git@osc的这个项目获取到:http://git.oschina.net/janpoem/webpack-tutorial

最后补一张示例代码的最终的效果图:

 

© 著作权归作者所有

曾建凯

曾建凯

粉丝 336
博文 66
码字总数 104794
作品 0
广州
技术主管
私信 提问
加载中

评论(18)

曾建凯
曾建凯 博主

引用来自“雷神雨石”的评论

引用来自“曾建凯”的评论

引用来自“红薯”的评论

你可真能写啊
好东西要共享~

红薯对你有想法了
@红薯 其实我从读书的时候就一直自己写小说,只是没出版。2000-2002年还在以前某个社区中做过网络写手,不过那时候没有网络版权,也不知道后来网络文学版权会发展成这样。早知道就不写程序,写小说了。程序写出bug,可不是开玩笑了,小说写出bug,顶多就是被网友喷喷。
Evila
Evila
不错,学习了
雷神雨石
雷神雨石

引用来自“曾建凯”的评论

引用来自“红薯”的评论

你可真能写啊
好东西要共享~

红薯对你有想法了
曾建凯
曾建凯 博主

引用来自“海诺者”的评论

我每次都是从零开始
这几年前端日新月异,哪次不是从零开始……
海诺者
海诺者
我每次都是从零开始
安正超
安正超
79
Rwing
Rwing
不错 好文 收藏
曾建凯
曾建凯 博主

引用来自“KisChang”的评论

好东西!!感谢lz分享
不过我觉得还是用Gulp + webpack 组合起来更好用一些
其实我也是gulp + webpack,尤其是之前老项目,一堆stylus文件,webpack的长处始终是处理js。但webpack有个好处是,修改了样式,不用刷新即可看到变化。
曾建凯
曾建凯 博主

引用来自“soxgoon”的评论

"初次使用" 里面的文件名 应该是 `app.js`
感谢,已经调整。
liujb88
liujb88
写得不错,点赞。
【翻译】Webpack 4 教程:从0配置到生产模式

原文链接:Webpack 4 Tutorial: from 0 Conf to Production Mode webpack 4 问世了! 这个流行的模块打包工具进行了大规模的升级。 webpack4,有什么更新?大幅度的性能优化,零配置和明智的...

Skandar-Ln
2018/05/15
0
0
一步步使用 webpack 第一篇:使用 webpack 编译 es6 代码

前言 团队使用的 webpack 功能很强大,有时候会碰到编译失败的情况,总得找工具的作者解决问题,自己很少去追究原因,感觉对于 webpack 的认知总是一知半解。因此,从本篇文章开始,自己从零...

NingBo
04/23
0
0
手把手教你升级到Webpack4

前端项目日益复杂,构建系统已经成为开发过程中不可或缺的一个部分,而模块打包(module bundler)正是前端构建系统的核心。Webpack作为当下最受欢迎的前端资源模块化管理和打包工具,它可以...

海说软件
2018/08/15
0
0
从零开始配置webpack(基于webpack 4 和 babel 7版本)

webpack启动后会从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有Module.每找到一个Module,就会根据配置的Loader去找出对应的转换规则,对Module进行转换后,再解析出当前的Module依...

大灰狼的小绵羊哥哥
04/13
0
0
精读《webpack4.0 升级指南》

本周精读的是 webpack4.0 一些变化,以及 typescript 该怎么做才能最大化利用 webpack4.0 的所有特性。 1 引言 前段时间尝试了 parcel 作为构建工具,就像农村人享受了都市的生活,就再也回不...

翱翔大空
2018/07/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

最简单的获取相机拍照的图片

  import android.content.Intent;import android.graphics.Bitmap;import android.os.Bundle;import android.os.Environment;import android.provider.MediaStore;import andr......

MrLins
20分钟前
2
0
说好不哭!数据可视化深度干货,前端开发下一个涨薪点在这里~

随着互联网在各行各业的影响不断深入,数据规模越来越大,各企业也越来越重视数据的价值。作为一家专业的数据智能公司,个推从消息推送服务起家,经过多年的持续耕耘,积累沉淀了海量数据,在...

个推
21分钟前
4
0
第三方支付-返回与回调注意事项

不管是支付宝,微信,还是其它第三方支付,第四方支付,支付机构服务商只要涉及到钱的交易都要进行如下校验,全部成功了才视为成功订单 1.http请求是否成功 2.校验商户号 3.校验订单号及状态...

Shingfi
24分钟前
3
0
简述Java内存分配和回收策略以及Minor GC 和 Major GC(Full GC)

内存分配: 1. 栈区:栈可分为Java虚拟机和本地方法栈 2. 堆区:堆被所有线程共享,在虚拟机启动时创建,是唯一的目的是存放对象实例,是gc的主要区域。通常可分为两个区块年轻代和年老代。更...

DustinChan
30分钟前
4
0
Excel插入批注:可在批注插入文字、形状、图片

1.批注一直显示:审阅选项卡-------->勾选显示批注选项: 2.插入批注快捷键:Shift+F2 组合键 3.在批注中插入图片:鼠标右键点击批注框的小圆点【重点不可以在批注文本框内点击】----->调出批...

东方墨天
53分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部