前言
当我在初学时,使用Vue-cli
脚手架来搭建Vue
项目,使用npm run serve
,启动我们的vue项目并在本地跑一个8080 端口的服务,并且当我们修改并保存时,本地的端口页面也会随之刷新,类似于live-server
的功能。
今天我就带大家来搭建一个基本的Vue-cli
,也可以让大家对Webpack
有更深入的了解!
开发模式配置
创建
csharp
npm init
文件的内容:
- package name: (vue-cli) ------ 项目包的名称 默认就是文件夹目录名称
- version: (1.0.0) ------ 项目版本 (大版本·次要版本·修订版本)
- description: 自定义脚手架工具 ------ 项目描述信息
- entry point: (index.js) ----- 项目入口文件 默认为 index.js 索引文件
- test command: ----- 测试脚本
- git repository: ----- git 源地址
- keywords: cli webpack ----- npmjs 包网的搜索关键字
- author: muddyrain ----- 这里就是此项目的作者
- license: (ISC) ----- 开源许可证这里使用默认的 ISC 就好了
这里都填写完后 终端会再来一句提示 - 直接回车就好了
node
Is this OK? (yes)
然后项目里就出现了一个文件名为 package.json
的文件
json
{
"name": "vue-cli",
"version": "1.0.0",
"description": "自定义脚手架工具",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [
"cli",
"webpack"
],
"author": "muddyrain",
"license": "ISC"
}
配置输入输出
js
// webpack.config.js
module.exports = {
entry: "./src/main.js",
output: {
path: undefined, // 开发模式,无输出目录,在内存中编译即可
filename: "static/js/[name].js", // 入口文件打包的文件名
chunkFilename: "static/js/[name].chunk.js", // 指定导入的chunk的文件名
assetModuleFilename: "static/media/[hash:10][ext][query]", // 图片的资源文件名
// clean: true, // 开发模式没有输出,不需要清空输出结果
},
};
配置处理css
js
const getStyleLoaders = (preProcessor) => {
return [
"vue-style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题,配合 package.json来指定
],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: undefined,
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/js/[hash:10][ext][query]",
},
module: {
rules: [
{
// 用来匹配 .css 结尾的文件
test: /.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoaders(),
},
{
// 处理 less
test: /.less$/,
use: getStyleLoaders("less-loader"),
},
{
// 处理 sass
test: /.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
// 处理 stylus
test: /.styl$/,
use: getStyleLoaders("stylus-loader"),
}
]
}
json
// package.json
{
"name": "vue-cli",
"version": "1.0.0",
"description": "自定义脚手架工具",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"browserslist": [
"last 2 version",// 兼顾前两个版本
"> 1%",// 支持99%
"not dead"// 支持存在的浏览器
],
"keywords": [
"cli",
"webpack"
],
"author": "muddyrain",
"license": "ISC"
}
配置处理图片和其他资源
js
// webpack.config.js
...
{
test: /.(png|jpe?g|gif|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb的图片会被base64处理,减少请求数
},
},
},
{
test: /.(ttf|woff2?)$/,
type: "asset/resource",
}
...
配置Eslint
js
// webpack.config.js
...
plugins: [
new ESLintWebpackPlugin({
// path.resolve()方法将路径片段解析为绝对路径,并返回规范化后的结果。
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",// 排除
cache: true,// 缓存开启
cacheLocation: path.resolve(// 存放 cache的配置目录
__dirname,
"../node_modules/.cache/.eslintcache"
),
})
]
js
// .eslintrc.js
// 官方推荐配置
module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
parserOptions: {
parser: "@babel/eslint-parser",
},
};
配置处理 js
js
// webpack.config.js
...
// 处理js
{
test: /\.js$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
{
test: /\.vue$/,
loader: "vue-loader",
options: {
// 开启缓存
cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader"),
},
}
...
js
// babel.config
// 官方推荐
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};
配置处理html
js
// webpack.config.js
...
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),// 选择模板
})
...
mode: "development",// 开发模式配置
devtool: "cheap-module-source-map",// 开启 source-map
optimization: { // 做好代码分割
splitChunks: {
chunks: "all",
},
runtimeChunk: { //做好命名,防止缓存失效
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
}
启动服务配置
js
// webpack.config.js
devServer: {
host: "localhost",
port: 3000,
open: true,
hot: true, // 开启HMR
historyApiFallback: true, // 解决前端history路由刷新 404问题
}
环境配置
js
// webpack.config.js
...
new VueLoaderPlugin(),
// cross-env定义的环境变量给打包工具使用
// DefinePlugin定义环境变量给源代码使用,从而解决vue3页面警告的问题
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
})
...
完整的开发模式代码
js
// webpack.config.js
const path = require("path");
const { DefinePlugin } = require("webpack");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
// 返回处理样式loader函数
const getStyleLoaders = (pre) => {
return [
"vue-style-loader",// 官方推荐使用该 loader
"css-loader",
{
// 处理css兼容性问题
// 配合package.json中browserslist来指定兼容性
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre,
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: undefined,
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
},
module: {
rules: [
// 处理css
{
test: /\.css$/,
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader"),
},
// 处理图片
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
// 处理其他资源
{
test: /\.(woff2?|ttf)$/,
type: "asset/resource",
},
// 处理js
{
test: /\.js$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
{
test: /\.vue$/,
loader: "vue-loader",
},
],
},
// 处理html
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
new VueLoaderPlugin(),
// cross-env定义的环境变量给打包工具使用
// DefinePlugin定义环境变量给源代码使用,从而解决vue3页面警告的问题
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
}),
],
mode: "development",
devtool: "cheap-module-source-map",
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
},
// webpack 解析模块加载选项
resolve: {
// 自动补全文件扩展名
// Webpack 会按照以下优先顺序解析模块的文件扩展名
extensions: [".vue", ".js", ".json"],
},
devServer: {
host: "localhost",
port: 3000,
open: true,
hot: true, // 开启HMR
historyApiFallback: true, // 解决前端路由刷新404问题
},
};
json
// package.json
{
"name": "vue-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
// "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
],
"devDependencies": {
"@babel/core": "^7.17.10",
"@babel/eslint-parser": "^7.17.0",
"@vue/cli-plugin-babel": "^5.0.4",
"babel-loader": "^8.2.5",
"copy-webpack-plugin": "^10.2.4",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"eslint-plugin-vue": "^8.7.1",
"eslint-webpack-plugin": "^3.1.1",
"html-webpack-plugin": "^5.5.0",
"image-minimizer-webpack-plugin": "^3.2.3",
"imagemin": "^8.0.1",
"imagemin-gifsicle": "^7.0.0",
"imagemin-jpegtran": "^7.0.0",
"imagemin-optipng": "^8.0.0",
"imagemin-svgo": "^10.0.1",
"less-loader": "^10.2.0",
"mini-css-extract-plugin": "^2.6.0",
"postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.5.0",
"sass": "^1.51.0",
"sass-loader": "^12.6.0",
"stylus-loader": "^6.2.0",
"unplugin-auto-import": "^0.7.1",
"unplugin-vue-components": "^0.19.3",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0"
},
"dependencies": {
"element-plus": "^2.2.0",
"vue": "^3.2.33",
"vue-router": "^4.0.15"
}
}
原神启动
敲下 npm run dev ,启动服务:

