一份完整的 vue-cli3 项目基础配置项

2020-05-12 14:02:17 +08:00
 bonnenuit

一份完整的 vue-cli3 项目基础配置项,可用作于 PC 网站开发、移动端网页,后台管理系统

网站例子

vipbic 是一个专注前端开发、网址导航、社区讨论综合网站,该网站使用前后端分离,运用 vue-cli3 本项目配置

安装依赖

cnpm install

开发模式

npm run dev

打包测试环境

npm run test

测试和生产一起打包

npm run publish

打包生产环境

npm run build

项目配置功能

  1. 配置全局 cdn,包含 js 、css
  2. 开启 Gzip 压缩,包含文件 js 、css
  3. 去掉注释、去掉 console.log
  4. 压缩图片
  5. 本地代理
  6. 设置别名,vscode 也能识别
  7. 配置环境变量开发模式、测试模式、生产模式
  8. 请求路由动态添加
  9. axios 配置
  10. 添加 mock 数据
  11. 配置全局 less
  12. 只打包改变的文件
  13. 开启分析打包日志
  14. 拷贝文件
  15. 添加可选链运算符
  16. 配置 px 转换 rem

附加功能

  1. vue 如何刷新当前页面
  2. 封装 WebSocket
  3. 自定义指令 directive

目录结构

├── public                      静态模板资源文件
├── src                         项目文件
├──|── assets                   静态文件 img 、css 、js    
├──|── components               全局组件
├──|── http                     请求配置
├──|── layout                   布局文件
├──|── mock                     测试数据
├──|── modules                  放置动态是添加路由的页面
├──|── plugin                   插件
├──|── router                   路由
├──|── store                    vuex 数据管理
├──|── utils                    工具文件
├──|── view                     页面文件
├──|── App.vue                  
├──|── main.js                  
├── .env.development            开发模式配置
├── .env.production             正式发布模式配置
├── .env.test                   测试模式配置
├── entrance.js                 入口文件
├── vue.config.js               config 配置文件

完整代码

github

html 模板配置 cdn

<% 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" />
<% } %>

<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>

// cdn 预加载使用
const externals = {
    'vue': 'Vue',
    'vue-router': 'VueRouter'
}
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'
        ]
    }
}
chainWebpack: config => {
    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
    })
}

开启 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,核心

router.beforeEach((to, from, next) => {
    const { hasRoute } = store.state; // hasRoute 设置一个状态,防止重复请求添加路由
    if (hasRoute) {
        next()
    } else {
        dynamicRouter(to, from, next, selfaddRoutes)
    }
})

如果动态添加路由抛警告,路由重复添加,需要添加 router.matcher = new VueRouter().matcher

axios 配置

其中响应拦截

const succeeCode = 1; // 成功
/**
 * 状态码判断 具体根据当前后台返回业务来定
 * @param {*请求状态码} status 
 * @param {*错误信息} err 
 */
const errorHandle = (status, err) => {
    switch (status) {
        case 401:
            vm.$message({ message: '你还未登录', type: 'warning' });
            break;
        case 404:
            vm.$message({ message: '请求路径不存在', type: 'warning' });
            break;
        default:
            console.log(err);
    }
}
/**
 * 响应拦截
 */
http.interceptors.response.use(response => {
    if (response.status === 200) {
        // 你只需改动的是这个 succeeCode,因为每个项目的后台返回的 code 码各不相同
        if (response.data.code === succeeCode) {
            return Promise.resolve(response);
        } else {
            vm.$message({ message: '警告哦,这是一条警告消息', type: 'warning' });
            return Promise.reject(response)
        }
    } else {
        return Promise.reject(response)
    }
}, error => {
    const { response } = error;
    if (response) {
        // 请求已发出,但是不在 2xx 的范围 
        errorHandle(response.status, response.data.msg);
        return Promise.reject(response);
    } else {
        // 处理断网的情况
        if (!window.navigator.onLine) {
            vm.$message({ message: '你的网络已断开,请检查网络', type: 'warning' });
        }
        return Promise.reject(error);
    }
})

http/request.js

import http from './src/http/request'
Vue.prototype.$http = http;
// 使用
this.$http.windPost('url','参数')

添加 mock 数据

const Mock = require('mockjs')
const produceNewsData = []
Mock.mock('/mock/menu', produceNewsData)

Mock 支持随机数据,具体参看官网列子 http://mockjs.com/examples.html

配置全局 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)
}

拷贝文件

安装npm i copy-webpack-plugin -D

const CopyWebpackPlugin = require('copy-webpack-plugin');
configureWebpack: config => {
    const plugins = [];
     plugins.push(
        new CopyWebpackPlugin([{ from: './NLwdLAxhwv.txt'}])
    )
}

from 为文件的路径,还有一个 to 属性是输出的文件夹路径,不写则默认复制到打包后文件的根目录

可选链运算符

安装依赖

cnpm install  @babel/plugin-proposal-optional-chaining -S

在 babel.config.js 中 的 plugins 中添加 "@babel/plugin-proposal-optional-chaining"

module.exports = {
    presets: [
        '@vue/cli-plugin-babel/preset'
    ],
    plugins: [
        '@babel/plugin-proposal-optional-chaining'
    ]
}

测试

    const obj = {
        foo: {
            bar: {
                baz: 42,
                fun: () => {
                    return 666;
                }
            }
        }
    };
    let baz = obj?.foo?.bar?.baz;
    let fun = obj?.foo?.bar?.fun();
    console.log(baz); // 42
    console.log(fun) // 666

