Vue项目改造抛弃vue-cli配置,重撸webpack4和babel配置

Vue项目改造抛弃vue-cli配置,重撸webpack4和babel配置

抛弃自带的vue.config.js的配置模式,手动使用webpack进行构建:

[TOC]

webpack4

webpack4 相关loaders和plugins

1
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
>>>>>>>>>>>>>>>>>>>>>>>相关loader<<<<<<<<<<<<<<<<<<<<<<

vue-loader
编译.vue文件

babel-loader
编译成ES5

file-loader
解决文件中 import/require() 的资源,转化为URL,再输出到指定文件夹内

url-loader
把图片转化成 base64 URLs, 可以根据limit大小自由控制

css-loader
css-loader 解释 @import and url() 比如 import/require() 然后解析他们

file-loader
file-loader 解析文件中的 import/require() 成一个URL 然后输出到输出文件中

vue-style-loader
style-loader
dev环境,把css注入到DOM

>>>>>>>>>>>>>>>>>>>>>>>相关plugin<<<<<<<<<<<<<<<<<<<<<<

mini-css-extract-plugin
提取css到单独的文件

clean-webpack-plugin
清理构建的资源

webpack-build-notifier
构建完成桌面提醒

html-webpack-plugin
生成html入口模板

optimize-css-/images-webpack-plugin
css去重压缩

purgecss-webpack-plugin
去除css中未使用的代码

webpack-dev-server
本地server

webpack-spritesmith
自动整合成雪碧图

compression-webpack-plugin
@gfx/zopfli
压缩代码,根据算法生成gzip

webpack-bundle-analyzer
生成bundle后分析报告,方便优化

progress-bar-webpack-plugin
显示构建进度条

安装依赖

1
yarn add -D webpack webpack-cli webpack-dev-server vue-loader babel-loader file-loader css-loader style-loader url-loader mini-css-extract-plugin  clean-webpack-plugin webpack-build-notifier html-webpack-plugin optimize-css-/images-webpack-plugin purgecss-webpack-plugin webpack-spritesmith compression-webpack-plugin webpack-bundle-analyzer

babel 7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@babel/core
@babel/preset-env
@babel/cli
@babel/polyfill
// runtime
@babel/runtime
@babel/plugin-transform-runtime
// 动态插入
@babel/plugin-syntax-dynamic-import
// 支持 ...spread
@babel/plugin-syntax-object-rest-spread
// commonjs
@babel/plugin-transform-modules-commonjs
// 支持 vue jsx语法
@babel/plugin-syntax-jsx
babel-plugin-transform-vue-jsx

//支持 element-ui 按需加载
babel-plugin-component

//支持 lodash 按需加载
babel-plugin-lodash

// 移除 console.log
babel-plugin-transform-remove-console

babel.config.js

1
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
const removeConsolePlugin = [];

if (process.env.NODE_ENV === 'production') {
removeConsolePlugin.push('transform-remove-console');
}

module.exports = {
// presets: ['@vue/app'],
presets: [
[
'@babel/preset-env',
{
// transform any
loose: true
}
]
],
// 借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的
plugins: [
// import
'@babel/plugin-syntax-dynamic-import',
// transform
'@babel/plugin-transform-runtime',
'@babel/plugin-transform-modules-commonjs',
// vue jsx语法
'@babel/plugin-syntax-jsx',
'transform-vue-jsx',
'lodash',
// spread ...
// '@babel/plugin-syntax-object-rest-spread',
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
],
...removeConsolePlugin
]
};

webpack.config.js

1
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
/**
* webpack 4 config
* @author master2011zhao@gmail.com
* @Date 20190910
*/
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// webpack4 使用 mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// extract 被废弃
// const ExtractTextPlugin = require('extract-text-webpack-plugin');
// clean project
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 压缩css
const OptimizeCss/imagesPlugin = require('optimize-css-/images-webpack-plugin');
// notifier
const WebpackBuildNotifierPlugin = require('webpack-build-notifier');
// 压缩代码
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const zopfli = require('@gfx/zopfli');
// vue loader
const VueLoaderPlugin = require('vue-loader/lib/plugin');
// 图片整合成雪碧图
const SpritesmithPlugin = require('webpack-spritesmith');
// bundle分析
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

