配置环境变量
通过在 package.json
里的 scripts
配置项中添加 --mode xxx
来选择不同环境。
NODE_ENV 和 BASE_URL 是两个特殊变量,在项目中始终可用。只有以 VUE_APP 开头的变量才会被 webpack.DefinePlugin
静态嵌入到客户端包中,代码中可以通过 process.env.VUE_APP_BASE_API
访问。
在项目根目录中新建 .env
, .env.production
, .env.analyz
等文件:
.env
serve
默认的开发环境配置:
NODE_ENV = 'development'
BASE_URL = './'
VUE_APP_PUBLIC_PATH = './'
VUE_APP_API = 'https://test.staven630.com/api'
2
3
4
.env.production
build
默认的生产环境配置:
NODE_ENV = 'production'
BASE_URL = 'https://prod.staven630.com/'
VUE_APP_PUBLIC_PATH = 'https://prod.oss.com/staven-blog'
VUE_APP_API = 'https://prod.staven630.com/api'
ACCESS_KEY_ID = 'xxxxxxxxxxxxx'
ACCESS_KEY_SECRET = 'xxxxxxxxxxxxx'
REGION = 'oss-cn-hangzhou'
BUCKET = 'staven-prod'
PREFIX = 'staven-blog'
2
3
4
5
6
7
8
9
10
.env.analyz
自定义
build
环境配置:
NODE_ENV = 'production'
BASE_URL = 'https://prod.staven630.com/'
VUE_APP_PUBLIC_PATH = 'https://prod.oss.com/staven-blog'
VUE_APP_API = 'https://prod.staven630.com/api'
ACCESS_KEY_ID = 'xxxxxxxxxxxxx'
ACCESS_KEY_SECRET = 'xxxxxxxxxxxxx'
REGION = 'oss-cn-hangzhou'
BUCKET = 'staven-prod'
PREFIX = 'staven-blog'
IS_ANALYZE = true
2
3
4
5
6
7
8
9
10
11
12
修改 package.json
:
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"analyz": "vue-cli-service build --mode analyze",
"lint": "vue-cli-service lint"
}
2
3
4
5
6
使用环境变量:
<template>
<div class="home">
<!-- template中使用环境变量 -->
{{ api }}
</div>
</template>
<script>
export default {
name: 'home',
data() {
return {
api: process.env.VUE_APP_API
}
},
mounted() {
// js代码中使用环境变量
console.log('BASE_URL', process.env.BASE_URL)
console.log('VUE_APP_API', process.env.VUE_APP_API)
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
配置 Proxy 代理
假设 mock
接口为 https://www.easy-mock.com/mock/5bc75b55dc36971c160cad1b/sheets/1
module.exports = {
devServer: {
proxy: {
'/api': {
// 目标代理接口地址
target: 'https://www.easy-mock.com/mock/5bc75b55dc36971c160cad1b/sheets',
secure: false,
// 开启代理,在本地创建一个虚拟服务端
changeOrigin: true,
// 是否启用websockets
ws: true,
pathRewrite: {
'^/api': '/'
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
配置图片压缩
npm i -D image-webpack-loader
module.exports = {
chainWebpack: config => {
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true, quality: 65 },
optipng: { enabled: false },
pngquant: { quality: '65-90', speed: 4 },
gifsicle: { interlaced: false },
webp: { quality: 75 }
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
配置雪碧图
默认 src/assets/icons
中存放需要生成雪碧图的 PNG 格式图片。首次运行 npm run serve/build
会生成雪碧图,并在根目录生成 icons.json
文件。再次运行命令时,会对比 icons
目录下图片文件与 icons.json
的匹配关系,确定是否需要再次执行 webpack-spritesmith
插件。
npm i -D webpack-spritesmith
const SpritesmithPlugin = require('webpack-spritesmith')
const path = require('path')
const fs = require('fs')
let has_sprite = true
try {
let result = fs.readFileSync(path.resolve(__dirname, './icons.json'), 'utf8')
result = JSON.parse(result)
const files = fs.readdirSync(path.resolve(__dirname, './src/assets/icons'))
if (files && files.length) {
has_sprite = files.some(item => {
let filename = item.toLocaleLowerCase().replace(/_/g, '-')
return !result[filename]
})
? true
: false
} else {
has_sprite = false
}
} catch (error) {}
// 雪碧图样式处理模板
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;
}
`
})
fs.writeFile(path.resolve(__dirname, './icons.json'), JSON.stringify(icons, null, 2), (err, data) => {})
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
配置 CDN 资源
防止将某些 import
的包(package)打包到 bundle
中,而是在运行时 runtime
再去从外部获取这些扩展依赖:
module.exports = {
configureWebpack: config => {
config.externals = {
vue: 'Vue',
vuex: 'Vuex',
axios: 'axios',
'element-ui': 'ELEMENT',
'vue-router': 'VueRouter'
}
},
chainWebpack: config => {
const cdn = {
css: ['//unpkg.com/[email protected]/lib/theme-chalk/index.css'],
js: [
'//unpkg.com/[email protected]/dist/vue.min.js',
'//unpkg.com/[email protected]/dist/vuex.min.js',
'//unpkg.com/[email protected]/dist/axios.min.js',
'//unpkg.com/[email protected]/lib/index.js',
'//unpkg.com/[email protected]/dist/vue-router.min.js'
]
}
// html中添加cdn
config.plugin('html').tap(args => {
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
在 html 中添加:
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="external nofollow" rel="preload" as="style" />
<% } %>
<!-- 使用CDN的CSS文件 -->
<% for (var css of htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%=css%>" rel="stylesheet" as="style" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="external nofollow" rel="preload" as="script" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var js of htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%=js%>"></script>
<% } %>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
配置模块打包
const isPro = process.env.NODE_ENV === 'production'
module.exports = {
chainWebpack: config => {
if (isPro) {
config.optimization = {
splitChunks: {
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
elementUI: {
name: 'chunk-elementUI',
priority: 20,
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
chunks: 'all'
}
}
}
}
}
}
}
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
配置页面预渲染
npm i -D prerender-spa-plugin
const PrerenderSpaPlugin = require('prerender-spa-plugin')
const path = require('path')
const resolve = dir => path.join(__dirname, dir)
const isPro = process.env.NODE_ENV === 'production'
module.exports = {
configureWebpack: config => {
const plugins = []
if (isPro) {
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
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
修复热更新失效
module.exports = {
chainWebpack: config => {
// 修复HMR
config.resolve.symlinks(true)
}
}
2
3
4
5
6
修复路由懒加载
module.exports = {
chainWebpack: config => {
config.plugin('html').tap(args => {
args[0].chunksSortMode = 'none'
return args
})
}
}
2
3
4
5
6
7
8
添加别名 alias
const path = require('path')
const resolve = dir => path.join(__dirname, dir)
const isPro = process.env.NODE_ENV === 'production'
module.exports = {
chainWebpack: config => {
// 添加别名
config.resolve.alias
.set('vue$', 'vue/dist/vue.esm.js')
.set('@', resolve('src'))
.set('@assets', resolve('src/assets'))
.set('@components', resolve('src/components'))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
添加 IE 兼容
npm i --save @babel/polyfill
在 main.js
中添加:
import '@babel/polyfill'
配置 babel.config.js
:
const plugins = []
module.exports = {
presets: [['@vue/app', { useBuiltIns: 'entry' }]],
plugins: plugins
}
2
3
4
5
6
添加打包分析
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
chainWebpack: config => {
// 打包分析
if (process.env.IS_ANALYZ) {
config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static'
}
])
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
添加 .env.analyz
文件:
NODE_ENV = 'production'
IS_ANALYZE = true
2
在package.json
的 scripts
中添加:
"analyz": "vue-cli-service build --mode analyze"
添加全局 Sass
可以通过在 main.js
中 Vue.prototype.$src = process.env.VUE_APP_PUBLIC_PATH;
挂载环境变量中的配置信息,然后在 js 中使用 $src
访问。
css 中可以使用注入 sass
变量访问环境变量中的配置信息:
module.exports = {
css: {
modules: false,
extract: isPro,
sourceMap: false,
loaderOptions: {
sass: {
// 向全局sass样式传入共享的全局变量
data: `@import "~assets/scss/variables.scss";
$src: "${process.env.VUE_APP_PUBLIC_PATH}";`
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
在 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
添加文件上传
开启文件上传 ali oss
,需要将 publicPath
改成 ali oss
资源 url
前缀,也就是修改 VUE_APP_PUBLIC_PATH:
npm i -D webpack-oss
const AliOssPlugin = require('webpack-oss')
module.exports = {
configureWebpack: config => {
if (isPro) {
const plugins = []
// 上传文件到oss
if (
process.env.ACCESS_KEY_ID ||
process.env.ACCESS_KEY_SECRET ||
process.env.REGION ||
process.env.BUCKET ||
process.env.PREFIX
) {
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$/,
deleteAll: false
})
)
}
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
添加 Gzip 压缩
npm i --D compression-webpack-plugin
const isPro = process.env.NODE_ENV === 'production'
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i
const CompressionWebpackPlugin = require('compression-webpack-plugin')
module.exports = {
configureWebpack: config => {
const plugins = []
if (isPro) {
plugins.push(
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8
})
)
}
config.plugins = [...config.plugins, ...plugins]
},
chainWebpack: config => {
if (isPro) {
config
.plugin('compression')
.use(CompressionWebpackPlugin, {
asset: '[path].gz[query]',
algorithm: 'gzip',
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8,
cache: true
})
.tap(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
32
33
34
35
36
如果服务端使用的是 nginx
服务器的话,需要配置开启 gzip
才能支持:
gzip on;
gzip_static on;
gzip_min_length 1024;
gzip_buffers 4 16k;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
2
3
4
5
6
7
8
除了 gzip
压缩,还有体验更好的 Zopfli 压缩:
npm i --save-dev @gfx/zopfli brotli-webpack-plugin
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const zopfli = require('@gfx/zopfli')
const BrotliPlugin = require('brotli-webpack-plugin')
const isPro = process.env.NODE_ENV === 'production'
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i
module.exports = {
configureWebpack: config => {
const plugins = []
if (isPro) {
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
移除无效 CSS
方案一:@fullhuman/postcss-purgecss
npm i -D postcss-import @fullhuman/postcss-purgecss
const autoprefixer = require('autoprefixer')
const postcssImport = require('postcss-import')
const purgecss = require('@fullhuman/postcss-purgecss')
const isPro = process.env.NODE_ENV === 'production'
let plugins = []
if (isPro) {
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
方案二: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 isPro = ["production", "prod"].includes(process.env.NODE_ENV);
module.exports = {
configureWebpack: config => {
const plugins = [];
if (isPro) {
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
移除 console.log
方法一:使用 babel-plugin-transform-remove-console
npm i --D babel-plugin-transform-remove-console
在 babel.config.js
中配置:
const isPro = process.env.NODE_ENV === 'production'
const plugins = []
if (isPro) {
plugins.push('transform-remove-console')
}
module.exports = {
presets: ['@vue/app', { useBuiltIns: 'entry' }],
plugins
}
2
3
4
5
6
7
8
9
10
11
方法二:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
configureWebpack: config => {
if (isPro) {
const plugins = []
plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
warnings: false,
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
}
},
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
配置默认为 []
, babel-loader
会忽略所有 node_modules
中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。配置需要转译的第三方库。
完整配置代码
const path = require('path')
const glob = require('glob-all')
const AliOssPlugin = require('webpack-oss')
const PurgecssPlugin = require('purgecss-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const PrerenderSpaPlugin = require('prerender-spa-plugin')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const resolve = dir => path.join(__dirname, dir)
const isPro = process.env.NODE_ENV === 'production'
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i
const addStylusResource = rule => {
rule
.use('style-resouce')
.loader('style-resources-loader')
.options({
patterns: [resolve('src/assets/stylus/variable.styl')]
})
}
module.exports = {
publicPath: process.env.VUE_APP_PUBLIC_PATH,
outputDir: process.env.outputDir || 'dist',
assetsDir: 'static',
configureWebpack: config => {
const plugins = []
if (isPro) {
// 移除无效CSS
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-_:/]+/g) || []
}
},
extensions: ['html', 'vue']
}
],
whitelist: ['html', 'body'],
whitelistPatterns: [/el-.*/],
whitelistPatternsChildren: [/^token/, /^pre/, /^code/]
})
)
// 移除打印
plugins.push(
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_console: true,
drop_debugger: false,
pure_funcs: ['console.log']
}
},
sourceMap: false,
parallel: true
})
)
// 文件压缩
plugins.push(
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8
})
)
// 页面预加载
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'
})
})
)
// 文件上传
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$/,
deleteAll: false
})
)
}
config.externals = {
vue: 'Vue',
'element-ui': 'ELEMENT',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios'
}
config.plugins = [...config.plugins, ...plugins]
},
chainWebpack: config => {
// CDN资源
const cdn = {
css: ['//unpkg.com/[email protected]/lib/theme-chalk/index.css'],
js: [
'//unpkg.com/[email protected]/dist/vue.min.js',
'//unpkg.com/[email protected]/dist/vue-router.min.js',
'//unpkg.com/[email protected]/dist/vuex.min.js',
'//unpkg.com/[email protected]/dist/axios.min.js',
'//unpkg.com/[email protected]/lib/index.js'
]
}
// 修复HMR
config.resolve.symlinks(true)
// 修复路由懒加载
config.plugin('html').tap(args => {
args[0].chunksSortMode = 'none'
// html中添加cdn
// args[0].cdn = cdn;
return args
})
// 添加别名
config.resolve.alias
.set('vue$', 'vue/dist/vue.esm.js')
.set('@', resolve('src'))
.set('@assets', resolve('src/assets'))
.set('@components', resolve('src/components'))
// 图片压缩
config.module
.rule("images")
.use("image-webpack-loader")
.loader("image-webpack-loader")
.options({
mozjpeg: { progressive: true, quality: 65 },
optipng: { enabled: false },
pngquant: { quality: "65-90", speed: 4 },
gifsicle: { interlaced: false },
webp: { quality: 75 }
})
const types = ["vue-modules", "vue", "normal-modules", "normal"];
types.forEach(type =>
addStylusResource(config.module.rule("stylus").oneOf(type))
)
// 打包分析
if (process.env.IS_ANALYZ) {
config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static'
}
])
}
if (isPro) {
// 打包模块
config.optimization.splitChunks({
cacheGroups: {
libs: {
name: 'chunk-libs',
chunks: 'initial',
priority: 10,
test: /[\\/]node_modules[\\/]/
},
elementUI: {
name: 'chunk-elementUI',
chunks: 'all'
priority: 20,
test: /[\\/]node_modules[\\/]element-ui[\\/]/
}
}
})
}
return config
},
css: {
modules: false,
extract: isPro,
sourceMap: false,
loaderOptions: {
sass: {
data: `@import "~assets/scss/variables.scss";
$src: "${process.env.VUE_APP_PUBLIC_PATH}";`
}
}
},
transpileDependencies: [],
lintOnSave: false,
runtimeCompiler: true,
productionSourceMap: !isPro,
parallel: require('os').cpus().length > 1,
pwa: {},
devServer: {
overlay: {
warnings: false,
errors: true
},
open: false,
host: "localhost",
port: "8080",
https: false,
hotOnly: false,
proxy: {
'/api': {
target: 'https://www.easy-mock.com/mock/5bc75b55dc36971c160cad1b/sheets',
secure: false,
changeOrigin: 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
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239