配置 px 转换 rem

安装

cnpm i lib-flexible -S
cnpm i postcss-pxtorem -D

入口 js

import 'lib-flexible/flexible.js'

如果不需要转 rem,注释即可,也不要引入 flexible.js

css: {
    loaderOptions: {
        postcss: {
            plugins: [
                require('postcss-pxtorem')({
                    rootValue : 75, // 换算的基数 1rem = 75px 这个是根据 750px 设计稿来的,如果是 620 的就写 62
                    // 忽略转换正则匹配项。插件会转化所有的样式的 px 。比如引入了三方 UI,也会被转化。目前我使用 selectorBlackList 字段,来过滤
                    //如果个别地方不想转化 px 。可以简单的使用大写的 PX 或 Px 。
                    selectorBlackList  : ['weui','mu'], //
                    propList : ['*'], // 需要做转化处理的属性,如`hight`、`width`、`margin`等,`*`表示全部
                })
            ]
        }
    }
}

vue 如何刷新当前页面

刷新当前页面适合在只改变了路由的 id 的页面,比如查看详情页面,当路由 id 发生时候,并不会去触发当前页面的钩子函数 查看App.vue

<template>
	<div class="app">
        <router-view v-if="isRouterAlive"></router-view>
    </div>
</template>
<script>
export default {
	name: "App",
	provide() {
		return {
			reload: this.reload
		};
	},
	data() {
		return {
			isRouterAlive: true
		};
	},
	methods: {
        // 重载页面 适合添加数据或者路由 id 改变
		reload() {
			this.isRouterAlive = false;
			this.$nextTick(()=>{
                this.isRouterAlive = true;
            });
		}
	}
};
</script>

然后其它任何想刷新自己的路由页面,都可以这样: this.reload()

封装 WebSocket

具体实例 utils\websocket.js

const WSS_URL = `wss://wss.xxxx.com/ws?appid=xxx`
let setIntervalWesocketPush = null

export default class websocket {
    createSocket() {
        if (!this.Socket) {
            console.log('建立 websocket 连接')
            this.Socket = new WebSocket(WSS_URL)
            this.Socket.onopen = this.onopenWS
            this.Socket.onmessage = this.onmessageWS
            this.Socket.onerror = this.onerrorWS
            this.Socket.onclose = this.oncloseWS
        } else {
            console.log('websocket 已连接')
        }
    }

    /**打开 WS 之后发送心跳 */
    onopenWS() {
        this.sendPing() //发送心跳
    }

    /**连接失败重连 */
    onerrorWS() {
        clearInterval(setIntervalWesocketPush)
        this.Socket.close()
        createSocket() //重连
    }

    /**WS 数据接收统一处理 */
    onmessageWS(res) {
        console.log(res)
    }

    /**
     * 发送数据
     * readyState = 3  连接已经关闭或者根本没有建立
     * readyState = 2  连接正在进行关闭握手,即将关闭
     * readyState = 1  连接成功建立,可以进行通信
     * readyState = 0  正在建立连接,连接还没有完成
     * @param {*发送内容} content 
     */
    sendWSPush(content) {
        if (this.Socket !== null && this.Socket.readyState === 3) {
            this.Socket.close()
            this.createSocket() //重连
        } else if (this.Socket.readyState === 1) {
            this.Socket.send(content)
        } else if (this.Socket.readyState === 0) {
            setTimeout(() => {
                this.Socket.send(content)
            }, 5000)
        }
    }

    /**关闭 WS */
    oncloseWS() {
        clearInterval(setIntervalWesocketPush)
        console.log('websocket 已断开')
    }


    /**发送心跳 */
    sendPing() {
        this.Socket.send('ping')
        setIntervalWesocketPush = setInterval(() => {
            this.Socket.send('ping')
        }, 5000)
    }
}

自定义指令 directive

import Vue from 'vue';
const has = {
    inserted: function (el, binding) {
        // 添加指令 传入的  value
        if (!binding.value) {
            el.parentNode.removeChild(el);
        }
    }
}
Vue.directive('has',has)
	<el-button type="primary" v-has="true">主要按钮 1</el-button>

项目截图

如有疑问

github提问

4193 次点击
所在节点    Vue.js
29 条回复
bonnenuit
2020-05-12 19:12:37 +08:00
@jjianwen68 同感😭
bonnenuit
2020-05-12 19:14:02 +08:00
@Tlin 能给个 star 就好😋😋
bonnenuit
2020-05-12 19:19:14 +08:00
@yukiloh publicPath 它是在静态资源路径前面加上路径的值,publicPath:'https://www.baidu.com/', 打包完后 https://www.baidu.com/assets/xxx.js
a4854857
2020-05-12 20:04:46 +08:00
不错的模板.收藏了
icharm
2020-05-13 09:06:47 +08:00
@belin520 不是浏览器禁用 js 的问题, 其他页面的 js 都正常运行的
bonnenuit
2020-05-13 13:44:14 +08:00
@a4854857 能帮到你最好了😋😋
bonnenuit
2020-05-13 13:45:54 +08:00
@icharm 这个应该不是 vue-cli 的问题了,毕竟存在了这么长的时间,你是用的 IE 浏览器嘛
icharm
2020-05-13 16:34:22 +08:00
@bonnenuit 不是 IE,chrome 最新版,所以说很奇怪
vipbic
2020-05-13 22:58:39 +08:00
很不错的一个 vue 脚手架模板

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/670903

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX