一份關於 vue-cli3 項目常用項配置
-
配置全局 cdn,包含 js、css
-
開啓 Gzip 壓縮,包含文件 js、css
-
去掉註釋、去掉 console.log
-
壓縮圖片
-
本地代理
-
設置別名,vscode 也能識別
-
配置環境變量開發模式、測試模式、生產模式
-
請求路由動態添加
-
axios 配置
-
添加 mock 數據
-
配置全局 less
-
只打包改變的文件
-
開啓分析打包日誌
vue.config.js
完整的架構配置
const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin') // 去掉註釋
const CompressionWebpackPlugin = require('compression-webpack-plugin'); // 開啓壓縮
const { HashedModuleIdsPlugin } = require('webpack');
function resolve(dir) {
return path.join(__dirname, dir)
}
const isProduction = process.env.NODE_ENV === 'production';
// cdn預加載使用
const externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios',
"element-ui": "ELEMENT"
}
const cdn = {
// 開發環境
dev: {
css: [
'https://unpkg.com/element-ui/lib/theme-chalk/index.css'
],
js: []
},
// 生產環境
build: {
css: [
'https://unpkg.com/element-ui/lib/theme-chalk/index.css'
],
js: [
'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js',
'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js',
'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js',
'https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js',
'https://unpkg.com/element-ui/lib/index.js'
]
}
}
module.exports = {
lintOnSave: false, // 關閉eslint
productionSourceMap: false,
publicPath: './',
outputDir: process.env.outputDir, // 生成文件的目錄名稱
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src'))
// 壓縮圖片
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true })
// webpack 會默認給commonChunk打進chunk-vendors,所以需要對webpack的配置進行delete
config.optimization.delete('splitChunks')
config.plugin('html').tap(args => {
if (process.env.NODE_ENV === 'production') {
args[0].cdn = cdn.build
}
if (process.env.NODE_ENV === 'development') {
args[0].cdn = cdn.dev
}
return args
})
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
},
configureWebpack: config => {
const plugins = [];
if (isProduction) {
plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
output: {
comments: false, // 去掉註釋
},
warnings: false,
compress: {
drop_console: true,
drop_debugger: false,
pure_funcs: ['console.log']//移除console
}
}
})
)
// 服務器也要相應開啓gzip
plugins.push(
new CompressionWebpackPlugin({
algorithm: 'gzip',
test: /\.(js|css)$/,// 匹配文件名
threshold: 10000, // 對超過10k的數據壓縮
deleteOriginalAssets: false, // 不刪除源文件
minRatio: 0.8 // 壓縮比
})
)
// 用於根據模塊的相對路徑生成 hash 作爲模塊 id, 一般用於生產環境
plugins.push(
new HashedModuleIdsPlugin()
)
// 開啓分離js
config.optimization = {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 1000 * 60,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// 排除node_modules 然後吧 @ 替換爲空 ,考慮到服務器的兼容
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
return `npm.${packageName.replace('@', '')}`
}
}
}
}
};
// 取消webpack警告的性能提示
config.performance = {
hints: 'warning',
//入口起點的最大體積
maxEntrypointSize: 1000 * 500,
//生成文件的最大體積
maxAssetSize: 1000 * 1000,
//只給出 js 文件的性能提示
assetFilter: function (assetFilename) {
return assetFilename.endsWith('.js');
}
}
// 打包時npm包轉CDN
config.externals = externals;
}
return { plugins }
},
pluginOptions: {
// 配置全局less
'style-resources-loader': {
preProcessor: 'less',
patterns: [resolve('./src/style/theme.less')]
}
},
devServer: {
open: false, // 自動啓動瀏覽器
host: '0.0.0.0', // localhost
port: 6060, // 端口號
https: false,
hotOnly: false, // 熱更新
proxy: {
'^/sso': {
target: process.env.VUE_APP_SSO, // 重寫路徑
ws: true, //開啓WebSocket
secure: false, // 如果是https接口,需要配置這個參數
changeOrigin: true
}
}
}
}
html 模板配置 cdn
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta >
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<% for (var i in
htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
<% } %>
</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 -->
<% for (var i in
htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
</body>
</html>
開啓 Gzip 壓縮,包含文件 js、css
new CompressionWebpackPlugin({
algorithm: 'gzip',
test: /\.(js|css)$/, // 匹配文件名
threshold: 10000, // 對超過10k的數據壓縮
deleteOriginalAssets: false, // 不刪除源文件
minRatio: 0.8 // 壓縮比
})
去掉註釋、去掉 console.log
安裝cnpm i uglifyjs-webpack-plugin -D
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
new UglifyJsPlugin({
uglifyOptions: {
output: {
comments: false, // 去掉註釋
},
warnings: false,
compress: {
drop_console: true,
drop_debugger: false,
pure_funcs: ['console.log'] //移除console
}
}
})
壓縮圖片
chainWebpack: config => {
// 壓縮圖片
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true })
}
本地代理
devServer: {
open: false, // 自動啓動瀏覽器
host: '0.0.0.0', // localhost
port: 6060, // 端口號
https: false,
hotOnly: false, // 熱更新
proxy: {
'^/sso': {
target: process.env.VUE_APP_SSO, // 重寫路徑
ws: true, //開啓WebSocket
secure: false, // 如果是https接口,需要配置這個參數
changeOrigin: true
}
}
}
設置 vscode 識別別名
在 vscode 中插件安裝欄搜索 Path Intellisense
插件,打開 settings.json 文件添加 以下代碼 "@": "${workspaceRoot}/src",安以下添加
{
"workbench.iconTheme": "material-icon-theme",
"editor.fontSize": 16,
"editor.detectIndentation": false,
"guides.enabled": false,
"workbench.colorTheme": "Monokai",
"path-intellisense.mappings": {
"@": "${workspaceRoot}/src"
}
}
在項目 package.json 所在同級目錄下創建文件 jsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": [
"node_modules"
]
}
如果還沒請客官移步在 vscode 中使用別名 @按住 ctrl 也能跳轉對應路徑
配置環境變量開發模式、測試模式、生產模式
在根目錄新建
.env.development
# 開發環境
NODE_ENV='development'
VUE_APP_SSO='http://http://localhost:9080'
.env.test
NODE_ENV = 'production' # 如果我們在.env.test文件中把NODE_ENV設置爲test的話,那麼打包出來的目錄結構是有差異的
VUE_APP_MODE = 'test'
VUE_APP_SSO='http://http://localhost:9080'
outputDir = test
.env.production
NODE_ENV = 'production'
VUE_APP_SSO='http://http://localhost:9080'
package.json
"scripts": {
"build": "vue-cli-service build", //生產打包
"lint": "vue-cli-service lint",
"dev": "vue-cli-service serve", // 開發模式
"test": "vue-cli-service build --mode test", // 測試打包
"publish": "vue-cli-service build && vue-cli-service build --mode test" // 測試和生產一起打包
}
請求路由動態添加
router/index.js 文件
import Vue from 'vue';
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import defaultRouter from './defaultRouter'
import dynamicRouter from './dynamicRouter';
import store from '@/store';
const router = new VueRouter({
routes: defaultRouter,
mode: 'hash',
scrollBehavior(to, from, savedPosition) {
// keep-alive 返回緩存頁面後記錄瀏覽位置
if (savedPosition && to.meta.keepAlive) {
return savedPosition;
}
// 異步滾動操作
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 200)
})
}
})
// 消除路由重複警告
const selfaddRoutes = function (params) {
router.matcher = new VueRouter().matcher;
router.addRoutes(params);
}
// 全局路由攔截
router.beforeEach((to, from, next) => {
const { hasRoute } = store.state; // 防止路由重複添加
if (hasRoute) {
next()
} else {
dynamicRouter(to, from, next, selfaddRoutes)
}
})
export default router;
dynamicRouter.js
import http from '@/http/request';
import defaultRouter from './defaultRouter'
import store from '@/store'
// 重新構建路由對象
const menusMap = function (menu) {
return menu.map(v => {
const { path, name, component } = v
const item = {
path,
name,
component: () => import(`@/${component}`)
}
return item;
})
}
// 獲取路由
const addPostRouter = function (to, from, next, selfaddRoutes) {
http.windPost('/mock/menu') // 發起請求獲取路由
.then(menu => {
defaultRouter[0].children.push(...menusMap(menu));
selfaddRoutes(defaultRouter);
store.commit('hasRoute', true);
next({ ...to, replace: true })
})
}
export default addPostRouter;
defaultRouter.js 默認路由
const main = r => require.ensure([], () => r(require('@/layout/main.vue')), 'main')
const index = r => require.ensure([], () => r(require('@/view/index/index.vue')), 'index')
const about = r => require.ensure([], () => r(require('@/view/about/about.vue')), 'about')
const detail = r => require.ensure([], () => r(require('@/view/detail/detail.vue')), 'detail')
const error = r => require.ensure([], () => r(require('@/view/404/404.vue')), 'error');
const defaultRouter = [
{
path: "/",
component: main, // 佈局頁
redirect: {
name: "index"
},
children:[
{
path: '/index',
component: index,
name: 'index',
meta: {
title: 'index'
}
},
{
path: '/about',
component: about,
name: 'about',
meta: {
title: 'about'
}
},
{
path: '/detail',
component: detail,
name: 'detail',
meta: {
title: 'detail'
}
}
]
},
{
path: '/404',
component: error,
name: '404',
meta: {
title: '404'
}
}
]
export default defaultRouter;
axios 配置
import axios from "axios";
import merge from 'lodash/merge'
import qs from 'qs'
/**
* 實例化
* config是庫的默認值,然後是實例的 defaults 屬性,最後是請求設置的 config 參數。後者將優先於前者
*/
const http = axios.create({
timeout: 1000 * 30,
withCredentials: true, // 表示跨域請求時是否需要使用憑證
});
/**
* 請求攔截
*/
http.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
/**
* 響應攔截
*/
http.interceptors.response.use(response => {
// 過期之類的操作
if (response.data && (response.data.code === 401)) {
// window.location.href = ''; 重定向
}
return response
}, error => {
return Promise.reject(error)
})
/**
* 請求地址處理
*/
http.adornUrl = (url) => {
return url;
}
/**
* get請求參數處理
* params 參數對象
* openDefultParams 是否開啓默認參數
*/
http.adornParams = (params = {}, openDefultParams = true) => {
var defaults = {
t: new Date().getTime()
}
return openDefultParams ? merge(defaults, params) : params
}
/**
* post請求數據處理
* @param {*} data 數據對象
* @param {*} openDefultdata 是否開啓默認數據?
* @param {*} contentType 數據格式
* json: 'application/json; charset=utf-8'
* form: 'application/x-www-form-urlencoded; charset=utf-8'
*/
http.adornData = (data = {}, openDefultdata = true, contentType = 'json') => {
var defaults = {
t: new Date().getTime()
}
data = openDefultdata ? merge(defaults, data) : data
return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data)
}
/**
* windPost請求
* @param {String} url [請求地址]
* @param {Object} params [請求攜帶參數]
*/
http.windPost = function (url, params) {
return new Promise((resolve, reject) => {
http.post(http.adornUrl(url), qs.stringify(params))
.then(res => {
resolve(res.data)
})
.catch(error => {
reject(error)
})
})
}
/**
* windJsonPost請求
* @param {String} url [請求地址]
* @param {Object} params [請求攜帶參數]
*/
http.windJsonPost = function (url, params) {
return new Promise((resolve, reject) => {
http.post(http.adornUrl(url), http.adornParams(params))
.then(res => {
resolve(res.data)
})
.catch(error => {
reject(error)
})
})
}
/**
* windGet請求
* @param {String} url [請求地址]
* @param {Object} params [請求攜帶參數]
*/
http.windGet = function (url, params) {
return new Promise((resolve, reject) => {
http.get(http.adornUrl(url), { params: params })
.then(res => {
resolve(res.data)
})
.catch(error => {
reject(error)
})
})
}
/**
* 上傳圖片
*/
http.upLoadPhoto = function (url, params, callback) {
let config = {}
if (callback !== null) {
config = {
onUploadProgress: function (progressEvent) {
//屬性lengthComputable主要表明總共需要完成的工作量和已經完成的工作是否可以被測量
//如果lengthComputable爲false,就獲取不到progressEvent.total和progressEvent.loaded
callback(progressEvent)
}
}
}
return new Promise((resolve, reject) => {
http.post(http.adornUrl(url), http.adornParams(params), config)
.then(res => {
resolve(res.data)
})
.catch(error => {
reject(error)
})
})
}
export default http;
添加 mock 數據
const Mock = require('mockjs')
// 獲取 mock.Random 對象
const Random = Mock.Random
// mock新聞數據,包括新聞標題title、內容content、創建時間createdTime
const produceNewsData = function () {
let newsList = []
for (let i = 0; i < 3; i++) {
let newNewsObject = {}
if(i === 0){
newNewsObject.path = '/add/article';
newNewsObject.name = 'add-article';
newNewsObject.component = 'modules/add/article/article';
}
if(i === 1){
newNewsObject.path = '/detail/article';
newNewsObject.name = 'detail-article';
newNewsObject.component = 'modules/detail/article/article'
}
if(i === 2){
newNewsObject.path = '/edit/article';
newNewsObject.name = 'edit-article';
newNewsObject.component = 'modules/edit/article/article'
}
newsList.push(newNewsObject)
}
return newsList;
}
Mock.mock('/mock/menu', produceNewsData)
配置全局 less
pluginOptions: {
// 配置全局less
'style-resources-loader': {
preProcessor: 'less',
patterns: [resolve('./src/style/theme.less')]
}
}
只打包改變的文件
安裝cnpm i webpack -D
const { HashedModuleIdsPlugin } = require('webpack');
configureWebpack: config => {
const plugins = [];
plugins.push(
new HashedModuleIdsPlugin()
)
}
開啓分析打包日誌
安裝cnpm i webpack-bundle-analyzer -D
chainWebpack: config => {
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
}
完整代碼
https://github.com/hangjob/vue-admin
轉自:vipbic
https://segmentfault.com/a/1190000022512358
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ffUcsTnVNtTb-VinH8Llvg