// path function
const resolve = src => {
return path.resolve(__dirname, src);
};

// nginx 配置二级目录 base url
let serverBaseUrl = '';

// customerTemplate
const templateFunction = function(data) {
// console.log('---', data)
const shared = `.sprite_ico { background-image: url(I);display:inline-block;background-size: Wpx Hpx;}`
.replace('I', data.sprites[0].image)
.replace('W', data.spritesheet.width)
.replace('H', data.spritesheet.height);

const perSprite = data.sprites
.map(function(sprite) {
return `.sprite_ico_N { width: Wpx; height: Hpx; background-position: Xpx Ypx;}`
.replace('N', sprite.name)
.replace('W', sprite.width)
.replace('H', sprite.height)
.replace('X', sprite.offset_x)
.replace('Y', sprite.offset_y);
})
.join('\n');

return '//out:false' + '\n' + shared + '\n' + perSprite;
};

module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';

console.log('isProduction', isProduction);

// 传递给 babel.config.js
process.env.NODE_ENV = argv.mode;

// console.log(process.env.NODE_ENV);

let plugins = [new VueLoaderPlugin()];

// 生成模板
let HtmlTemplates = [];

// 生产环境
if (isProduction) {
// 清理项目, 清理不干净,需要使用 rm.sh
plugins.push(
new CleanWebpackPlugin({
dry: false,
verbose: true
})
);

// 雪碧图
plugins.push(
new SpritesmithPlugin({
src: {
//下面的路径,根据自己的实际路径配置
cwd: path.resolve(__dirname, 'src//images/icons'),
glob: '*.png'
},
// 输出雪碧图文件及样式文件
target: {
//下面的路径,根据自己的实际路径配置
image: path.resolve(__dirname, 'src//images/sprite.png'),
css: [
[
path.resolve(__dirname, 'src/less/sprite.less'),
{
format: 'function_based_template'
}
]
]
// css: path.resolve(__dirname, './src/less/sprite.less')
},
// 自定义模板
customTemplates: {
function_based_template: templateFunction
},
// 样式文件中调用雪碧图地址写法
apiOptions: {
// 这个路径根据自己页面配置
cssImageRef: './images/sprite.png'
},
spritesmithOptions: {
// algorithm: 'top-down'
padding: 5
}
})
);

// 构建完成提醒
plugins.push(
new WebpackBuildNotifierPlugin({
title: 'project build',
suppressSuccess: true,
suppressWarning: true,
messageFormatter: function() {
return 'build completely';
}
})
);

// 分离css
// plugins.push(new ExtractTextPlugin('css/[name].[hash:8].css'));
plugins.push(
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// all options are optional
filename: 'css/[name].[hash:8].css',
chunkFilename: 'css/[name].[hash:8].css',
publicPath: './' + serverBaseUrl,
ignoreOrder: false // Enable to remove warnings about conflicting order
})
);

// 去除重复的 less, 比如 common
plugins.push(
new OptimizeCss/imagesPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: [
'default',
{
discardComments: {
removeAll: true
}
}
]
},
canPrint: true
})
);

//再次压缩代码
plugins.push(
new CompressionWebpackPlugin({
deleteOriginal/images: false,
test: /\.(js|css|html|woff|ttf|png|jpg|jpeg)$/,
compressionOptions: {
numiterations: 15
},
threshold: 10240,
minRatio: 0.8,
algorithm(input, compressionOptions, callback) {
return zopfli.gzip(input, compressionOptions, callback);
}
})
);

// 公共提取的chunk
const commonChunks = ['chunk-vendors', 'runtime', 'chunk-commons', 'css-commons'];

const minify = {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
};

