項目初始化
以你喜歡的任意方式,創建項目根目錄,如:
mkdir vue-hello
初始化包模塊管理文件
進入項目根目錄,初始化項目包模塊管理文件package.json:
npm init
命令臺會提示你輸入一堆信息,如果你想簡單一點,可以添加-y
參數,跳過輸入步驟,生成默認信息:
npm init -y
初始化源碼目錄
在項目根目錄下創建源碼目錄結構,通常源碼目錄是src或app,個人喜好是使用src:
webpack簡述及使用
雖然在本篇文章我們不會對webpack做太過詳細的介紹,但是依然希望能幫助讀者對webpack擁有更清晰的瞭解,webpack是什麼?
webpack is a tool to build JavaScript modules in your application
webpack是一個幫助你的應用構建JavaScript模塊的工具。
接下來,我們介紹幾個知識點幫助理解webpack:模塊化,webpack原理及其與grunt和gulp的比較。
模塊化
模塊化 指解決一個複雜問題時自頂向下逐層把系統劃分成若干模塊的過程。
模塊是一個可組合、分解和更換的單元,將一個系統分解成若干模塊,單元;大家遵循一定的規範,各司其職,各自開發不同模塊;之後可以較低成本的將模塊組合起來,構成一個完整的系統,極大方便了團隊成員之間的協助開發,產出效率得到有效提升。
webpack原理
webpack是一個幫助你的應用構建JavaScript模塊的工具,其本質只能處理JavaScript,那你會疑惑了,不是說使用webpack,可以很方便的在JavaScript代碼中引用圖片,CSS等資源嘛?是的,這正是webpack的優勢,那怎麼實現的呢?這就要涉及到webpack中的一個概念:加載器(loader)。
加載器 是作用於應用資源文件的轉換器,它們是一系列JavaScript函數,接受資源文件的內容做參數,然後返回新的資源(以一個JavaScript模塊的形式返回)。
所以,對於webpack,我們明確三點:
- 模塊:webpack中一切資源文件(JavaScript, 樣式, 圖片資源等)皆視爲模塊;
- 加載器:webpack通過加載器(JavaScript函數)將其他資源處理(構建)成JavaScript模塊;
- 管理依賴:webpack在編譯模塊時,就能分析查找該模塊內的依賴,可以很好的處理不同模塊間的依賴;
webpack對比grunt/gulp
-
grunt
打開grunt官網,你可以看到最醒目的介紹:
The JavaScript Task Runner
,還有一個關鍵字automation
- 自動化,其定位是一個JavaScript的自動化構建任務處理器,幫助開發者自動化處理項目的構建流程; -
gulp
gulp官網的定義是:
Automate and enhance your workflow
,自動強化項目構建流程,其與grunt的目標一致,都是幫助開發者自動化處理項目的構建流程,不同的是gulp實現方式是基於流的,即以流的方式處理文件,而grunt是以二進制方式處理文件,gulp使用性能是要強於grunt的; -
總結
- webpack定位是一個模塊化管理工具,而grunt/gulp都是自動化任務流程構建工具;
- grunt基於二進制處理文件,gulp基於流式處理文件,效率比grunt更高一些;
- webpack強大特性,使得其添加諸多插件可以替代grunt/gulp,但是目前的實踐項目中,通常webpack結合gulp或grunt使用(各自處理各自專長的任務);
安裝
首先安裝webpack,npm或yarn都可以,無甚區別:
npm install --save-dev webpack
關於此處的--save
和-dev
參數做簡要說明:
--save
是聲明將安裝依賴添加入package.json文件;- 默認地,使用npm安裝包模塊依賴時,依賴關係存儲在在
"dependencies"
屬性對象內,表示項目依賴; - webpack是作爲開發環境依賴的,不是作爲源碼直接調用,所以添加
-dev
參數,以聲明其是開發環境依賴;
webpack配置介紹
實踐項目使用webpack完成自動化構建,本地服務調試與熱加載,首先在根目錄下創建webpack的配置入口文件webpack.config.js,基本內容結構如下:
var path = require('path');
module.exports = {
context: path.resolve(__dirname, 'src/'),
entry: {
app: './scripts/app.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist/scripts/')
},
module: {
loaders: []
},
resolve: {
modules:[],
alias: {},
extensions: []
},
plugins: [],
devServer: {}
};
如上,webpack配置文件使用module.exports方式導出配置對象,webpack執行時會默認讀取項目根目錄下webpack.config.js文件,當然可以手動配置指定一個文件作爲配置文件,我們不討論,可以參考webpack文檔,接下來對webpack配置內容做簡要介紹,如果你對webpack使用比較熟悉,可以跳過此節。
文件處理配置
和webpack文件處理相關的幾個配置屬性主要有三個:目錄上下文信息(context), 處理文件入口信息(entry), 文件輸出信息(output)。
目錄上下文信息(context)
設置解析處理文件入口的相對目錄,值爲一個絕對目錄路徑,默認爲當前執行目錄,通常即項目根目錄,在Node中其值與process.cwd()
相同,如:
context: path.resolve(__dirname, 'src/'),
如上即解析爲項目根目錄下的src目錄。
處理文件入口信息(entry)
處理文件入口,值可以是字符串,或數組,或對象,值爲字符串或數組時,即輸出單文件,值爲對象,可以輸出多文件,輸出文件名稱等信息參考文件輸出信息(output)。
文件輸出信息(output)
此配置聲明webpack編譯輸出文件的文件名等信息,如:
filename: '[name].js',
聲明文件名就是模塊(chunk)名,對應在entry中定義的入口,你可能還見過[id]
,[hash]
,[chunkhash]
這些用法,現做簡單介紹:
- [id]:該值表示webpack編譯模塊(chunk)的id,通常是一個數字;
- [name]:該值表示webpack編譯模塊(chunk)名,對應entry中定義的入口名或文件名;
- [chunkhash]:該值表示webpack編譯模塊(chunk)hash值,根據模塊內容計算出的一個md5值;
- [hash]:該值表示webapck編譯對象hash值,根據編譯對象計算出的md5值;
編譯對象,即webpack執行時讀取配置後生成的一個編譯配置對象,包含模塊,待編譯文件,相對於上次編譯的變更文件等諸多信息,需要注意的是該對象在webpack啓動讀取配置文件後形成,在此次編譯過程保持不變。
- output.path:定義輸出文件所在目錄;
- output.publicPath:定義輸出文件在瀏覽器訪問時的基礎相對路徑,可以與後文
webpack-dev-server
一起使用。
webpack加載器與模塊
在介紹加載器配置之前,先看看加載器是什麼:
Loaders are transformations that are applied on a resource file of your app. They are functions (running in node.js) that take the source of a resource file as the parameter and return the new source.
加載器是作用於應用資源文件的轉換器,它們是一系列函數,接受資源文件的內容做參數,然後返回新的資源(以一個模塊的形式返回)。
webpack
webpack解析文件時使用的加載器都聲明在module
屬性的loaders
數組中,可以設置一個或多個加載器。
- module.noParse:指定某些文件不需要使用解析,支持傳入文件路徑或正則表達式;
- module.loaders:指定解析文件的加載器模塊及各模塊解析規則,可以設置以下屬性:
test
: 待解析文件需匹配的規則,通常以文件後綴名稱匹配文件;include
:待解析文件所處目錄;exclude
: 過濾掉的目錄;loader
:加載器模塊名稱;loaders
多個加載器;
模塊解析規則配置
webpack支持在resolve
屬性對象中配置模塊解析規則,主要有root
,modules
, alias
和extensions
屬性。
resolve.root與resolve.modules
該屬性設置的是在開發代碼中使用require
或import
加載某模塊時,webpack查找該模塊所遵循的查找目錄範圍,如在源代碼中存在:
var utils = require('utils/utils.js');
而root
配置如下:
resolve: {
root: [
path.resolve('./src/'),
'node_modules'
]
}
webpack編譯時將自動在項目根目錄下的src
目錄內的utils
目錄下查找utils.js
,若存在,則返回,否則進入node_modules
目錄內查找utils.js
模塊。
當然,若未設置resolve.root
屬性值,則webpack默認先從node_modules
查找模塊,然後在根目錄下查找。
注意:resolve.root在webpack v1版本中使用,而在webpack v2 中,使用modules代替,建議使用v2版本,後文也將統一使用modules屬性。
resolve.alias
alias,有別名的意思,這裏的作用是設置其他模塊或路徑的別名,webpack在解析模塊時,將使用配置值替換該別名,如,在未設置alias屬性內容時,即resolve.alias默認是空對象{}
時,我們在代碼中引用模塊:
var Vue = require('vue/dist/vue.js');
var TopHeader = require('components/header.js');
webpack在編譯代碼時,按resolve.modules
聲明的順序依次查找對應模塊,如按照上一節定義的resolve.modules
:
resolve: {
root: [
path.resolve('./src/'),
'node_modules'
]
}
查找模塊時,將首先在src/vue/dist/
目錄下尋找vue
模塊,在src/components/
目錄下查找header.js
模塊,查找到後返回,否則進入node_modules
目錄查找。
現在,我們還可以通過resolve.alias
爲模塊或路徑聲明一個別名,更方便的聲明加載模塊:
resolve: {
alias: {
'vue$': 'vue/dist/vue.js'
components: path.resolve(__dirname, 'src/components/')
}
}
我可以使用如下方式加載模塊:
var Vue = require('vue');
var TopHeader = require('components/header.js');
$
符號
關於alias
的詳細使用介紹,請參見文檔,本文並不是要介紹webpack文檔,在這裏介紹一下聲明的vue$
別名中的$
符號:
這裏的$
符合是正則的文末匹配符,即只有當vue
是最後一級路徑時,webpack纔會將該值解析成別名,進行別名與對應值替換,如vue/test.js
中的vue是不會當作別名解析的,而components/header.js
中的components則會按照聲明的components
別名進行解析,其結果是src/components/header.js
。
resolve.extensions
該值定義解析模塊時的查找文件的後綴順序,如["", ".js", ".json"]
,會優先返回js文件,其次json文件,然後是其他文件,注意,這裏數組傳入了一個空字符串,他的作用是在未找到配置中所有列舉的類型文件時,支持webpack返回其他類型文件。
webpack插件配置
webpack還支持多種多樣的插件拓展,你可以使用它們對你的項目webpack模塊構建過程進行額外處理,如代碼壓縮,圖片等資源提交壓縮,構建異常捕獲和提升,構建流程時間消耗比,等等,而關於這些插件使用的配置在plugins
屬性數組內,將在後續進行介紹。
項目本地調試與開發
爲了方便開發和調試,通常都需要在本地主機開啓服務,提供局域網內多終端訪問,並且在文件變更時,實時刷新頁面,正如grunt和gulp中Browsersync
插件提供的功能一樣,webpack可以使用webpack-dev-server
模塊實現。
webpack-dev-server是一個Node.js的express服務器,以webpack開發中間件的形式爲webpack包提供服務,當監聽到源碼文件變更時,會自動重新打包,並且支持配置自動刷新瀏覽器,重新加載資源。
安裝
由於該插件只用於開發模式,所以通過以下npm指令安裝:
npm install webpack-dev-server --save-dev
配置
啓用webpack-dev-server
時,其默認開啓8080端口,訪問localhost:8080
返回當前目錄下的index.html
,即執行指令當前目錄下的靜態資源,當然可以通過指令傳遞參數或在配置文件進行配置指定靜態資源目錄。
另外需要注意的是,開啓
webpack-dev-server
後,變更文件重新打包後,並不會實際輸出到配置的output
目錄,而是在publicPath
屬性聲明的相對路徑所在的內存中讀取。
靜態資源目錄配置(content base)
webpack-dev-server --content-base src/
執行以上指令開啓服務後,webpack-dev-server
將默認在src/
目錄返回靜態資源,當然也可以在webpack.config.js
配置文件中指定:
devServer: {
contentBase: './src'
}
訪問http://localhost:8080
和http://localhost:8080/index.html
效果一樣,均返回src
目錄下的index.html
文件。
publicPath與輸出文件訪問
在前文webpack配置一節中提到output.publicPath
屬性值表示,在瀏覽器訪問webpack output
輸出的文件時的基礎相對路徑,如設置:
output: {
path: 'dist/scripts',
filename: 'app.js',
publicPath: 'assets/'
}
則在html文件中引用該app.js
文件的方式如下:
<script src="assets/app.js"></script>
在瀏覽器中訪問app.js
的方式爲:http://localhost:8080/assets/app.js
自動刷新(Automatic Refresh)
前面說到webpack-dev-server
支持文件變更時,自動刷新瀏覽器,這也是開發者急需的功能,webpack-dev-server
支持兩種方式實現:
iframe
模式(iframe mode):頁面通過iframe
窗口插入然後變更時自動重新加載;inline
模式(inline mode):通過sock.js(比如websocket協議,輪詢等方式)在頁面嵌入一個小型客戶端與webpack-dev-server
服務器建立連接,當發生變更重新打包時,通過此連接通知頁面重新加載;
iframe模式
使用默認的iframe
模式時,不需要進行任何配置,可以直接訪問:http://localhost:8080/webpack-dev-server/index.html
即可,如圖,通過iframe
插入我們的頁面:
注意:
- 在應用頁面上方會有狀態欄顯示當前應用信息;
- 應用URL的變更發生在
iframe
內部,不會反映在瀏覽器地址欄;
inline模式
要開啓inline
模式,只需要指定--inline
命令行參數或在配置文件中指定inline: true
:
devServer: {
contentBase: './src',
inline: true
}
在此模式下,直接訪問http://localhost:8080/index.html
或http://localhost:8080
即可,此模式與iframe
模式的區別在於:
- 訪問URL不同;
- 需指定
inline
配置參數; - 應用信息在控制檯輸出;
- 應用URL的變更直接反映在瀏覽器地址欄;
其實inline
模式還可以配合Node.Js服務運行,之後再介紹,這裏只說明了其在HTML文檔中的使用。
熱替換(Hot Module Replacement)
除了自動刷新,webpack-dev-server
的另一大賣點是模塊熱替換,那麼熱替換到底是什麼?
熱替換,即文件發生變更時,webpack包只替換髮生變更的模塊,而不需要全部替換;瀏覽器不需要完全重新加載,而可以直接將變更的模塊注入到運行中的應用。
開啓熱替換功能需要指定--hot
指令參數,或在配置文件中添加:
devServer: {
contentBase: './src',
inline: true,
hot: true
}
注意到以上代碼,熱替換需要與inline
模式一起使用,另外需要指定output.publicPath
值。
HotModuleReplacementPlugin
配置好後還需要使用webpack.HotModuleReplacementPlugin
插件,才能真正啓用熱替換功能:
plugins: [
new webpack.HotModuleReplacementPlugin()
]
同樣的,熱替換可以搭配Node.js服務一起使用,之後介紹。
如圖,爲每個文件(app.js, test.js)都實現了HMR熱替換,當發生變更時,只更新變更模塊,而不是重新加載所有文件:
擁抱ES6
ES6推出以來,廣受Jser們青睞,其確實有很多新特性和新規範,值得我們深入學習並使用,可以參考ECMAScript 6入門,未來所有的JavaScript應用都應該擁抱ES6,不過,目前各大瀏覽器都在推進ES6的實現,在兼容實現之前,我們還需要過渡性的將其轉換成ES5語法,最通用的方式就是使用babel
轉換。
使用babel加載器
首先,爲了能使用webpack和babel轉換js代碼,需要使用babel-loader
加載器,另外還需要安裝babel
轉換js的轉換規則插件,如babel-preset-es2015
定義了轉換規則,安裝方式如下:
npm install --save-dev babel-loader babel-preset-es2015
然後在根目錄下創建.babelrc
文件,內容:
{
"presets": ["es2015"]
}
在webpack.config.js配置文件中,添加相關loader配置:
module: {
loaders: [
{
test: /\.(js|vue)$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
其中,test
匹配需要轉換的文件,exclude
匹配不需要轉換的文件或目錄。
babel-polyfill
我們需要明白的一點是babel-preset-es2015
能做的,只是轉換ES6代碼成ES5,使得瀏覽器可以解析執行,但是對於ES6新提出的API,如Promise,Generator等無法簡單的轉換成ES5代碼,這時就需要babel-polyfill
了,babel-polyfill
是一個墊片,它可以模擬提供所有的ES6功能和特性,可以看作是提供了一個模擬的全局ES6環境。
- 安裝
安裝依然很簡單,由於是墊片,是需要在源碼中使用的,所有指定--save
參數:
npm install --save babel-polyfill
- 使用
不同於babel-preset-es2015
,babel-polyfill
需要直接打包進源碼,而且需要在使用ES6代碼前引入一次:
import 'babel-polyfill'
只需要引入一次,因爲該墊片提供的是一個模擬的全局ES6環境,而不是模塊內部的。
或者直接在webpack.config.js中配置打包入口文件時加入該墊片:
entry: {
app: ['babel-polyfill', './scripts/app.js']
},
現在你可以在你的代碼中使用任何你想用的ES6 API了。
到此,基本介紹瞭如何使用webpack搭建開發環境,下一篇將開始介紹如何使用webpack處理CSS及如何使用Vue進行組件化開發。