手写一个vue 项目的webpack的配置 2
上面我们已经做完一个基本的webpack 的vue 项目的基础版本,但是在实际的应用中我们会用到我们要对我们的基本版本进行进一步的优化
- css 分离
- 环境配置(生产和开发)
- 开发版本配置
webpack.base.config.js - 生产环境配置
压缩 混淆 拆包 - 优化策略
那我们就一起开始优化我们的项目吧,
- 改名
webpack.config.js 修改为webpack.base.config.js
2 配置环境变量
我们之前的默认的环境 mode development
现在我们就要 从mode 下手了,我们有两种方式可以处理环境变量,一种就是通过我们的process.env
process.env.NODE_ENV === 'production' ? 'production' : 'developmen'
通过 package.json 配置环境变量
"dev": "cross-env NODE_ENV=development
webpack-dev-server --config build/webpack.config.js",
"build": "cross-env NODE_ENV=production
webpack --config build/webpack.config.js"
我们今天用的是第二种方式
通过 process.argv进行
process.argv[0]——返回启动Node.js进程的可执行文件所在的绝对路径
ocess.argv[1]——为当前执行的JavaScript文件路径
剩余的元素为其他命令行参数
所以我么可以配置 --mode=production 来判断是否为生产环境
此时我们把我们的文件分为
webpack.base.config.js
webpack.dev.js //配置dev 环境
webpack.prod.js //配置生产环境和打包配置
- 配置我们的webpack.base.config.js
我们要做两件事,拆出公用的代码
单独配置生产和开发
我们先改造一下 我们的mode 两种方式
const prodMode = process.argv[2] ==='--mode=production'
const prodMode = process.argv.indexOf('--mode=production')> -1;
所以我们webpack.base.config.js为
热更新放到 dev 环境里 mode 去掉
const path= require('path')
// 清除dist文件夹
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
// vue 加载项
const vueLoaderPlugin = require('vue-loader/lib/plugin')
// 为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题
// 可以生成创建html入口文件,配置N个html-webpack-plugin可以生成N个页面入口
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 如果在JS中导入了css,那么就需要使用 css-loader 来识别这个模块,通过特定的语法规则进行转换内容最后导出
// css-loader会处理 import / require() @import / url 引入的内容。
// sass-loader把scss转成css
// less-loader把less转成css
// style-loader把css转换成脚本加载的JavaScript代码 几者配合使用
module.exports= {
// mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
entry:{
app:path.resolve(__dirname,'../src/main.js') //配置入口
},
output:{
filename:'[name].[hash:8].js', // 打包后的文件名称
path:path.resolve(__dirname, '../dist') //打包的目录
},
// module 配置 loader
module:{
rules:[{
test:/\.vue$/,
use:['vue-loader']
},
// 编译css
{
test:/\.css$/,
use: ['vue-style-loader','css-loader',{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
}] // 从右向左解析
},
// 编译less
{
test:/\.less$/,
use: ['vue-style-loader','css-loader',{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
},'less-loader'] //// 从右向左解析
},
//file-loader vs url-loader
// 二者一般只选择一个来进行对文件的打包,防止有冲突出现导致图片加载失败
// 但是 可以在url-loader的fallback属性指定不满足条件时的 loader
// url-loader 依赖于 file-loader,如果不安装 file-loader,会报错误
// url-loader会通过配置规则将一定范围大小的图片打包成base64的字符串,
// 放到dist.js文件里,而不是单独生成一个图片文件。而file-loader在打包时一定会生成单独的图片文件。
{
test: /\.(jpe?g|png|gif)$/i, //图片文件
use: [
{
loader: 'url-loader',
options: {
limit: 10*1024,
fallback: {
loader: 'file-loader',
options: {
name: 'images/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体文件
use: [
{
loader: 'url-loader',
options: {
limit: 49460,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
},
]},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [
{
loader: 'url-loader',
options: {
limit: 49460,
fallback: {
loader: 'file-loader',
options: {
name: 'medias/[name].[hash:8].[ext]'
}
}
}
}
]
},
// babel-loader是webpack 与 babel的通信桥梁,
// 不会做把es6转成 es5的⼯作,这部分工作需要⽤用到@babel/preset-env来做 ,
// babel/core babel的核心库,提供了很多核心语法
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:[
['@babel/preset-env']
]
}
}
},
]
},
resolve:{
// 在组件之间相互引用时我们可以省略后缀
//类似import Hello from '../src.components/Hello' 是可以被识别的;
extensions:['*','.js','.json','.vue'],
// 配置 别名使用 我们就可以省略 多级目录 比如上面的加入这个配置就可以
// 写成import Hello from '@components/Hello';
alias:{
'vue$':'vue/dist/vue.runtime.esm.js',
'@':path.resolve(__dirname,'../src'),
//我们自定义一个别名 通过@img 代替../src/assets/images
//'@img':path.resolve(__dirname,'../src/assets/images'),
},
},
// 配置插件
plugins:[
// vue加载程序在没有相应插件的情况下使用。确保在Web包配置中包含VueLoaderPlugin。
new vueLoaderPlugin(),
// 另外的配置大家可以看官方文档
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'), //html模板所在的文件路径
filename:'index.html' //输出的html的文件名称
// inject
// 注入选项。有四个选项值 true, body, head, false.
// true:默认值,script标签位于html文件的 body 底部
// body:script标签位于html文件的 body 底部(同 true)
// head:script 标签位于 head 标签内
// false:不插入生成的 js 文件,只是单纯的生成一个 html 文件
}),
// 清除dist文件
new CleanWebpackPlugin()
]
}
webpack.dev.js
这里跟大家说两个东西
一个叫做 webpack-merge 可以合并配置
另外一个叫做 cheap-module-eval-source-map
它是什么东西哪
Webpack打包生成的.map后缀文件,使得我们的开发调试更加方便,它能帮助我们链接到断点对应的源代码的位置进行调试,而devtool就是用来指定source-maps的配置方式的。
而使用 eval 方式可大幅提高持续构建效率
启动sourceMap
1、source-map:产生文件,产生行列
devtool: 'source-map',
2、eval-source-map:不产生文件,产生行类
devtool: 'eval-source-map',
3、cheap-source-map:产生文件,不产生列
devtool: 'cheap-module-source-map',
4、cheap-module-eval-source-map:不产生文件,不产生列
const path= require('path')
// 启用热更新的第1步
let Webpack = require('webpack');
const baseConfig = require('./webpack.base.config')
// webpack-merge 合并配置
// 通过结构取其中的merge 函数
const {merge} = require('webpack-merge')
module.exports = merge(baseConfig,{
mode:'development',
// 启动sourceMap
// 1、source-map:产生文件,产生行列
// devtool: 'source-map',
// 2、eval-source-map:不产生文件,产生行类
// devtool: 'eval-source-map',
// 3、cheap-source-map:产生文件,不产生列
// devtool: 'cheap-module-source-map',
// 4、cheap-module-eval-source-map:不产生文件,不产生列
devtool:'cheap-module-eval-source-map',
// 启用热更新的第二步
devServer: {
// 这是配置 dev-server
open: true, // 自动打开浏览器
port: 3000, // 设置启动时候的运行端口
contentBase:path.join(__dirname,'../dist'),//静态文件路径
hot: true // 启动热更新的第一步
},
plugins:[
// new 一个热更新的模块对象,这是启用热更新的第三步
new Webpack.HotModuleReplacementPlugin()
]
})
下面我么来配置一下生产环境
说到生产环境我们来看下我们常用的几个生产的插件
合并配置
const WebpackMerge = require('webpack-merge')
拷贝资源
const CopyWebpackPlugin = require('copy-webpack-plugin')
压缩css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
压缩js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
开启gzip
const CompressionWebpackPlugin = require('compression-webpack-plugin');
代码混淆压缩
const TerserPlugin = require('terser-webpack-plugin');
cnpm i -D webpack-merge copy-webpack-plugin
optimize-css-assets-webpack-plugin
uglifyjs-webpack-plugin terser-webpack-plugin
compression-webpack-plugin
// copy-webpack-plugin 拷贝资源
// optimize-css-assets-webpack-plugin 压缩css
// uglifyjs-webpack-plugin 压缩js
// webpack mode设置production的时候会自动压缩js代码。
// 原则上不需要引入uglifyjs-webpack-plugin进行重复工作。
// 但是optimize-css-assets-webpack-plugin压缩css的同时会破坏原有的js压缩,
// 所以这里我们引入uglifyjs 或terser-webpack-plugin进行压缩
const path = require('path')
const baseConfig = require('./webpack.base.config')
// 合并配置
const {merge} = require('webpack-merge')
// 拷贝资源
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 压缩css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
// 压缩js
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 开启gip
const CompressionWebpackPlugin = require('compression-webpack-plugin');
// 代码混淆压缩
const TerserPlugin = require('terser-webpack-plugin');
module.exports = merge(baseConfig,{
mode:'production',
// 启动sourceMap
// 1、source-map:产生文件,产生行列
// devtool: 'source-map',
// 2、eval-source-map:不产生文件,产生行类
// devtool: 'eval-source-map',
// 3、cheap-source-map:产生文件,不产生列
// devtool: 'cheap-module-source-map',
// 4、cheap-module-eval-source-map:不产生文件,不产生列 生产不需要
devtool:'cheap-module-source-map',
plugins:[
new CopyWebpackPlugin(
// 下面这种写法报错
// [{
// from:path.resolve(__dirname,'../public'),
// to:path.resolve(__dirname,'../dist')
// }]
//正确写法
{
patterns: [
{
from:path.resolve(__dirname,'../public'),
to:path.resolve(__dirname,'../dist')
}
],
}
),
//打包的时候开启gzip可以大大减少体积,非常适合于上线部署
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(' + ['html', 'js', 'css'].join('|') + ')$'),
threshold: 10240, // 只有大小大于该值的资源会被处理 10240
minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
deleteOriginalAssets: false // 删除原文件
}),
],
optimization:{
minimizer:[
// new UglifyJsPlugin({//压缩js
// cache:true,
// parallel:true,
// sourceMap:true
// }),
//两个都是 js 压缩 两个选一个都行
new TerserPlugin({
terserOptions: {
warnings: false,
compress: {
drop_console: true,
pure_funcs: ['console.log'] //删除console.log
}
},
sourceMap: false,
parallel: true
}),
new OptimizeCssAssetsPlugin({}),
],
splitChunks:{
chunks:'all',
cacheGroups:{
vendors: {
name: "common",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial" // 只打包初始时依赖的第三方
}
}
}
}
})
- 接下来我们还要两件事基本上大功告成
拆分 css mini-css-extract-plugin
mini-css-extract-plugin插件来打包css文件 生成一个文件
生产环境需要打包,不是生产环境则不需要
cnpm i -D mini-css-extract-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
{
test:/\.less$/,
use:[miniCssExtractPlugin.loader,'css-loader',{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
},'less-loader'] // 从右向左解析原则
},
new miniCssExtractPlugin({
filename:'[name].[hash:8].css',
chunkFilename:'[id].css'
}),
配置代理
proxy: {
'/api': {
// target: 'http://xxxx:8020',
changeOrigin: true,
pathRewrite: {
'^/api': '' //这里理解成用'/api'代替target里面的地址,比如我要调用'http://40.00.100.100:3002/user/add',直接写'/api/user/add'即可
}
}
}
这里因篇幅较长就不展示代码
具体请参照 github 每一行都有详细的注释
https://github.com/hegegetellstory1/webpack-hand
推荐一下webpack 的插件
webpack-bundle-analyzer 体积分析插件
speed-measure-webpack-plugin 速度分析插件
thread-loader和HappyPack loader 多线程构建
三种主流压缩代码方案
parallel-uglify-plugin 多进程并行压缩代码
uglifyjs-webpack-plugin 内置压缩
terser-webpack-plugin 支持预编译
DllPlugin 预编译模块
hard-source-webpack-plugin 缓存提高构建速度
Tree-shaking 清除代码中无用的部分