創(chuàng)建項(xiàng)目
vue cli是一個(gè)基于vue.js進(jìn)行快速開發(fā)的完整系統(tǒng),通常包含三個(gè)組件,分別是:
- cli:
@vue/cli是全局安裝的NPM包,提供終端vue命令,比如vue create、vue serve、vue ui等命令。 - cli服務(wù):
@vue/cli-service是開發(fā)環(huán)境以來,構(gòu)建在webpack和webpack-dev-server之上,提供諸如serve、build、inspect等命令。 - cli插件,為vue項(xiàng)目提供可選功能的NPM包,比如
Babel/TypeScript轉(zhuǎn)譯、ESLint集成、UNIT和E2E測試等。
安裝
- 安裝 vue-cli3 需要 Node.js 8.9+ 版本,推薦Node.js 8.11.0+版本。
- vue-cli3 的包名由
vue-cli改為@vue/cli,如果已安裝vue-cli1.x或2.x,可先通過npm uninstall -g vue-cli卸載。
- 檢查Node.js版本
- 卸載Vue-cli2.x
- 重新安裝@vue/cli
- 創(chuàng)建應(yīng)用
- 允許服務(wù)
# 查看Node.js版本
$ node -v
v12.14.0
# 卸載vue-cli
$ npm uninstall -g vue-cli
# 安裝vue腳手架
$ npm i -g @vue/cli
# 查看vue版本
$ vue --version
2.9.6
# 使用腳手架創(chuàng)建項(xiàng)目
$ vue create chat
$ cd chat
$ npm run serve
瀏覽器訪問 http://127.0.0.1:8080
選擇預(yù)設(shè)
創(chuàng)建Vue項(xiàng)目會(huì)提示選取一個(gè)preset預(yù)設(shè),可選默認(rèn)包含基本的Babel + ESLint設(shè)置的預(yù)設(shè),也可手工選擇特性。
Vue CLI v4.1.2
? Please pick a preset:
> default(babel, eslint)
Manually select features
- 默認(rèn)預(yù)設(shè):
default(babel, eslint)
默認(rèn)設(shè)置適合快速創(chuàng)建新項(xiàng)目的原型,無任何輔助功能的NPM包。
- 手工選擇特性:
Manually select features
手動(dòng)配置,使用方向鍵控制,使用空格鍵選中,使用a鍵選擇。選項(xiàng)是所需面向生產(chǎn)環(huán)境的項(xiàng)目,提供可供選擇功能的NPM包。
手動(dòng)配置提供的NPM包
Vue CLI v4.1.2
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
( ) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
VUE CLI使用一套基于插件的架構(gòu),打開package.jsoon會(huì)發(fā)現(xiàn)依賴都是以@vue/cli-plugin-開頭。插件可以修改webpack內(nèi)部配置,也可以向vue-cli-service注入命令。
安裝插件
例如:手工添加eslint插件,命令會(huì)將@vue/eslint解析為完整的包名@vue/cli-plugin-eslint,然后從NPM安裝并調(diào)用其生成器。若不帶@vue前綴,命令會(huì)換做解析一個(gè)unscoped包,可基于一個(gè)指定的scope使用。
$ vue add @vue/eslint
| NPM包 | 描述 |
|---|---|
| Babel | 轉(zhuǎn)碼器,將ES6轉(zhuǎn)換為ES5。 |
| TypeScript | JS超集擴(kuò)展了JS語法,需編譯輸出為JS。 |
| PWA | 漸進(jìn)式Web應(yīng)用程序 |
| Router | Vue路由 |
| Vuex | Vue狀態(tài)管理模式 |
| CSS Pre-processors | CSS預(yù)處理器 |
| Linter/Formatter | 代碼風(fēng)格檢查和格式化 |
| Unit Testing | 單元測試 |
| E2E Testing | e2e測試 |
babel
Babel是一個(gè)JavaScript編譯器,用于將ECMAScript2015+版本的代碼轉(zhuǎn)換為先后兼容的JavaScript語句。
- 語法轉(zhuǎn)換
- 通過Polyfill在目標(biāo)環(huán)境中添加缺失的特性
- 源碼轉(zhuǎn)換
vue-router
vue-router默認(rèn)采用hash模式,也可選擇history模式。vue-router利用瀏覽器自身的hash模式和history模式的特性來實(shí)現(xiàn)前端路由,通過了瀏覽器提供的接口。
- hash:瀏覽器URL地址欄中
#符號(hào),hash不被包括在HTTP請求中,對后端完全沒有影響,因此改變hash不會(huì)重新加載頁面。 - history:利用HTML5 History Interface中新增的
pushState()和replaceState()方法,需特定瀏覽器支持,適用于單頁客戶端應(yīng)用。history mode徐后臺(tái)配置支持。
vue cli 3中采用src/router.js文件替代了vue cli 2的src/router/index.js文件
vuex
vuex用于狀態(tài)管理,vue cli 3中默認(rèn)使用store.js代替原vue cli 2種store文件夾中的三個(gè)JS文件action、mutations、state以及store的getters用法。
css pre-processors
css預(yù)處理器用于解決瀏覽器兼容并簡化css代碼等問題
目錄結(jié)構(gòu)
- vue-cli3.x相比vue-cli2.x目錄簡潔了很多,沒有了build和config等文件夾。
- vue-cli3.x相比vue-cli2.x所創(chuàng)建目錄中已經(jīng)看不到webpack的配置
- vue-cli3.x中創(chuàng)建vue.config.js文件通過configureWebpack來配置webpack
| 文件 | 描述 |
|---|---|
| .browserslistrc | 用于指定項(xiàng)目的目標(biāo)瀏覽器范圍 |
| package.json | 定義項(xiàng)目所需模塊及項(xiàng)目信息 |
| package-lock.json | 鎖定安裝時(shí)的包版本號(hào) |
| tsconfig.json | TypeScript配置文件 |
| babel.config.js | Babel配置文件 |
| .eslintrc.js | eslint檢測規(guī)則配置 |
| .gitignore | GIT配置文件 |
| public | 配置ico和index.html |
| src | vue項(xiàng)目文件夾 |
| src/assets/ | 用于存放項(xiàng)目靜態(tài)文件,包括圖片、JS、SVG等,生產(chǎn)環(huán)境下會(huì)被WebPack復(fù)制。 |
| src/components/ | 存放公用Vue組件頁面 |
| src/styles | 存放重寫reset.css以及字體圖標(biāo)CSS文件 |
| src/views | 存放較大模塊,比如登錄頁、注冊頁、首頁等。 |
public
vue cli 3摒棄vue cli2的static文件夾新增了public文件夾,vue cli 2中static文件夾是webpack存放默認(rèn)靜態(tài)資源的文件夾,打包時(shí)會(huì)直接復(fù)制一份到dist文件夾中,且不會(huì)經(jīng)過webpack編譯。vue cli 3中靜態(tài)資源有兩種處理方式:
- 經(jīng)過webpack處理:在JS被導(dǎo)入或在
template/css中通過相對路徑被引用的資源會(huì)編譯并壓縮。 - 不經(jīng)過webpack處理:放置在public文件夾下或通過絕對路徑被引用的資源將會(huì)直接拷貝一份,且不做任何編譯壓縮處理。
vue cli 3中public/index.html模板會(huì)被html-webpack-plugin處理。
src/views
vue cli 3的src文件夾中新增了views文件夾用于存放頁面,用于區(qū)分components組件。
環(huán)境配置.env
項(xiàng)目中通常包含多種模式,常見比如開發(fā)模式development、生產(chǎn)模式production等,開發(fā)中會(huì)根據(jù)環(huán)境變量process.env.NODE_ENV進(jìn)行區(qū)分。
在根目錄下創(chuàng)建.env.production生產(chǎn)環(huán)境配置和.env.development開發(fā)環(huán)境配置,配置文件以鍵值對的方式,配置項(xiàng)必須以VUE_APP_以前綴。NODE_ENV和BASE_URL是兩個(gè)特殊變量,在代碼中始終可用。
$ vim .env.development
BASE_URL = http://127.0.0.1:8080
VUE_APP_API_URL = http://127.0.0.1:4000
VUE_APP_WX_APPID = wx6fe244f7d2197mc1
加載環(huán)境配置文件
vue會(huì)根據(jù)啟動(dòng)命令啟動(dòng)加載對應(yīng)的環(huán)境文件,這是因?yàn)関ue是根據(jù)文件名進(jìn)行加載的。
-
npm run serve運(yùn)行服務(wù)會(huì)自動(dòng)加載.env.development開發(fā)環(huán)境配置文件 -
npm run build運(yùn)行構(gòu)建會(huì)自動(dòng)加載.env.production生產(chǎn)環(huán)境配置文件
運(yùn)行npm run serve啟動(dòng)服務(wù)命令時(shí),若在vue.config.js核心中配置了devServer開發(fā)服務(wù)器選項(xiàng)的proxy代理設(shè)置后,開發(fā)環(huán)境中會(huì)使用proxy代理服務(wù)器訪問接口數(shù)據(jù)。但正式生產(chǎn)環(huán)境下此選項(xiàng)會(huì)失效。因?yàn)榇藭r(shí)vue會(huì)讀取.env.production環(huán)境配置,而忽略vue.config.js中的devServer選項(xiàng)。
環(huán)境配置自定義
通過在package.json的scripts配置項(xiàng)中添加 --mode xxx來選擇不同環(huán)境。
$ vim package.json
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint"
}
"scripts": {
"serve": "vue-cli-service serve --mode development",
"build": "vue-cli-service build --mode development",
"test:unit": "vue-cli-service test:unit --mode development",
"lint": "vue-cli-service lint --mode development"
},
項(xiàng)目配置vue.config.js
vue.config.js是一個(gè)可選的配置文件,若項(xiàng)目根目錄中存在此文件,則會(huì)被@vue/cli-service自動(dòng)加載。
vue-cli 3.x 創(chuàng)建的目錄下看不到WebPack配置。手工配置webpack可在根目錄下創(chuàng)建vue.config.js文件。在根目錄下創(chuàng)建vue.config.js文件作為Vue項(xiàng)目配置文件,其中配置輸出路徑名、根目錄、預(yù)處理、devServer配置、PWA、DLL、第三方插件等。
$ vim vue.config.js
module.exports = {
configureWebpack:config=>{
if(process.env.NODE_ENV === "production"){
}else{
}
}
};
配置選項(xiàng)
| 選項(xiàng) | 描述 |
|---|---|
| publicPath | 部署應(yīng)用寶的基本URL,和webpack的output.publicPath一致。 |
| outputDir | 運(yùn)行vue-cli-service build時(shí)生成的生產(chǎn)環(huán)境構(gòu)建文件的目錄,目標(biāo)目錄在夠堅(jiān)強(qiáng)會(huì)被清除。 |
| assetsDir | 靜態(tài)資源目錄 |
| indexPath | 指定生成的index.html的輸出路徑 |
| filenameHashing | 是否在生成的靜態(tài)資源文件名中包含hash以控制緩存 |
| pages | 在多頁模式下構(gòu)建應(yīng)用,每個(gè)頁面對應(yīng)一個(gè)入口文件。 |
| lintOnSave | 是否在開發(fā)環(huán)境下通過eslint-loader在每次保存時(shí)lint代碼 |
| runtimeCompiler | 是否使用包含運(yùn)行時(shí)編譯器的vue構(gòu)建版本 |
CSS Pre-processors
VUE CLI支持CSS Modules、PostCSS和SASS、LESS、Stylus在內(nèi)的預(yù)處理器。所有編譯后的CSS會(huì)通過 css-loader 來解析其中的url()引用,并將應(yīng)用作為模塊請求來處理。這意味著可以根據(jù)本地文件結(jié)構(gòu)使用相對路徑引用靜態(tài)資源。若要應(yīng)用NPM以來中的文件,或使用webpack alias,則需在路徑前添加 ~ 前綴來避免歧義。
TypeScript
安裝TypeScript
$ npm i -S typescript
$ npm i -S @vue/cli-plugin-typescript
配置TypeScript
若目錄下存在tsconfig.json文件則意味著該目錄是TypeScript項(xiàng)目的根目錄,tsconfig.json文件中指定了用來編譯項(xiàng)目的根文件和編譯選項(xiàng)。項(xiàng)目可使用tsconfig.json來編譯。
- 當(dāng)不帶任何輸出文件的情況下調(diào)用
tsc文件,編譯器會(huì)從當(dāng)前目錄開始查找tsconfig.json文件,并逐級(jí)向上搜索父級(jí)目錄。 - 當(dāng)不帶任何輸出文件時(shí)調(diào)用
tsc文件且使用命令行參數(shù)--project或-p指定一個(gè)包含tsconfig.json文件的目錄。 - 當(dāng)在命令行上指定了輸入文件時(shí),
tsconfig.json文件會(huì)被忽略。
根目錄下創(chuàng)建tsconfig.json,默認(rèn)情況下typescript只負(fù)責(zé)靜態(tài)檢查,即使遇到錯(cuò)誤也僅僅在編譯時(shí)報(bào)錯(cuò)并不會(huì)中斷編譯,最終還是會(huì)生成一份JS文件,如果想要在報(bào)錯(cuò)時(shí)終止JS文件的生成,可在tsconfig.json配置中設(shè)置noEmitOnError選項(xiàng)為true。
$ vim tsconfig.json
{
//編譯選項(xiàng)
"compilerOptions": {
//編譯目標(biāo)版本
"target": "esnext",
//指定模塊系統(tǒng)
"module": "esnext",
//是否啟用嚴(yán)格類型檢查
"strict": true,
//在.tsx文件中支持JSX
"jsx": "preserve",
//從tslib導(dǎo)入輔助工具函數(shù)
"importHelpers": true,
//模塊處理方式,默認(rèn)classic
"moduleResolution": "node",
//是否啟用實(shí)驗(yàn)性的ES裝飾器
"experimentalDecorators": true,
//報(bào)錯(cuò)時(shí)不生成輸出文件
"noEmitOnError":true,
//通過為所有導(dǎo)入創(chuàng)建名稱空間對象,支持CommonJS和ES模塊之間的互操作性。意味著allowSyntheticDefaultImports。
"esModuleInterop": true,
//是否允許從沒有設(shè)置默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入,僅用于類型檢查。
"allowSyntheticDefaultImports": true,
//是否生成map文件
"sourceMap": true,
//工作根目錄
"baseUrl": ".",
//指定引入的類型聲明文件,默認(rèn)自動(dòng)引入所有聲明文件,一旦指定則會(huì)禁用自動(dòng)引入,只引入指定的類型。
"types": [
"webpack-env",
"mocha",
"chai"
],
//指定模塊的路徑,和baseUrl有關(guān)聯(lián),和webpack中resolve.alias配置一樣。
"paths": {
"@/*": [
"src/*"
]
},
//編譯過程中需引入的庫文件列表
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
//指定匹配列表,屬于自動(dòng)指定該路徑下所有TS相關(guān)文件。
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
//指定排除列表,include的反向操作。
"exclude": [
"node_modules"
]
}
錯(cuò)誤處理
$ npm run serve
> sxyh_web_stats@0.1.0 serve D:\vue\workspace\sxyh_web_stats
> vue-cli-service serve
INFO Starting development server...
ERROR WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.entry[2] should be a string.
-> A non-empty string
WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration.entry[2] should be a string.
-> A non-empty string
例如:針對多頁應(yīng)用的項(xiàng)目配置
$ vim vue.config.js
const path = require("path");
const fs = require("fs");
// const glob = require("glob");
const pxtoviewport = require("postcss-px-to-viewport");
//是否開發(fā)調(diào)試模式
const debug = process.env.NODE_ENV === "development" ? true : false;
//獲取文件路徑
const joinPath = (...args)=>path.join(__dirname, ...args);
/*獲取配置*/
const config = (filename,field="")=>{
let value = require(joinPath("config", filename));
if(field!==""){
value = value[field];
}
return value;
};
/*獲取多頁面配置選項(xiàng)*/
const getPages = ()=>{
let pages = {};
//獲取pages目錄下所有文件夾,即每個(gè)單頁。
fs.readdirSync(joinPath("src", "pages")).forEach(dirname=>{
//生成應(yīng)用組件文件
const app = joinPath("src", "pages", dirname, "App.vue");
if(!fs.existsSync(app)){
//todo 讀取模板內(nèi)容 替換內(nèi)容后寫入
let code = `
<template>
<router-view/>
</template>
<script>
export default {
name:"app"
}
</script>
<style scoped>
</style>
`;
fs.writeFileSync(app, code);
}
//生成路由文件
const router = joinPath("src", "pages", dirname, "router.js");
if(!fs.existsSync(router)){
let code = `
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
export default new Router({routes:[
]});
`;
fs.writeFileSync(router, code);
}
//生成入口文件
const entry = joinPath("src", "pages", dirname, "main.js");
if(!fs.existsSync(entry)){
let code = `
import Vue from "vue";
import Axios from "axios";
import App from "./App.vue";
import router from "./router.js";
Vue.config.productionTip = false;
Vue.prototype.axios = Axios;
new Vue({render:h=>h(App), router}).$mount("#${dirname}");
`;
fs.writeFileSync(entry, code);
}
//單頁配置選項(xiàng)
const template = "index.html";
const filename = `${dirname}.html`;
const chunks = ['chunk-vendors', 'chunk-common', dirname];
const chunksSortMode = "manual";
const minify = false;
const inject = true;
//自定義頁面數(shù)據(jù)
const pageData = config("page", dirname) || {};
if(pageData.title === undefined){
Object.assign(pageData, {title:dirname});
}
if(pageData.idname === undefined){
Object.assign(pageData, {idname:dirname});
}
pages[dirname] = {entry, template, filename, pageData, chunks, chunksSortMode, minify, inject};
});
return pages;
};
module.exports = {
publicPath:debug?"/":"",
outputDir:"dist",
assetsDir:"assets",
filenameHashing:true,
lintOnSave:!debug,
runtimeCompiler:!debug,
pages:getPages(),
configureWebpack:config=>{
const extensions = [".js", ".json", ".vue", ".css"];
const alias = {
"@":path.join(__dirname, "src"),
"src":path.join(__dirname, "../src"),
"assets":path.join(__dirname, "../src/assets"),
"components":path.join(__dirname, "../src/components")
};
config.resolve = {extensions, alias};
},
css:{
loaderOptions:{
postcss:{
plugins:[
pxtoviewport({
unitToConvert:"px",
unitPrecision:3,
viewportWidth:750,
viewportUnit:"vw",
fontViewportUnit:"vw",
minPixelValue:1,
mediaQuery:false,
replace:true,
propList:["*"],
selectorBlackList:[],
exclude:/(\/|\\)(node_modules)(\/|\\)/,
landscape:false,
landscapeUnit:"vh",
landscapeWidth:1334
})
]
}
}
},
//開發(fā)服務(wù)器
devServer:{
//設(shè)置代理
proxy:{
"/api":{
target:"http://127.0.0.1:4000",
ws:false,
changeOrigin:true
}
}
}
};
啟動(dòng)入口
Vue2實(shí)例啟動(dòng)入口文件默認(rèn)為main.js
$ vim src/pages/index/main.js
import Vue from 'vue';
import Axios from "axios";
import App from './App.vue';
import router from "./router.js";
Vue.config.productionTip = false;
Vue.prototype.axios = Axios;
Vue.prototype.apiUrl = process.env.VUE_APP_API_URL;
Vue.prototype.debug = process.env.NODE_ENV === "development";
//創(chuàng)建vue應(yīng)用實(shí)例并掛在到#index元素上
const vm = new Vue({render:h=>h(App), router:router});//未掛載狀態(tài)
//手工掛載vm實(shí)例
vm.$mount('#index');
這里運(yùn)用了Vue2新增的Render方法,為了得到更好地運(yùn)行速度,Vue2也采用了Virtual DOM虛擬DOM技術(shù)。Virtual DOM是一種比瀏覽器原生DOM性能更好的虛擬組件模型。
const vm = new Vue({render:h=>h(App), router:router});//未掛載狀態(tài)
通過import將Vue.js文件引入后創(chuàng)建Vue實(shí)例對象,在Vue實(shí)例中使用Render方法來繪制App這個(gè)Vue組件以完成初始化。
vm.$mount('#index');//手工掛載vm實(shí)例
將Vue實(shí)例綁定到頁面中ID為index的元素上,這樣App Vue程序就引導(dǎo)成功了。
一個(gè)Vue實(shí)例必須與一個(gè)頁面元素綁定,Vue實(shí)例一般用作Vue的全局配置來使用。
$ vim public/index.html
<% const page = htmlWebpackPlugin.options.pageData; %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- 在 head 標(biāo)簽中添加 meta 標(biāo)簽,并設(shè)置 viewport-fit=cover 值 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
<!-- 開啟 safe-area-inset-bottom 屬性 -->
<van-number-keyboard safe-area-inset-bottom />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title><%= page.title %></title>
<style>
body{
margin: 0;
overflow-x: auto;
color: #323233;
font-size: 16px;
font-family: PingFang SC, 'Helvetica Neue', Arial, sans-serif;
background-color: #f7f8fa;
-webkit-font-smoothing: antialiased;
}
</style>
</head>
<body>
<noscript>
<strong>很抱歉,如果沒有啟用javascript,vue-cli3無法正常工作。請啟用它以繼續(xù)。</strong>
</noscript>
<div id="app">
<div id="<%= page.idname %>"></div>
</div>
</body>
</html>
單頁組件
Vue2默認(rèn)Vue組件文件為App.vue,*.vue是Vue特色的文件格式表示是一個(gè)Vue組件,是Vue特色又被稱為單頁式組件。*.vue文件同時(shí)承載視圖模板、樣式定義和組件代碼。
$ vim src/pages/index/App.vue
<template>
<router-view/>
</template>
<script>
export default {
name: "app",
components:{},
data(){
return {
};
},
created(){
//console.log(this.$route);
},
methods:{
}
}
</script>
<style scoped>
</style>
Vue的組件系統(tǒng)提供了一種抽象,使用獨(dú)立可復(fù)用的組件來構(gòu)建大型應(yīng)用。因此,幾乎任意類型應(yīng)用的界面都可以抽象為一個(gè)組件樹。
單頁組件由三部分組成
-
<template>視圖模板 -
<script>組件定義 -
<style>組件樣式表