最近在看許多React的資料,發(fā)現(xiàn)了大部分的項目都是用 webpack 行模塊化管理的工具。這次也是借著寫了一個React-Todos的小應(yīng)用,對webPack最基本實用的功能體驗了一番,順帶做個小記錄。
#為什么用webpack
CommonJs與AMD
在一開始,我們先講一下它和以往我們所用的模塊管理工具有什么不一樣。在最開始的階段,Js并沒有這些模塊機制,各種Js到處飛,得不到有效妥善的管理。后來前端圈開始制定規(guī)范,最耳熟能詳?shù)氖荂ommonJs和AMD。
CommonJs是應(yīng)用在NodeJs,是一種同步的模塊機制。它的寫法大致如下:
var firstModule = require("firstModule");
//your code...
module.export = anotherModule
AMD的應(yīng)用場景則是瀏覽器,異步加載的模塊機制。 require.js 的寫法大致如下:
define(['firstModule'], function(module){
//your code...
return anotherModule
})
其實我們單比較寫法,就知道CommonJs是更為優(yōu)秀的。它是一種同步的寫法,對Human友好,而且代碼也不會繁瑣臃腫。但更重要的原因是,
隨著npm成為主流的JavaScript組件發(fā)布平臺,越來越多的前端項目也依賴于npm上的項目,或者自身就會發(fā)布到npm平臺。
所以我們對如何可以使用npm包中的模塊是我們的一大需求。所以
browserify
工具就出現(xiàn)了,它支持我們直接使用
require()
的同步語法去加載npm模塊。
當(dāng)然我們這里不得不說的是,ES2015(ES6)里也有了自己的模塊機制,也就是說ES6的模塊機制是官方規(guī)定的,我們通過 babel (一種6to5的編譯器)可以使用比較多的新特性了,包括我們提到的模塊機制,而它的寫法大致如下:
import {someModule} from "someModule";
// your codes...
export anotherModule;
當(dāng)然上面的寫法只是最基本的,還有其他的不同加載模塊的寫法,可以看一下阮一峰老師的 ECMAScript 6 入門 或者babel的相關(guān)文檔 Learn ES2015 。
功能特性
browserify的出現(xiàn)非常棒,但webpack更勝一籌!
我們來看看webpack支持哪些功能特性:
-
支持CommonJs和AMD模塊,意思也就是我們基本可以無痛遷移舊項目。
-
支持模塊加載器和插件機制,可對模塊靈活定制。特別是我最愛的babel-loader,有效支持ES6。
-
可以通過配置,打包成多個文件。有效利用瀏覽器的緩存功能提升性能。
-
將樣式文件和圖片等靜態(tài)資源也可視為模塊進(jìn)行打包。配合loader加載器,可以支持sass,less等CSS預(yù)處理器。
- 內(nèi)置有source map,即使打包在一起依舊方便調(diào)試。
看完上面這些,可以想象它就是一個前端工具,可以讓我們進(jìn)行各種模塊加載,預(yù)處理后,再打包。之前我們對這些的處理是放在grunt或gulp等前端自動化工具中。有了webpack,我們無需借助自動化工具對模塊進(jìn)行各種處理,讓我們工具的任務(wù)分的更加清晰。
我們看一下官方對webpack理解的圖。
任何靜態(tài)資源都可以視作模塊,然后模塊之間也可以相互依賴,通過webpack對模塊進(jìn)行處理后,可以打包成我們想要的靜態(tài)資源。
既然已經(jīng)大致知道為什么我們要使用webpack了,我們接下來就開始使用webpack吧!
#開始使用webpack
首先新建一個webpack101的項目,我們將在webpack101這里開展我們接下來的各項學(xué)習(xí)。
$ npm init // 用于初始化項目的package.json
//初始化文件目錄:
webpack101
--- src
--- entry.js
--- module1.js
--- index.html
--- package.json
--- webpack.config.js
安裝webpack
我們通過npm來將webpack安裝到全局
$ npm install webpack -g
一個最簡單的webpack
webpack配置
webpack是需要進(jìn)行配置的,我們在使用webpack的時候,會默認(rèn)
webpack.config.js
為我們的配置文件。所以接下來,我們新建這個js文件。
// webpack.config.js
var path = require("path");
module.exports = {
entry: '../src/entry.js', //演示單入口文件
output: {
path: path.join(__dirname, 'out'), //打包輸出的路徑
filename: 'bundle.js', //打包后的名字
publicPath: "./out/" //html引用路徑,在這里是本地地址。
}
};
編寫入口文件
接下來就編寫我們的入口文件
entry.js
和第一個模塊文件
module1.js
。我們一切從簡,里面只用來加載一個Js模塊。
// entry.js
require("./module1"); // 使用CommonJs來加載模塊
下一個文件
// module1.js
console.log("Hello Webpack!");
啟動webpack
一切準(zhǔn)備好后,我們僅需要在項目根目錄下,用命令行
webpack
執(zhí)行一下即可。
// webpack 命令行的幾種基本命令
$ webpack // 最基本的啟動webpack方法
$ webpack -w // 提供watch方法,實時進(jìn)行打包更新
$ webpack -p // 對打包后的文件進(jìn)行壓縮,提供production
$ webpack -d // 提供source map,方便調(diào)試。
webpack成功運行后,我們就可以看到根目錄出現(xiàn)了out文件夾,里面有我們打包生成的
bundle.js
。我們最后通過在
index.html
里對這個文件引入就可以了。我們可以在控制臺看到我們想要的結(jié)果,
Hello Webpack !
多模塊依賴
剛才的例子,我們僅僅是跑通了webpack通過
entry.js
入口文件進(jìn)行打包的例子。下面我們就來看一下它是否真的支持CommonJs和AMD兩種模塊機制呢?下面我們新建多幾個js文件吧!
// 修改module1.js
require(["./module3"], function(){
console.log("Hello Webpack!");
});
下一個文件
// module2.js,使用的是CommonJs機制導(dǎo)出包
module.exports = function(a, b){
return a + b;
}
下一個文件
// module3.js,使用AMD模塊機制
define(['./module2.js'], function(sum){
return console.log("1 + 2 = " + sum(1, 2));
})
其實像上面這樣混用兩種不同機制非常不好,這里僅僅是展示用的,在開發(fā)新項目時還是推薦CommonJs或ES2015的Module。當(dāng)然我個人更傾向于ES2015的模塊機制的~
loader加載器
到了我最喜歡也是最激動人心的功能了!我們先想想應(yīng)用場景,前端社區(qū)有許多預(yù)處理器供我們使用。我們可以使用這些預(yù)處理器做一些強大的事情,大家都聽過的就是
CoffeeScript
和
Sass
了。我們以前要編譯這些預(yù)處理器,就是用
gulp
進(jìn)行編譯。但是我們對這些文件處理其實也挺繁瑣的,webpack可以一次性解決!
在這里我們用Sass和babel編譯ES2015為例子,看一下loader是如何使用的。
安裝loader
我們第一步就是先要安裝好各個必須的loader,我們直接看看需要通過npm安裝什么。
$ npm install style-loader css-loader url-loader babel-loader sass-loader file-loader --save-dev
配置loader
安裝完各個loader后,我們就需要配置一下我們的
webpack.config.js
,載入我們的loader。
// webpack.config.js
module.exports = {
entry: path.join(__dirname, 'src/entry.js'),
output: {
path: path.join(__dirname, 'out'),
publicPath: "./out/",
filename: 'bundle.js'
},
// 新添加的module屬性
module: {
loaders: [
{test: /\.js$/, loader: "babel"},
{test: /\.css$/, loader: "style!css"},
{test: /\.(jpg|png)$/, loader: "url?limit=8192"},
{test: /\.scss$/, loader: "style!css!sass"}
]
}
};
我們主要看看module的loaders。loaders是一個數(shù)組,里面的每一個對象都用正則表達(dá)式,對應(yīng)著一種配對方案。比如匹配到j(luò)s后綴名就用babel-loader,匹配到scss后綴名的就先用sass,再用css,最后用style處理,不同的處理器通過
!
分隔并串聯(lián)起來。這里的loader是可以省略掉
-loader
這樣的,也就是原本應(yīng)該寫成
style-loader!css-loader!sass-loader
,當(dāng)然我們必須惜字如金,所以都去掉后面的東東。
我們僅僅是配置一下,已經(jīng)是可以直接用ES2015和SASS去寫我們的前端代碼了。在此之前,我們對src文件夾里再細(xì)分成js,css,image三個文件夾,處理好分層。話不多說,趕緊試試。
稍微復(fù)雜的webpack項目
bebel-loader
// js/es6-module.js
class People{
constructor(name){
this.name = name;
}
sayhi(){
console.log(`hi ${this.name} !`);
}
}
exports.module = People;
寫好模塊后,我們直接在
entry.js
入口文件中引入該模塊。
// entry.js
// javascript
require('./js/module1');
let People = require('./js/es6-module');
let p = new People("Yika");
p.sayHi();
// css
require('./css/main.scss');
哈哈哈,不能再爽!這下子我們可以使用很多優(yōu)秀的ES6特性去構(gòu)建大型的web了。
sass-loader
大家或許注意到了下方的css的require,那就是用來加載Sass樣式的。我們通過啟動style-loader會將css代碼轉(zhuǎn)化到
<style>
標(biāo)簽內(nèi),我們看一下里面的內(nèi)容。
// css/main.scss
html, body{
background: #dfdfdf;
}
最后我們打開
index.html
觀察我們所有的結(jié)果,首先背景已經(jīng)是淡灰色的,并且控制臺也有我們想要的內(nèi)容。我們通過查看DOM結(jié)構(gòu),可以發(fā)現(xiàn)
head
標(biāo)簽里多出了
style
標(biāo)簽,里面正是我們想要定制的樣式。
關(guān)于對圖片的打包
我們之前也說,webpack對與靜態(tài)資源來說,也是看作模塊來加載的。CSS我們是已經(jīng)看過了,那圖片是怎么作為模塊打包加載進(jìn)來呢?這里我們可以想到,圖片我們是用url-loader加載的。我們在css文件里的url屬性,其實就是一種封裝處理過require操作。當(dāng)然我們還有一種方式就是直接對元素的src屬性進(jìn)行require賦值。
div.img{
background: url(../image/xxx.jpg)
}
//或者
var img = document.createElement("img");
img.src = require("../image/xxx.jpg");
document.body.appendChild(img);
上述兩種方法都會對符合要求的圖片進(jìn)行處理。而要求就是在url-loader后面通過query參數(shù)的方式實現(xiàn)的,這里就是說只有不大于8kb的圖片才會打包處理成Base64的圖片。關(guān)于query,請看文檔: Query parameters
{test: /\.(jpg|png)$/, loader: "url?limit=8192"}
打包成多個資源文件
我們在開發(fā)多頁面的站點的時候,還是需要希望能有多個資源文件的。這樣我們就可以有效利用緩存提升性能,做到文件按需加載。如何寫入口文件,這里就不再贅述了,我們直接看如何對
webpack.config.js
進(jìn)行修改。
// webpack.config.js
entry: {
page1: "entry.js",
page2: "entry2.js"
},
output: {
path: path.join(__dirname, 'out'),
publicPath: "./out/",
filename: '[name].js'
}
這里重點關(guān)注兩個地方,entry屬性可以是一個對象,而對象名也就是key會作為下面output的filename屬性的
[name]
。當(dāng)然entry也可以是一個數(shù)組,更多用法都可以去webpack的
官方文檔
進(jìn)行查看。
當(dāng)然webpack也考慮到公共模塊的利用,我們利用插件就可以智能提取公共部分,以提供我們?yōu)g覽器的緩存復(fù)用。我們只需要在
webpack.config.js
添加下面的代碼即可。
// 修改添加,webpack.config.js
var webpack = require('webpack');
module.exports = {
// ....省略各種代碼
plugins: [
new webpack.optimize.CommonsChunkPlugin('common.js')
]
}
我們做個小測試,讓第二個入口文件也加載我們之前的
es6-module.js
。然后我們用webpack進(jìn)行打包,就發(fā)現(xiàn)生成的
common.js
里是有相應(yīng)代碼的。我們需要手動在html上去加載
common.js
,并且是
必須要最先加載
。
獨立出css樣式
如果我們希望樣式通過
<link>
引入,而不是放在
<style>
標(biāo)簽內(nèi)呢,即使這樣做會多一個請求。這個時候我們就要配合插件一起使用啦,我們一起來看看。
$ npm install extract-text-webpack-plugin --save-dev
安裝完插件就要配置
webpack.config.js
了。我們添加以下代碼
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
// ...省略各種代碼
module: {
loaders: [
{test: /\.js$/, loader: "babel"},
{test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader")},
{test: /\.(jpg|png|svg)$/, loader: "url?limit=8192"},
{test: /\.scss$/, loader: "style!css!sass"}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('common.js'),
new ExtractTextPlugin("[name].css")
]
}
為了區(qū)分開用
<link>
鏈接和用
<style>
,我們這里以CSS后綴結(jié)尾的模塊用插件。我們重點關(guān)注一下使用了ExtractTextPlugin的模塊,在ExtractTextPlugin的extract方法有兩個參數(shù),第一個參數(shù)是經(jīng)過編譯后通過style-loader單獨提取出文件來,而第二個參數(shù)就是用來編譯代碼的loader。
當(dāng)然,插件也支持所有獨立樣式打包成一個css文件。增加多一個參數(shù)即可。
new ExtractTextPlugin("style.css", {allChunks: true})
至于怎樣加載樣式是最佳實踐,這個就要自己平時多思考了。多站點多樣式的時候,是做到一次性打包加載呢,還是按需加載呢?我這里就建議一項,主頁盡量做到最精簡,畢竟決定用戶存留時間。
#總結(jié)
前端社區(qū)不斷發(fā)展,越來越趨向于組件化的發(fā)展。通過webpack,我們就能體驗到
one component one module
的開發(fā)感覺。當(dāng)然如何更好的使用webpack還是要通過不斷的思考總結(jié),才能找到最優(yōu)的方案。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
