管理端adm-tpl-vue模版
# 简介
两种可选技术栈
- vue-cli4.x + webpack4.x + axios + postcss-px2rem + antd-vue1.x + less;
- vue-cli4.x + webpack4.x + axios + postcss-px2rem + element2.x + sass ;
# 技术点
主要包括如下技术点:
- vue-cli4脚手架
- vant按需引入
- 移动端rem适配
- axios拦截封装
- util工具类函数封装
- vue-router配置
- 登录权限校验
- 多环境变量配置
- vue.config.js配置
- 跨域代理设置
- webpack打包可视化分析
- CDN资源优化
- gzip打包优化
- 首页添加骨架屏
- toast组件封装
- dialog组件封装
# 使用
# 初始化
结合rat-cli
脚手架快速初始化;
rat init tpl
# 配置多环境变量
package.json
里的 scripts
配置 serve
stage
build
,通过 --mode xxx
来执行不同环境
- 通过
npm run serve
启动本地 , 执行development
- 通过
npm run stage
打包测试 , 执行staging
- 通过
npm run build
打包正式 , 执行production
"scripts": {
"serve": "vue-cli-service serve --open",
"stage": "vue-cli-service build --mode staging",
"build": "vue-cli-service build",
#"test": "vue-cli-service build --mode test",
}
2
3
4
5
6
# 配置介绍
以 VUE_APP_
开头的变量,在代码中可以通过 process.env.VUE_APP_
访问。
比如,VUE_APP_ENV = 'development'
通过process.env.VUE_APP_ENV
访问。
除了 VUE_APP_*
变量之外,在你的应用代码中始终可用的还有两个特殊的变量NODE_ENV
和BASE_URL
在项目根目录中新建.env.*
- .env.development 本地开发环境配置
NODE_ENV='development'
VUE_APP_ENV = 'dev'
2
- .env.staging 测试预备环境配置
NODE_ENV='staging'
VUE_APP_ENV = 'staging'
2
- .env.production 正式环境配置
NODE_ENV='production'
VUE_APP_ENV = 'prod'
2
这里我们并没有定义很多变量,只定义了基础的 VUE_APP_ENV dev
staging
prod
变量我们统一在 src/config/env.*.js
里进行管理。
# config 配置
config 下新建三个对应的文件;修改起来方便,不需要重启项目,符合开发习惯。
config/index.js
const environment = process.env.VUE_APP_ENV || 'prod'
const config = require('./env.' + environment)
module.exports = {
...config,
IS_PROD: ['production', 'prod'].includes(process.env.VUE_APP_ENV) //// 获取 VUE_APP_ENV 非 NODE_ENV,
}
2
3
4
5
6
配置对应环境的变量,拿本地环境文件 env.development.js
举例,用户可以根据需求修改
module.exports = {
title: 'h5-tpl-vue',
baseUrl: 'http://localhost:9527',
baseApi: 'https://test.yessz.cn/adm/api',
port: 9527,
$cdn: 'https://www.yessz.cn/static',
APPID: 'xxx',
APPSECRET: 'xxx',
}
2
3
4
5
6
7
8
9
根据环境不同,变量使用;
// 根据环境不同引入不同baseApi地址
import { baseApi } from '@/config'
// 设置 js中可以访问 $cdn
import { $cdn } from '@/config'
Vue.prototype.$cdn = $cdn
2
3
4
5
6
# Webpack配置
vue.config.js
配置
如果你的 Vue Router
模式是 hash publicPath: './',
如果你的 Vue Router
模式是 history 这里的 publicPath 和你的 Vue Router
base
保持一直, publicPath: '/app/',
vue-cli3 开始,新建的脚手架都需要在 vue.config.js 配置项目的东西。主要包括
# 基础配置
const { title, port, IS_PROD, $cdn } = require('./src/config/index.js')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const { name: pkgName } = require('./package.json')
const resolve = dir => path.join(__dirname, dir)
const name = title || pkgName
module.exports = {
publicPath: './', // 署应用包时的基本 URL。 vue-router hash 模式使用
// publicPath: '/app/', //署应用包时的基本 URL。 vue-router history模式使用
outputDir: 'dist', // 生产环境构建文件的目录
assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录
indexPath: 'index.html', // 指定生成的 index.html 的输出路径
runtimeCompiler: false, // 是否使用包含运行时编译器的 Vue 构建版本。
// 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。
transpileDependencies: [],
lintOnSave: !IS_PROD,
productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
devServer: {
port: port, // 端口
open: false, // 启动后打开浏览器
// host: "localhost",
// port: "8080", // 代理断就
// https: false,
// hotOnly: false, // 热更新
overlay: {
warnings: false,// 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
errors: true
},
proxy: {
[`${apiBase}`]: {
target: apiHost,
changOrigin: true
// ws:true,
// pathRewrite: {
// '^/api': '/'
// }
}
}
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 配置 alias 别名
const path = require('path')
const resolve = dir => path.join(__dirname, dir)
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set("vue$", "vue/dist/vue.esm.js")
.set('@', resolve('src'))
.set('assets', resolve('src/assets'))
.set('api', resolve('src/api'))
.set('views', resolve('src/views'))
.set('components', resolve('src/components'))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 配置 proxy 跨域
如果你的项目需要跨域设置,你需要打来 vue.config.js
proxy
注释 并且配置相应参数
!!!注意:还需要将 src/config/env.development.js
里的 baseApi
设置成 '/'
devServer: {
port: port, // 端口
https: false, // https:{type:Boolean}
open: false, // 配置自动启动浏览器 open: 'Google Chrome'-默认启动谷歌
overlay: {
// 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
warnings: false,
errors: true
},
proxy: {
[`${apiBase}`]: {
target: apiHost,
changOrigin: true // 开启代理,在本地创建一个虚拟服务端
// ws:true, // 是否启用websockets
// pathRewrite: {
// '^/api': '/'
// }
}
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
使用 例如: src/api/home.js
export function getUserInfo(params) {
return request({
url: '/api/userinfo',
method: 'post',
data: qs.stringify(params)
})
}
2
3
4
5
6
7
# 压缩图片
npm i -D image-webpack-loader
在某些版本的 OSX 上安装可能会因缺少 libpng 依赖项而引发错误。可以通过安装最新版本的 libpng 来解决。
brew install libpng
module.exports = {
chainWebpack: config => {
if (IS_PROD) {
config.module
.rule("images")
.use("image-webpack-loader")
.loader("image-webpack-loader")
.options({
mozjpeg: { progressive: true, quality: 65 },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.9], speed: 4 },
gifsicle: { interlaced: false }
// webp: { quality: 75 }
});
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 自动生成雪碧图
默认 src/assets/icons 中存放需要生成雪碧图的 png 文件。首次运行 npm run serve/build 会生成雪碧图,并在跟目录生成 icons.json 文件。再次运行命令时,会对比 icons 目录内文件与 icons.json 的匹配关系,确定是否需要再次执行 webpack-spritesmith 插件。
npm i -D webpack-spritesmith
let has_sprite = true;
let files = [];
const icons = {};
try {
fs.statSync(resolve("./src/assets/icons"));
files = fs.readdirSync(resolve("./src/assets/icons"));
files.forEach(item => {
let filename = item.toLocaleLowerCase().replace(/_/g, "-");
icons[filename] = true;
});
} catch (error) {
fs.mkdirSync(resolve("./src/assets/icons"));
}
if (!files.length) {
has_sprite = false;
} else {
try {
let iconsObj = fs.readFileSync(resolve("./icons.json"), "utf8");
iconsObj = JSON.parse(iconsObj);
has_sprite = files.some(item => {
let filename = item.toLocaleLowerCase().replace(/_/g, "-");
return !iconsObj[filename];
});
if (has_sprite) {
fs.writeFileSync(resolve("./icons.json"), JSON.stringify(icons, null, 2));
}
} catch (error) {
fs.writeFileSync(resolve("./icons.json"), JSON.stringify(icons, null, 2));
has_sprite = true;
}
}
// 雪碧图样式处理模板
const SpritesmithTemplate = function(data) {
// pc
let icons = {};
let tpl = `.ico {
display: inline-block;
background-image: url(${data.sprites[0].image});
background-size: ${data.spritesheet.width}px ${data.spritesheet.height}px;
}`;
data.sprites.forEach(sprite => {
const name = "" + sprite.name.toLocaleLowerCase().replace(/_/g, "-");
icons[`${name}.png`] = true;
tpl = `${tpl}
.ico-${name}{
width: ${sprite.width}px;
height: ${sprite.height}px;
background-position: ${sprite.offset_x}px ${sprite.offset_y}px;
}
`;
});
return tpl;
};
module.exports = {
configureWebpack: config => {
const plugins = [];
if (has_sprite) {
plugins.push(
new SpritesmithPlugin({
src: {
cwd: path.resolve(__dirname, "./src/assets/icons/"), // 图标根路径
glob: "**/*.png" // 匹配任意 png 图标
},
target: {
image: path.resolve(__dirname, "./src/assets/images/sprites.png"), // 生成雪碧图目标路径与名称
// 设置生成CSS背景及其定位的文件或方式
css: [
[
path.resolve(__dirname, "./src/assets/scss/sprites.scss"),
{
format: "function_based_template"
}
]
]
},
customTemplates: {
function_based_template: SpritesmithTemplate
},
apiOptions: {
cssImageRef: "../images/sprites.png" // css文件中引用雪碧图的相对位置路径配置
},
spritesmithOptions: {
padding: 2
}
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 修复 HMR(热更新)失效
如果热更新失效,如下操作:
module.exports = {
chainWebpack: config => {
config.resolve.symlinks(true); // 修复HMR
}
};
2
3
4
5
# 修复 Lazy loading routes Error
Cyclic dependency https://github.com/vuejs/vue-cli/issues/1669
module.exports = {
chainWebpack: config => {
// 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
config.plugin("html").tap(args => {
// 修复 Lazy loading routes Error
args[0].chunksSortMode = "none";
return args;
});
}
};
2
3
4
5
6
7
8
9
10
# 删除 moment 语言包
删除 moment 除 zh-cn 中文包外的其它语言包,无需在代码中手动引入 zh-cn 语言包。
const webpack = require("webpack");
module.exports = {
chainWebpack: config => {
config
.plugin("ignore")
.use(
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/)
);
return config;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
# 去掉 console.log
方法一:使用 babel-plugin-transform-remove-console 插件
npm i -D babel-plugin-transform-remove-console
在 babel.config.js 中配置
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const plugins = [];
if (IS_PROD) {
plugins.push("transform-remove-console");
}
module.exports = {
presets: ["@vue/app", { useBuiltIns: "entry" }],
plugins
};
2
3
4
5
6
7
8
9
10
11
方法二:uglifyjs-webpack-plugin
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
configureWebpack: config => {
if (IS_PROD) {
const plugins = [];
plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_console: true,
drop_debugger: false,
pure_funcs: ["console.log"] //移除console
}
},
sourceMap: false,
parallel: true
})
);
config.plugins = [...config.plugins, ...plugins];
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果使用 uglifyjs-webpack-plugin 会报错,可能存在 node_modules 中有些依赖需要 babel 转译。
而 vue-cli 的transpileDependencies (opens new window)配置默认为[], babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。配置需要转译的第三方库。
# 利用 splitChunks 单独打包第三方模块
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
module.exports = {
configureWebpack: config => {
if (IS_PROD) {
config.optimization = {
splitChunks: {
cacheGroups: {
common: {
name: "chunk-common",
chunks: "initial",
minChunks: 2,
maxInitialRequests: 5,
minSize: 0,
priority: 1,
reuseExistingChunk: true,
enforce: true
},
vendors: {
name: "chunk-vendors",
test: /[\\/]node_modules[\\/]/,
chunks: "initial",
priority: 2,
reuseExistingChunk: true,
enforce: true
},
elementUI: {
name: "chunk-elementui",
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
chunks: "all",
priority: 3,
reuseExistingChunk: true,
enforce: true
},
echarts: {
name: "chunk-echarts",
test: /[\\/]node_modules[\\/](vue-)?echarts[\\/]/,
chunks: "all",
priority: 4,
reuseExistingChunk: true,
enforce: true
}
}
}
};
}
},
chainWebpack: config => {
if (IS_PROD) {
config.optimization.delete("splitChunks");
}
return config;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 开启 gzip 压缩
npm i -D compression-webpack-plugin
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
module.exports = {
configureWebpack: config => {
const plugins = [];
if (IS_PROD) {
plugins.push(
new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
还可以开启比 gzip 体验更好的 Zopfli 压缩详见https://webpack.js.org/plugins/compression-webpack-plugin
npm i -D @gfx/zopfli brotli-webpack-plugin
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const zopfli = require("@gfx/zopfli");
const BrotliPlugin = require("brotli-webpack-plugin");
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
module.exports = {
configureWebpack: config => {
const plugins = [];
if (IS_PROD) {
plugins.push(
new CompressionWebpackPlugin({
algorithm(input, compressionOptions, callback) {
return zopfli.gzip(input, compressionOptions, callback);
},
compressionOptions: {
numiterations: 15
},
minRatio: 0.99,
test: productionGzipExtensions
})
);
plugins.push(
new BrotliPlugin({
test: productionGzipExtensions,
minRatio: 0.99
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 去除多余无效的 css
注:谨慎使用。可能出现各种样式丢失现象。
- 方案一:@fullhuman/postcss-purgecss
npm i -D postcss-import @fullhuman/postcss-purgecss
更新 postcss.config.js
const autoprefixer = require("autoprefixer");
const postcssImport = require("postcss-import");
const purgecss = require("@fullhuman/postcss-purgecss");
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
let plugins = [];
if (IS_PROD) {
plugins.push(postcssImport);
plugins.push(
purgecss({
content: [
"./layouts/**/*.vue",
"./components/**/*.vue",
"./pages/**/*.vue"
],
extractors: [
{
extractor: class Extractor {
static extract(content) {
const validSection = content.replace(
/<style([\s\S]*?)<\/style>+/gim,
""
);
return (
validSection.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []
);
}
},
extensions: ["html", "vue"]
}
],
whitelist: ["html", "body"],
whitelistPatterns: [
/el-.*/,
/-(leave|enter|appear)(|-(to|from|active))$/,
/^(?!cursor-move).+-move$/,
/^router-link(|-exact)-active$/
],
whitelistPatternsChildren: [/^token/, /^pre/, /^code/]
})
);
}
module.exports = {
plugins: [...plugins, autoprefixer]
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- 方案二:purgecss-webpack-plugin
npm i -D glob-all purgecss-webpack-plugin
const path = require("path");
const glob = require("glob-all");
const PurgecssPlugin = require("purgecss-webpack-plugin");
const resolve = dir => path.join(__dirname, dir);
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
module.exports = {
configureWebpack: config => {
const plugins = [];
if (IS_PROD) {
plugins.push(
new PurgecssPlugin({
paths: glob.sync([resolve("./**/*.vue")]),
extractors: [
{
extractor: class Extractor {
static extract(content) {
const validSection = content.replace(
/<style([\s\S]*?)<\/style>+/gim,
""
);
return (
validSection.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []
);
}
},
extensions: ["html", "vue"]
}
],
whitelist: ["html", "body"],
whitelistPatterns: [
/el-.*/,
/-(leave|enter|appear)(|-(to|from|active))$/,
/^(?!cursor-move).+-move$/,
/^router-link(|-exact)-active$/
],
whitelistPatternsChildren: [/^token/, /^pre/, /^code/]
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 开启 stylelint 检测scss, css语法
npm i -D stylelint stylelint-config-standard stylelint-config-prettier stylelint-webpack-plugin
在文件夹创建stylelint.config.js,详细配置在这里 (opens new window)
module.exports = {
ignoreFiles: ["**/*.js", "src/assets/css/element-variables.scss", "theme/"],
extends: ["stylelint-config-standard", "stylelint-config-prettier"],
rules: {
"no-empty-source": null,
"at-rule-no-unknown": [
true,
{
ignoreAtRules: ["extend"]
}
]
}
};
2
3
4
5
6
7
8
9
10
11
12
13
启用webpack配置
const StylelintPlugin = require("stylelint-webpack-plugin");
module.exports = {
configureWebpack: config => {
const plugins = [];
if (IS_DEV) {
plugins.push(
new StylelintPlugin({
files: ["src/**/*.vue", "src/assets/**/*.scss"],
fix: true //打开自动修复(谨慎使用!注意上面的配置不要加入js或html文件,会发生问题,js文件请手动修复)
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 为 sass 提供全局样式,以及全局变量
可以通过在 main.js 中 Vue.prototype.$src = process.env.VUE_APP_PUBLIC_PATH;挂载环境变量中的配置信息,然后在js中使用$src 访问。
css 中可以使用注入 sass 变量访问环境变量中的配置信息
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
module.exports = {
css: {
extract: IS_PROD,
sourceMap: false,
loaderOptions: {
scss: {
// 向全局sass样式传入共享的全局变量, $src可以配置图片cdn前缀
// 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
prependData: `
@import "@scss/variables.scss";
@import "@scss/mixins.scss";
@import "@scss/function.scss";
$src: "${process.env.VUE_APP_OSS_SRC}";
`
}
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在 scss 中引用
.home {
background: url($src+"/images/500.png");
}
2
3
# 为 stylus 提供全局变量
npm i -D style-resources-loader
const path = require("path");
const resolve = dir => path.resolve(__dirname, dir);
const addStylusResource = rule => {
rule
.use("style-resouce")
.loader("style-resources-loader")
.options({
patterns: [resolve("src/assets/stylus/variable.styl")]
});
};
module.exports = {
chainWebpack: config => {
const types = ["vue-modules", "vue", "normal-modules", "normal"];
types.forEach(type =>
addStylusResource(config.module.rule("stylus").oneOf(type))
);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 添加打包分析
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = {
chainWebpack: config => {
// 打包分析
if (IS_PROD) {
config.plugin("webpack-report").use(BundleAnalyzerPlugin, [
{
analyzerMode: "static"
}
]);
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 配置 externals 引入 cdn 资源
防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖
module.exports = {
configureWebpack: config => {
config.externals = {
vue: "Vue",
"element-ui": "ELEMENT",
"vue-router": "VueRouter",
vuex: "Vuex",
axios: "axios"
};
},
chainWebpack: config => {
const cdn = {
// 访问https://unpkg.com/element-ui/lib/theme-chalk/index.css获取最新版本
css: ["//unpkg.com/element-ui@2.10.1/lib/theme-chalk/index.css"],
js: [
"//unpkg.com/vue@2.6.10/dist/vue.min.js", // 访问https://unpkg.com/vue/dist/vue.min.js获取最新版本
"//unpkg.com/vue-router@3.0.6/dist/vue-router.min.js",
"//unpkg.com/vuex@3.1.1/dist/vuex.min.js",
"//unpkg.com/axios@0.19.0/dist/axios.min.js",
"//unpkg.com/element-ui@2.10.1/lib/index.js"
]
};
// 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中
config.plugin("html").tap(args => {
// html中添加cdn
args[0].cdn = cdn;
return args;
});
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
在 html 中添加
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.js) { %>
<script
type="text/javascript"
src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"
></script>
<% } %>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 多页面打包 multi-page
多入口页面打包,建议在 src 目录下新建 pages 目录存放多页面模块。
- pages.config.js
配置多页面信息。src/main.js 文件对应 main 字段,其他根据参照 pages 为根路径为字段。如下:
module.exports = {
'admin': {
template: 'public/index.html',
filename: 'admin.html',
title: '后台管理',
},
'mobile': {
template: 'public/index.html',
filename: 'mobile.html',
title: '移动端',
},
'pc/crm': {
template: 'public/index.html',
filename: 'pc-crm.html',
title: '预发服务',
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- vue.config.js
vue.config.js 的 pages 字段为多页面提供配置
const glob = require("glob");
const pagesInfo = require("./pages.config");
const pages = {};
glob.sync('./src/pages/**/main.js').forEach(entry => {
let chunk = entry.match(/\.\/src\/pages\/(.*)\/main\.js/)[1];
const curr = pagesInfo[chunk];
if (curr) {
pages[chunk] = {
entry,
...curr,
chunk: ["chunk-vendors", "chunk-common", chunk]
}
}
})
module.exports = {
chainWebpack: config => {
// 防止多页面打包卡顿
config => config.plugins.delete("named-chunks");
return config;
},
pages
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
如果多页面打包需要使用 CDN,使用 vue inspect –plugins 查看 html 是否在结果数组中的形式。上例中 plugins 列表中存在’html-main’,’html-pages/admin’,’html-pages/mobile’, 没有’html’。因此不能再使用 config.plugin(“html”)。
const path = require("path");
const resolve = dir => path.join(__dirname, dir);
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const glob = require("glob");
const pagesInfo = require("./pages.config");
const pages = {};
glob.sync('./src/pages/**/main.js').forEach(entry => {
let chunk = entry.match(/\.\/src\/pages\/(.*)\/main\.js/)[1];
const curr = pagesInfo[chunk];
if (curr) {
pages[chunk] = {
entry,
...curr,
chunk: ["chunk-vendors", "chunk-common", chunk]
}
}
});
module.exports = {
publicPath: IS_PROD ? process.env.VUE_APP_PUBLIC_PATH : "./", //
configureWebpack: config => {
config.externals = {
vue: "Vue",
"element-ui": "ELEMENT",
"vue-router": "VueRouter",
vuex: "Vuex",
axios: "axios"
};
},
chainWebpack: config => {
const cdn = {
// 访问https://unpkg.com/element-ui/lib/theme-chalk/index.css获取最新版本
css: ["//unpkg.com/element-ui@2.10.1/lib/theme-chalk/index.css"],
js: [
"//unpkg.com/vue@2.6.10/dist/vue.min.js", // 访问https://unpkg.com/vue/dist/vue.min.js获取最新版本
"//unpkg.com/vue-router@3.0.6/dist/vue-router.min.js",
"//unpkg.com/vuex@3.1.1/dist/vuex.min.js",
"//unpkg.com/axios@0.19.0/dist/axios.min.js",
"//unpkg.com/element-ui@2.10.1/lib/index.js"
]
};
// 防止多页面打包卡顿
config => config.plugins.delete("named-chunks");
// 多页面cdn添加
Object.keys(pagesInfo).forEach(page => {
config.plugin(`html-${page}`).tap(args => {
// html中添加cdn
args[0].cdn = cdn;
// 修复 Lazy loading routes Error
args[0].chunksSortMode = "none";
return args;
});
});
return config;
},
pages
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 静态资源自动打包上传阿里 oss、华为 obs
开启文件上传 ali oss,需要将 publicPath 改成 ali oss 资源 url 前缀,也就是修改 VUE_APP_PUBLIC_PATH。具体配置参见阿里 oss 插件 webpack-oss (opens new window)、华为 obs 插件 huawei-obs-plugin (opens new window)
npm i -D webpack-oss
const AliOssPlugin = require("webpack-oss");
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const format = AliOssPlugin.getFormat();
module.exports = {
publicPath: IS_PROD ? `${process.env.VUE_APP_PUBLIC_PATH}/${format}` : "./", // 默认'/',部署应用包时的基本 URL
configureWebpack: config => {
const plugins = [];
if (IS_PROD) {
plugins.push(
new AliOssPlugin({
accessKeyId: process.env.ACCESS_KEY_ID,
accessKeySecret: process.env.ACCESS_KEY_SECRET,
region: process.env.REGION,
bucket: process.env.BUCKET,
prefix: process.env.PREFIX,
exclude: /.*\.html$/,
format
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 预渲染 prerender-spa-plugin
npm i -D prerender-spa-plugin
const PrerenderSpaPlugin = require("prerender-spa-plugin");
const path = require("path");
const resolve = dir => path.join(__dirname, dir);
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
module.exports = {
configureWebpack: config => {
const plugins = [];
if (IS_PROD) {
plugins.push(
new PrerenderSpaPlugin({
staticDir: resolve("dist"),
routes: ["/"],
postProcess(ctx) {
ctx.route = ctx.originalRoute;
ctx.html = ctx.html.split(/>[\s]+</gim).join("><");
if (ctx.route.endsWith(".html")) {
ctx.outputPath = path.join(__dirname, "dist", ctx.route);
}
return ctx;
},
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
renderer: new PrerenderSpaPlugin.PuppeteerRenderer({
// 需要注入一个值,这样就可以检测页面当前是否是预渲染的
inject: {},
headless: false,
// 视图组件是在API请求获取所有必要数据后呈现的,因此我们在dom中存在“data view”属性后创建页面快照
renderAfterDocumentEvent: "render-event"
})
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
mounted()中添加 document.dispatchEvent(new Event(‘render-event’))
new Vue({
router,
store,
render: h => h(App),
mounted() {
document.dispatchEvent(new Event("render-event"));
}
}).$mount("#app");
2
3
4
5
6
7
8
为自定义预渲染页面添加自定义 title、description、content
- 删除 public/index.html 中关于 description、content 的 meta 标签。保留 title 标签
- 配置 router-config.js
module.exports = {
"/": {
title: "首页",
keywords: "首页关键词",
description: "这是首页描述"
},
"/about.html": {
title: "关于我们",
keywords: "关于我们页面关键词",
description: "关于我们页面关键词描述"
}
};
2
3
4
5
6
7
8
9
10
11
12
- vue.config.js
const path = require("path");
const PrerenderSpaPlugin = require("prerender-spa-plugin");
const routesConfig = require("./router-config");
const resolve = dir => path.join(__dirname, dir);
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
module.exports = {
configureWebpack: config => {
const plugins = [];
if (IS_PROD) {
// 预加载
plugins.push(
new PrerenderSpaPlugin({
staticDir: resolve("dist"),
routes: Object.keys(routesConfig),
postProcess(ctx) {
ctx.route = ctx.originalRoute;
ctx.html = ctx.html.split(/>[\s]+</gim).join("><");
ctx.html = ctx.html.replace(
/<title>(.*?)<\/title>/gi,
`<title>${
routesConfig[ctx.route].title
}</title><meta name="keywords" content="${
routesConfig[ctx.route].keywords
}" /><meta name="description" content="${
routesConfig[ctx.route].description
}" />`
);
if (ctx.route.endsWith(".html")) {
ctx.outputPath = path.join(__dirname, "dist", ctx.route);
}
return ctx;
},
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
renderer: new PrerenderSpaPlugin.PuppeteerRenderer({
// 需要注入一个值,这样就可以检测页面当前是否是预渲染的
inject: {},
headless: false,
// 视图组件是在API请求获取所有必要数据后呈现的,因此我们在dom中存在“data view”属性后创建页面快照
renderAfterDocumentEvent: "render-event"
})
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 配置 IE 兼容
之前的方式 会报 @babel/polyfill
is deprecated. Please, use required parts of core-js
and
regenerator-runtime/runtime
separately
@babel/polyfill
废弃,使用 core-js
和 regenerator-runtime
npm i --save core-js regenerator-runtime
在 main.js
中添加
// 兼容 IE
// https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#babelpolyfill
import 'core-js/stable'
import 'regenerator-runtime/runtime'
2
3
4
配置 babel.config.js
const plugins = []
module.exports = {
presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage', corejs: 3 }]],
plugins
}
2
3
4
5
6
# 相关链接
https://staven630.github.io/vue-cli4-config/