作者:羊先生
转发链接:
https://segmentfault.com/a/1190000022512358
前言
配置全局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, // 关闭eslintproductionSourceMap: 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', // localhostport: 6060, // 端口号https: false, | |
hotOnly: false, // 热更新proxy: { | |
'^/sso': { | |
target: process.env.VUE_APP_SSO, // 重写路径ws: true, //开启WebSocketsecure: false, // 如果是https接口,需要配置这个参数changeOrigin: true | |
} | |
} | |
} | |
} |
html模板配置cdn
<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"><link rel="icon" href="<%= BASE_URL %>favicon.ico"><title><%= htmlWebpackPlugin.options.title %></title><% for (var i inhtmlWebpackPlugin.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 inhtmlWebpackPlugin.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', // localhostport: 6060, // 端口号https: false,hotOnly: false, // 热更新proxy: {'^/sso': {target: process.env.VUE_APP_SSO, // 重写路径ws: true, //开启WebSocketsecure: 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
作者:羊先生
转发链接:
https://segmentfault.com/a/1190000022512358