其他章節請看:
vue loader 擴充套件
在vue loader一文中,我們學會了從零搭建一個簡單的,用於單檔案元件開發的腳手架。本篇將在此基礎上繼續引入一些常用的庫:vue-router、vuex、axios、mockjs、i18n、jquery、lodash。
環境準備
Tip: 此環境本質就是“vue loader”一文最終生成的程式碼,略微精簡一下:刪除不必要的檔案、wepback.config.js 註釋掉 eslint 以及自定義 loader。
專案結構:
vue-loader-test
- src // 專案原始碼
- index.html // 頁面模板
- index.js // 入口
- package.json // 存放了專案依賴的包
- webpack.config.js // webpack配置檔案
// index.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>Document</title>
</head>
<body></body>
</html>
// index.js
console.log('hello');
// package.json
{
"name": "vue-loader-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/preset-env": "^7.14.7",
"babel-loader": "^8.2.2",
"css-loader": "^5.2.4",
"eslint": "^7.30.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-vue": "^7.12.1",
"eslint-webpack-plugin": "^2.5.4",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.2",
"less": "^4.1.1",
"less-loader": "^7.3.0",
"mini-css-extract-plugin": "^1.6.2",
"node-sass": "^6.0.1",
"postcss-loader": "^4.3.0",
"postcss-preset-env": "^6.7.0",
"pug": "^3.0.2",
"pug-plain-loader": "^1.1.0",
"sass-loader": "^10.2.0",
"style-loader": "^2.0.0",
"stylus": "^0.54.8",
"stylus-loader": "^4.3.3",
"ts-loader": "^7.0.5",
"typescript": "^4.3.5",
"url-loader": "^4.1.1",
"vue": "^2.6.14",
"vue-loader": "^15.9.7",
"vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {
VueLoaderPlugin
} = require('vue-loader');
const process = require('process');
process.env.NODE_ENV = 'production'
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const postcssLoader = {
loader: 'postcss-loader',
options: {
// postcss 只是個平臺,具體功能需要使用外掛
postcssOptions: {
plugins: [
[
"postcss-preset-env",
{
browsers: 'ie >= 8, chrome > 10',
},
],
]
}
}
}
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
oneOf: [
// 這裡匹配 `<style module>`
{
resourceQuery: /module/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// 開啟 CSS Modules
modules: {
// 自定義生成的類名
localIdentName: '[local]_[hash:base64:8]'
}
}
}
]
},
{
use: [
process.env.NODE_ENV !== 'production'
? 'vue-style-loader'
: MiniCssExtractPlugin.loader,
'css-loader'
]
},
]
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
hotReload: true // 關閉熱過載
}
},
{
test: /\.(png|jpg|gif)$/i,
use: [{
loader: 'url-loader',
options: {
// 調整的比 6.68 要小,這樣圖片就不會打包成 base64
limit: 1024 * 6,
esModule: false,
},
},],
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.sass$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
// sass-loader version >= 8
sassOptions: {
indentedSyntax: true
},
additionalData: `$size: 3em;`,
}
}
]
},
{
test: /\.less$/,
use: [
'vue-style-loader',
// +
{
loader: 'css-loader',
options: {
// 開啟 CSS Modules
modules: {
localIdentName: '[local]_[hash:base64:8]'
}
}
},
postcssLoader,
'less-loader'
]
},
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
},
{
test: /\.js$/,
// exclude: /node_modules/,
exclude: file => (
/node_modules/.test(file) &&
!/\.vue\.js/.test(file)
),
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.vue$/] }
},
{
test: /\.pug$/,
loader: 'pug-plain-loader'
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin(),
],
mode: 'development',
devServer: {
hot: true,
open: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/'),
},
extensions: ['.ts', '.js'],
},
};
安裝依賴,啟動服務,如果在自動開啟的網頁的控制檯中輸出:hello,則說明環境準備就緒。
> npm i
// 啟動服務
> npm run dev
vue-router
Tip:由於 vue-router 官網的"起步"章節,不是以單頁面元件的形式介紹的,所以筆者另外參考了 vue-cli 建立帶有 vue-router 的專案。
將 vue-router 引入工程,步驟如下:
> npm i vue-router@3
建立 App.vue,包含一個導航,導航指向兩個元件,分別是 Home 和 About:
// src/App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
在 src/views 資料夾中建立兩個元件:
// views/Home.vue
<template>
<div>
i am Home
</div>
</template>
// views/About.vue
<template>
<div>
i am About
</div>
</template>
建立路由,並將路由統一放在 src/router 資料夾中:
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
// 匯入 Home 元件
import Home from '../views/Home.vue'
// 在一個模組化工程中使用 vue-router,必須通過 Vue.use() 明確地安裝路由功能
Vue.use(VueRouter)
// 定義路由 map
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
// 建立路由例項
const router = new VueRouter({
routes
})
export default router
最後在 src/index.js 中使用路由:
// index.js
import Vue from 'vue';
import router from './router';
import App from './App.vue';
new Vue({
// 通過 router 配置引數注入路由,從而讓整個應用都有路由功能
router,
el: 'body',
render: (h) => h(App),
});
重啟服務,頁面顯示如下:
Home | About
i am home
預設是導航是 Home,點選 About,主體內容則變成i am About
。
vuex
Tip: 參考 vue-cli 建立的帶有 vuex 的專案。
將 vuex 引入工程,步驟如下:
> npm i vuex@3
建立 vuex 檔案,統一將 vuex 放在 src/store 資料夾中:
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
// 在一個模組化的打包系統中,必須顯式地通過 Vue.use() 來安裝 Vuex
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
},
mutations: {
},
actions: {
},
modules: {
},
});
通過 store 配置引數注入 vuex,從而讓整個應用都有 vuex 功能:
// src/index.js
+ import store from './store';
new Vue({
// 為了在 Vue 元件中訪問 this.$store property,需要為 Vue 例項提供建立好的 store
// Vuex 提供了一個從根元件向所有子元件,以 store 選項的方式“注入”該 store 的機制
+ store,
el: 'body',
render: (h) => h(App),
});
在子元件(Home.vue)中訪問 store:
// views/Home.vue
...
<script>
export default {
created() {
console.log(this.$store.state.count);
},
};
</script>
重啟服務,瀏覽器控制檯輸出 0。
axios
Tip: 參考 vue-cli 安裝 axios 外掛後生成的程式碼。
將 axios 引入工程,步驟如下:
> npm i axios@0
建立 axios.js:
// src/plugins/axios.js
import Vue from 'vue';
import axios from 'axios';
// Full config: https://github.com/axios/axios#request-config
// axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
// axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
const config = {
// baseURL: process.env.baseURL || process.env.apiUrl || ""
// timeout: 60 * 1000, // Timeout
// withCredentials: true, // Check cross-site Access-Control
};
const _axios = axios.create(config);
_axios.interceptors.request.use(
(config) =>
// Do something before request is sent
config,
(error) =>
// Do something with request error
Promise.reject(error)
,
);
// Add a response interceptor
_axios.interceptors.response.use(
(response) =>
// Do something with response data
response,
(error) =>
// Do something with response error
Promise.reject(error)
,
);
// Vue.use(Plugin) 會呼叫此方法
// 方法中會將 _axios 暴露給 Vue.axios、window.axios
// 並可以通過 Vue 的例項上的 axios 和 $axios 訪問到 _axios
Plugin.install = function (Vue, options) {
Vue.axios = _axios;
window.axios = _axios;
Object.defineProperties(Vue.prototype, {
axios: {
get() {
return _axios;
},
},
$axios: {
get() {
return _axios;
},
},
});
};
Vue.use(Plugin);
export default Plugin;
在入口檔案中引入 axios.js:
// index.js
import './plugins/axios';
在 Home.vue 中使用 axios:
// Home.vue
...
<script>
export default {
created() {
console.log('this.$axios: ', this.$axios);
},
};
</script>
重啟服務,瀏覽器控制檯輸出:
this.$axios: ƒ wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
}
至此,axios.js 已成功引入到我們的工程。在 mockjs 中我們還會用到 axios。
mockjs
注:在 npm 中檢視 vue-cli-plugin-mock,好似需要在 vue.config.js 中配置。我們的工程不是通過 vue-cli 建立的,所以這裡直接使用 mockjs。
將 mockjs 引入工程,步驟如下:
> npm i -D mockjs@1
建立 mock:
// src/mock/index.js
import Mock from 'mockjs';
// 定義了一個請求攔截
Mock.mock('/list', {
'name': '@name',
'age|10-20': 10,
'birthday': '@date("yyyy-MM-dd")'
});
在入口檔案中引入 mock:
// index.js
// 會自動載入 mock 下的 index.js
import './mock'
最後測試 mock 是否能攔截 /list
請求:
// Home.vue
...
<script>
export default {
created() {
// 傳送請求
this.$axios.get('/list').then(res => {
console.log('res.data: ', res.data);
})
},
};
</script>
重啟服務,瀏覽器控制檯輸出如下類似資料:
res.data: {name: "Ruth Brown", age: 14, birthday: "1973-08-18"}
Tip: 在實際專案中,mock 還需要進一步配置,比如可以在 mock/index.js 中載入 mock 資料夾中其他的 mock 檔案,方便擴充套件。
i18n
Tip:參考 vue i18n 官方文件 和 vue-cli 安裝 i18n 外掛後生成的程式碼。
由於我們是 vue 專案,所以就選用 vue i18n。
Vue I18n 是 Vue.js 的國際化外掛。它可以輕鬆地將一些本地化功能整合到你的 Vue.js 應用程式中。
將 i18n 引入工程,步驟如下:
> npm i vue-i18n@8
新建中英文兩個 json 檔案,用於存放需要翻譯的欄位(中文和英文),並統一放在 src/language 目錄中。
// en.json
{
"apple": "Apple"
}
// zh.json
{
"apple": "蘋果"
}
新建 i18n 模組,建立並匯出 i18n 例項:
// src/i18n.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
function loadLocaleMessages() {
// 通過 require.context() 函式來建立自己的 context
// 可以給這個函式傳入三個引數:一個要搜尋的目錄,一個標記表示是否還搜尋其子目錄, 以及一個匹配檔案的正規表示式。
// 返回一個函式,函式有三個屬性:resolve, keys, id。
// keys 也是一個函式,它返回一個陣列,由所有可能被此 context module 處理的請求
const locales = require.context('./language', true, /\.json$/i)
const messages = {}
locales.keys().forEach(file => {
const matched = file.match(/([A-Za-z0-9-_]+)\./i)
const locale = matched[1]
messages[locale] = locales(file)
})
// messages: {en: {…}, zh: {…}}
console.log('messages: ', messages);
return messages
}
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})
在入口檔案中匯入 i18n 例項,並配置到 Vue 例項中:
// index.js
...
import i18n from './i18n'
new Vue({
...
i18n,
el: 'body',
render: (h) => h(App),
});
接著就可以在 vue 元件中使用:
// Home.vue
<template>
<div>
i am home
<p>{{ $t("apple") }}</p>
</div>
</template>
重啟服務,頁面顯示 ”Apple“:
Home | About
i am home
Apple
為什麼顯示的是英文,而非中文?由 i18n.js 中的這段程式碼決定:
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})
process 是 node 中的物件,而這裡是在 .js 中使用。可以這麼理解:
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
等效於
locale: undefined || 'en',
所以是英文。如果想在模組中使用 process.env.VUE_APP_I18N_LOCALE,還需要一些配置。
// webpack.config.js
const webpack = require('webpack');
module.exports = {
...
plugins: [
new webpack.DefinePlugin({
// DefinePlugin 會直接將內容替換了,而不是一個字串
// 所以我們不能直接寫 'zh',經常會這麼寫:JSON.stringify('zh')
'process.env.VUE_APP_I18N_LOCALE': JSON.stringify('zh'),
'process.env.VUE_APP_I18N_FALLBACK_LOCALE': JSON.stringify('zh'),
})
]
};
Tip: DefinePlugin 允許在編譯時將你程式碼中的變數替換為其他值或表示式
重啟服務,頁面顯示 ”蘋果“:
Home | About
i am home
蘋果
在瀏覽器中找到如下程式碼:
{\n locale: \"zh\" || false,\n fallbackLocale: \"zh\" || false,\n messages: loadLocaleMessages()\n}
i18n.js 中的 process.env.VUE_APP_I18N_LOCALE 被替換成 zh。
jquery
將 jquery 引入工程,步驟如下:
> npm i -D jquery@3
配置別名:
// webpack.config.js
module.exports = {
resolve: {
alias: {
'jquery': path.resolve(__dirname, './node_modules/jquery/dist/jquery.min.js')
},
},
在需要使用的地方引用:
// Home.vue
...
<script>
import $ from 'jquery';
export default {
mounted() {
$('#app').css('color', 'blue')
},
};
</script>
重啟服務,頁面中文字變為藍色,說明 jquery 引入成功。
lodash
將 lodash 引入工程,步驟如下:
> npm i -D lodash@4
在 Home.vue 中使用 lodash:
// Home.vue
...
<script>
// webpack 模組 能以各種方式表達它們的依賴關係
// 比如 es 的 import、commonjs 的 require等等
let _ = require('lodash')
export default {
mounted() {
// 呼叫 lodash 的 random 方法
console.log(_.random(1,10))
},
};
</script>
重啟服務,瀏覽器控制檯輸出1~10之間一個隨機數,說明 lodash 引入成功。
Tip: jquery 同樣可以使用這種方式
其他章節請看: