完整的架構配置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 } } } }
<!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>
new CompressionWebpackPlugin({ algorithm: 'gzip', test: /\.(js|css)$/, // 匹配文件名 threshold: 10000, // 對超過10k的數據壓縮 deleteOriginalAssets: false, // 不刪除源文件 minRatio: 0.8 // 壓縮比 })
安裝cnpm i uglifyjs-webpack-plugin -D
html
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中插件安裝欄搜索 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
# 開發環境 NODE_ENV='development' VUE_APP_SSO='http://http://localhost:9080'
NODE_ENV = 'production' # 若是咱們在.env.test文件中把NODE_ENV設置爲test的話,那麼打包出來的目錄結構是有差別的 VUE_APP_MODE = 'test' VUE_APP_SSO='http://http://localhost:9080' outputDir = test
NODE_ENV = 'production' VUE_APP_SSO='http://http://localhost:9080'
"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;
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;
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)
pluginOptions: { // 配置全局less 'style-resources-loader': { preProcessor: 'less', patterns: [resolve('./src/style/theme.less')] } }
安裝cnpm i webpack -D
vue-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) }