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