其他章節請看:
Vue CLI 4.x 下
在 vue loader 一文中我們已經學會從零搭建一個簡單的,用於單檔案元件開發的腳手架;本篇,我們將全面學習 vue-cli 這個官方的、成熟的腳手架。
分上下兩篇進行,上篇主要是”基礎“
,下篇主要是“開發”
Tip:介紹順序儘可能保持與官方文件一致
準備環境
注:本篇所有的實驗都將基於專案 vue-example
vue-example
通過 vue create
命令建立專案 vue-example:
// 專案預設 `[Vue 2] less`, `babel`, `router`, `vuex`, `eslint`
exercise> vue create vue-example
Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Vuex, CSS Pre-processors, Linter
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in production) No
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Less
? Pick a linter / formatter config: Standard
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
Vue CLI v4.5.13
✨ Creating project in D:\aaron\blogv2\exercise\vue-example.
� Initializing git repository...
⚙️ Installing CLI plugins. This might take a while...
added 1278 packages, and audited 1279 packages in 1m
80 packages are looking for funding
run `npm fund` for details
11 moderate severity vulnerabilities
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
� Invoking generators...
� Installing additional dependencies...
added 124 packages, and audited 1403 packages in 14s
91 packages are looking for funding
run `npm fund` for details
11 moderate severity vulnerabilities
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
⚓ Running completion hooks...
� Generating README.md...
� Successfully created project vue-example.
� Get started with the following commands:
$ cd vue-example
$ npm run serve
Tip:專案預設 [Vue 2] less
, babel
, router
, vuex
, eslint
webpack.config.development.js
提取出 webpack
開發配置以及生產配置:
// 提取出 webpack開發配置,匯出到 webpack.config.development.js 中
vue-example> npx vue-cli-service inspect --mode development >> webpack.config.development.js
// 提取出 webpack 生產配置
vue-example> npx vue-cli-service inspect --mode production >> webpack.config.production.js
注:接下來我們學習過程中會參考這兩個配置檔案
開發
瀏覽器相容性
browserslist
你會發現有 package.json 檔案裡的 browserslist
欄位 (或一個單獨的 .browserslistrc
檔案),指定了專案的目標瀏覽器的範圍。這個值會被 @babel/preset-env 和 Autoprefixer 用來確定需要轉譯的 JavaScript 特性和需要新增的 CSS 瀏覽器字首。
// vue-example/.browserslistrc
> 1%
last 2 versions
not dead
Tip:有關browserslist
的更多介紹可以檢視筆者的webpack 實戰一
Polyfill
Tip: 如果初次接觸 Polyfill,可以先看webpack 實戰一 -> js 相容性處理
useBuiltIns: 'usage'
一個預設的 Vue CLI 專案會使用 @vue/babel-preset-app,它通過 @babel/preset-env 和 browserslist 配置來決定專案需要的 polyfill。
預設情況下,它會把 useBuiltIns: 'usage'
傳遞給 @babel/preset-env,這樣它會根據原始碼中出現的語言特性自動檢測需要的 polyfill。這確保了最終包裡 polyfill 數量的最小化。然而,這也意味著如果其中一個依賴需要特殊的 polyfill,預設情況下 Babel 無法將其檢測出來。
構建庫或是 Web Component 時的 Polyfills
當使用 Vue CLI 來構建一個庫或是 Web Component 時,推薦給 @vue/babel-preset-app 傳入 useBuiltIns: false
選項。這能夠確保你的庫或是元件不包含不必要的 polyfills。通常來說,打包 polyfills 應當是最終使用你的庫的應用的責任。
現代模式
有了 Babel 我們可以兼顧所有最新的 ES2015+ 語言特性,但也意味著我們需要交付轉譯和 polyfill 後的包以支援舊瀏覽器。這些轉譯後的包通常都比原生的 ES2015+ 程式碼會更冗長,執行更慢。現如今絕大多數現代瀏覽器都已經支援了原生的 ES2015,所以因為要支援更老的瀏覽器而為它們交付笨重的程式碼是一種浪費。
Vue CLI 提供了一個“現代模式”幫你解決這個問題。以如下命令為生產環境構建:
vue-cli-service build --modern
Vue CLI 會產生兩個應用的版本:一個現代版的包,面向支援 ES modules 的現代瀏覽器,另一箇舊版的包,面向不支援的舊瀏覽器。
最酷的是這裡沒有特殊的部署要求。其生成的 HTML 檔案會自動使用 Phillip Walton 精彩的博文中討論到的技術:
- 現代版的包會通過
<script type="module">
在被支援的瀏覽器中載入;它們還會使用<link rel="modulepreload">
進行預載入。 - 舊版的包會通過
<script nomodule>
載入,並會被支援 ES modules 的瀏覽器忽略。 - 一個針對 Safari 10 中
<script nomodule>
的修復會被自動注入。
對於一個 Hello World 應用來說,現代版的包已經小了 16%。在生產環境下,現代版的包通常都會表現出顯著的解析速度和運算速度,從而改善應用的載入效能。
Tip:<script type="module">
需要配合始終開啟的 CORS 進行載入。這意味著你的伺服器必須返回諸如 Access-Control-Allow-Origin: *
的有效的 CORS 頭。如果你想要通過認證來獲取指令碼,可使將 crossorigin 選項設定為 use-credentials
。
HTML 和靜態資源
HTML
Index 檔案
public/index.html
檔案是一個會被 html-webpack-plugin
處理的模板。在構建過程中,資源連結會被自動注入。另外,Vue CLI 也會自動注入 resource hint (preload/prefetch、manifest 和圖示連結 (當用到 PWA 外掛時) 以及構建過程中處理的 JavaScript 和 CSS 檔案的資源連結。
Tip:html-webpack-plugin
在 初步認識 webpack 一文中有詳細介紹;prefetch 在webapck 效能一文中有介紹。
插值
因為 index 檔案被用作模板,所以你可以使用 lodash template 語法插入內容:
<%= VALUE %>
用來做不轉義插值;<%- VALUE %>
用來做 HTML 轉義插值;<% expression %>
用來描述 JavaScript 流程控制。
除了被 html-webpack-plugin 暴露的預設值之外,所有客戶端環境變數也可以直接使用。例如,BASE_URL 的用法:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
我們可以給 html-webpack-plugin
和客戶端環境變數
各定義一個變數,並在 index.html
中輸出:
// vue-example/vue.config.js
module.exports = {
chainWebpack: config => {
// 客戶端環境變數
config
.plugin('define')
.tap(args => {
// [ { 'process.env': { NODE_ENV: '"development"', BASE_URL: '"/"' } } ]
console.log(args);
// 'aaron'會報錯,需要雙引號。因為 webpack.config.development.js 中也有雙引號 `NODE_ENV: '"development"'`
args[0]['process.env'].name = '"aaron"'
return args
})
// html-webpack-plugin
config
.plugin('html')
.tap(args => {
// [{title: 'vue-example',...}]
console.log(args);
args[0].age = '18'
return args;
})
}
}
Tip:可以通過 webpack.config.development.js
知曉 config.plugin('html') 對應 HtmlWebpackPlugin
、config.plugin('define') 對應 DefinePlugin
。
// vue-example/public/index.html
<body>
<p>
<%= htmlWebpackPlugin.options.age %>
<%= process.env.name %>
</p>
...
</body>
重啟服務(npm run serve
),頁面中會顯示 18 aaron
。
Preload
<link rel="preload">
是一種 resource hint,用來指定頁面載入後很快會被用到的資源,所以在頁面載入的過程中,我們希望在瀏覽器開始主體渲染之前儘早 preload。
預設情況下,一個 Vue CLI 應用會為所有初始化渲染需要的檔案自動生成 preload 提示。
這些提示會被 @vue/preload-webpack-plugin
注入,並且可以通過 chainWebpack
的 config.plugin('preload')
進行修改和刪除。
Tip: preload 預載入就是 link 元素中的一個屬性,目前是非標準,更多資訊可以檢視mdn's link
Prefetch
Tip:本小節示例參考webpack 效能 一文中“懶載入”
、“預獲取“
兩小節
<link rel="prefetch">
是一種 resource hint,用來告訴瀏覽器在頁面載入完成後,利用空閒時間提前獲取使用者未來可能會訪問的內容。
預設情況下,一個 Vue CLI 應用會為所有作為 async chunk 生成的 JavaScript 檔案 (通過動態 import()
按需 code splitting 的產物) 自動生成 prefetch 提示。
這些提示會被 @vue/preload-webpack-plugin
注入,並且可以通過 chainWebpack
的 config.plugin('prefetch')
進行修改和刪除。
接下來我們通過一組實驗來理解一下上文所述。
首先驗證:通過 import()
動態匯入會自動生成 prefetch 提示。步驟如下:
- 啟動服務,瀏覽器開啟頁面
- 進入Home導航,在網路中能看到 about.js 請求
- 然後切換到 About,發現 about.js 這個請求的Size顯示(預取快取)
Tip: About.vue 是通過 import()
動態匯入,所以 about 應該是預獲取。
// src/router/index.js
const routes = [
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
接著修改配置,將 prefetch
關閉:
// vue.config.js
module.exports = {
chainWebpack: config => {
// 移除 prefetch 外掛
config.plugins.delete('prefetch')
}
}
再次重啟服務,重複上面步驟,發現 about.js
不在來自快取。
若需要單獨開啟 about.js 的 prefetch
,可以這麼做:
// src/router/index.js
const routes = [
{
path: '/about',
name: 'About',
- omponent: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
+ component: () => import(/* webpackChunkName: "about", webpackPrefetch: true */ '../views/About.vue')
}
]
Tip:Prefetch 連結將會消耗頻寬。如果你的應用很大且有很多 async chunk,而使用者主要使用的是對頻寬較敏感的移動端,那麼你可能需要關掉 prefetch 連結並手動選擇要提前獲取的程式碼區塊。
不生成 index
當基於已有的後端使用 Vue CLI 時,你可能不需要生成 index.html
,這樣生成的資源可以用於一個服務端渲染的頁面。
然而這樣做並不是很推薦,因為:
- 硬編碼的檔名不利於實現高效率的快取控制。
- 硬編碼的檔名也無法很好的進行 code-splitting (程式碼分段),因為無法用變化的檔名生成額外的 JavaScript 檔案。
- 硬編碼的檔名無法在現代模式下工作。
修改配置檔案:
// vue.config.js
module.exports = {
// 去掉檔名中的 hash
filenameHashing: false,
// 刪除 HTML 相關的 webpack 外掛
chainWebpack: config => {
config.plugins.delete('html')
config.plugins.delete('preload')
config.plugins.delete('prefetch')
}
}
重啟服務,index.html
內容如下:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
於是我們也就知道上面所說的”硬編碼“
是什麼意思。
構建一個多頁應用
不是每個應用都需要是一個單頁應用。Vue CLI 支援使用 vue.config.js
中的 pages 選項構建一個多頁面的應用。構建好的應用將會在不同的入口之間高效共享通用的 chunk 以獲得最佳的載入效能。
接下來我們給專案增加一個頁面,步驟如下:
首先修改配置檔案:
// vue.config.js
module.exports = {
pages: {
index: {
// page 的入口
entry: 'src/main.js',
// 模板來源
template: 'public/index.html',
// 在 dist/index.html 的輸出
filename: 'index.html',
// 當使用 title 選項時,
// template 中的 title 標籤需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Index Page',
// 在這個頁面中包含的塊,預設情況下會包含
// 提取出來的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 當使用只有入口的字串格式時,
// 模板會被推導為 `public/subpage.html`
// 並且如果找不到的話,就回退到 `public/index.html`。
// 輸出檔名會被推導為 `subpage.html`。
subpage: 'src/bmain.js'
}
}
接著新建第二個頁面的模板,以及入口檔案:
// public/subpage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>page b</title>
</head>
<body>
</body>
</html>
// src/bmain.js
console.log('i am bmain.js')
重啟服務,即可通過 http://localhost:8081/subpage
訪問第二個頁面,並在控制檯輸出”i am bmain.js“
:
vue-example> npm run serve
> vue-example@0.1.0 serve
> vue-cli-service serve
- Local: http://localhost:8081/
- Network: http://192.168.0.103:8081/
處理靜態資源
靜態資源可以通過兩種方式進行處理:
- 在
JavaScript
被匯入或在template/CSS
中通過相對路徑被引用。這類引用會被 webpack 處理。 - 放置在
public
目錄下或通過絕對路徑被引用。這類資源將會直接被拷貝,而不會經過 webpack 的處理。
從相對路徑匯入
當你在 JavaScript
、CSS
或 *.vue
檔案中使用相對路徑 (必須以 .
開頭) 引用一個靜態資源時,該資源將會被包含進入 webpack 的依賴圖中。在其編譯過程中,所有諸如 <img src="...">
、background: url(...)
和 CSS @import
的資源 URL 都會被解析為一個模組依賴。
例如,url(./image.png)
會被翻譯為 require('./image.png')
,而:
<img src="./image.png">
將會被編譯到:
h('img', { attrs: { src: require('./image.png') }})
在其內部,我們通過 file-loader
用版本雜湊值和正確的公共基礎路徑來決定最終的檔案路徑,再用 url-loader
將小於 4kb 的資源內聯,以減少 HTTP 請求的數量。
Tip: url-loader
的詳細介紹請檢視webpack 實戰一->”打包圖片“
你可以通過 chainWebpack
調整內聯檔案的大小限制。例如,下列程式碼會將其限制設定為 10kb:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.tap(options => Object.assign(options, { limit: 10240 }))
}
}
URL 轉換規則
-
如果 URL 是一個絕對路徑 (例如
/images/foo.png
),它將會被保留不變。 -
如果 URL 以
.
開頭,它會作為一個相對模組請求被解釋且基於你的檔案系統中的目錄結構進行解析。 -
如果 URL 以
~
開頭,其後的任何內容都會作為一個模組請求被解析。這意味著你甚至可以引用 Node 模組中的資源:
<img src="~some-npm-package/foo.png">
測試過程中遇到一些問題:
- 將
src/assets/logo.png
拷貝到node_modules/vue
目錄下,然後將<img src="~vue/logo.png" />
放入App.vue
或Home.vue
中生效,但在public/index.html
中無效,瀏覽器檢視原始碼還是<img src="~vue/logo.png" />
- 將
public/favicon.ico
拷貝到node_modules/vue
目錄下,在 App.vue 中輸入<img src="~vue/favicon.ico" />
報錯
- 如果 URL 以
@
開頭,它也會作為一個模組請求被解析。它的用處在於 Vue CLI 預設會設定一個指向<projectRoot>/src
的別名 @。(僅作用於模版中)
比如在 App.vue
中新增如下程式碼,你將會在頁面中看見該圖片:
<img src="@/assets/logo.png" alt="" />
Tip:在 webpack.config.development.js
中有如下程式碼:
alias: {
'@': 'exercise\\vue-example\\src',
vue$: 'vue/dist/vue.runtime.esm.js'
}
public 資料夾
任何放置在 public 資料夾的靜態資源都會被簡單的複製,而不經過 webpack。你需要通過絕對路徑來引用它們。
注:推薦將資源作為你的模組依賴圖的一部分匯入,這樣它們會通過 webpack 的處理並獲得如下好處:
- 指令碼和樣式表會被壓縮且打包在一起,從而避免額外的網路請求。
- 檔案丟失會直接在編譯時報錯,而不是到了使用者端才產生 404 錯誤。
- 最終生成的檔名包含了內容雜湊,因此你不必擔心瀏覽器會快取它們的老版本。
public 目錄提供的是一個應急手段,當你通過絕對路徑引用它時,留意應用將會部署到哪裡。如果你的應用沒有部署在域名的根部,那麼你需要為你的 URL 配置 publicPath
字首。
目前構建後的 index.html
有如下語句:
<link rel="icon" href="/favicon.ico">
通過修改 publicPath
:
// vue.config.js
module.exports = {
publicPath: '/a/b'
}
再次構建,將會變成:
<link rel="icon" href="/a/b/favicon.ico">
Tip:預設情況下,Vue CLI 會假設你的應用是被部署在一個域名的根路徑上,例如 https://www.my-app.com/
。如果應用被部署在一個子路徑上,你就需要用這個選項指定這個子路徑
注:記得還原 publicPath
,方便後續實驗。
何時使用 public 資料夾
-
你需要在構建輸出中指定一個檔案的名字。
-
你有上千個圖片,需要動態引用它們的路徑。
-
有些庫可能和 webpack 不相容,這時你除了將其用一個獨立的
<script>
標籤引入沒有別的選擇。
我們驗證一下最後一點:
在 public 資料夾中新建一個 js 檔案:
// public/a.js
console.log('apple');
在 index.html 中引入新建的 js 檔案:
// public/index.html
+ <script src='/a.js'></script>
重啟服務,在瀏覽器控制檯會輸出”apple“
。
CSS 相關
Vue CLI 專案天生支援 PostCSS
、CSS Modules
和包含 Sass
、Less
、Stylus
在內的前處理器。
Tip:通過檢視 webapck.config.development.js
就可以看見相關配置
引用一個靜態資源
所有編譯後的 CSS 都會通過 css-loader
來解析其中的 url()
引用,並將這些引用作為模組請求來處理。這意味著你可以根據本地的檔案結構用相對路徑來引用靜態資源。另外要注意的是如果你想要引用一個 npm 依賴中的檔案,或是想要用 webpack alias
,則需要在路徑前加上 ~
的字首來避免歧義。
來個簡單的示例:
建立一個 css 檔案 :
// src/style/a.css
p{color:yellow}
在 main.js 中引入新建立的 css 檔案:
+ import './style/a.css'
重啟服務,在 Home 導航頁面中就能看見黃色
文字。
前處理器
你可以在建立專案的時候選擇前處理器 (Sass/Less/Stylus
)。如果當時沒有選好,內建的 webpack 仍然會被預配置為可以完成所有的處理。你也可以手動安裝相應的 webpack loader
:
# Sass
npm install -D sass-loader sass
# Less
npm install -D less-loader less
# Stylus
npm install -D stylus-loader stylus
然後你就可以匯入相應的檔案型別,或在 *.vue
檔案中這樣來使用:
<style lang="scss">
$color: red;
</style>
Tip:更多細節請查閱vue loader->使用前處理器
自動化匯入
如果你想自動化匯入檔案 (用於顏色、變數、mixin……),你可以使用 style-resources-loader
。
這裡有一個關於 Stylus 的在每個單檔案元件和 Stylus 檔案中匯入 ./src/styles/imports.styl
的例子(雖然你可能只是用 less 開發):
// vue.config.js
const path = require('path')
module.exports = {
chainWebpack: config => {
const types = ['vue-modules', 'vue', 'normal-modules', 'normal']
types.forEach(type => addStyleResource(config.module.rule('stylus').oneOf(type)))
},
}
function addStyleResource (rule) {
rule.use('style-resource')
.loader('style-resources-loader')
.options({
patterns: [
path.resolve(__dirname, './src/styles/imports.styl'),
],
})
}
你也可以選擇使用 vue-cli-plugin-style-resources-loader
。
Tip:style-resources-loader
,這個載入器是一個用於 webpack 的 CSS 處理器資源載入器,它將你的樣式資源(例如variables
, mixins
)注入到多個匯入的css
, sass
, scss
, less
, stylus
模組中。
它主要用於:
variables
,mixins
,functions
在所有樣式檔案中共享您的檔案,因此您無需@import
手動使用它們。- 覆蓋
variables
其他庫(例如ant-design)提供的樣式檔案並自定義您自己的主題。
PostCSS
Vue CLI 內部使用了 PostCSS。
你可以通過 .postcssrc
或任何 postcss-load-config
支援的配置源來配置 PostCSS。也可以通過 vue.config.js
中的 css.loaderOptions.postcss
配置 postcss-loader。
我們預設開啟了 autoprefixer
。如果要配置目標瀏覽器,可使用 package.json 的 browserslist
欄位。
Tip:更多細節請查閱webpack 實戰一->使用 PostCSS
CSS Modules
你可以通過 <style module>
以開箱即用的方式在 *.vue
檔案中使用 CSS Modules
Tip: 更多細節請查閱vue loader 下->css Modules
向前處理器 Loader 傳遞選項
有的時候你想要向 webpack 的前處理器 loader 傳遞選項。你可以使用 vue.config.js
中的 css.loaderOptions
選項。比如你可以這樣向所有 Sass/Less
樣式傳入共享的全域性變數:
// vue.config.js
module.exports = {
css: {
loaderOptions: {
// 給 sass-loader 傳遞選項
sass: {
// @/ 是 src/ 的別名
// 所以這裡假設你有 `src/variables.sass` 這個檔案
// 注意:在 sass-loader v8 中,這個選項名是 "prependData"
additionalData: `@import "~@/variables.sass"`
},
// 預設情況下 `sass` 選項會同時對 `sass` 和 `scss` 語法同時生效
// 因為 `scss` 語法在內部也是由 sass-loader 處理的
// 但是在配置 `prependData` 選項的時候
// `scss` 語法會要求語句結尾必須有分號,`sass` 則要求必須沒有分號
// 在這種情況下,我們可以使用 `scss` 選項,對 `scss` 語法進行單獨配置
scss: {
additionalData: `@import "~@/variables.scss";`
},
// 給 less-loader 傳遞 Less.js 相關選項
less:{
// http://lesscss.org/usage/#less-options-strict-units `Global Variables`
// `primary` is global variables fields name
globalVars: {
primary: '#fff'
}
}
}
}
}
我們做個實驗:
修改 vue.config.js
,給 less 定義一個全域性變數:
// vue.config.js
module.exports = {
css: {
loaderOptions: {
// 給 less-loader 傳遞 Less.js 相關選項
less: {
// http://lesscss.org/usage/#less-options-strict-units `Global Variables`
// `primary` is global variables fields name
globalVars: {
primary: 'pink'
}
}
}
}
}
在 App.vue
中使用 less 定義的全域性變數:
<style lang="less">
p {
color: @primary;
}
</style>
重啟服務,在頁面中出現粉色
文字。
Loader 可以通過 loaderOptions
配置,包括:
- css-loader
- postcss-loader
- sass-loader
- less-loader
- stylus-loader
Tip:這樣做比使用 chainWebpack
手動指定 loader 更推薦,因為這些選項需要應用在使用了相應 loader 的多個地方。
webpack 相關
簡單的配置方式
調整 webpack 配置最簡單的方式就是在 vue.config.js
中的 configureWebpack
選項提供一個物件:
// vue.config.js
module.exports = {
configureWebpack: {
plugins: [
new MyAwesomeWebpackPlugin()
]
}
}
該物件將會被 webpack-merge
合併入最終的 webpack 配置。
注:有些 webpack 選項是基於 vue.config.js
中的值設定的,所以不能直接修改。例如你應該修改 vue.config.js 中的 outputDir
選項而不是修改 output.path
;你應該修改 vue.config.js 中的 publicPath
選項而不是修改 output.publicPath
。這樣做是因為 vue.config.js
中的值會被用在配置裡的多個地方,以確保所有的部分都能正常工作在一起。
如果你需要基於環境有條件地配置行為,或者想要直接修改配置,那就換成一個函式 (該函式會在環境變數被設定之後懶執行)。該方法的第一個引數會收到已經解析好的配置。在函式內,你可以直接修改配置,或者返回一個將會被合併的物件。就像這樣:
修改配置檔案:
// vue.config.js
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 為生產環境修改配置...
console.log('生產環境');
} else {
// 為開發環境修改配置...
console.log('開發環境');
}
}
}
執行 npm run serve
將會在終端控制檯輸出開發環境
執行 npm run build
將會在終端控制檯輸出生產環境
鏈式操作 (高階)
Vue CLI 內部的 webpack 配置是通過 webpack-chain
維護的。這個庫提供了一個 webpack 原始配置的上層抽象,使其可以定義具名的 loader 規則和具名外掛,並有機會在後期進入這些規則並對它們的選項進行修改。
它允許我們更細粒度的控制其內部配置。接下來有一些常見的在 vue.config.js 中的 chainWebpack
修改的例子。
Tip:npm 包 webpack-chain
介紹:
- webpack 的核心配置基於建立和修改一個潛在的笨拙的 JavaScript 物件。雖然這適用於單個專案的配置,但嘗試跨專案共享這些物件並進行後續修改會變得混亂,因為您需要深入瞭解底層物件結構才能進行這些更改。
webpack-chain
嘗試通過提供可連結或流暢的 API 來建立和修改 webpack 配置來改進此過程。API 的關鍵部分可以由使用者指定的名稱引用,這有助於標準化如何跨專案修改配置。
修改 Loader 選項
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
console.log(options)
// 修改它的選項...
return options
})
}
}
重啟服務,將會在終端控制檯輸出:
// 類似
{
compilerOptions: { whitespace: 'condense' },
cacheDirectory: 'node_modules\\.cache\\vue-loader',
cacheIdentifier: 'a4b2cefc'
}
Tip:對於 CSS 相關 loader 來說,我們推薦使用 css.loaderOptions
而不是直接鏈式指定 loader。這是因為每種 CSS 檔案型別都有多個規則,而 css.loaderOptions
可以確保你通過一個地方影響所有的規則。如果你看一下 webpack.config.development.js
,你會發現 css-loader
有很多匹配。
新增一個新的 Loader
// vue.config.js
module.exports = {
chainWebpack: config => {
// GraphQL Loader
config.module
.rule('graphql')
.test(/\.graphql$/)
.use('graphql-tag/loader')
.loader('graphql-tag/loader')
.end()
// 你還可以再新增一個 loader
.use('other-loader')
.loader('other-loader')
.end()
}
}
替換一個規則裡的 Loader
如果你想要替換一個已有的基礎 loader,例如為內聯的 SVG 檔案使用 vue-svg-loader
而不是載入這個檔案:
// vue.config.js
module.exports = {
chainWebpack: config => {
const svgRule = config.module.rule('svg')
// 清除已有的所有 loader。
// 如果你不這樣做,接下來的 loader 會附加在該規則現有的 loader 之後。
svgRule.uses.clear()
// 新增要替換的 loader
svgRule
.use('vue-svg-loader')
.loader('vue-svg-loader')
}
}
修改外掛選項
// vue.config.js
module.exports = {
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
return [/* 傳遞給 html-webpack-plugin's 建構函式的新引數 */]
})
}
}
Tip: 更詳細的用法請檢視本篇 HTML 和靜態資源->HTML->插值
審查專案的 webpack 配置
因為 @vue/cli-service
對 webpack 配置進行了抽象,所以理解配置中包含的東西會比較困難,尤其是當你打算自行對其調整的時候。
vue-cli-service
暴露了 inspect
命令用於審查解析好的 webpack 配置。那個全域性的 vue 可執行程式同樣提供了 inspect
命令,這個命令只是簡單的把 vue-cli-service inspect
代理到了你的專案中。
執行下面兩條命令,將會生成 output.js
和 webpack.config.development.js
:
vue-example> vue inspect > output.js
vue-example> npx vue-cli-service inspect --mode development >> webpack.config.development.js
兩個檔案生成的內容相同,內容就像這樣:
{
mode: 'development',
context: 'exercise\\vue-example',
node:{
...
},
resolve: {
...
},
resovleLoader: {
...
},
// loader
module: {
...
},
// 優化
optimization: {
...
},
// 外掛
plugins: [
...
],
entry: {
app: [
'./src/main.js'
]
}
}
注:它輸出的並不是一個有效的 webpack
配置檔案,而是一個用於審查的被序列化的格式
你也可以通過指定一個路徑來審查配置的一小部分:
// 只審查第一條規則
> vue inspect module.rules.0
/* config.module.rule('vue') */
{
test: /\.vue$/,
use: [
/* config.module.rule('vue').use('cache-loader') */
{
loader: 'exercise\\vue-example\\node_modules\\cache-loader\\dist\\cjs.js',
options: {
cacheDirectory: 'exercise\\vue-example\\node_modules\\.cache\\vue-loader',
cacheIdentifier: '3a7662ca'
}
},
/* config.module.rule('vue').use('vue-loader') */
{
loader: 'exercise\\vue-example\\node_modules\\vue-loader\\lib\\index.js',
options: {
compilerOptions: {
whitespace: 'condense'
},
cacheDirectory: 'exercise\\vue-example\\node_modules\\.cache\\vue-loader',
cacheIdentifier: '3a7662ca'
}
}
]
}
或者指向一個規則或外掛的名字:
vue inspect --rule vue
vue inspect --plugin html
最後,你可以列出所有規則和外掛的名字:
> vue inspect --rules
[
'vue',
'images',
'svg',
'media',
'fonts',
'pug',
'css',
'postcss',
'scss',
'sass',
'less',
'stylus',
'js',
'eslint'
]
> vue inspect --plugins
[
'vue-loader',
'define',
'case-sensitive-paths',
'friendly-errors',
'html',
'preload',
'prefetch',
'copy'
]
模式和環境變數
模式
模式是 Vue CLI 專案中一個重要的概念。預設情況下,一個 Vue CLI 專案有三個模式:
development
模式用於vue-cli-service serve
test
模式用於vue-cli-service test:unit
production
模式用於vue-cli-service build
和vue-cli-service test:e2e
你可以通過傳遞 --mode
選項引數為命令列覆寫預設的模式。例如,如果你想要在構建命令中使用開發環境變數:
vue-example> npx vue-cli-service build --mode development
當執行 vue-cli-service
命令時,所有的環境變數都從對應的環境檔案
(見下一小節”環境變數“
)中載入。如果檔案內部不包含 NODE_ENV
變數,它的值將取決於模式,例如,在 production 模式下被設定為 "production"
,在 test 模式下被設定為 "test"
,預設則是 "development"
。
做個實驗:
新建 .env.development
檔案:
// vue-example/.env.development
NODE_ENV=production
再次執行 npx vue-cli-service build --mode development
,你會發現 dist/index.html
變為一行,說明 .env.development
檔案生效。
NODE_ENV
將決定您的應用執行的模式,是開發,生產還是測試,因此也決定了建立哪種 webpack 配置。
例如通過將 NODE_ENV
設定為 "test"
,Vue CLI 會建立一個優化過後的,並且旨在用於單元測試的 webpack 配置,它並不會處理圖片以及一些對單元測試非必需的其他資源。
同理,NODE_ENV=development
建立一個 webpack 配置,該配置啟用熱更新,不會對資源進行 hash 也不會打出 vendor bundles,目的是為了在開發的時候能夠快速重新構建。
當你執行 vue-cli-service build
命令時,無論你要部署到哪個環境,應該始終把 NODE_ENV
設定為 "production"
來獲取可用於部署的應用程式。
注:如果在環境中有預設的 NODE_ENV
,你應該移除它或在執行 vue-cli-service 命令的時候明確地設定 NODE_ENV
。
環境變數
你可以在你的專案根目錄中放置下列檔案來指定環境變數:
.env # 在所有的環境中被載入
.env.local # 在所有的環境中被載入,但會被 git 忽略
.env.[mode] # 只在指定的模式中被載入
.env.[mode].local # 只在指定的模式中被載入,但會被 git 忽略
Tip:上一小節我們已經使用了.env.[mode]
一個環境檔案只包含環境變數的“鍵=值”
對:
FOO=bar
VUE_APP_NOT_SECRET_CODE=some_value
注:不要在你的應用程式中儲存任何機密資訊(例如私有 API 金鑰)!環境變數會隨著構建打包嵌入到輸出程式碼,意味著任何人都有機會能夠看到它。
請注意,只有 NODE_ENV
,BASE_URL
和以 VUE_APP_
開頭的變數將通過 webpack.DefinePlugin
靜態地嵌入到客戶端側的程式碼中。這是為了避免意外公開機器上可能具有相同名稱的私鑰。
你在 webpack.config.development.js
中能看到如下程式碼:
/* config.plugin('define') */
new DefinePlugin(
{
'process.env': {
NODE_ENV: '"development"',
BASE_URL: '"/"'
}
}
),
想要了解解析環境檔案規則的細節,請參考 dotenv
。我們也使用 dotenv-expand
來實現變數擴充套件 (Vue CLI 3.5+ 支援)。
Tip:dotenv
是一個零依賴模組,它將環境變數從 .env
檔案載入到 process.env
中;Dotenv-expand
在 dotenv
之上新增了變數擴充套件。如果您發現自己需要擴充套件機器上已經存在的環境變數,那麼 dotenv-expand
就是您的工具。
例如:
FOO=foo
BAR=bar
CONCAT=$FOO$BAR # CONCAT=foobar
被載入的變數將會對 vue-cli-service
的所有命令、外掛和依賴可用。
環境檔案載入優先順序:
- 為一個特定模式準備的環境檔案 (例如
.env.production
) 將會比一般的環境檔案 (例如.env
) 擁有更高的優先順序。 - Vue CLI 啟動時已經存在的環境變數擁有最高優先順序,並不會被
.env
檔案覆寫 .env
環境檔案是通過執行 vue-cli-service 命令載入的,因此環境檔案發生變化,你需要重啟服務
示例:Staging 模式
假設我們有一個應用包含以下 .env
檔案:
VUE_APP_TITLE=My App
和 .env.staging
檔案:
NODE_ENV=production
VUE_APP_TITLE=My App (staging)
-
vue-cli-service build
會載入可能存在的.env
、.env.production
和.env.production.local
檔案然後構建出生產環境應用。 -
vue-cli-service build --mode staging
會在 staging 模式下載入可能存在的.env
、.env.staging
和.env.staging.local
檔案然後構建出生產環境應用。
這兩種情況下,根據 NODE_ENV
,構建出的應用都是生產環境應用,但是在 staging 版本中,process.env.VUE_APP_TITLE
被覆寫成了另一個值。
在客戶端側程式碼中使用環境變數
只有以 VUE_APP_
開頭的變數會被 webpack.DefinePlugin
靜態嵌入到客戶端側的包中。你可以在應用的程式碼中這樣訪問它們:
console.log(process.env.VUE_APP_SECRET)
在構建過程中,process.env.VUE_APP_SECRET
將會被相應的值所取代。在 VUE_APP_SECRET=secret
的情況下,它會被替換為 "secret"
。
做個實驗:
新建 .env
:
// vue-example/.env
VUE_APP_NAME=aaron
在 App.vue
中使用環境變數:
// App.vue
...
<script>
console.log(process.env.VUE_APP_NAME)
</script>
重啟服務(記得重啟,否則不生效),瀏覽器控制檯將輸出 aaron
。
除了 VUE_APP_*
變數之外,在你的應用程式碼中始終可用的還有兩個特殊的變數:
NODE_ENV
- 會是"development"
、"production"
或"test"
中的一個。具體的值取決於應用執行的模式。BASE_URL
- 會和vue.config.js
中的publicPath
選項相符,即你的應用會部署到的基礎路徑。
// App.vue
...
<script>
console.log(process.env.NODE_ENV)
</script>
所有解析出來的環境變數都可以在 public/index.html
中以 HTML 插值中介紹的方式使用。
你可以在 vue.config.js 檔案中計算環境變數。它們仍然需要以 VUE_APP_
字首開頭。這可以用於版本資訊。
在 vue.config.js 中定義變數 VUE_APP_VERSION
:
// vue.config.js
process.env.VUE_APP_VERSION = require('./package.json').version
App.vue 中使用版本變數:
<script>
console.log(process.env.VUE_APP_VERSION)
</script>
重啟服務,瀏覽器控制檯會輸出 0.1.0
(package.json 中定義了 "version": "0.1.0"
)。
只在本地有效的變數
有的時候你可能有一些不應該提交到程式碼倉庫中的變數,尤其是當你的專案託管在公共倉庫時。這種情況下你應該使用一個 .env.local
檔案取而代之。本地環境檔案預設會被忽略,且出現在 .gitignore
中。
.local
也可以加在指定模式的環境檔案上,比如 .env.development.local
將會在 development 模式下被載入,且被 git 忽略。
構建目標
當你執行 vue-cli-service build
時,你可以通過 --target
選項指定不同的構建目標。它允許你將相同的原始碼根據不同的用例生成不同的構建。
應用
應用模式是預設的模式。在這個模式中:
index.html
會帶有注入的資源和 resource hint- 第三方庫會被分到一個獨立包以便更好的快取
- 小於 4kb 的靜態資源會被內聯在 JavaScript 中
public
中的靜態資源會被複制到輸出目錄中
庫
你可以通過下面的命令將一個單獨的入口構建為一個庫:
vue-cli-service build --target lib --name myLib [entry]
這個入口可以是一個 .js
或一個 .vue
檔案。如果沒有指定入口,則會使用 src/App.vue
我們在 vue-example 中嘗試使用該命令:
vue-example> npx vue-cli-service build --target lib --name myLib
/ Building for production as library (commonjs,umd,umd-min)...
File Size Gzipped
dist\myLib.umd.min.js 4.48 KiB 1.85 KiB
dist\myLib.umd.js 18.74 KiB 4.56 KiB
dist\myLib.common.js 18.36 KiB 4.46 KiB
dist\myLib.css 0.25 KiB 0.19 KiB
Images and other types of assets omitted.
dist/myLib.common.js
:一個給打包器用的 CommonJS 包 (不幸的是,webpack 目前還並沒有支援 ES modules 輸出格式的包)dist/myLib.umd.js
:一個直接給瀏覽器或 AMD loader 使用的 UMD 包dist/myLib.umd.min.js
:壓縮後的 UMD 構建版本dist/myLib.css
:提取出來的 CSS 檔案 (可以通過在vue.config.js
中設定css: { extract: false }
強制內聯)
Web Components 元件
你可以通過下面的命令將一個單獨的入口構建為一個 Web Components 元件:
vue-cli-service build --target wc --name my-element [entry]
注意這裡的入口應該是一個 *.vue
檔案。Vue CLI 將會把這個元件自動包裹並註冊為 Web Components 元件,無需在 main.js
裡自行註冊。也可以在開發時把 main.js
作為 demo app 單獨使用。
我們做個實驗:
vue-example> npx vue-cli-service build --target wc --name my-element
| Building for production as web component...
File Size Gzipped
dist\my-element.min.js 9.52 KiB 4.04 KiB
dist\my-element.js 32.19 KiB 8.61 KiB
Images and other types of assets omitted.
dist 目錄還生成了一個 html 檔案,內容如下:
// demo.html
<meta charset="utf-8">
<title>my-element demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="./my-element.js"></script>
<my-element></my-element>
將這個 html 以網頁(需啟動服務,筆者使用 vscode 的外掛 Live server
)形式開啟,頁面正常顯示,能看到一張 vue 的圖片,以及 Home
和 About
。
Tip:這個包依賴了在頁面上全域性可用的 Vue。這裡的 https://unpkg.com/vue
就是 vue 框架。
這個模式允許你的元件的使用者以一個普通 DOM 元素的方式使用這個 Vue 元件。
<!-- vue 庫 -->
<script src="https://unpkg.com/vue"></script>
<!-- 元件 -->
<script src="path/to/my-element.js"></script>
<!-- 可在普通 HTML 中或者其它任何框架中使用 -->
<my-element></my-element>
註冊多個 Web Components 元件的包
當你構建一個 Web Components 元件包的時候,你也可以使用一個 glob 表示式作為入口指定多個元件目標:
vue-cli-service build --target wc --name foo 'src/components/*.vue'
當你構建多個 web component 時,--name
將會用於設定字首,同時自定義元素的名稱會由元件的檔名推導得出。比如一個名為 HelloWorld.vue
的元件攜帶 --name foo
將會生成的自定義元素名為 <foo-hello-world>
。
非同步 Web Components 元件
當指定多個 Web Components 元件作為目標時,這個包可能會變得非常大,並且使用者可能只想使用你的包中註冊的一部分元件。這時非同步 Web Components 模式會生成一個 code-split 的包,帶一個只提供所有元件共享的執行時,並預先註冊所有的自定義元件小入口檔案。一個元件真正的實現只會在頁面中用到自定義元素相應的一個例項時按需獲取:
vue-cli-service build --target wc-async --name foo 'src/components/*.vue'
File Size Gzipped
dist/foo.0.min.js 12.80 kb 8.09 kb
dist/foo.min.js 7.45 kb 3.17 kb
dist/foo.1.min.js 2.91 kb 1.02 kb
dist/foo.js 22.51 kb 6.67 kb
dist/foo.0.js 17.27 kb 8.83 kb
dist/foo.1.js 5.24 kb 1.64 kb
現在使用者在該頁面上只需要引入 Vue 和這個入口檔案即可:
<script src="https://unpkg.com/vue"></script>
<script src="path/to/foo.min.js"></script>
<!-- foo-one 的實現的 chunk 會在用到的時候自動獲取 -->
<foo-one></foo-one>
部署
通用指南
如果你用 Vue CLI 處理靜態資源並和後端框架一起作為部署的一部分,那麼你需要的僅僅是確保 Vue CLI 生成的構建檔案在正確的位置,並遵循後端框架的釋出方式即可。
如果你獨立於後端部署前端應用——也就是說後端暴露一個前端可訪問的 API,然後前端實際上是純靜態應用。那麼你可以將 dist
目錄裡構建的內容部署到任何靜態檔案伺服器中,但要確保正確的 publicPath
。
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/'
: '/'
}
本地預覽
dist 目錄需要啟動一個 HTTP 伺服器來訪問 (除非你已經將 publicPath
配置為了一個相對的值),所以以 file://
協議直接開啟 dist/index.html
是不會工作的。在本地預覽生產環境構建最簡單的方式就是使用一個 Node.js 靜態檔案伺服器,例如 serve
:
npm install -g serve
# -s 引數的意思是將其架設在 Single-Page Application 模式下
# 這個模式會處理即將提到的路由問題
serve -s dist
注:這裡提到以 serve -s dist
啟動會處理路由問題。到底是什麼問題呢?請接著看:
vue-router 有 hash
模式和 history
模式。hash 比較醜;而 history 的 url 正常,也好看,不過需要後臺配置,因為我們的應用是個單頁客戶端應用,如果後臺沒有正確的配置,當使用者在瀏覽器直接訪問一個不存在的 url(http://oursite.com/user/id
) 就會返回 404,這就不好看了。
將路由改為 hash 模式:
// vue-example/src/router/index.js
const router = new VueRouter({
- mode: 'history',
base: process.env.BASE_URL,
routes
})
重啟服務(npm run serve
),瀏覽器頁面的 url 是 http://localhost:8080/#/
,比較醜。修改 url,訪問一個不存在的頁面,還是會”正常“顯示,而不是報 404。
// 訪問不存在的資源
http://localhost:8080/#/9999
而使用 history 模式時,URL 就像正常的 url,例如 http://localhost:8080/about
,而不是很醜的 http://localhost:8080/about#/
。
接下來我們用 express 寫個伺服器來訪問構建後的程式碼,步驟如下:
修改輸出目錄:
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/'
: '/',
outputDir: process.env.NODE_ENV === 'production'
? 'dist/production-sub-path'
: 'dist',
}
重新構建,所有資源都在 dist/production-sub-path
目錄中:
vue-example> npm run build
File Size Gzipped
dist\production-sub-path\js\chunk-vendors.2c244d24.js 137.04 KiB 47.60 KiB
dist\production-sub-path\js\app.60a619a9.js 6.43 KiB 2.31 KiB
dist\production-sub-path\js\about.994a1f46.js 0.89 KiB 0.45 KiB
dist\production-sub-path\a.js 0.02 KiB 0.04 KiB
dist\production-sub-path\css\app.9da74d00.css 0.42 KiB 0.26 KiB
新建 express 伺服器,即建立 dist/nodeServer.js
:
// dist/nodeServer.js
const path = require('path')
const express = require('express')
const app = express()
const port = 3000
// 提供服務的目錄的絕對路徑會更安全,否則如果不在 dist 目錄中啟動,就請求不到頁面
app.use('/production-sub-path', express.static(path.join(__dirname, '/production-sub-path')))
app.get('/', (req, res) => {
const index = path.join(__dirname, '/production-sub-path/index.html')
// 無需通過 res.render() 渲染 HTML。 你可以通過 res.sendFile() 直接對外輸出 HTML 檔案。
// 需要絕對路徑
res.sendFile(index)
})
// 請求不到則返回404
app.use(function (req, res, next) {
res.status(404).send("Sorry can't find that!")
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
安裝 express 包,啟動服務,訪問 url,頁面正常顯示:
vue-example> npm i -D express
vue-example> nodemon dist\nodeServer.js
...
Example app listening at http://localhost:3000
瀏覽器訪問 http://localhost:3000
,如果訪問一個不存在的 url,例如 http://localhost:3000/#/about2
,頁面也顯示“正常”(不會出現類似 404)
接下來我們改為 history 模式,重新編譯:
// vue-example/src/router/index.js
const router = new VueRouter({
+ mode: 'history',
base: process.env.BASE_URL,
routes
})
vue-example> npm run build
再次訪問 http://localhost:3000/about
,url 也醜了,現在已經是 history 模式。進行 2 個測試:
- 訪問不存在的 url(
http://localhost:3000/about2
),頁面顯示”Sorry can't find that!“
- 在
http://localhost:3000/about
,按ctrl + r
重新整理頁面,頁面顯示”Sorry can't find that!“
以上兩個問題可以通過下面的後端配置來解決,請看操作:
現在我們要配置一下後端,當我們請求不存在的資源時,返回到首頁。
Tip:在 vue-router
中提到:對於 Node.js/Express
,請考慮使用 connect-history-api-fallback
中介軟體。
我們首先安裝這個包,然後配置:
> npm i -D connect-history-api-fallback
// vue.config.js
...
const port = 3000
const history = require('connect-history-api-fallback')
app.use(history({
index: '/'
}))
...
Tip:有關更多後端配置,例如 Apache、nginx等,請參考 vue-router
官網。
CORS
CORS,即跨源資源共享。當前端程式碼部署在一個域,後端伺服器又是另一個域,此時前端是請求不到後端的介面。
我們在上一小節的基礎上做個實驗:
再建一個後端伺服器:
// vue-example/dist/endServer.js
const express = require('express')
const app = express()
const port = 3010
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
啟動後端伺服器:
vue-example\dist> nodemon .\endServer.js
...
Example app listening at http://localhost:3010
瀏覽器訪問 http://localhost:3010
,頁面顯示 Hello World!
,說明這個介面沒有問題。
接下來由前端給這個伺服器傳送請求(存在跨域,因為埠一個是 3000,一個是 3010):
// 直接修改 dist/production-sub-path/index.html ,給伺服器傳送請求
...
<script>
var xhr = new XMLHttpRequest(),
method = "GET",
url = "http://localhost:3010/";
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.send();
</script>
瀏覽器開啟頁面 http://localhost:3000/
,控制檯報錯如下:
Access to XMLHttpRequest at 'http://localhost:3010/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
給伺服器配置一下 CORS:
// endServer.js
...
app.get('/', (req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*')
res.send('Hello World!')
})
再次訪問 http://localhost:3000/
,瀏覽器控制檯輸出 hello world!
PWA
如果你使用了 PWA 外掛,那麼應用必須架設在 HTTPS 上,這樣 Service Worker 才能被正確註冊。
Tip:更多介紹請看效能->漸進式網路應用程式
平臺指南
雲開發 CloudBase
雲開發 CloudBase 是一個雲原生一體化的 Serverless 雲平臺,支援靜態網站、容器等多種託管能力,並提供簡便的部署工具 CloudBase Framework) 來一鍵部署應用。
步驟一:安裝雲開發 CloudBase CLI
CloudBase CLI 整合了 CloudBase Framework 的能力,全域性安裝 CloudBase CLI 請執行以下命令:
npm install -g @cloudbase/cli
步驟二:一鍵部署
在專案根目錄執行以下命令部署 Vue CLI 建立的應用,在部署之前可以先開通環境
cloudbase init --without-template
cloudbase framework:deploy
CloudBase CLI 首先跳轉到控制檯進行登入授權,然後將會互動式進行以下步驟:
- 選擇一個環境,如果沒有可以選擇新建環境
- 自動檢測專案並確認構建指令碼,輸出目錄、部署雲端路徑等資訊
確認資訊後會立即進行部署,部署完成後,可以獲得一個自動 SSL,CDN 加速的網站應用,你也可以搭配使用 Github Action 來持續部署 Github 上的 Vue 應用。
我們現在就來部署,具體步驟如下:
首先建立一個新專案:
exercise> vue create vue-hello
...
� Successfully created project vue-hello.
� Get started with the following commands:
$ cd vue-hello
$ npm run serve
接著進入專案,全域性安裝 CloudBase CLI:
vue-hello> npm install -g @cloudbase/cli
執行初始化命令,會自動彈出瀏覽器頁面,而終端會停在 獲取授權中...
vue-hello> cloudbase init --without-template
Tip: cloudbase 命令可以簡寫為 tcb
CloudBase CLI 1.8.0
CloudBase Framework 1.8.3
⚠️ 此命令將被廢棄,請使用新的命令 => tcb new <appName> [template]
i 你還沒有登入,請在控制檯中授權登入
\ 獲取授權中...
瀏覽器頁面會開啟騰訊雲
,掃描登入後,出現 CLI 授權
,點選 確認授權
,CLI 工具授權成功後,點選 前往開發者首頁
現在授權完成了,終端往下走,筆者選擇hello-cloudbase
\vue-hello> cloudbase init --without-template
Tip: cloudbase 命令可以簡寫為 tcb
CloudBase CLI 1.8.0
CloudBase Framework 1.8.3
⚠️ 此命令將被廢棄,請使用新的命令 => tcb new <appName> [template]
i 你還沒有登入,請在控制檯中授權登入
√ 授權登入成功!
? 選擇關聯環境 ...
> hello-cloudbase - [hello-cloudbase-8gwug930dc0ce23c:按量計費]
建立新環境
終端顯示初始化專案成功!
...
√ 選擇關聯環境 · hello-cloudbase - [hello-cloudbase-8gwug930dc0ce23c:按量計費]
√ 初始化專案成功!
i � 開發完成後,執行命令 cloudbase framework:deploy 一鍵部署
根據提示我們輸入 cloudbase framework:deploy
一鍵部署(中途需要我們回答幾個問題):
vue-hello> cloudbase framework:deploy
Tip: cloudbase 命令可以簡寫為 tcb
CloudBase CLI 1.8.0
CloudBase Framework 1.8.3
⚠️ 此命令將被廢棄,請使用新的命令 tcb framework deploy [module] 代替
______ __ __ ____
/ ____// /____ __ __ ____/ // __ ) ____ _ _____ ___
/ / / // __ \ / / / // __ // __ |/ __ `// ___// _ \
/ /___ / // /_/ // /_/ // /_/ // /_/ // /_/ /(__ )/ __/
\_________\____/ \__,_/ \__,_//_____/ \__,_//____/ \___/ __
/ ____/_____ ____ _ ____ ___ ___ _ __ ____ _____ / /__
/ /_ / ___// __ `// __ `__ \ / _ \| | /| / // __ \ / ___// //_/
/ __/ / / / /_/ // / / / / // __/| |/ |/ // /_/ // / / ,<
/_/ /_/ \__,_//_/ /_/ /_/ \___/ |__/|__/ \____//_/ /_/|_|
CloudBase Framework info Version v1.8.3
CloudBase Framework info Github: https://github.com/Tencent/cloudbase-framework
CloudBase Framework info EnvId hello-cloudbase-8gwug930dc0ce23c
CloudBase Framework info Region ap-shanghai
? 請輸入應用唯一標識(支援 A-Z a-z 0-9 及 -, 同一環境下不能相同) vue-hello
? 檢測到當前專案包含 Vue.js 專案
� 構建指令碼 `npm run build`
� 本地靜態檔案目錄 `dist`
是否需要修改預設配置 No
? 是否需要儲存當前專案配置到專案中 Yes
CloudBase Framework info Validate config file success.
CloudBase Framework info AppName vue-hello
CloudBase Framework info ◆ install plugins
CloudBase Framework info callHooks 'preDeploy'
added 587 packages in 38s
30 packages are looking for funding
run `npm fund` for details
CloudBase Framework info ◆ init: vue...
CloudBase Framework info Website 外掛會自動開啟靜態網頁託管能力,需要當前環境為 [按量計費] 模式
CloudBase Framework info Website 外掛會部署應用資源到當前靜態託管的 / 目錄下
CloudBase Framework info ◆ build: vue...
CloudBase Framework info running 'npm install --prefer-offline --no-audit --progress=false'
up to date in 1s
85 packages are looking for funding
run `npm fund` for details
CloudBase Framework info running 'npm run build'
> vue-hello@0.1.0 build
> vue-cli-service build
DONE Compiled successfully in 6859ms 9:10:24 ├F10: PM┤
File Size Gzipped
dist\js\chunk-vendors.62acfa1e.js 90.99 KiB 32.57 KiB
dist\js\app.93515e7b.js 4.58 KiB 1.64 KiB
dist\css\app.fb0c6e1c.css 0.33 KiB 0.23 KiB
Images and other types of assets omitted.
DONE Build complete. The dist directory is ready to be deployed.
INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html
CloudBase Framework info ◆ compile: vue...
CloudBase Framework info callHooks 'postCompile'
正在部署[░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 100% 5.1 s
CloudBase Framework info ◆ deploy: vue...
CloudBase Framework info ◆ 網站部署成功
CloudBase Framework info callHooks 'postDeploy'
CloudBase Framework info ◆ 應用入口資訊:
◆ 網站入口: https://hello-cloudbase-xxxx.com/
CloudBase Framework info ✨ done
部署成功,直接訪問 https://hello-cloudbase-xxxx.com/
即可看到我們的專案。
下次修改程式碼,直接執行部署的命令,即可看到更新後的效果。
Tip:其他平臺(比如 Github Pages
、混合部署
、Docker (Nginx)
...)請直接參考 vue-cli
官網
配置參考
Tip: 配置參考比較簡單,這裡只稍微介紹 lintOnSave
,其他配置直接檢視官網。
全域性 CLI 配置
有些針對 @vue/cli
的全域性配置,例如你慣用的包管理器和你本地儲存的 preset,都儲存在 home 目錄下一個名叫 .vuerc
的 JSON 檔案。你可以用編輯器直接編輯這個檔案來更改已儲存的選項。
你也可以使用 vue config
命令來審查或修改全域性的 CLI 配置。
// 進入 home 目錄
vue-hello> cd ~
PS C:\Users\79764>
79764> cat .vuerc
{
"useTaobaoRegistry": false,
"latestVersion": "4.5.13",
"lastChecked": 1606462273301,
"presets": {
"presetNameA": {
"useConfigFiles": true,
"plugins": {
"@vue/cli-plugin-babel": {},
"@vue/cli-plugin-typescript": {
"classComponent": true,
"useTsWithBabel": true
},
"@vue/cli-plugin-pwa": {},
"@vue/cli-plugin-router": {
"historyMode": false
},
"@vue/cli-plugin-vuex": {},
"@vue/cli-plugin-eslint": {
"config": "standard",
"lintOn": [
"save"
]
},
"@vue/cli-plugin-unit-mocha": {},
"@vue/cli-plugin-e2e-cypress": {}
},
"vueVersion": "2",
"cssPreprocessor": "less"
},
...
}
}
vue.config.js
vue.config.js 是一個可選的配置檔案,如果專案的 (和 package.json
同級的) 根目錄中存在這個檔案,那麼它會被 @vue/cli-service
自動載入。
這個檔案應該匯出一個包含了選項的物件:
// vue.config.js
module.exports = {
// 選項...
}
lintOnSave
- Type:
boolean
|'warning'
|'default'
|'error'
- Default:
'default'
是否在開發環境下通過 eslint-loader
在每次儲存時 lint 程式碼。這個值會在 @vue/cli-plugin-eslint
被安裝之後生效。
設定為 true
或 'warning'
時,eslint-loader
會將 lint 錯誤輸出為編譯警告。預設情況下,警告僅僅會被輸出到命令列,且不會使得編譯失敗。
如果你希望讓 lint 錯誤在開發時直接顯示在瀏覽器中,你可以使用 lintOnSave: 'default'
。這會強制 eslint-loader
將 lint 錯誤輸出為編譯錯誤,同時也意味著 lint 錯誤將會導致編譯失敗。
設定為 error
將會使得 eslint-loader
把 lint 警告也輸出為編譯錯誤,這意味著 lint 警告將會導致編譯失敗。
或者,你也可以通過設定讓瀏覽器 overlay
同時顯示警告和錯誤:
// vue.config.js
module.exports = {
devServer: {
overlay: {
warnings: true,
errors: true
}
}
}
當 lintOnSave
是一個 truthy
的值時,eslint-loader
在開發和生產構建下都會被啟用。如果你想要在生產構建時禁用 eslint-loader
,你可以用如下配置:
// vue.config.js
module.exports = {
lintOnSave: process.env.NODE_ENV !== 'production'
}
我們來看一下這個屬性:
通過 npm run serve
啟動我們的專案 vue-example
,如果你在 main.js
末尾中增加 console.log(1);
,終端會編譯失敗,而且瀏覽器也會出現一個錯誤遮罩:
98% after emitting CopyPlugin
ERROR Failed to compile with 1 error 9:13:31 ├F10: AM┤
error in ./src/main.js
Module Error (from ./node_modules/eslint-loader/index.js):
exercise\vue-example\src\main.js
11:15 error Extra semicolon semi
✖ 1 problem (1 error, 0 warnings)
1 error and 0 warnings potentially fixable with the `--fix` option.
這個特性對應上文提到的 default
如果你想讓其不中斷編譯,可以這樣:
// vue.config.js
module.exports = {
lintOnSave: process.env.NODE_ENV !== 'production'
}
重複上面操作,終端則不會在中斷,瀏覽器頁面也沒有錯誤遮罩:
98% after emitting CopyPlugin
WARNING Compiled with 1 warning
其他章節請看: