2016-10-04 更新為使用 Vue.js 2.xcss
前端的世界變化之快速,從 2010 開始小弟經歷了 jQuery, Backbone, Angular, 到 React。這一路走來雖然學習到了許多高明開發者融合於框架或函式庫中的智慧,卻也因為不斷快速變化感到疲憊。時至 2016 小弟認為在實務與理想之間取得一個完美平衡的前端框架大概就屬 vue.js
了。html
當然這前端世界裡並沒有萬能藥能夠完美的處理全部問題,不過 vue.js
的精美,不僅容易與傳統 MVC 框架(Rails, ASP.NET MVC)等結合,當要使用最新的設計模式如 Flux, redux 等也都是沒問題的,再加上易學與一些你確定能感覺到做者從實戰淬煉出來的特性。所以在 2016 我也決定轉戰 vue.js
。前端
隨著 Javascript 社羣快速的演進,很可怕一個問題是 - 專案的環境設定,關於那些 tooling
這不僅是 React 的問題,當你想使用 ES2015 的新語法,方便的持續整合與測試,匯入匯出模組時,我們就須要設定這些專案工具。vue
雖然 vue 自己有提供指令介面 vue-cli
讓我們快速創建專案,但對這些相關技術和設定有些瞭解確定能幫助你執行更多客製的行為。node
從頭本身一點一點設定有一些好處:webpack
每個專案都有不一樣的需求,您能夠根據自身的需求來設定git
我們也提到 Javascript(nodejs) 的世界變得很快,若是有局部的套件壞了那我們也比較清楚該怎麼處理github
直接使用別人的 start-kit 也許會多裝了一堆你不須要的東西web
這篇文章將會透過實做介紹最基本的概念,使用 webpack 設定一個基本的 vue 專案vue-cli
$ mkdir [project_name] $ cd [project_name] $ npm init -y $ npm install vue -S
我們先把須要的程式與目錄結構準備好,需求是使用 Vue
+ ES2015
來開發。第一步在根目錄創建一個 index.html
下面是一個簡單的 vue 範例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue.js v2</title> </head> <body> <div id="app">{{ message }}</div> <script src="dist/bundle.js"></script> </body> </html>
注意到兩件事
我們使用 dist/build.js 這個檔案在編譯以前是不存在的
{{message}} 這個語法是 vue.js 處理的
創建 src
目錄與 src/main.js
檔案,這邊您能夠隨您本身的偏好組織專案架構
import Vue from 'vue' new Vue({ el: '#app', data: { message: "Hello Vue" } })
在這一步我們已經完成一個簡單的 Vue 專案,可是關於建置編譯的設定我們還未完成。
Do not mount Vue to <html> or <body> - mount to normal elements instead. 關於 v2 之後值得注意的地方,即是我們不能直接將元件掛載到 html 或 body 上。
為了專注在基本的說明,本次更新已將一些不屬於基本功能的模組移除。
# 2016-10-04 更新 $ npm i webpack webpack-dev-server webpack-merge css-loader style-loader file-loader url-loader babel-loader babel-core babel-plugin-transform-runtime babel-preset-es2015 vue-loader vue-hot-reload-api -D
"dependencies": { "vue": "^2.0.1" }, "devDependencies": { "babel-core": "^6.17.0", // babel 核心程式 "babel-loader": "^6.2.5", // webpack 使用的 babel 編譯器 "babel-plugin-transform-runtime": "^6.15.0", // 預設 babel 會在每一隻編譯檔案注入 polyfill 的程式碼,為了避免重複而將這部分抽出去。詳細說明:http://babeljs.io/docs/plugins/transform-runtime/ "babel-preset-es2015": "^6.16.0", // 支援 ES2015 語法 "css-loader": "^0.25.0", // webpack 使用於處理 css "file-loader": "^0.9.0", // webpack 使用於處理檔案 "style-loader": "^0.13.1", // webpack 將 css 整合進元件中 "url-loader": "^0.5.7", // 編譯匯入檔案類型的資源,把檔案轉成 base64 "vue-hot-reload-api": "^2.0.6", // 支援 Hot Reload "vue-loader": "^9.5.1", // 使用 Vue Component Spec "webpack": "^1.13.2", "webpack-dev-server": "^1.16.1", // webpack 開發伺服器 "webpack-merge": "^0.14.1" // 合併 webpack 設定參數 }
webpack.config.js
根目錄下創建與撰寫 webpack.config.js
var path = require('path') var config = { entry: path.join(__dirname, 'src', 'main'), output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', publicPath: '/dist/' }, module: { loaders: [ { test: /\.js$/, loader: 'babel', exclude: /node_modules/ } ] }, resolve: { extensions: ['', '.js', '.vue'], /** * Vue v2.x 之後 NPM Package 預設只會匯出 runtime-only 版本,若要使用 standalone 功能則需下列設定 */ alias: { vue: 'vue/dist/vue.js' } } } module.exports = config
Failed to mount component: template or render function not defined. (found in root instance)
若您看到上面錯誤訊息,這是由於 Vue 2 之後分紅 standalone 完整版與 runtime-only 版。差異在於完整版包含了編譯器,支援 template 以及使用了瀏覽器的 API。 而 NPM 模組預設只會匯出 runtime-only ,若要加入 compiler 和 template 支援則需增長 webpack 的設定。
根目錄創建 .babelrc
簡化 webpack.config.js
,這是因為 babel 6
之後把功能拆散了,要用就要裝。同時也能夠用 .babelrc
來設定,若是不使用這個檔案我們就須要在 webapck.config.js
設定。
.babelrc
{ "presets": ["es2015"], "plugins": ["transform-runtime"] }
另外
package.json
和環境變數也能夠設定,不過為了單純起見我們選擇創建 .babelrc 。當然您也能夠選擇設定在 package.json 中。
package.json
{ "name": "YOUR PROJECT NAME", ..., "babel": { "presets": [ "es2015" ], "plugins": [ "transform-runtime" ] } }
上面我們已經完成基本的設定,雖然我們一口氣安裝了不少 loaders 但相關設定我們只先設定了 babel 的部份。到了這一步我們的專案架構已經能夠被編譯執行了。
# 若是在這一步您想先執行編譯看看能夠安裝全域的 webpack $ npm i webpack -g $ webpack $ open index.html # 編譯後檢視內容
編譯之後點擊 index.html
便可以運行。眼尖的讀者可能會好奇,那我們剛剛有裝 vue-loader
那是在幹嘛的?
vue-loader
的用途是提供一種更方便的組織方式讓我們把元件即一個 component 中須要的 js 行為, css 樣式, template 樣板放在一個 .vue
的檔案中。
首先讓我們先修改 index.html
,加入 <app>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue.js v2</title> </head> <body> <div id="app"> <app></app> </div> <script src="dist/bundle.js"></script> </body> </html>
接著我們在 main.js
把元件 app.vue
加入 components,在這邊我們是反向的推導回去,從想
怎麼使用接著反著創建程式檔案。
import Vue from 'vue' import App from './app.vue' new Vue({ el: '#app', components: { App } })
最後我們新增一個 app.vue
檔案
<template lang="html"> <div> <div class="message"> {{ message }} </div> </div> </template> <script> export default { data () { return { message: 'Helo, Vue.js 2.0' } } } </script> <style lang="css"> .message { color: pink; font-size: 1.4em; } </style>
這個時候若是直接執行 webpack
編譯會產生錯誤,因為我們還沒設定 webpack.config.js
處理 .vue
檔案的部分
var path = require('path') var config = { entry: [ 'webpack/hot/dev-server', path.join(__dirname, 'src', 'main') ], output: { publicPath: '/dist/', path: path.join(__dirname, 'dist'), filename: 'bundle.js' }, module: { loaders: [ { test: /\.js$/, loader: 'babel', exclude: /node_modules/ }, { test: /\.vue$/, loader: 'vue' } ] }, resolve: { /** * Vue v2.x 之後 NPM Package 預設只會匯出 runtime-only 版本 */ alias: { vue: 'vue/dist/vue.js' }, extensions: ['.js', '.vue'] } } module.exports = config
再次執行 webpack
編譯,我們的 app.vue
能夠正常運做了。
若是您有發現修改之後卻沒有改變的問題,請注意關於路徑部分,取得的是編譯後的實體檔案還是 webpack-dev-server 使用記憶體中的內容
Hot Module Replacement
或稱 Hot Reload
是 Javascript 世界中近期很熱門的新技術,簡單的說就是當你在開發時,你一存檔,改寫的部份就即時更新元件到執行環境。大體上流程就是
處於開發 app 階段,撰寫程式碼
打開瀏覽器觀察 app 行為
app 在瀏覽器畫面上運做
當你發現一些 bug 或行為不如您所預期您一般會編輯程式碼,然後從新載入
使用 HRM 時,當你一存檔 webpack 就會偵測那些改變的部分並更新瀏覽器
重點是一些關於狀態
的資料並不會被洗掉
要完成這功能,我們會須要 webpack-dev-server
以及套件 vue-hot-reload-api
。然後執行。
在這以前,我們須要修改一下 webpack.config.js 加入 webpack/hot/dev-server
var webpack = require('webpack') var config = { entry: [ 'webpack/hot/dev-server', path.join(__dirname, 'src', 'main') ], ... plugins: [ new webpack.HotModuleReplacementPlugin() ] }
接著,您能夠選擇在全域安裝 webpack-dev-server
$ npm i webpack-dev-server -g $ webpack-dev-server --inline --hot
又或者使用我們早先已安裝在專案中的 webpack-dev-server,通常來說會建議使用專案相依的這個。
須要在 package.json 加上 scripts
"scripts": { "dev": "webpack-dev-server" }
然後執行:
$ npm run dev
為了觀察出我們是否有正確的啟用 hot reload 我們修改 app.vue
<template lang="html"> <div> <div class="message"> {{ message }} </div> <div> {{ count }} </div> </div> </template> <script> export default { data () { return { message: 'Helo, Vue.js 2.0', count: 0 } }, mounted () { this.handle = setInterval(() => { this.count++ }, 1000) }, destroyed () { clearInterval(this.handle) } } </script> <style lang="css"> .message { color: pink; font-size: 1.4em; } </style>
打開 http://localhost:8080
然後異動 app.vue
css 與 template 的部分觀察看看變化。
至此我們已經跑完一次基本的用法。
文章剩下的部份則是整理一些 webpack 的指令與設定。
$ webpack [source] [destination] $ webpack src/v1.js dist/v1.bundle.js $ webpack bar=./src/v2.js "dist/[name].bundle.js" # >> output dist/bar.bundle.js
# 指定設定檔 $ webpack --config [webpack.config.js]
注意 require 的 path 分紅
函式庫
相對路徑
絕對路徑
函式庫:什麼都不加,單純 library name
相對路徑:
./
開頭絕對路徑:
/
開頭
$ npm init -y $ npm i webpack -D # Add webpack.config.js # Add scripts to package.json # Setup webpack-dev-server $ npm i webpack-dev-server -D # 由於 prod & dev 會須要不一樣的設定所以我們須要至少兩份設定檔 # 有許多實做方式以下: # 1. 維護多份設定檔,透過 `--config` 指定不一樣的檔案 # 2. 把設定組織成一份 Library # 3. 在一份檔案中依據 `環境` 或 `指令` 套用不一樣設定 # 使用 webpack-merge,合併設定更方便 $ npm i webpack-merge -D $ npm i css-loader style-loader -D $ npm i file-loader url-loader -D # 強大のplugin # npm i npm-install-webpack-plugin -D # 安裝處理 ES2015 $ npm i babel-core babel-loader babel-plugin-transform-runtime babel-preset-es2015 -D # 加入 .babelrc # 加入 loader 設定 # (Optional)安裝 vue-loader $ npm i vue-loader vue-hot-reload-api -D # 安裝 vue 與所需的模組
{ "presets": ["es2015"], "plugins": ["transform-runtime"] }
var path = require('path') var webpack = require('webpack') var merge = require('webpack-merge') var precss = require('precss'); var autoprefixer = require('autoprefixer'); var T = process.env.npm_lifecycle_event var common = { entry: { main: path.join(__dirname, 'src', 'main'), venders: path.join(__dirname, 'src', 'venders') }, output: { path: path.join(__dirname, 'dist'), filename: '[name].bundle.js' }, module: { loaders: [ { test: /\.js$/, loader: 'babel', exclude: /node_modules/ }, { test: /\.vue$/, loader: 'vue' }, { test: /\.css$/, loaders: ['style', 'css', 'postcss'] /* include: path.join(__dirname, 'src') */ }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url', query: { limit: 10000, name: path.posix.join(__dirname, 'public', '[name].[hash:7].[ext]') } } ] }, resolve: { extensions: ['', '.js', '.vue', '.json', '.css'] }, postcss: function () { return [precss, autoprefixer]; } } if (T === 'dev' || !T) { var config = merge(common, { devServer: { historyApiFallback: true, hot: true, inline: true, progress: true, stats: 'errors-only', host: process.env.HOST || '0.0.0.0', port: process.env.PORT }, devtool: 'eval-source-map', plugins: [ new webpack.HotModuleReplacementPlugin() ] }) config.entry.main = ['webpack/hot/dev-server', config.entry.main] module.exports = config } if (T === 'build') { module.exports = merge(common, {}) }
"scripts": { "build": "webpack", "dev": "webpack-dev-server" }