前言
webpack2和vue2已經(jīng)不是新鮮東西了,滿大街的文章在講解webpack和vue,但是很多內(nèi)容寫(xiě)的不是很詳細(xì),對(duì)于很多個(gè)性化配置還是需要自己過(guò)一遍文檔。Vue官方提供了多個(gè)vue-templates,基于vue-cli用的最多,不過(guò)對(duì)于很多人來(lái)說(shuō),vue-cli 的配置還是過(guò)于復(fù)雜,對(duì)于我們了解細(xì)節(jié)實(shí)現(xiàn)不是很好,所以想自己從零開(kāi)始搭建一個(gè)模板工程,也順便重新認(rèn)識(shí)一下webpack和vue工程化。
webpack 核心概念
Webpack 是當(dāng)下最熱門(mén)的前端資源模塊化管理和打包工具。它可以將許多松散的模塊按照依賴(lài)和規(guī)則打包成符合生產(chǎn)環(huán)境部署的前端資源。還可以將按需加載的模塊進(jìn)行代碼分隔,等到實(shí)際需要的時(shí)候再異步加載。通過(guò) loader 的轉(zhuǎn)換,任何形式的資源都可以視作模塊,比如 CommonJs 模塊、 AMD 模塊、 ES6 模塊、CSS、圖片、 JSON、Coffeescript、 LESS 等。
官方網(wǎng)站:https://webpack.js.org/
安裝
在開(kāi)始前,先要確認(rèn)你已經(jīng)安裝Node.js的最新版本。使用 Node.js 最新的 LTS 版本,是理想的起步。使用舊版本,你可能遇到各種問(wèn)題,因?yàn)樗鼈兛赡苋鄙?webpack 功能或缺少相關(guān) package 包。
本地局部安裝:
# 安裝 latest release
npm install --save-dev webpack
# 簡(jiǎn)寫(xiě)模式
npm install -D webpack
# 安裝特定版本
npm install --save-dev webpack@<version>
全局安裝:
npm install -g webpack
注意:不推薦全局安裝 webpack。這會(huì)鎖定 webpack 到指定版本,并且在使用不同的 webpack 版本的項(xiàng)目中可能會(huì)導(dǎo)致構(gòu)建失敗。但是全局安裝可以在命令行調(diào)用 webpack 命令。
【補(bǔ)充】npm install 安裝模塊參數(shù)說(shuō)明:
-g, --global 全局安裝(global)
-S, --save 安裝包信息將加入到dependencies(生產(chǎn)階段的依賴(lài))
-D, --save-dev 安裝包信息將加入到devDependencies(開(kāi)發(fā)階段的依賴(lài)),所以開(kāi)發(fā)階段一般使用它
-O, --save-optional 安裝包信息將加入到optionalDependencies(可選階段的依賴(lài))
-E, --save-exact 精確安裝指定模塊版本
npm 相關(guān)的更多命令參考這篇文章:npm 常用命令詳解
然后在根目錄下創(chuàng)建一個(gè) webpack.config.js
文件后,你可以通過(guò)配置定義webpack的相關(guān)操作。
入口(Entry)
入口起點(diǎn)告訴 webpack 從哪里開(kāi)始,并遵循著依賴(lài)關(guān)系圖表知道要打包什么。可以將您應(yīng)用程序的入口起點(diǎn)認(rèn)為是根上下文(contextual root)或 app 第一個(gè)啟動(dòng)文件。
單個(gè)入口(簡(jiǎn)寫(xiě))語(yǔ)法:
用法:entry: string|Array<string>
webpack.config.js:
module.exports = {
entry: './src/main.js'
};
對(duì)象語(yǔ)法:
用法:entry: {[entryChunkName: string]: string|Array<string>}
webpack.config.js:
module.exports = {
entry: {
app: './src/main.js',
vendor: ['vue']
}
};
這里我們將vue作為庫(kù)vendor打包,業(yè)務(wù)邏輯代碼作為app打包,實(shí)現(xiàn)了多個(gè)入口,同時(shí)也可以將多個(gè)頁(yè)面分開(kāi)打包。
多頁(yè)面應(yīng)用程序通常使用對(duì)象語(yǔ)法構(gòu)建。對(duì)象語(yǔ)法是“可擴(kuò)展的 webpack 配置”,可重用并且可以與其他配置組合使用。這是一種流行的技術(shù),用于將關(guān)注點(diǎn)(concern)從環(huán)境(environment)、構(gòu)建目標(biāo)(build target)、運(yùn)行時(shí)(runtime)中分離。然后使用專(zhuān)門(mén)的工具(如webpack-merge)將它們合并。
注:vue-cli 生成的模板中build文件夾下有四個(gè)配置文件:
- webpack.base.conf.js:基本配置
- webpack.dev.conf.js:開(kāi)發(fā)階段配置
- webpack.prod.conf.js:準(zhǔn)生產(chǎn)階段配置
- webpack.test.conf.js:測(cè)試配置
后三個(gè)文件通過(guò)webpack-merge插件合并了基本配置,將不同環(huán)境下的配置拆分多個(gè)文件,這樣更加方便管理。
出口(Output)
將所有的資源(assets)歸攏在一起后,還需要告訴 webpack 在哪里打包應(yīng)用程序。webpack 的 output 屬性描述了如何處理歸攏在一起的代碼(bundled code)。output 選項(xiàng)控制 webpack 如何向硬盤(pán)寫(xiě)入編譯文件。注意,即使可以存在多個(gè)入口起點(diǎn),但只指定一個(gè)輸出配置。
在 webpack 中配置output 屬性的最低要求是,將它的值設(shè)置為一個(gè)對(duì)象,包括以下兩點(diǎn):
- output.filename:編譯文件的文件名;
- output.path對(duì)應(yīng)一個(gè)絕對(duì)路徑,此路徑是你希望一次性打包的目錄。
單個(gè)入口:
const path = require('path');
module.exports = {
entry: './src/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build') //__dirname + '/build'
}
}
多個(gè)入口:
如果你的配置創(chuàng)建了多個(gè) "chunk"(例如使用多個(gè)入口起點(diǎn)或使用類(lèi)似CommonsChunkPlugin 的插件),你應(yīng)該使用以下的替換方式來(lái)確保每個(gè)文件名都不重復(fù)。
- [name] 被 chunk 的 name 替換。
- [hash] 被 compilation 生命周期的 hash 替換。
- [chunkhash] 被 chunk 的 hash 替換。
const path = require('path');
module.exports = {
entry: {
app: './src/main.js',
vendor: ['vue']
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build')
}
}
// 寫(xiě)入到硬盤(pán):./build/app.js, ./build/vendor.js
加載器(Loaders)
loader 用于對(duì)模塊的源代碼進(jìn)行轉(zhuǎn)換。loader 可以使你在 require() 或"加載"模塊時(shí)預(yù)處理文件。因此,loader 類(lèi)似于其他構(gòu)建工具中“任務(wù)(task)”,并提供了處理前端構(gòu)建步驟的強(qiáng)大方法。loader 可以將文件從不同的語(yǔ)言(如 TypeScript)轉(zhuǎn)換為 JavaScript,或?qū)?nèi)聯(lián)圖像轉(zhuǎn)換為 data URL。loader 甚至允許你在 JavaScript 中 require() CSS文件!
在你的應(yīng)用程序中,有三種方式使用 loader:
這里我們主要說(shuō)明一下使用webpack.config.js配置,使用loader需要在module的rules下配置相應(yīng)的規(guī)則,以css-loader的webpack.config.js為例說(shuō)明:
module.exports = {
module: {
rules: [
{test: /\.css$/, use: 'css-loader'}
]
}
};
這三種配置方式等效:
{test: /\.css$/, use: 'css-loader'}
{test: /\.css$/, loader: 'css-loader',options: { modules: true }}
{test: /\.css$/, use: {
loader: 'css-loader',
options: {
modules: true
}
}}
注:loader/query可以和options可以在同一級(jí)使用,但是不要使用use和options在同一級(jí)使用。
CSS樣式分離
為了用 webpack 對(duì) CSS 文件進(jìn)行打包,你可以像其它模塊一樣將 CSS 引入到你的 JavaScript 代碼中,同時(shí)用css-loader(像 JS 模塊一樣輸出 CSS),也可以選擇使用ExtractTextWebpackPlugin(將打好包的 CSS 提出出來(lái)并輸出成 CSS 文件)。
引入 CSS:
import 'bootstrap/dist/css/bootstrap.css';
安裝css-loader和style-loader:
npm install --save-dev css-loader style-loader
在 webpack.config.js 中配置如下:
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
}
資源路徑處理
因?yàn)?png等圖片文件不是一個(gè) JavaScript 文件,你需要配置 Webpack 使用file-loader或者url-loader去處理它們。使用它們的好處:
- file-loader 可以指定要復(fù)制和放置資源文件的位置,以及如何使用版本哈希命名以獲得更好的緩存。此外,這意味著 你可以就近管理你的圖片文件,可以使用相對(duì)路徑而不用擔(dān)心布署時(shí)URL問(wèn)題。使用正確的配置,Webpack 將會(huì)在打包輸出中自動(dòng)重寫(xiě)文件路徑為正確的URL。
- url-loader 允許你有條件將文件轉(zhuǎn)換為內(nèi)聯(lián)的 base-64 URL(當(dāng)文件小于給定的閾值),這會(huì)減少小文件的 HTTP 請(qǐng)求。如果文件大于該閾值,會(huì)自動(dòng)的交給 file-loader 處理。
安裝 file-loader 和 url-loader:
npm install --save-dev file-loader url-loader
配置說(shuō)明:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'img/[name]_[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
插件(Plugins)
由于 loader 僅在每個(gè)文件的基礎(chǔ)上執(zhí)行轉(zhuǎn)換,而插件(plugins)最常用于(但不限于)在打包模塊的“compilation”和“chunk”生命周期執(zhí)行操作和自定義功能(查看更多)。webpack 的插件系統(tǒng)極其強(qiáng)大和可定制化。
想要使用一個(gè)插件,你只需要 require() 它,然后把它添加到 plugins 數(shù)組中。多數(shù)插件可以通過(guò)選項(xiàng)(option)自定義。你也可以在一個(gè)配置文件中因?yàn)椴煌康亩啻问褂猛粋€(gè)插件,你需要使用 new 創(chuàng)建實(shí)例來(lái)調(diào)用它。
生產(chǎn)環(huán)境構(gòu)建
對(duì)于Vue生產(chǎn)環(huán)境構(gòu)建過(guò)程中壓縮應(yīng)用代碼和使用Vue.js 指南 - 刪除警告去除 Vue.js 中的警告,這里我們參考vue-loader文檔中的配置說(shuō)明:
if (process.env.NODE_ENV === 'production') {
// http://vue-loader.vuejs.org/zh-cn/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: false,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
顯然我們不想在開(kāi)發(fā)過(guò)程中使用這些配置,所以這里我們需要使用環(huán)境變量動(dòng)態(tài)構(gòu)建,我們也可以使用兩個(gè)分開(kāi)的 Webpack 配置文件,一個(gè)用于開(kāi)發(fā)環(huán)境,一個(gè)用于生產(chǎn)環(huán)境,類(lèi)似于vue-cli中使用 webpack-merge 合并配置的方式。
可以使用 Node.js 模塊的標(biāo)準(zhǔn)方式:在運(yùn)行 webpack 時(shí)設(shè)置環(huán)境變量,并且使用 Node.js 的process.env
來(lái)引用變量。NODE_ENV變量通常被視為事實(shí)標(biāo)準(zhǔn)(查看這里)。使用cross-env
包來(lái)跨平臺(tái)設(shè)置(cross-platform-set)環(huán)境變量。
安裝cross-env:
npm install --save-dev cross-env
設(shè)置package.json中的scripts字段:
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
}
這里我們使用了cross-env插件,cross-env使得你可以使用單個(gè)命令,而無(wú)需擔(dān)心為平臺(tái)正確設(shè)置或使用環(huán)境變量。
模塊熱替換
模塊熱替換功能會(huì)在應(yīng)用程序運(yùn)行過(guò)程中替換、添加或刪除模塊,而無(wú)需重新加載頁(yè)面。這使得你可以在獨(dú)立模塊變更后,無(wú)需刷新整個(gè)頁(yè)面,就可以更新這些模塊,極大地加速了開(kāi)發(fā)時(shí)間。
這里我們使用webpack-dev-server插件,webpack-dev-server 為你提供了一個(gè)服務(wù)器和實(shí)時(shí)重載(live reloading)功能。webpack-dev-server是一個(gè)小型的node.js Express服務(wù)器,它使用webpack-dev-middleware中間件來(lái)為通過(guò)webpack打包生成的資源文件提供Web服務(wù)。它還有一個(gè)通過(guò)Socket.IO連接著webpack-dev-server服務(wù)器的小型運(yùn)行時(shí)程序。webpack-dev-server發(fā)送關(guān)于編譯狀態(tài)的消息到客戶端,客戶端根據(jù)消息作出響應(yīng)。
安裝 webpack-dev-server:
npm install --save-dev webpack-dev-server
安裝完成之后,你應(yīng)該可以使用 webpack-dev-server 了,方式如下:
webpack-dev-server --open
上述命令應(yīng)該自動(dòng)在瀏覽器中打開(kāi) http://localhost:8080。
webpack.config.js配置:
module.exports = {
...
devServer: {
historyApiFallback: true, // 任意的 404 響應(yīng)都替代為 index.html
hot: true, // 啟用 webpack 的模塊熱替換特性
inline: true // 啟用內(nèi)聯(lián)模式
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
...
}
更多的配置說(shuō)明可以看文檔:DevServer
動(dòng)態(tài)生成 html 文件
該插件將為你生成一個(gè)HTML5文件,其中包括使用script標(biāo)簽的body中的所有webpack包,也就是我們不需要手動(dòng)通過(guò)script去引入打包生成的js,特別是如果我們生成的文件名是動(dòng)態(tài)變化的,使用這個(gè)插件就可以輕松的解決,只需添加插件到您的webpack配置如下:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
...
}
提取 CSS 文件
extract-text-webpack-plugin
是一個(gè) 可以將*.vue
文件內(nèi)的 <style> 提取,以及JavaScript 中導(dǎo)入的 CSS 提取為單個(gè) CSS 文件。配置文檔具體見(jiàn)這里:extract-text-webpack-plugin。
安裝:
npm install --save-dev extract-text-webpack-plugin
配置:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin("styles.css"),
]
}
同時(shí)支持我們可以配置生成多個(gè)css文件,這樣我們可以將業(yè)務(wù)邏輯代碼和引用的樣式組件庫(kù)分離。
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// Create multiple instances
const extractCSS = new ExtractTextPlugin('stylesheets/[name]-one.css');
const extractLESS = new ExtractTextPlugin('stylesheets/[name]-two.css');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: extractCSS.extract([ 'css-loader', 'postcss-loader' ])
},
{
test: /\.less$/i,
use: extractLESS.extract([ 'css-loader', 'less-loader' ])
},
]
},
plugins: [
extractCSS,
extractLESS
]
};
clean-webpack-plugin
在編譯前,刪除之前編譯結(jié)果目錄或文件:
npm install --save-dev clean-webpack-plugin
配置:
plugins: [
new CleanWebpackPlugin(['dist'])
]
這樣當(dāng)我們?cè)跇?gòu)建的時(shí)候可以自動(dòng)刪除之前編譯的代碼。
解析(Resolve)
這些選項(xiàng)能設(shè)置模塊如何被解析。webpack 提供合理的默認(rèn)值,但是還是可能會(huì)修改一些解析的細(xì)節(jié)。
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': path.join(__dirname, 'src')
},
extensions: ['.js', '.json', '.vue', '.css']
}
我們使用最多的就是別名(alias)和自動(dòng)解析確定的擴(kuò)展(extensions),例如上面的@可以代替項(xiàng)目中src的路徑,例如:
import tab from '@/components/tab.vue'
我們引用src/components目錄下的tab.vue組件,不需要通過(guò)../
之類(lèi)的計(jì)算文件相對(duì)路徑。這里的extensions可以讓我們?cè)谝肽K時(shí)不帶擴(kuò)展:
import tab from '@/components/tab'
至此我們已經(jīng)學(xué)習(xí)了我們項(xiàng)目devDependencies依賴(lài)中常用的模塊:
webpack
css-loader / style-loader
file-loader / url-loader
cross-env
webpack-dev-server
html-webpack-plugin
extract-text-webpack-plugin
clean-webpack-plugin
這里我們只說(shuō)明了css、圖片、html模板資源webpack相關(guān)的加載器和插件,對(duì)于js相關(guān)的內(nèi)容絲毫沒(méi)有提到,顯然這是不合乎情理的。之所以要把js單獨(dú)拿出來(lái)是因?yàn)閖s相關(guān)的內(nèi)容很重要,獨(dú)立出來(lái)詳細(xì)去歸納一下更合適。
webpack 中如何使用 es6 ~ es8?
作為一個(gè)前端,相信 es6 幾乎是無(wú)人不知,很多人也一定知道可以使用Babel做語(yǔ)法轉(zhuǎn)換,但是對(duì)于Babel有哪一些版本,每個(gè)版本支持的es6語(yǔ)法有哪一些應(yīng)該不是所有人都清楚的,這就是這部份內(nèi)容要寫(xiě)的意義。畢竟如果我們的插件只用到了es6中的沒(méi)一些新特性,為此將整個(gè)包引入就有點(diǎn)不太合適,另外為了更好的用上新特性,我們至少要明白有哪一些新特性吧。
ECMAScript 標(biāo)準(zhǔn)建立的過(guò)程
ECMAScript 和 JavaScript 的關(guān)系在此不再贅述,建議閱讀一下阮一峰老師的《ECMAScript 6簡(jiǎn)介》,我們需要了解的是從ECMAScript 2016開(kāi)始,ECMAScript將進(jìn)入每年發(fā)布一次新標(biāo)準(zhǔn)的階段。制定ECMAScript 標(biāo)準(zhǔn)的組織是ECMAScript TC39,TC39(ECMA技術(shù)委員為39)是推動(dòng)JavaScript發(fā)展的委員會(huì)。 它的成員是都是企業(yè)(主要是瀏覽器廠商)。TC39會(huì)定期的開(kāi)會(huì), 會(huì)議的主要成員時(shí)是成員公司的代表,以及受邀請(qǐng)的專(zhuān)家。
一種新的語(yǔ)法從提案到變成正式標(biāo)準(zhǔn),需要經(jīng)歷五個(gè)階段。每個(gè)階段的變動(dòng)都需要由 TC39 委員會(huì)批準(zhǔn)。
- Stage 0 - Strawman(展示階段)
- Stage 1 - Proposal(征求意見(jiàn)階段)
- Stage 2 - Draft(草案階段)
- Stage 3 - Candidate(候選人階段)
- Stage 4 - Finished(定案階段)
建議看一下alinode 團(tuán)隊(duì)的圖說(shuō)ECMAScript新標(biāo)準(zhǔn)(一)就可以大致了解整個(gè)過(guò)程。
安裝 Babel
Babel 現(xiàn)在的官網(wǎng)提供了一個(gè)可以根據(jù)你的工具提示下載合適的包,具體見(jiàn)這里:Using Babel。
如果你想要在命令行使用Babel,你可以安裝babel-cli,但是全局的安裝babel-cli不是一個(gè)好的選擇,因?yàn)檫@樣限定了你Babel的版本;如果你需要在一個(gè)Node項(xiàng)目中使用Babel,你可以使用babel-core。
我們這里自然選擇webpack構(gòu)建我們的工程,下載方案如下:
npm install --save-dev babel-loader babel-core
然后我們需要在項(xiàng)目根目錄下建立.babelrc
文件:
{
"presets": [],
"plugins": []
}
注:在window下無(wú)法通過(guò) 右鍵=>新建 命令來(lái)創(chuàng)建以點(diǎn)開(kāi)頭的文件和文件夾,我們可以通過(guò)下面的命令生成.babelrc
文件:
type NUL > .babelrc
Linux和Mac下可以通過(guò)touch命令生成:
touch .babelrc
Babel 預(yù)設(shè)(presets)
Babel是一個(gè)編譯器。 在高層次上,它有3個(gè)階段,它運(yùn)行代碼:解析,轉(zhuǎn)換和生成(像許多其他編譯器)。默認(rèn)情況下,Babel 6并沒(méi)有攜帶任何轉(zhuǎn)換器,因此如果對(duì)你的代碼使用Babel的話,它將會(huì)原文輸出你的代碼,不會(huì)有任何的改變。因此你需要根據(jù)你需要完成的任務(wù)來(lái)單獨(dú)安裝相應(yīng)的插件。
你可以通過(guò)安裝插件(plugins)或預(yù)設(shè)(presets,也就是一組插件)來(lái)指示 Babel 去做什么事情。Babel 提供了多個(gè)版本的官方預(yù)設(shè):
babel-preset-env
babel-preset-env可以根據(jù)你配置的選項(xiàng),自動(dòng)添加一些其他的轉(zhuǎn)換器,來(lái)滿足你當(dāng)前的裝換需求。.babelrc文件新增了options選項(xiàng):
{
"presets": ["env", options]
}
具體的配置內(nèi)容:
- targets.node 支持到哪個(gè)版本的 node
- targets.browsers 支持到哪個(gè)版本的瀏覽器
- loose 啟動(dòng)寬松模式,配合 webpack 的 loader 使用
- modules 使用何種模塊加載機(jī)制
- debug 開(kāi)啟調(diào)試模式
- include 包含哪些文件
- exclude 排除哪些文件
- useBuiltIns 是否對(duì) babel-polyfill 進(jìn)行分解,只引入所需的部分
babel-preset-es2015
es2015(ES6)相關(guān)方法轉(zhuǎn)譯使用的插件,具體見(jiàn)文檔。
- check-es2015-constants // 檢驗(yàn)const常量是否被重新賦值
- transform-es2015-arrow-functions // 編譯箭頭函數(shù)
- transform-es2015-block-scoped-functions // 函數(shù)聲明在作用域內(nèi)
- transform-es2015-block-scoping // 編譯const和let
- transform-es2015-classes // 編譯class
- transform-es2015-computed-properties // 編譯計(jì)算對(duì)象屬性
- transform-es2015-destructuring // 編譯解構(gòu)賦值
- transform-es2015-duplicate-keys // 編譯對(duì)象中重復(fù)的key,其實(shí)是轉(zhuǎn)換成計(jì)算對(duì)象屬性
- transform-es2015-for-of // 編譯for...of
- transform-es2015-function-name // 將function.name語(yǔ)義應(yīng)用于所有的function
- transform-es2015-literals // 編譯整數(shù)(8進(jìn)制/16進(jìn)制)和unicode
- transform-es2015-modules-commonjs // 將modules編譯成commonjs
- transform-es2015-object-super // 編譯super
- transform-es2015-parameters // 編譯參數(shù),包括默認(rèn)參數(shù),不定參數(shù)和解構(gòu)參數(shù)
- transform-es2015-shorthand-properties // 編譯屬性縮寫(xiě)
- transform-es2015-spread // 編譯展開(kāi)運(yùn)算符
- transform-es2015-sticky-regex // 正則添加sticky屬性
- transform-es2015-template-literals // 編譯模版字符串
- transform-es2015-typeof-symbol // 編譯Symbol類(lèi)型
- transform-es2015-unicode-regex // 正則添加unicode模式
- transform-regenerator // 編譯generator函數(shù)
babel-preset-es2016
es2016(ES7)相關(guān)方法轉(zhuǎn)譯使用的插件,具體見(jiàn)文檔。
- transform-exponentiation-operator // 編譯冪運(yùn)算符
babel-preset-es2017
es2017(ES8)相關(guān)方法轉(zhuǎn)譯使用的插件,具體見(jiàn)文檔。
- syntax-trailing-function-commas // function最后一個(gè)參數(shù)允許使用逗號(hào)
- transform-async-to-generator // 把a(bǔ)sync函數(shù)轉(zhuǎn)化成generator函數(shù)
babel-preset-latest
latest是一個(gè)特殊的presets,包括了es2015,es2016,es2017的插件,不過(guò)已經(jīng)廢棄,使用babel-preset-env代替,具體見(jiàn)文檔。
stage-x(stage-0/1/2/3/4)
stage-x預(yù)設(shè)中的任何轉(zhuǎn)換都是尚未被批準(zhǔn)為發(fā)布Javascript的語(yǔ)言(如ES6 / ES2015)的更改。
stage-x和上面的es2015等有些類(lèi)似,但是它是按照J(rèn)avaScript的提案階段區(qū)分的,一共有5個(gè)階段。而數(shù)字越小,階段越靠后,存在依賴(lài)關(guān)系。也就是說(shuō)stage-0是包括stage-1的,以此類(lèi)推。
babel-preset-stage-4:
stage-4的插件:
- syntax-trailing-function-commas // function最后一個(gè)參數(shù)允許使用逗號(hào)(ES8已經(jīng)存在)
- transform-async-to-generator // 把a(bǔ)sync函數(shù)轉(zhuǎn)化成generator函數(shù)(ES8已經(jīng)存在)
- transform-exponentiation-operator // 編譯冪運(yùn)算符(ES7已經(jīng)存在)
babel-preset-stage-3:
除了stage-4的內(nèi)容,還包括以下插件:
- transform-object-rest-spread // 編譯對(duì)象的解構(gòu)賦值和不定參數(shù)
- transform-async-generator-functions // 將async generator function和for await編譯為es2015的generator。
babel-preset-stage-2:
除了stage-3的內(nèi)容,還包括以下插件:
- syntax-dynamic-import // 動(dòng)態(tài)加載模塊
- transform-class-properties // 編譯靜態(tài)屬性(es2015)和屬性初始化語(yǔ)法聲明的屬性(es2016)。
-
transform-decorators已禁用的等待提案更新(可以在此期間使用舊版轉(zhuǎn)換)
babel-preset-stage-1:
除了stage-2的內(nèi)容,還包括以下插件:
- transform-class-constructor-call(棄用) // 編譯class中的constructor,在Babel7中會(huì)被移除
- transform-export-extensions // 編譯額外的export語(yǔ)法,如export * as ns from "mod";細(xì)節(jié)可以看這個(gè)。
babel-preset-stage-0:
除了stage-1的內(nèi)容,還包括以下插件:
- transform-do-expressions // 編譯do表達(dá)式
-
transform-function-bind // 編譯bind運(yùn)算符,即
::
為了方便,我們暫時(shí)引用 babel-preset-env 和babel-preset-stage-2這兩個(gè)預(yù)設(shè)。為了啟用預(yù)設(shè),必須在.babelrc文件中定義預(yù)設(shè)的相關(guān)配置,這里參考vue-cli 模板中的配置。
安裝:
npminstall --save-dev babel-preset-env babel-preset-stage-2
.babelrc配置說(shuō)明:
{
"presets": [
["env", {
"modules": false
}],
"stage-2"
]
}
Babel 插件(plugins)
我們看一下預(yù)設(shè)的構(gòu)成就知道,其實(shí)就是plugins的組合。如果你不采用presets,完全可以單獨(dú)引入某個(gè)功能,比如以下的設(shè)置就會(huì)引入編譯箭頭函數(shù)的功能,在.babelrc文件中進(jìn)行配置:
{
"plugins": ["transform-es2015-arrow-functions"]
}
babel-polyfill 與 babel-runtime
Babel默認(rèn)只轉(zhuǎn)換新的JavaScript句法(syntax),而不轉(zhuǎn)換新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對(duì)象,以及一些定義在全局對(duì)象上的方法(比如 Object.assign)都不會(huì)轉(zhuǎn)碼。
舉例來(lái)說(shuō),ES6在 Array 對(duì)象上新增了 Array.from 方法。Babel就不會(huì)轉(zhuǎn)碼這個(gè)方法。如果想讓這個(gè)方法運(yùn)行,必須使用 babel-polyfill ,為當(dāng)前環(huán)境提供一個(gè)墊片。babel-polyfill 是對(duì)瀏覽器缺失API的支持。
babel-runtime 是為了減少重復(fù)代碼而生的。 babel生成的代碼,可能會(huì)用到一些_extend(), classCallCheck() 之類(lèi)的工具函數(shù),默認(rèn)情況下,這些工具函數(shù)的代碼會(huì)包含在編譯后的文件中。如果存在多個(gè)文件,那每個(gè)文件都有可能含有一份重復(fù)的代碼。babel-runtime插件能夠?qū)⑦@些工具函數(shù)的代碼轉(zhuǎn)換成require語(yǔ)句,指向?yàn)閷?duì)babel-runtime的引用,如require('babel-runtime/helpers/classCallCheck')
. 這樣, classCallCheck的代碼就不需要在每個(gè)文件中都存在了。
啟用插件 babel-plugin-transform-runtime 后,Babel 就會(huì)使用 babel-runtime 下的工具函數(shù)。除此之外,babel 還為源代碼的非實(shí)例方法(Object.assign,實(shí)例方法是類(lèi)似這樣的 "foobar".includes("foo"))和 babel-runtime/helps 下的工具函數(shù)自動(dòng)引用了 polyfill。這樣可以避免污染全局命名空間,非常適合于 JavaScript 庫(kù)和工具包的實(shí)現(xiàn)。
總結(jié):
- 具體項(xiàng)目還是需要使用 babel-polyfill,只使用 babel-runtime 的話,實(shí)例方法不能正常工作(例如 "foobar".includes("foo"));
- JavaScript 庫(kù)和工具可以使用 babel-runtime,在實(shí)際項(xiàng)目中使用這些庫(kù)和工具,需要該項(xiàng)目本身提供 polyfill。
- transform-runtime只會(huì)對(duì)es6的語(yǔ)法進(jìn)行轉(zhuǎn)換,而不會(huì)對(duì)新api進(jìn)行轉(zhuǎn)換。如果需要轉(zhuǎn)換新api,就要引入babel-polyfill。
安裝插件
npm install --save-dev babel-plugin-transform-runtime
.babelrc 配置:
{
"plugins": ["transform-runtime", options]
}
options主要有以下設(shè)置項(xiàng):
- helpers: boolean,默認(rèn)true,使用babel的helper函數(shù);
- polyfill: boolean,默認(rèn)true,使用babel的polyfill,但是不能完全取代babel-polyfill;
- regenerator: boolean,默認(rèn)true,使用babel的regenerator;
- moduleName: string,默認(rèn)babel-runtime,使用對(duì)應(yīng)module處理。
注:默認(rèn)moduleName為babel-runtime,這里我們可以不必顯式的下載babel-runtime,因?yàn)閎abel-plugin-transform-runtime依賴(lài)于babel-runtime。
babel-register
babel-register 模塊改寫(xiě) require 命令,為它加上一個(gè)鉤子。此后,每當(dāng)使用 require 加載 .js 、 .jsx 、 .es 和 .es6 后綴名的文件,就會(huì)先用Babel進(jìn)行轉(zhuǎn)碼。引入babel-register,這樣后面的文件就可以用 import 代替require,import的優(yōu)點(diǎn)在于可以引入所需方法或者變量,而不需要加載整個(gè)模塊,提高了性能。
安裝:
npm install --save-dev babel-register
這部分我們又介紹了下面幾個(gè)模塊的安裝:
babel-loader
babel-core
babel-preset-env
babel-preset-stage-2
babel-plugin-transform-runtime
babel-register
webpack 中如何使用 vue?
既然本文的目標(biāo)是vue的自定義模板工程,那么自然這里需要單獨(dú)介紹一下webpack中vue相關(guān)的插件。
Vue2 dist 目錄下各個(gè)文件的區(qū)別
npm 安裝:
npm install --save vue
vue2 經(jīng)過(guò) 2.2 版本升級(jí)后, 文件變成 8 個(gè):
|| UMD | CommonJS | ES Module |
| :----: | :----: | :----: |
| 獨(dú)立構(gòu)建 | vue.js | vue.common.js | vue.esm.js |
| 運(yùn)行構(gòu)建 | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js |
vue.min.js 和 vue.runtime.min.js 都是對(duì)應(yīng)的壓縮版。
- AMD:異步模塊規(guī)范
vue 沒(méi)有單獨(dú)提供 AMD 模塊的版本,但是UMD版本中進(jìn)行了包裝,可以直接用作 AMD 模塊,使用方法如下:
define(["Vue"],function(Vue) {
function myFn() {
...
}
return myFn;
});
- CommonJS:
node中常用的模塊規(guī)范,通過(guò)require引入模塊,module.exports導(dǎo)出模塊。
...
function Vue$3() {
...
}
...
module.exports = Vue$3;
- UMD: 通用模塊規(guī)范
兼容了AMD和CommonJS,同時(shí)還支持老式的“全局”變量規(guī)范:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Vue = factory());
}(this, (function () { 'use strict';
...
function Vue$3() {
...
}
...
return Vue$3;
})));
- ES Module
ES6在語(yǔ)言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)的模塊功能。模塊功能主要由兩個(gè)命令構(gòu)成:export和import。export命令用于規(guī)定模塊的對(duì)外接口,import命令用于輸入其他模塊提供的功能。
...
function Vue$3() {
...
}
export default Vue$3;
總結(jié):
- vue.js 和 vue.runtime.js 可以用于直接 CDN 引用;
- vue.common.js和vue.runtime.common.js可以使用Webpack1 / Browserify 打包構(gòu)建;
- vue.esm.js和vue.runtime.esm.js可以使用Webpack2 / rollup 打包構(gòu)建。
vue有兩種構(gòu)建方式,獨(dú)立構(gòu)建和運(yùn)行時(shí)構(gòu)建。它們的區(qū)別獨(dú)立構(gòu)建前者包含模板編譯器而運(yùn)行構(gòu)建不包含。模板編譯器的職責(zé)是將模板字符串編譯為純 JavaScript 的渲染函數(shù)。如果你想要在組件中使用 template 選項(xiàng),你就需要編譯器。
- 獨(dú)立構(gòu)建包含模板編譯器并支持 template 選項(xiàng)。 它也依賴(lài)于瀏覽器的接口的存在,所以你不能使用它來(lái)為服務(wù)器端渲染。
- 運(yùn)行時(shí)構(gòu)建不包含模板編譯器,因此不支持 template 選項(xiàng),只能用 render 選項(xiàng),但即使使用運(yùn)行時(shí)構(gòu)建,在單文件組件中也依然可以寫(xiě)模板,因?yàn)閱挝募M件的模板會(huì)在構(gòu)建時(shí)預(yù)編譯為 render 函數(shù)。運(yùn)行時(shí)構(gòu)建比獨(dú)立構(gòu)建要輕量30%,只有 17.14 Kb min+gzip大小。
獨(dú)立構(gòu)建方式可以這樣使用template選項(xiàng):
import Vue from 'vue'
new Vue({
template: `
<div id="app">
<h1>Basic</h1>
</div>
`
}).$mount('#app')
這里我們使用ES Module規(guī)范,默認(rèn) NPM 包導(dǎo)出的是運(yùn)行時(shí)構(gòu)建。為了使用獨(dú)立構(gòu)建,在 webpack 配置中添加下面的別名:
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
vue-loader
安裝:
npm install --save-dev vue-loader vue-template-compiler
vue-loader 依賴(lài)于 vue-template-compiler。
vue-loader 是一個(gè) Webpack 的 loader,可以將用下面這個(gè)格式編寫(xiě)的 Vue 組件轉(zhuǎn)換為 JavaScript 模塊。這里有一些 vue-loader 提供的很酷的特性:
- ES2015 默認(rèn)支持;
- 允許對(duì) Vue 組件的組成部分使用其它 Webpack loaders,比如對(duì)
<style>
使用 SASS 和對(duì)<template>
使用 Jade; - .vue 文件中允許自定義節(jié)點(diǎn),然后使用自定義的 loader 處理他們;
- 把
<style>
和<template>
中的靜態(tài)資源當(dāng)作模塊來(lái)對(duì)待,并使用 Webpack loaders 進(jìn)行處理; - 對(duì)每個(gè)組件模擬出 CSS 作用域;
- 支持開(kāi)發(fā)期組件的熱重載。
簡(jiǎn)而言之,編寫(xiě) Vue.js 應(yīng)用程序時(shí),組合使用 Webpack 和 vue-loader 能帶來(lái)一個(gè)現(xiàn)代,靈活并且非常強(qiáng)大的前端工作流程。
在 Webpack 中,所有的預(yù)處理器需要匹配對(duì)應(yīng)的 loader。 vue-loader 允許你使用其它 Webpack loaders 處理 Vue 組件的某一部分。它會(huì)根據(jù) lang 屬性自動(dòng)推斷出要使用的 loaders。
上述我們提到extract-text-webpack-plugin插件提取css,這里說(shuō)明一下.vue中style標(biāo)簽之間的樣式提取的辦法:
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
css: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader' // <- 這是vue-loader的依賴(lài),所以如果使用npm3,則不需要顯式安裝
})
}
}
}
]
},
plugins: [
new ExtractTextPlugin("app.css")
]
}
pug 模板
用過(guò)模板的都知道,熟悉了模板寫(xiě)起來(lái)快多了,大名鼎鼎的jade恐怕無(wú)人不知吧。pug是什么鬼?第一次聽(tīng)到的時(shí)候我也好奇了,然后查了一下才知道,Pug原名不叫Pug,原來(lái)是大名鼎鼎的jade,后來(lái)由于商標(biāo)的原因,改為Pug,哈巴狗。以下是官方解釋?zhuān)?/p>
it has been revealed to us that "Jade" is a registered trademark, and as a result a rename is needed. After some discussion among the maintainers, "Pug" has been chosen as the new name for this project.
簡(jiǎn)單看了看還是原來(lái)jade熟悉的語(yǔ)法規(guī)則,果斷在這個(gè)模板工程里面用上。
vue-loader里面對(duì)于模版的處理方式略有不同,因?yàn)榇蠖鄶?shù) Webpack 模版處理器(比如 pug-loader)會(huì)返回模版處理函數(shù),而不是編譯的 HTML 字符串,我們使用原始的 pug 替代 pug-loader:
npm install pug --save-dev
使用:
<template lang="pug">
div
h1 Hello world!
</template>
重要: 如果你使用 vue-loader@<8.2.0, 你還需要安裝
template-html-loader
。
PostCSS
安裝vue-loader的時(shí)候默認(rèn)安裝了postcss,由vue-loader處理的 CSS 輸出,都是通過(guò)PostCSS進(jìn)行作用域重寫(xiě),你還可以為 PostCSS 添加自定義插件,例如autoprefixer或者CSSNext。
在 webpack 工程中使用 postcss,我們需要下載 postcss-loader:
npm install --save-dev postcss-loader
cssnext
cssnext 是一個(gè) CSS transpiler,允許你使用最新的 CSS 語(yǔ)法。cssnext 把 新 CSS 規(guī)范轉(zhuǎn)換成兼容性更強(qiáng)的 CSS,所以不需要等待各種瀏覽器支持。
安裝:
npm install --save-dev postcss-cssnext
postcss.config.js:
module.exports = {
plugins: [
require('postcss-cssnext')
]
}
webpack.config.js:
module.exports = {
module: {
loaders: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
}
}
cssnext 依賴(lài)了autoprefixer,所以我們無(wú)需顯式下載autoprefixer。更多關(guān)于postcss的插件可以看這里:postcss plugins。
這一部分我們學(xué)習(xí)了這些依賴(lài):
vue
vue-loader
vue-template-compiler
pug
postcss-loader
postcss-cssnext
webpack2 開(kāi)啟 eslint 校驗(yàn)
規(guī)范自己的代碼從ESlint開(kāi)始。ESlint和webpack集成,在babel編譯代碼開(kāi)始前,進(jìn)行代碼規(guī)范檢測(cè)。這里我們使用javascript-style-standard風(fēng)格的校驗(yàn)。
主要依賴(lài)的幾個(gè)包:
eslint —— 基礎(chǔ)包
eslint-loader —— webpack loader
babel-eslint —— 校驗(yàn)babel
eslint-plugin-html —— 提取并檢驗(yàn)?zāi)愕?.vue 文件中的 JavaScript
eslint-friendly-formatter —— 生成美化的報(bào)告格式
# javascript-style-standard 依賴(lài)的包
eslint-config-standard
eslint-plugin-import
eslint-plugin-node
eslint-plugin-promise
eslint-plugin-standard
安裝:
npm install --save-dev eslint eslint-loader babel-eslint eslint-plugin-html eslint-friendly-formatter eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-node eslint-plugin-promise eslint-plugin-standard
關(guān)于eslint的配置方式,比較多元化,具體可以看配置文檔:
- js注釋
- .eslintrc.*文件
- package.json里面配置eslintConfig字段
安裝eslint-loader之后,我們可以在webpack配置中使用eslint加載器。webpack.config.js
...
module: {
loaders: [
{
test: /\.vue|js$/,
enforce: 'pre',
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
use: [{
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter')
}
}]
}
]
},
...
此外,我們既可以在webpack配置文件中指定檢測(cè)規(guī)則,也可以遵循最佳實(shí)踐在一個(gè)專(zhuān)門(mén)的文件中指定檢測(cè)規(guī)則,我們就采用后面的方式。
在根目錄下:
touch .eslintrc.js
.eslintrc.js:
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true
},
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
rules: {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}
這部份我們主要學(xué)習(xí)了一下eslint相關(guān)插件的含義和配置方法。
創(chuàng)建屬于你的模板
如果你對(duì)官方的模板不感興趣,你可以自己fork下來(lái)然后進(jìn)行修改(或者重新寫(xiě)一個(gè)),然后用 vue-cli 來(lái)調(diào)用。因?yàn)?vue-cli 可以直接拉取 git源:
vue init username/repo my-project
這里我們參考vue-cli的模板工程自己寫(xiě)一個(gè)模板工程,主要是需要通過(guò)meta.*(js,json)進(jìn)行配置:
module.exports = {
"helpers": {
"if_or": function (v1, v2, options) {
if (v1 || v2) {
return options.fn(this);
}
return options.inverse(this);
}
},
"prompts": {
"name": {
"type": "string",
"required": true,
"message": "Project name"
},
"version": {
"type": "string",
"required": false,
"message": "Project version",
"default": "1.0.0"
},
"description": {
"type": "string",
"required": false,
"message": "Project description",
"default": "A Vue.js project"
},
"author": {
"type": "string",
"message": "Author"
},
"router": {
"type": "confirm",
"message": "Install vue-router?"
},
"vuex": {
"type": "confirm",
"message": "Install vuex?"
}
},
"completeMessage": "To get started:\n\n {{^inPlace}}cd {{destDirName}}\n {{/inPlace}}npm install\n npm run dev\n\nDocumentation can be found at https://github.com/zhaomenghuan/vue-webpack-template"
};
這里我們就是采用最簡(jiǎn)單的方式,對(duì)于vue-router、vuex的配置每個(gè)人習(xí)慣不一樣,所以不寫(xiě)在模板工程里面。
然后使用vue-cli使用這個(gè)模板創(chuàng)建工程,沒(méi)有安裝vue-cli的執(zhí)行:
npm install --global vue-cli
然后創(chuàng)建工程:
# 創(chuàng)建一個(gè)基于 webpack 模板的新項(xiàng)目
vue init zhaomenghuan/vue-webpack-template my-project
# 安裝依賴(lài),走你
cd my-project
npm install
npm run dev
這里按照國(guó)際慣例安利一下本文的模板工程:vue-webpack-template
參考
webpack官方文檔
babel官方文檔
vue-loader中文文檔
JavaScript books by Dr. Axel Rauschmayer
ES7新特性及ECMAScript標(biāo)準(zhǔn)的制定流程
如何寫(xiě)好.babelrc?Babel的presets和plugins配置解析
babel的polyfill和runtime的區(qū)別
webpack2集成eslint