// 生成模板
HtmlTemplates = [
new HtmlWebpackPlugin({
title: 'Index',
template: resolve('public/index.html'),
filename: 'index.html',
hash: true,
minify,
chunks: [...commonChunks, 'index'],
favicon: resolve('public/favicon.ico')
})
];

// 分析生成的包
plugins.push(
new BundleAnalyzerPlugin({
// 生成report.html
analyzerMode: 'static'
})
);
} else {
// 生成模板
HtmlTemplates = [
new HtmlWebpackPlugin({
title: 'Index',
template: resolve('./public/index.html'),
filename: 'index.html',
favicon: resolve('public/favicon.ico'),
chunks: ['index', 'runtime']
})
];
}

return {
entry: {
index: resolve('src/main.js')
},
output: {
path: resolve('cdn'),
filename: 'js/[name].[hash:8].js',
publicPath: isProduction ? './' + serverBaseUrl : ''
},
// 本地调试
devtool: !isProduction ? 'inline-source-map' : '',
devServer: {
port: 3000,
open: true,
hot: true,
// 配置 browserHistory 路由,防止刷新就 404
historyApiFallback: true,
compress: true,
contentBase: path.resolve(__dirname, ''),
noInfo: false,
overlay: {
warnings: true,
errors: true
},
proxy: {
'/api/v1': {
target: 'http://192.168.1.100:18080',
changeOrigin: true,
router: {
'/shareIndex': 'http://192.168.1.110:18080',
}
},
}
},
resolve: {
// 别名
alias: {
'@': resolve('src'),
'@c': resolve('src/components'),
'@less': resolve('src/less'),
'@util': resolve('src/utils'),
'@/images': resolve('src//images'),
'@pages': resolve('src/pages')
},
// 自动添加后缀
extensions: ['.vue', '.js', '.less']
},
module: {
rules: [
{
test: /\.vue?$/,
use: 'vue-loader'
},
{
test: /\.js?$/,
use: 'babel-loader'
},
{
test: /\.css?$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
{
loader: 'css-loader',
options: {}
}
]
},
{
test: /\.less$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
javascriptEnabled: true
}
}
]
},
{
test: /\.(png|jpg|svg|gif|ico|woff|ttf)?$/,
use: [
{
loader: 'url-loader',
options: {
// 这里的options选项参数可以定义多大的图片转换为base64
fallback: 'file-loader',
limit: 10 * 1024, // 表示小于10kb的图片转为base64,大于10kb的是路径
outputPath: 'images', //定义输出的图片文件夹名字
publicPath: '../images', //css中的路径
// name: '[name].[contenthash:8].[ext]'
name: '[sha512:contenthash:base64:8].[ext]'
}
}
]
}
]
},
plugins: [...plugins, ...HtmlTemplates],
optimization: {
splitChunks: {
// 静态资源缓存
// test, priority and reuseExistingChunk can only be configured on cache group level.
cacheGroups: {
// 提取 node_modules 里面依赖的代码
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'chunk-vendors',
chunks: 'all',
minSize: 0,
minChunks: 2, //2个共享以及以上都提取
priority: -10 //优先级
},
// 提出每个模块公共的代码
commons: {
name: 'chunk-commons',
test: /\.js$/,
chunks: 'initial',
minChunks: 2, //两个共享以及以上都提取,
minSize: 0,
priority: -20, //优先级
reuseExistingChunk: true
},
css: {
name: 'css-commons',
test: /\.less$/,
minChunks: 2,
minSize: 0,
priority: -30,
chunks: 'initial',
reuseExistingChunk: true
}
}
},
// I pull the Webpack runtime out into its own bundle file so that the
// contentHash of each subsequent bundle will remain the same as long as the
// source code of said bundles remain the same.
runtimeChunk: 'single'
}
};
};

package.json

1
2
3
4
scripts:{
"dev": "webpack-dev-server --mode development",
"build": "webpack --mode production",
}

1569495769879

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2014-2020 Alex Wong
  • Powered by Hexo Theme Ayer
  • PV: UV:

请我喝杯咖啡吧~~~

支付宝
微信