1. 程式人生 > >[轉] Vue + Webpack 元件式開發(練習環境)

[轉] 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 的必裝 loadercss-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');//輸出路徑 varExtractTextPluginrequire('extract-text-webpack-plugin'); module.exports entryapp:path.resolve(APP_PATH'app.js'}, outputpathBUILD_PATHfilename'bundle.js'//因為只有一個入口檔案, 因此直接寫死了 }, resolvealias//注意, 這裡匯入的是/node_module/vue/dist/vue.js, 跟 vue-router.js 的不同vue'vue/dist/vue.js' }, //開啟 dev source map devtool'eval-source-map'//開啟 dev server devServer{historyApiFallbacktruehottrue,//HMR inlinetrueprogresstrue }, moduleloaders:[ test:/\.vue$/loader'vue' }, test/\.css$/loaderExtractTextPlugin.extract('css-loader'}, test:/\.js$/loader'babel'includeROOT_PATHexclude/node_modules/ }, test/\.html$/loader'vue-html'}, vue:{ loaderscssExtractTextPlugin.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 標籤即可. injecttrue }) };

關於這個配置, 有點東西需要說一下.

從上往下, 首先是 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<scriptnew Vue({ el'#app'renderfunction () with (this__h__('div',{staticAttrs:{"id":"app"}}, [("\n Hello "+__toString__(who)+"\n")], '' }, data{who'Vue'}) </script>

而 vue 使用 compiler 編譯 template 後的 js 在執行的時候發現有 render 函式的話就直接執行 rendertemplate 欄位下的內容會被忽略. 而執行編譯後的 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:{ loaderscssExtractTextPlugin.extract('vue-style-loader','css-loader'}

改成:

vue:{ loaderscssExtractTextPlugin.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 去掉來測試:

moduleloaders:[ 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 標籤即可. injecttrue }) ]

這個 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 之後直接執行, 後者效率更高.

之後是效果頁面:

頁面效果圖 (若圖片顯示較小請在右鍵在新標籤頁單獨檢視)

看圖就明白什麼意思啦.

還有不明白的請看文件.