再看一个例子:



生产模式
基础上新增和修改的配置
js
const path = require("path"); // 导入Node.js中的path模块,用于处理文件路径
const EslintWebpackPlugin = require("eslint-webpack-plugin"); // 导入ESLint Webpack插件,用于代码检查
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 导入HTML Webpack插件,用于生成HTML文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 导入Mini CSS Extract插件,用于提取CSS为单独的文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin"); // 导入CSS Minimizer插件,用于压缩CSS文件
const TerserWebpackPlugin = require("terser-webpack-plugin"); // 导入Terser插件,用于压缩JavaScript文件
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 导入Image Minimizer插件,用于压缩图片文件
const CopyPlugin = require("copy-webpack-plugin"); // 导入Copy插件,用于复制文件或文件夹
const { VueLoaderPlugin } = require("vue-loader"); // 导入Vue Loader插件,用于加载Vue组件
const { DefinePlugin } = require("webpack"); // 导入Webpack插件,用于定义全局变量
// 返回处理样式loader函数
const getStyleLoaders = (pre) => {
return [
MiniCssExtractPlugin.loader, // 使用Mini CSS Extract插件的loader
"css-loader", // 使用CSS Loader处理CSS文件
{
// 处理css兼容性问题
// 配合package.json中browserslist来指定兼容性
loader: "postcss-loader", // 使用PostCSS Loader处理CSS文件
options: {
postcssOptions: {
plugins: ["postcss-preset-env"], // 使用postcss-preset-env插件处理CSS兼容性
},
},
},
pre,
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js", // 入口文件路径
output: {
path: path.resolve(__dirname, "../dist"), // 输出文件夹路径
filename: "static/js/[name].[contenthash:10].js", // 为了更好做缓存,输出文件名格式带上 hash
chunkFilename: "static/js/[name].[contenthash:10].chunk.js", // 分割的代码块文件名格式
assetModuleFilename: "static/media/[hash:10][ext][query]", // 静态资源文件名格式
clean: true, // 构建时清理输出目录
},
module: {
rules: [
// 处理css
{
test: /\.css$/, // 匹配以.css结尾的文件
use: getStyleLoaders(),
},
{
test: /\.less$/, // 匹配以.less结尾的文件
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/, // 匹配以.scss或.sass结尾的文件
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/, // 匹配以.styl结尾的文件
use: getStyleLoaders("stylus-loader"),
},
// 处理图片
{
test: /\.(jpe?g|png|gif|webp|svg)$/, // 匹配以.jpg、.jpeg、.png、.gif、.webp或.svg结尾的文件
type: "asset", // 使用Webpack Asset Module进行处理
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 当文件大小小于等于10KB时,转为data URL
},
},
},
// 处理其他资源
{
test: /\.(woff2?|ttf)$/, // 匹配以.woff、.woff2或.ttf结尾的文件
type: "asset/resource", // 使用Webpack Asset Module进行处理,并输出资源文件
},
// 处理js
{
test: /\.js$/, // 匹配以.js结尾的文件
include: path.resolve(__dirname, "../src"), // 只在src目录下进行处理
loader: "babel-loader", // 使用Babel Loader处理JavaScript文件
options: {
cacheDirectory: true, // 开启缓存
cacheCompression: false, // 关闭缓存压缩
},
},
{
test: /\.vue$/, // 匹配以.vue结尾的文件
loader: "vue-loader", // 使用Vue Loader处理Vue组件
},
],
},
// 处理html
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, "../src"), // ESLint检查的根目录
exclude: "node_modules", // 排除node_modules目录
cache: true, // 开启缓存
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"), // 指定ESLint缓存路径
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"), // 指定HTML模板文件
}),
new MiniCssExtractPlugin({ // 将css提取成单独文件
filename: "static/css/[name].[contenthash:10].css", // 提取的CSS文件名格式
chunkFilename: "static/css/[name].[contenthash:10].chunk.css", // 分割的CSS文件名格式
}),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"), // 复制的源文件夹路径
to: path.resolve(__dirname, "../dist"), // 复制到的目标文件夹路径
globOptions: {
// 忽略index.html文件
ignore: ["**/index.html"], // 忽略匹配的文件或文件夹
},
},
],
}),
new VueLoaderPlugin(), // Vue Loader插件
// cross-env定义的环境变量给打包工具使用
// DefinePlugin定义环境变量给源代码使用,从而解决vue3页面警告的问题
new DefinePlugin({
__VUE_OPTIONS_API__: true, // 定义Vue的Options API为true
__VUE_PROD_DEVTOOLS__: false, // 定义Vue的开发工具为false
}),
],
mode: "production", // 设置模式为生产模式
devtool: "source-map", // 生成完整的source map文件
optimization: {
splitChunks: {
chunks: "all", // 拆分代码块,将公共模块提取出来
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`, // 给运行时代码块命名
},
minimizer: [
new CssMinimizerWebpackPlugin(), // 压缩CSS文件
new TerserWebpackPlugin(), // 压缩JavaScript文件
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
],
},
// webpack解析模块加载选项
resolve: {
// 自动补全文件扩展名
extensions: [".vue", ".js", ".json"],
},
};


合并开发模式和生产配置
js
const path = require("path");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");
const AutoImport = require("unplugin-auto-import/webpack");
const Components = require("unplugin-vue-components/webpack");
const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
const isProduction = process.env.NODE_ENV === "production";
// 返回处理样式loader函数
const getStyleLoaders = (pre) => {
return [
isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
"css-loader",
{
// 处理css兼容性问题
// 配合package.json中browserslist来指定兼容性
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre && {
loader: pre,
options:
pre === "sass-loader"
? {
additionalData: `@use "@/styles/element/index.scss" as *;`,
}
: {},
},
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
filename: isProduction ? "static/js/[name].[contenthash:10].js" : "static/js/[name].js",
chunkFilename: isProduction ? "static/js/[name].[contenthash:10].chunk.js" : "static/js/[name].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
clean: true,
},
module: {
rules: [
// 处理css
{
test: /\.css$/,
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader"),
},
// 处理图片
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
// 处理其他资源
{
test: /\.(woff2?|ttf)$/,
type: "asset/resource",
},
// 处理js
{
test: /\.js$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
{
test: /\.vue$/,
loader: "vue-loader",
options: {
// 开启缓存
cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader"),
},
},
],
},
// 处理html
plugins: [
new EslintWebpackPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
isProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),
isProduction &&
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
globOptions: {
// 忽略index.html文件
ignore: ["**/index.html"],
},
},
],
}),
new VueLoaderPlugin(),
// cross-env定义的环境变量给打包工具使用
// DefinePlugin定义环境变量给源代码使用,从而解决vue3页面警告的问题
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
}),
// 按需加载element-plus
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [
ElementPlusResolver({
// 自定义主题,引入sass
importStyle: "sass",
}),
],
}),
].filter(Boolean),
mode: isProduction ? "production" : "development",
devtool: isProduction ? "source-map" : "cheap-module-source-map",
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
vue: {
test: /[\\/]node_modules[\\/]vue(.*)?[\\/]/,
name: "vue-chunk",
priority: 40,
},
elementPlus: {
test: /[\\/]node_modules[\\/]element-plus[\\/]/,
name: "elementPlus-chunk",
priority: 30,
},
libs: {
test: /[\\/]node_modules[\\/]/,
name: "libs-chunk",
priority: 20,
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
minimize: isProduction,
minimizer: [
new CssMinimizerWebpackPlugin(),
new TerserWebpackPlugin(),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
],
},
// webpack解析模块加载选项
resolve: {
// 自动补全文件扩展名
extensions: [".vue", ".js", ".json"],
// 路径别名
alias: {
"@": path.resolve(__dirname, "../src"),
},
},
devServer: {
host: "localhost",
port: 3000,
open: true,
hot: true, // 开启HMR
historyApiFallback: true, // 解决前端路由刷新404问题
},
performance: false,
};
参考: 尚硅谷Webpack5入门到原理