webpack從零開始

webpack是一個基於node.js編寫的資源整合打包器(官方原稱:MODULE BUNDLER),經過指定入口文件,他能將該入口文件中引用的全部前端資源都合併打包,並最終輸出到你指定的輸出目錄。webpack進階比gulp要複雜得多,本文只以從零開始上手使用爲指導。javascript

本文所示代碼,可經過git@osc的這個項目獲取到:http://git.oschina.net/janpoem/webpack-tutorialphp

本文中可能在一些細節上表述得不夠清晰,徹底按照文章的順序去閱讀和執行,可能會碰到一些問題(也是webpack有太多的細節),因此建議先獲取到源代碼,先執行個npm install,讓他慢慢跑,而後一邊看, 一邊參考着代碼來閱讀,能更好的幫助理解文章說到的一些東西。css

而且若是有碰到本文中執行不下去,出問題的,歡迎提出嚴厲的批評和指責!html

簡單的開始

從零開始

如下部分若是你已經瞭解node.js和npm,請忽略跳過,直接進入《webpack安裝》。前端

首先,你要確保你的電腦安裝了node.js,點擊這裏找到適合你係統的版本下載並安裝。並確保你的系統內能正確執行如下命令:java

npm -v
node -v

node.js是什麼?node

Node.js是一個Javascript運行環境(runtime)。實際上它是對Google V8引擎進行了封裝。V8引 擎執行Javascript的速度很是快,性能很是好。Node.js對一些特殊用例進行了優化,提供了替代的API,使得V8在非瀏覽器環境下運行得更好。react

npm是什麼?webpack

NPM的全稱是Node Package Manager ,是一個NodeJS包管理和分發工具,已經成爲了非官方的發佈Node模塊(包)的標準。git

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近親系的es六、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

最後補一張示例代碼的最終的效果圖:

相關文章
相關標籤/搜索