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 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 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)
}
原作者姓名:羊先生
原出处:segmentfault
原文链接:一份关于vue-cli3项目常用项配置 - vipbic - SegmentFault 思否