玖叶教程网

前端编程开发入门

一份关于vue-cli3项目常用项配置(vuecli3配置文件)


作者:羊先生

转发链接: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/[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, // 关闭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)
    }

    完整代码

    点击获取完整代码github:https://github.com/hangjob/vue-admin

    推荐Vue和React学习资料文章:

    怎样为你的 Vue.js 单页应用提速

    聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总

    【新消息】Vue 3.0 Beta 版本发布,你还学的动么?

    Vue真是太好了 壹万多字的Vue知识点 超详细!

    Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5

    深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】

    手把手教你深入浅出vue-cli3升级vue-cli4的方法

    Vue 3.0 Beta 和React 开发者分别杠上了

    手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件

    Vue3 尝鲜

    总结Vue组件的通信

    手把手让你成为更好的Vue.js开发人员的12个技巧和窍门【实践】

    Vue 开源项目 TOP45

    2020 年,Vue 受欢迎程度是否会超过 React?

    尤雨溪:Vue 3.0的设计原则

    使用vue实现HTML页面生成图片

    实现全栈收银系统(Node+Vue)(上)

    实现全栈收银系统(Node+Vue)(下)

    vue引入原生高德地图

    Vue合理配置WebSocket并实现群聊

    多年vue项目实战经验汇总

    vue之将echart封装为组件

    基于 Vue 的两层吸顶踩坑总结

    Vue插件总结【前端开发必备】

    Vue 开发必须知道的 36 个技巧【近1W字】

    构建大型 Vue.js 项目的10条建议

    深入理解vue中的slot与slot-scope

    手把手教你Vue解析pdf(base64)转图片【实践】

    使用vue+node搭建前端异常监控系统

    推荐 8 个漂亮的 vue.js 进度条组件

    基于Vue实现拖拽升级(九宫格拖拽)

    手摸手,带你用vue撸后台 系列二(登录权限篇)

    手摸手,带你用vue撸后台 系列三(实战篇)

    前端框架用vue还是react?清晰对比两者差异

    Vue组件间通信几种方式,你用哪种?【实践】

    浅析 React / Vue 跨端渲染原理与实现

    10个Vue开发技巧助力成为更好的工程师

    手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】

    1W字长文+多图,带你了解vue的双向数据绑定源码实现

    深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】

    干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

    基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现

    手把手教你D3.js 实现数据可视化极速上手到Vue应用

    吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】

    吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】

    吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】

    Vue3.0权限管理实现流程【实践】

    后台管理系统,前端Vue根据角色动态设置菜单栏和路由

    13个精选的React JS框架

    深入浅出画图讲解React Diff原理【实践】

    【React深入】React事件机制

    Vue 3.0 Beta 和React 开发者分别杠上了

    手把手深入Redux react-redux中间件设计及原理(上)【实践】

    手把手深入Redux react-redux中间件设计及原理(下)【实践】

    前端框架用vue还是react?清晰对比两者差异

    为了学好 React Hooks, 我解析了 Vue Composition API

    【React 高级进阶】探索 store 设计、从零实现 react-redux

    写React Hooks前必读

    深入浅出掌握React 与 React Native这两个框架

    可靠React组件设计的7个准则之SRP

    React Router v6 新特性及迁移指南

    用React Hooks做一个搜索栏

    你需要的 React + TypeScript 50 条规范和经验

    手把手教你绕开React useEffect的陷阱

    浅析 React / Vue 跨端渲染原理与实现

    React 开发必须知道的 34 个技巧【近1W字】

    三张图详细解说React组件的生命周期

    手把手教你深入浅出实现Vue3 & React Hooks新UI Modal弹窗

    手把手教你搭建一个React TS 项目模板

    全平台(Vue/React/微信小程序)任意角度旋图片裁剪组件

    40行代码把Vue3的响应式集成进React做状态管理

    手把手教你深入浅出React 迷惑的问题点【完整版】

    React可用于哪些Web开发场景?具体怎么做?

    作者:羊先生

    转发链接:https://segmentfault.com/a/1190000022512358

    发表评论:

    控制面板
    您好,欢迎到访网站!
      查看权限
    网站分类
    最新留言