一份关于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/[email protected]/dist/vue.min.js','https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js','https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.min.js','https://cdn.jsdelivr.net/npm/[email protected]/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的配置进行deleteconfig.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}}}))// 服务器也要相应开启gzipplugins.push(new CompressionWebpackPlugin({algorithm: 'gzip',test: /\.(js|css)$/,// 匹配文件名threshold: 10000, // 对超过10k的数据压缩deleteOriginalAssets: false, // 不删除源文件minRatio: 0.8 // 压缩比}))// 用于根据模块的相对路径生成 hash 作为模块 id, 一般用于生产环境plugins.push(new HashedModuleIdsPlugin())// 开启分离jsconfig.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包转CDNconfig.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
<!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 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 ></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 } = vconst 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) : datareturn 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.loadedcallback(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、创建时间createdTimeconst 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)}
完整代码
点击获取完整代码github
