# 配置(VueCLI)

前言

掌握一些项目中常见的 webpack 配置,能够从容应对一些开发场景问题

# 1.Vue/cli 2.x

# 1.1 css 相关

  • style-loader ( 处理 style 内联样式 )
  • css-loader (处理.css 文件)
  • postcss-loader (处理 css 兼容)
  • less-loader (处理.less 文件)
  • sass-loader (处理.sass/.scss 文件)
let comment = [
  "style-loader",
  "css-loader",
  {
    loader: "postcss-loader",
    options: {
      postcssOptions: {
        plugins: ["postcss-preset-env"],
      },
    },
  },
]
module.exports = {
  module: {
    rules: [
      {
        test: /.css/,
        use: [...comment],
      },
      {
        test: /.less$/,
        use: [...comment, "less-loader"],
      },
    ],
  },
}
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

# 1.2 js 相关

  • babel-loader
  • @babel/core
  • @babel/preset-env
  • @babel/polyfill
module.exports = {
 entry:['@babel/polyfill','./src/index.js']
 ...
 module:{
  rules:[
    {
    test:/.js$/,
    use:[
        {
          loader:'babel-loader',
          options:{
            presets:['@babel/preset-env']
            // 或者
            presets:[['env',{module:false}]]
          }
        }
      ]
    }
  ]
 }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 1.3 文件相关

  • url-loader
  • file-loader
  • html-loader
 module.exports = {
  ...
  module:{
    rules:[
      {
        test:/.(png|jpg|gif)$/,
        use:[
          {
          loader:file-loader,
          options:{
          limit:4 *1024,
          name:'img/[name]_[hash:10].[ext]'
          }
          }
        ]
      },
      {
        test:/.html$/,
        loader:'html-loader'
      }
    ]
  }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 1.4 vue 相关

  • vue-loader
  • vue-style-loader
 module.exports = {
  ...
  module:{
  rules:[
      {
        test:/.vue$/,
        loader:'vue-loader'
      },
      {
        test:/.css$/,
        use:[
          'vue-style-loader',
          'css-loader'
        ]
      }
    ]
  }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2.vue-cli 3.x

# 2.1 默认配置

  • vue-cli 3.x 为我们默认封装了项目运行常用的 webpack 配置
  • 除了使用vue inspect plugins我们还可以通过运行 vue ui进入可视化页面查看
const VueLoaderPlugin = require("vue-loader/lib/plugin") // vue-loader 是webpack的加载器,允许你以单文件组件的格式编写vue组件
const { DefinePlugin } = require("webpack") // webpack 内置插件,用于创建编译时可以配置的全局常量
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin") // 用于强制所有默认的完整路径必须与磁盘上实际路径的确切大小写相匹配
const FriendlyErrorsPlugin = require("friendly-error-webpack-plugin") //识别某些类型的webpack错误并整理,以提供开发人员更好的体验。
const MiniCssExtractPlugin = require("mini-css-extract-plugin") // 将css 提取到单独的文件中,为每个包含css的js文件创建一个css文件
const { HashedModuleIdsPLugin } = require("webpack") // webpack内置插件,用于根据模块的相对路径生成hash作为模块id,一般用于生产环境
const HtmlWebpackPlugin = require("html-webpack-plugin") //用于根据模块或使用加载器生成HTML文件
const PreloadPlugin = require("preload-webpack-plugin") // 用于在使用html-webpack-plugin生成的html中添加<link rel="preload" >或<link rel="prefetch">,有助于异步加载
const CopyWebpackPlugin = require("copy-webpack-plugin") //用于将单个文件或整个目录复制到构建目录

module.exports = {
  plugins: [
    new VueLoaderPlugin(),
    new DefinePlugin(),
    new FriendlyErrorsWebpackPlugin(),
    new MiniCssExtractPlugin(),
    new OptimizeCssnanoPlugin(),
    new OptimizeCssnanoPlugin(),
    new HtmlWebpackPlugin(),
    new PreloadPlugin(),
    new CopyWebpackPlugin(),
  ],
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 2.2 vue.config.js

注意

新创建的 vue 项目没有这个文件,需要自己在根目录创建 vue.config.js

├── vue.config.js
│   ├── 其他配置
│   ├── chainWebpack
│   ├── configureWebpack
│   ├── devServer
│   ├── css
│   ├── pages
1
2
3
4
5
6
7

# 2.2.1.其他配置

  • 1.publicPath:配置二级目录:默认打开页面http://localhost:8080 改为 http://localhost:8080/project
  • 2.outputDir:配置打包目录:默认dist 改为 mydist
  • 3.productionSourceMap:配置 source-map:线上环境代码方便调试
    module.exports = {
      assetsDir: "./",
      publicPath: "/project/",
      outputDir: "mydist",
      runtimeCompiler: true,
      lintOnSave: false,
      transpileDependencies: ["vue-particles"],
      productionSourceMap: true,
      parallel: require("os").cpus().length > 1,
      pluginOptions: {},
      ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

# 2.2.2.chainWebapck

集成webpack-chain插件链式操作来修改配置,更细粒度的控制 webpack 的内部配置

  • 1.url-loader:处理图片

    const merge = require('webpack-merge')
    module.exports = {
      ...
      chainWebpack: config => {// config 参数为已经解析好的 webpack配置
        // 压缩图片 需要 npm i -D image-webpack-loader
        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,
            },
          })
          .end()
    
        config.module
          .rule("vue")
          .use("vue-loader")
          .loader("vue-loader")
          .tap((options) => {
            let op = {
              ...options,
              loaders: {
                scss: "style-loader!css-loader!sass-loader",
                sass: "style-loader!css-loader!sass-loader?indentedSyntax",
              },
            }
            return op
          })
    
        config.module
          .rule("img")
          .test(/\.(png|jpg|gif)$/i)
          .use("url-loader")
          .loader("url-loader")
          .tap((options) =>
            merge(options, {
              limit: 5120,
            })
          )
    
        config.module
          .rule("image")
          .test(/\.(png|jpg|gif)$/i)
          .use("file-loader")
          .loader("file-loader")
          .end()
    
        config.module
          .rule("html")
          .test(/\.html$/)
          .use("html-withimg-loader")
          .loader("html-withimg-loader")
          .end()
    
        config.module
          .rule("compile")
          .test(/\.[js|ts]$/)
          .include.add(resolve("src"))
          .add("/node_modules/")
          .end()
          .use("babel")
          .loader("babel-loader")
          .options({
            presets: [
              [
                "@babel/preset-env",
                {
                  modules: false,
                },
              ],
            ],
          })
    
        config.module
          .rule('image')
          .use('url-loader')
          .tap(options =>
            merge(options,{ // 这里需要注意的是我们使用了 webpack-merge 这一插件,该插件用于做 webpack 配置的合并处理,这样 options 下面的其他值就不会被覆盖或改变
              limit: 5120
            })
          )
        config.plugin("html").tap((args) => {
          args[0].title = "我的简历"
          return args
        })
        // 修复热更新失效
        config.resolve.symlinks(true)
        // 打包去除多余代码
        config.optimization.minimizer("terser").tap((args) => {
          args[0].terserOptions.compress.drop_console = true
          args[0].terserOptions.compress.drop_debugger = true
          args[0].terserOptions.compress.pure_funcs = ["console.log"]
          args[0].terserOptions.output = {
            comments: false,
          }
          return args
        })
      }
      ...
    }
    
    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

    以上操作我们可以成功修改 webpack 中 module 项里配置 rules 规则为图片下的 url-loader 值,将其 limit 限制改为 5M

    module: {
      rules: [
        {
          test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
          use: [
            {
              loader: "url-loader",
              options: {
                limit: 5120,
                name: "img/[name].[hash:8].[ext]",
              },
            },
          ],
        },
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 2.简化路径

  module.exports = {
    ...
    chainWebpack: config => {// config 参数为已经解析好的 webpack配置
      config.resolve.alias
      .set('@', resolve('src'))
      .set('static', resolve('static'))
    }
    ...
  }
1
2
3
4
5
6
7
8
9

# 2.2.3.configureWebpack

除了使用 chainWebpack 配置外,我们还可以使用 configureWebpack 来修改,两者的不同点在于 chianWebpack 是链式修改,而 configureWebapck 更倾向于整体替换和修改

  • configWbpack 可以直接是一个对象,也可以是一个函数,如果是对象他会直接使用 webpack-merge 对其进行合并处理
  • configWbpack 如果是函数,你可以直接使用其 config 参数来修改 webpack 中的配置,或者返回一个对象进行 merge 处理
module.exports = {
  // config 参数为已经解析好的webpack配置
  configureWebpack: (config) => {
    // config.plugins = [];//这样会直接将plugins置空
    return {
      plugins: [
        // 分析各种包的大小
        new webpack.DefinePlugin({
          "process.env": {
            NODE_ENV: JSON.stringify(process.env.NODE_ENV),
          },
        }),
        // 打包时gzip压缩,需要 npm i -D compression-webpack-plugin
        new CompressionWebpackPlugin({
          filename: "[path].gz[query]",
          algorithm: "gzip",
          test: new RegExp("\\.(js|css)$"),
          threshold: 10240,
          minRatio: 0.8,
        }),
      ], // 使用return 一个对象会通过 webpack-merge进行合并,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
  • 项目目录下运行 vue inspect来查看你修改后的 webpackp 完整配置
vue inspect plugins # 缩小审查范围,只查看 plugins的内容
1

# 2.2.4.devServer

devServer 可以对本地服务器配置,我们在命令中运行的yarn serve对应的命令vue-cli-service server其实便是基于 webpack-dev-server 开启的一个本地服务器

module.exports = {
  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
    compress: true, //开发服务器是否启动gzip等压缩
    host: "localhost", //开发服务器监听的主机地址
    hot: true,
    hotOnly: true, // 热更新
    https: false, // https:{type:Boolean}
    open: true,
    overlay: {
      errors: true, // 让浏览器 overlay 同时显示警告和错误
      warnings: false,
    },
    port: 8090, //开发服务器监听的端口
    proxy: {
      //配置不同的后台API地址
      "/api": {
        target: "http://localhost:8090",
        ws: false, //是否开启websocket
        changeOrigin: true, //允许跨域
        pathRewrite: {
          "^/api": "/mock",
        },
      },
    },
    before(app) {
      // mock数据
    },
  },
}
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

当然除了以上参数,其支持所有的 webpack-dev-server 中的选项,比如hostoryApiFallback用于重写路由、progress 将运行进度输出到控制台等

# 2.2.5.css

  • 1.响应式页面
module.exports = {
  css: {
    extract: true,
    sourceMap: false,
    loaderOptions: {
      scss: {
        prependData: `@import "@/assets/scss/index.scss";`,
      },
      postcss: {
        plugins: [
          require("postcss-pxtorem")({
            rootValue: 192,
            // unitPrecision: 5,
            propList: ["*"],
            // selectorBlackList: [],
            // replace: true,
            mediaQuery: false,
            minPixelValue: 3,
            exclude: /node_modules/i,
          }),
        ],
      },
    },
  },
}
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
  • 2.全局样式 全局sass样式文件进行全局引入





     
     
     
     
     
     
     
     
     
     
     
     




    // vue.config.js
    ...
    css: {
      sourceMap: false,
      loaderOptions: {
        scss: {
          additionalData: `
              @import "~@/assets/styles/norm.scss";
              @import "~@/assets/styles/mixins.scss";
            `
        },
        sass: {
          additionalData: `
              @import "~@/assets/styles/norm.scss"
              @import "~@/assets/styles/mixins.scss"
            `
        }
      }
    }
    ...
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

# 配置

/* eslint-disable @typescript-eslint/no-var-requires */
const { name } = require("./package.json")
const lodash = require("lodash")
const path = require("path")
const merge = require("webpack-merge")
const CompressionWebpackPlugin = require("compression-webpack-plugin") // 开启gzip压缩, 按需引用
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin
const webpack = require("webpack")
const px2rem = require("postcss-px2rem")
const isDev = process.env.NODE_ENV === "dev"
console.error(process.env.NODE_ENV, "process.env.NODE_ENV ")

function resolve(dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  assetsDir: "./",
  publicPath: "/resume/",
  outputDir: "resume",
  runtimeCompiler: true,
  lintOnSave: false,
  transpileDependencies: ["vue-particles"],
  productionSourceMap: true,
  parallel: require("os").cpus().length > 1,
  pluginOptions: {},
  chainWebpack: (config) => {
    config.plugin("html").tap((args) => {
      args[0].title = "我的简历"
      return args
    })
    // 修复热更新失效
    config.resolve.symlinks(true)

    // 如果使用多页面打包,使用vue inspect --plugins查看html是否在结果数组中,对微前端有问题
    // config.plugin("html").tap((args) => {
    //   // 修复 Lazy loading routes Error
    //   args[0].chunksSortMode = "none";
    //   return args;
    // });
    config.optimization.minimizer("terser").tap((args) => {
      args[0].terserOptions.compress.drop_console = true
      args[0].terserOptions.compress.drop_debugger = true
      args[0].terserOptions.compress.pure_funcs = ["console.log"]
      args[0].terserOptions.output = {
        comments: false,
      }
      return args
    })
    // 配置别名
    config.resolve.alias
      .set("@", resolve("src"))
      .set("assets", resolve("src/assets"))
      .set("views", resolve("src/views"))
      .set("static", resolve("static"))
      .set("WebEcharts", resolve("web-echarts/components"))
      .set("WebOpenlayers", resolve("web-openlayers/components"))
    // 压缩图片 需要 npm i -D image-webpack-loader
    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,
        },
      })
      .end()

    config.module
      .rule("vue")
      .use("vue-loader")
      .loader("vue-loader")
      .tap((options) => {
        let op = {
          ...options,
          loaders: {
            scss: "style-loader!css-loader!sass-loader",
            sass: "style-loader!css-loader!sass-loader?indentedSyntax",
          },
        }
        return op
      })

    config.module
      .rule("img")
      .test(/\.(png|jpg|gif)$/i)
      .use("url-loader")
      .loader("url-loader")
      .tap((options) =>
        merge(options, {
          limit: 5120,
        })
      )

    config.module
      .rule("image")
      .test(/\.(png|jpg|gif)$/i)
      .use("file-loader")
      .loader("file-loader")
      .end()

    config.module
      .rule("html")
      .test(/\.html$/)
      .use("html-withimg-loader")
      .loader("html-withimg-loader")
      .end()

    config.module
      .rule("compile")
      .test(/\.[js|ts]$/)
      .include.add(resolve("src"))
      .add("/node_modules/")
      .end()
      .use("babel")
      .loader("babel-loader")
      .options({
        presets: [
          [
            "@babel/preset-env",
            {
              modules: false,
            },
          ],
        ],
      })
  },
  /**
   * 使用整体替换来修改配置
   */
  configureWebpack: () => {
    let targetobj = {
      output: {
        library: `${name}-[name]`,
        libraryTarget: "umd",
        jsonpFunction: `webpackJsonp_${name}`,
      },
      plugins: [
        //提供全局变量,在模块中使用无需用require引入
        new webpack.ProvidePlugin({
          _: lodash,
        }),
        // 动态链接库:指定需要用到的 manifest 文件,webpack 会根据这个 manifest 文件的信息,分析出哪些模块无需打包,直接从另外的文件暴露出来的内容中获取
        // new webpack.DllReferencePlugin({
        //   manifest: path.resolve(__dirname, "dist", "manifest.json"),
        // }),
      ],
    }
    if (isDev) {
      targetobj.plugins.concat([
        // 分析各种包的大小
        new webpack.DefinePlugin({
          "process.env": {
            NODE_ENV: JSON.stringify(process.env.NODE_ENV),
          },
        }),
      ])
    } else {
      targetobj.plugins.concat([
        // 需要 npm i -D compression-webpack-plugin
        new CompressionWebpackPlugin({
          filename: "[path].gz[query]",
          algorithm: "gzip",
          test: new RegExp("\\.(js|css)$"),
          threshold: 10240,
          minRatio: 0.8,
        }),
      ])
    }
    return targetobj
  },
  css: {
    extract: true,
    sourceMap: false,
    loaderOptions: {
      scss: {
        prependData: `@import "@/assets/scss/index.scss";`,
      },
      less: {
        globalVars: {
          primary: "#fff",
        },
      },
      postcss: {
        plugins: [
          px2rem({
            // 基准大小 baseSize,需要和rem.js中相同
            remUnit: 16,
          }),
        ],
      },
    },
  },
  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
    overlay: {
      warnings: true,
      errors: true,
    },
    open: true,
    https: false, // https:{type:Boolean}
    hotOnly: true, // 热更新
    hot: true,
    // contentBase: path.resolve(__dirname, 'dist'), //配置开发服务运行时的文件根目录
    host: "localhost", //开发服务器监听的主机地址
    compress: true, //开发服务器是否启动gzip等压缩
    port: 8090, //开发服务器监听的端口
    // proxy: { //配置不同的后台API地址
    //   '/api': {
    //     target: '',
    //     ws: true,
    //     changeOrigin: true,
    //     pathRewrite: {
    //       "^/api": "/"
    //     }
    //   },
    //   '/foo': {
    //     target: ''
    //   }
    // }
  },
}
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

总结

了解 vue 项目中的 vuecli 的作用,以及相关的配置方式,能在项目中遇到相关问题时轻松应对