一份關於vue-cli3項目經常使用項配置

  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. 開啓分析打包日誌

vue.config.js

完整的架構配置css

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 -Dhtml

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",安如下添加vue

{
    "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.jsonnode

{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "allowSyntheticDefaultImports": true,
        "baseUrl": "./",
        "paths": {
          "@/*": ["src/*"]
        }
    },
    "exclude": [
        "node_modules"
    ]
}

若是還沒請客官移步在vscode中使用別名@按住ctrl也能跳轉對應路徑webpack

配置環境變量開發模式、測試模式、生產模式

在根目錄新建ios

.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文件git

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.jsgithub

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 默認路由web

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 -Dvue-router

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)
}

完整代碼

image.png

點擊獲取完整代碼github

獲取更多相關知識

橫版二維碼.png

相關文章
相關標籤/搜索