[轉] Vue + Webpack 元件式開發(練習環境)
前言
研究了下別人的 vue
多頁面框架, 都是直接複製 package.json
檔案,然後在本地 npm install
一下即可, 或者使用官網 vue-cli
工具生成一個專案, 覺得這樣雖然看的懂, 但是記不住, 因此有必要從零開始搭建一個使用 .vue
作為元件的專案練習一下, 因此有了這個專案.
既然使用了 .vue
元件, 就不能像之前使用 jQuery
一樣把 vue.js
引入頁面中, 而是使用配套的 webpack
babel
+ 各種 loader
工具編譯 .vue
檔案, 再打包生成 html
.
FBI warning
切記
: 因為是最基本的初體驗, 所以一些正式開發中必裝的 loader
和 plugin
就沒有裝, 因為只是想按照官方教程手動敲出來加深印象, 特別是進階教程中比較麻煩的父子元件傳參, 作用域插槽, 遞迴元件以及 slot
等. 因此這個配置不可能作為正式開發的參照配置, 只可作為了解 vue
元件工作原理的練手專案.
配置說明
以下配置的詳細說明在後面可以找到, 不想看的話直接複製下面的 package.json
即可, 但是為了加深印象還是建議手動敲一遍.
廢話不多說, 開始.
首先, 既然是 webpack+vue
, 那相應的安裝包少不了, 這裡我們使用 [email protected]
和 [email protected]
:
npm install webpack@1.12.2 vue@2.2.4 --save-dev
然後是 babel
和相應的 loader
es2015
這個配置, 用最新的就好:
npm install babel bebel-core babel-loader babel-preset-es2015 --save-dev
然後是 webpack
的必裝 loader
, css-loader
用來處理 css
中 url()
的資源, style-loader
用來將 require
的 css
抽出放到 style
標籤中, 然後加到頁面 head
部分. html-webpack-plugin
用來將入口檔案 js
變成 html
, 入口檔案中的各種資源由各種 loader
處理後插入到它生成的 html
中, extract-text-webpack-plugin
用來將被 js
通過 style
標籤 append
到 head
中的樣式抽出到單獨的 .css
檔案中:
npm install css-loader style-loader html-webpack-plugin extract-text-webpack-plugin@1.0.1 --save-dev
然後是 vue
相關的東西, 因為一個 .vue
裡面有至少有三個標籤 template/style/script
, 因此需要三個 loader
來處理, 再加上一個總的 vue-loader
, 就是四個 loader
,這裡:
vue-html-loader
是 webpack
的官方 html-loader
的 fork
, 作者放到這裡只是為了能在 webpack.config.js
中的 module.export.vue
物件上使用 html
選項來單獨配置 vue
的 相關 html
(本專案安裝 vue-loader
即可, 這裡只是順帶安裝說明一下);
vue-style-loader
用來處理 .vue
檔案中 style
中的內容, 是 webpack
的官方 style-loader
的 fork
(本專案安裝 vue-loader
即可, 這裡只是順帶安裝說明一下);
vue-template-compiler
用來處理 .vue
檔案中 template
中的內容, 除非是用它編譯後的檔案做其他事情才需要單獨配置(即寫 build tools
, 否則這個不是必須的, 因為 vue-loader
已經預設使用它了)(本專案安裝 vue-loader 即可, 這裡只是順帶安裝說明一下);
vue-loader
用來處理 .vue
字尾的內容, 在遇到相關的內容時, 會呼叫上述三個相關的 loader
來處理.
npm install vue-html-loader vue-loader vue-style-loader vue-template-compiler --save-dev
最後就是開發用的 webpack-dev-server
, 這裡我們安裝 1.12.1
版本:
npm install webpack-dev-server --save-dev
下面是總的 package.json
配置檔案, 而具體的每個 package.json
欄位的含義, 可以檢視這個網站,
{ "name": "vue-components", "version": "0.0.1", "description": "vue components test", "main": "app/app.js","scripts": { "dev": "webpack-dev-server --hot", "build": "webpack" }, "keywords": [ "vue", "components" ],"author": "xheldon", "license": "MIT", "dependencies": { "vue": "^2.2.4" }, "devDependencies": { "babel":"^6.23.0", "babel-core": "^6.24.0", "babel-loader": "^6.4.1", "babel-preset-es2015": "^6.24.0", "css-loader":"^0.27.3", "extract-text-webpack-plugin": "^1.0.1", "html-webpack-plugin": "^2.28.0", "style-loader":"^0.16.0", "vue-html-loader": "^1.2.4", "vue-loader": "^11.3.1", "vue-style-loader": "^2.0.4", "vue-template-compiler": "^2.2.4", "webpack": "^1.12.2", "webpack-dev-server": "^1.12.1" } }
專案說明
Ok, 依賴安裝完了, 接下來看下 webpack
配置, 因為是想盡快測試 vue
官方文件的元件部分, 所以一切從簡了:
var path = require('path'); var webpack = require('webpack'); var HtmlwebpackPlugin = require('html-webpack-plugin'); // 常用配置,專案較小不抽出了 var ROOT_PATH = path.resolve(__dirname);//根路徑 var APP_PATH =path.resolve(ROOT_PATH, 'app');//開發路徑 var BUILD_PATH = path.resolve(ROOT_PATH, 'build');//輸出路徑 varExtractTextPlugin= require('extract-text-webpack-plugin'); module.exports = { entry: { app:path.resolve(APP_PATH, 'app.js') }, output: { path: BUILD_PATH, filename: 'bundle.js'//因為只有一個入口檔案, 因此直接寫死了 }, resolve: { alias: { //注意, 這裡匯入的是/node_module/vue/dist/vue.js, 跟 vue-router.js 的不同vue: 'vue/dist/vue.js' } }, //開啟 dev source map devtool: 'eval-source-map', //開啟 dev server devServer: {historyApiFallback: true, hot: true,//HMR inline: true, progress: true }, module: { loaders:[ { test:/\.vue$/, loader: 'vue' }, { test: /\.css$/, loader: ExtractTextPlugin.extract('css-loader') }, { test:/\.js$/, loader: 'babel', include: ROOT_PATH, exclude: /node_modules/ }, { test: /\.html$/, loader: 'vue-html'} ] }, vue:{ loaders: { css: ExtractTextPlugin.extract('css-loader') } }, plugins:[ new HtmlwebpackPlugin({title: 'Vue component test', filename: 'this_is_final_filename_address_you_visit_in_browser.html',//生成的最終檔名 template: 'app/this_is_main_page_which_you_put_components_into.html',//放置元件的地方, 一般是一個 body 下一個孤零零的 app 標籤即可. inject: true }) ] };
關於這個配置, 有點東西需要說一下.
從上往下, 首先是 alias
, 使用過 vue-router
的人可能不需要這個配置, 但是使用 .vue
元件的專案必須這個配置,因為需要指定使用的 vue
的 js
型別, 看下本專案下 node_module/vue/dist/
資料夾下的檔案, 有 vue.js
和 vue.common.js
兩種, 其中 vue
編譯 template
元件的時候是需要一個 compiler.js
的, 目的是把 template
中的 html
內容編譯成 render
函式:
編譯前:
<div id="app"> Hello </div> <script> new Vue({ el: '#app', data: {who: 'Vue'} }) </script>
編譯後:
<div id="app"></div> <script> new Vue({ el: '#app', render: function () { with (this) { __h__('div',{staticAttrs:{"id":"app"}}, [("\n Hello "+__toString__(who)+"\n")], '' ) } }, data: {who: 'Vue'} }) </script>
而 vue
使用 compiler
編譯 template
後的 js
在執行的時候發現有 render
函式的話就直接執行 render
, template
欄位下的內容會被忽略. 而執行編譯後的 render
的任務,是由 vue.common.js
完成的.因此:
vue.js = vue.common.js + compiler.js
所以, 如果你使用的是 vue-router
, 它的 package.json
中的 main
欄位是指向 node_module/dist/vue.common.js
的, 如果你直接複製這個到你的專案下, 執行的時候會提示你類似於 vue.common.js
的 runtime
錯誤之類的資訊.
其次需要說的是這個 css-loader
和 style-loader
以及 vue-style-loader
, 有了 style-loader
為何還要個 vue-style-loader
呢? 看了下 vue-style-loader
的說明, 明白了其僅僅是一個 style-loader
的 fork
, 但是為了單獨處理 .vue
檔案, 同時為了讓使用者配置 vue
更清晰, 將其加到了 [email protected]
的配置檔案中的 vue
欄位中. 可以通過將 extract-text-webpack-plugin
外掛的配置從:
vue:{ loaders: { css: ExtractTextPlugin.extract('vue-style-loader','css-loader') } }
改成:
vue:{ loaders: { css: ExtractTextPlugin.extract('style-loader','css-loader') } }
發現編譯後的結果一樣證實.
而預設情況下, vue-loader
是自動使用 vue-style-loader
的, 所以如果你不在 .vue
檔案中 @import
任何 css
, 那麼你不需要手動把 vue-loader-style
放到 vue.loaders
欄位中. vue-loader
會自動處理 .vue
檔案中的 style
標籤中的內容, 並將其放到 style
標籤中插入頁面. 而如果你需要在 .vue
檔案中的 style
標籤內 @import css
檔案, 那麼你就需要在 module.exports.vue
單獨配置.可以通過把 vue
欄位的 vue-style-loader
去掉來測試:
module: { loaders:[ { test: /\.vue$/, loader: 'vue' } ] }, vue:{ // loaders: { // css: 'vue-style-loader!css-loader' // } }
此外, 入口檔案 js
中的 require('xxx.css')
是預設的 module.exports.module.loader
處理的, 這點可以通過在預設 loader
中使用 extract
外掛, 而在 module.exports.vue
中不使用 extract
外掛證實, 因為從結果可以發現, 入口檔案中的 css
被提取了, 但是 .vue
中的 @import
來的 css
沒有被提取.
而如果你需要 將入口檔案中 require
來的 css
檔案單獨提取出來, 那麼你就需要在 module.exports.module.loader
設定 extract-text-webpack-plugin
了.
注意: vue-style-loader
放在 module.exports.vue.loaders
欄位中是為了能提取出 .vue
檔案中的 style
標籤內容到一個單獨的 .css
檔案 link
在頁面中, 把 style-loader
和 css-loader
放在預設的 module.exports.module.loaders
中, 對處理 vue
中的 style
標籤內容無效————起碼在 Vue 1.x
版本和 webpack 1.x
版本無效, webpack 2.x
版本移除了第三方的欄位, 限制在 module.export
中隨意新增欄位.
最後, css-loader
和 style-loader
總是寫在一起的, 因為 css-loader
的作用是 resolve css
檔案中的 @import
和 屬性值 url()
中的依賴關係, 單獨寫其實是沒什麼用的. style-loader
才是處理 css
, 並將其打包到 js
中, 最後以 <style>
標籤的形式插入到 head
(插入位置可配置)中的 loader
.
最後講講 extract-text-webpack-plugin
, 其接受三個引數:
第一個引數是可選引數, 傳入一個 loader
, 當 css
樣式沒有被抽取的時候可以使用該 loader
. 第二個引數則是用於編譯解析的 css
檔案 loader
, 很明顯這個是必須傳入的, 就像上述例子的 css-loader
. 第三個引數是一些額外的備選項, 貌似目前只有傳入 publicPath
, 用於當前 loader
的路徑.
那什麼時候需要傳入第一個引數呢,那就得明白什麼時候樣式不會被抽取出來. 瞭解過 code splitting
的同學便會知道,我們有些程式碼在載入頁面的時候不會被使用時, 使用 code splitting
, 可以實現將這部分不會使用的程式碼分離出去, 獨立成一個單獨的檔案,實現按需載入.
那麼如果在這些分離出去的程式碼中如果有使用 require
引入樣式檔案, 那麼使用 ExtractTextPlugin
這部分樣式程式碼是不會被抽取出來的. 這部分不會抽取出來的程式碼, 可以使用 loader
做一些處理, 這就是 ExtractTextPlugin.extract
第一個引數的作用.
OK, 聊完了配置檔案, 再說說這個專案是怎麼工作的.
一圖勝千言, 上圖, 首先是程式碼介面:
(若圖片顯示較小請在右鍵在新標籤頁單獨檢視)
看箭頭所示就明白啦, 首先一個頁面是至少有一個元件的, 這個我直接就一個頁面一個元件來寫了, 沒有 import
其他的元件.
因此, 一個頁面下是至少三個檔案的, .vue
檔案, .js
入口檔案, 和 .html
, 元件插入的檔案.
html
中寫一個元件 app
的名字, 入口檔案例項化一個 vue
, 然後使用 app
這個元件, 同時這個叫做 app
元件的模板來自 index.vue
, 元件對應的 css
和 js
以及 mvvm
的特色:資料繫結也寫在 index.vue
裡面.
有同學會疑惑, 入口檔案 js
是怎麼找到同目錄的 html
檔案的呢? 其實這個在 webpack.config.js
配置檔案就已經寫好了:
plugins:[ new HtmlwebpackPlugin({ title: 'Vue component test', filename:'this_is_final_filename_address_you_visit_in_browser.html',//生成的最終檔名 template:'app/this_is_main_page_which_you_put_components_into.html',//放置元件的地方, 一般是一個 body 下一個孤零零的 app 標籤即可. inject: true }) ]
這個 html
生成的外掛告訴 js
入口檔案, 所需要的模板來自 app
下的 xxx.html
, 而最後打包的 bundle.js
也是 inject
這個裡面, 再生成最終的頁面.
還有同學會問, 在入口 js
檔案中, vue
例項化的時候用到的 components.App
到底是在編譯過程就找到了 this_is_main_page_which_you_put_components_into.html
檔案中的 <app>
元件引用, 還是在 runtime
的時候, 從最終打包的 bundle.js
中執行, 然後尋找 this_is_final_filename_address_you_visit_in_browser.html
頁面中的 <app>
標籤呢? 答案是後者, 因為剛才說的 HtmlwebpackPlugin
外掛只負責生成 html
和注入打包後的 bundle.js
, 而 vue
被打包進了 bundle.js
之後例項化 vue
時候才會尋找 <app>
標籤.
這麼一看, 和直接在 script
標籤中引用 vue.js
檔案再渲染的效果是一樣的, 只是 webpack
這種開發方式幫我們分離了元件, 使開發過程的程式碼/元件結構更清晰, 而且直接引用 vue.js
是前端 runtime render
, 一個是 compiler render
之後直接執行, 後者效率更高.
之後是效果頁面:
(若圖片顯示較小請在右鍵在新標籤頁單獨檢視)
看圖就明白什麼意思啦.
還有不明白的請看文件.