likes
comments
collection
share

使用webpack5实现简易的vue-cli脚手架功能

作者站长头像
站长
· 阅读数 1

主要涉及到对vue的支持以及elementui的自动按需引入首先解释一下webpack中使用最多的loaderplugin的概念:

  • loader是将一些webpack不能处理的资源(vue、scss、图片等)或者浏览器不能识别的资源(es6语法)使用资源对应的loader进行处理
  • plugin是在webpack打包处理过程中加入一些功能,扩展webpack打包的功能

直接放代码:

build/webpack.config.js

1、项目根目录新建build/webpack.config.js

// build/webpack.config.js
// 生产环境和开发环境一体的webpack配置
const os = require('os')
const path = require('path')
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");
const CopyPlugin = require("copy-webpack-plugin");
//配置element按需加载步骤(包括配置自定义样式)
//配置自动按需引入后就不需要在项目中使用import引入组件和样式了,直接使用就行了
//1、引入
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

// 区分开发环境和生产环境下的webpack配置(在package.json文件使用cross-env控制两种环境)
const isProduction = process.env.NODE_ENV === 'production'

//获取电脑cpu进程数
//注意:进程开启需要时间,所以小项目不要使用多进程,大型项目再使用
const threads = os.cpus().length

// 封装获取处理样式的Loaders
const getStyleLoaders = (pre) => {
    return [
        //将css提取为单独的文件时需要将style-loader替换为MiniCssExtractPlugin.loader
        isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
        "css-loader",
        //postcss-loader可以处理css的浏览器兼容问题,对需要兼容的css代码自动加上浏览器前缀
        //需要在package.json的browserslist配置浏览器兼容需求
        {
            loader: "postcss-loader",
            options: {
                postcssOptions: {
                    plugins: [
                        "postcss-preset-env", // 能解决大多数样式兼容性问题
                    ],
                },
            },
        },
        //2、修改element默认的scss样式
        pre && {
            loader: pre,
            options:
                //自定义element的scss样式
                pre === 'sass-loader' ? {
                    //自定义样式文件的路径
                    additionalData: `@use "@/assets/styles/element.scss" as *;`,
                } : {},

        },
    ].filter(Boolean);  //过滤掉没有传参的
};

// webpack配置项
module.exports = {
    //模式
    mode: isProduction ? "production" : 'development',
    //使用sourcemap生成js对应的map文件便于调错
    //生产模式使用source-map,生成代码的行和列映射,打包速度慢
    devtool: isProduction ? "source-map" : 'cheap-module-source-map',
    //单文件入口
    entry: path.resolve(__dirname, '../src/main.js'),     //绝对路径或相对路径
    //出口
    output: {
        path: isProduction ? path.resolve(__dirname, '../dist') : undefined,    //绝对路径
        //入口js文件的打包路径([name]就是入口文件配置的名称,单文件入口默认为main)
        filename: isProduction ? "static/js/[name].[contenthash:8].js" : "static/js/[name].js",
        //其他js文件(按需加载文件、复用代码单独打包文件等)的打包路径([name]为按需引入文件中配置的名字)
        chunkFilename: isProduction ? "static/js/[name].[contenthash:8].chunk.js" : "static/js/[name].chunk.js",
        //统一配置type:asset类型的图片、字体等资源的打包路径(注意用hash)(在这里配置之后就不需要再单独去配置了)([name]默认为资源的名字)
        assetModuleFilename: isProduction ? "static/media/[name].[hash:8][ext]" : "static/media/[name].[ext]",
        //webpack5中打包前清空dist文件夹内容(webpack4需要使用插件)
        clean: true
    },
    //加载器
    module: {
        //配置loader
        rules: [
            //css
            {
                test: /.css$/,
                use: getStyleLoaders()
            },
            //scss
            {
                test: /.scss$/,
                use: getStyleLoaders('sass-loader')
            },
            //图片(解析图片的loader在webpack5中内置了)
            {
                test: /.(png|jpe?g|gif|webp|svg)$/,
                //asset允许使用资源文件(字体,图标,图片,视频等)而无需配置额外 loader
                //asset可以将url转base64
                type: 'asset',
                //将小于10kb的图片转base64格式
                //base64格式:优点是可以不需要发请求获取图片资源,直接解析base64字符串,缺点是图片转base64后体积会变大,所以将小图片转base64,大图片依旧请求获取
                parser: {
                    dataUrlCondition: {
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                // //图片资源打包路径
                // generator: {
                //     //hash避免名字冲突生成的随机数|ext扩展名|query参数
                //     filename: 'static/images/[hash:10][ext][query]'
                // }
            },
            //字体图标/视频等不需要转base64直接打包的资源
            {
                test: /.(ttf|woff2?|mp3|mp4|avi)$/,
                //asset允许使用资源文件(字体,图标,图片,视频等)而无需配置额外 loader
                //asset/resource不转base64,直接打包
                type: 'asset/resource',
                // generator: {
                //     filename: "static/fonts/[hash:8][ext][query]",
                // },
            },
            //babel编译js语法(es6转es5)
            {
                test: /.js$/,
                exclude: /node_modules/,    //排除不被编译的目录
                use: [
                    //开启多进程
                    {
                        loader: 'thread-loader',
                        options: {
                            workers: threads
                        }
                    },
                    //配置babel
                    {
                        loader: 'babel-loader',
                        options: {
                            cacheDirectory: true,   //开启babel编译缓存,提升二次编译打包速度
                            cacheCompression: false,   //缓存文件不要压缩
                            plugins: ['@babel/plugin-transform-runtime']   //减少编译后的js代码体积
                        }
                    }
                ]
            },
            // vue-loader不支持oneOf
            {
                test: /.vue$/,
                loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
                options: {
                    // 开启缓存
                    cacheDirectory: path.resolve(
                        __dirname,
                        "node_modules/.cache/vue-loader"
                    ),
                },
            }
        ],

    },
    //插件plugins
    plugins: [
        //配置自动按需导入element-plus组件
        AutoImport({
            resolvers: [ElementPlusResolver()],
        }),
        Components({
            resolvers: [ElementPlusResolver({
                //配置自定义样式时需指定
                importStyle: "sass", // 自定义主题
            })],
        }),
        //eslint(下载插件)
        //1、根目录创建.eslintrc.js文件用于配置检验规则
        //2、webpack.config.js中配置需要eslint检验的目录
        //3、如果vscode下载了eslint插件,还需要根目录创建.eslintignore忽略不需要eslint检验的目录
        new ESLintWebpackPlugin({
            // 指定检查文件的根目录src
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 默认值
            cache: true, // 开启eslint检验缓存
            // 缓存目录
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
            threads, // 开启多进程
        }),
        //打包html,将js/css文件自动引入到html中
        new HtmlWebpackPlugin({
            // 以 public/index.html 为模板创建文件
            // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html')
        }),
        //将css样式代码提取为单独的文件(所有的css代码都会合并到一个css文件中)
        //将css提取为单独的文件后会自动在html中引入这个css文件让样式代码生效
        isProduction && new MiniCssExtractPlugin({
            //提取css文件的指定目录
            //css文件的打包路径
            filename: 'static/css/[name].[contenthash:8].css',
            //css中代码分割单独生成的css文件的路径(和js一样)
            chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
        }),
        //对css代码进行压缩(生产模式下打包的html和js代码都默认进行压缩了,不需要额外配置)
        // //使用了HtmlWebpackPlugin就会默认进行html压缩
        // new CssMinimizerPlugin(),
        //预加载js资源(比如按需加载的js就可以预先加载到缓存中,需要用的时候就可以直接用)
        isProduction && new PreloadWebpackPlugin({
            rel: "preload", // preload兼容性更好
            as: "script",
            // rel: 'prefetch' // prefetch兼容性更差
        }),
        //使用pwa离线访问技术
        // new WorkboxPlugin.GenerateSW({
        //     // 这些选项帮助快速启用 ServiceWorkers
        //     // 不允许遗留任何“旧的” ServiceWorkers
        //     clientsClaim: true,
        //     skipWaiting: true,
        // }),
        //vue
        new VueLoaderPlugin(),
        // 解决vue页面警告
        //cross-env定义的环境变量给打包工具使用
        //DefinePlugin定义的环境变量给vue源代码使用
        new DefinePlugin({
            __VUE_OPTIONS_API__: true,
            __VUE_PROD_DEVTOOLS__: false,
        }),
        //将public文件夹下的内容不做处理,复制到dist文件夹下
        isProduction && new CopyPlugin({
            patterns: [
                {
                    from: path.resolve(__dirname, "../public"),
                    to: path.resolve(__dirname, "../dist"),
                    toType: "dir",
                    noErrorOnMissing: true,
                    globOptions: {
                        ignore: ["**/index.html"],
                    },
                    info: {
                        minimized: true,
                    },
                },
            ],
        }),

    ].filter(Boolean), //filter(Boolean)可以过滤掉没有被使用的配置

    resolve: {
        // 自动补全文件扩展名,可以直接引用文件名,不加扩展名,会自动补全
        extensions: [".vue", ".js", ".json"],
        //配置别名
        alias: {
            '@': path.resolve(__dirname, '../src')
        }
    },
    //优化
    //生产环境对于开发环境来说主要就多了压缩这一块的配置
    optimization: {
        minimize: true,
        //这里面使用插件压缩--压缩统一在这里面配置
        minimizer: [
            // css压缩也可以写到optimization.minimizer里面,效果一样的
            new CssMinimizerPlugin(),
            // 当生产模式会默认开启TerserPlugin(压缩js),但是我们需要进行其他配置,就要重新写了
            new TerserPlugin({
                parallel: threads // 开启多进程
            }),
            // 压缩图片
            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",
                                            },
                                        },
                                    ],
                                },
                            ],
                        ],
                    },
                },
            }),
        ],
        // 代码分割配置(单独打包为js/css文件)
        splitChunks: {
            chunks: "all", // 对所有模块都进行分割(包括node_modules和复用代码单独打包)
            // 其他内容用默认配置即可
        // 以下是默认值
            // minSize: 20000, // 分割代码最小的大小(代码大于这个就可以分割为单独的js文件)
            // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
            // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割(至少在1个文件中引入过这段代码就可以被分割为单独js文件)
            // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
            // maxInitialRequests: 30, // 入口js文件最大并行请求数量
            // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
            // cacheGroups: { // 组,哪些模块要打包到一个组
            //   defaultVendors: { // 组名
            //     test: /[\/]node_modules[\/]/, // 需要打包到一起的模块
            //     priority: -10, // 权重(越大越高)
            //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
            //   },
            //   default: { // 其他没有写的配置会使用上面的默认值(这里写了的配置就会覆盖上面的默认配置)
            //     minChunks: 2, // 这里的minChunks权重更大
            //     priority: -20,
            //     reuseExistingChunk: true,
            //   },
            // },
            // 修改配置
            //配置第三方依赖的分包
            cacheGroups: {
                // layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
                // 可以单独打包,从而复用
                // 如果项目中没有,请删除
                // layouts: {
                //     name: "layouts",
                //     test: path.resolve(__dirname, "../src/layouts"),
                //     priority: 40,
                // },
                // 如果项目中使用element-plus,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
                // 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
                // 如果项目中没有,请删除
                elementUI: {
                    name: "elementPlus-chunk",
                    //正则匹配node_modules下的element-plus,将该依赖模块单独打包
                    test: /[\/]node_modules[\/]_?element-plus(.*)/,
                    priority: 30,
                },
                // 将vue相关的库单独打包,减少node_modules的chunk体积。
                vue: {
                    name: "vue-chunk",
                    //正则匹配node_modules下的vue,将该依赖模块单独打包
                    test: /[\/]node_modules[\/]_?vue(.*)/,
                    //chunks: "initial",
                    priority: 40,
                },
                libs: {
                    name: "libs-chunk",
                    //正则匹配node_modules下的其他所有包,将匹配到的所有依赖模块统一打包
                    test: /[\/]node_modules[\/]_?/,
                    priority: 10, // 权重最低,优先考虑前面内容
                    //chunks: "initial",
                },
            },
        },
        // 提取runtime文件(解决某个文件改变导致文件名的hash值改变而引起的所有依赖了这个文件的文件都会改变的问题)
        runtimeChunk: {
            name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
        },
    },
    //开发环境下服务器
    devServer: {
        //启动生产环境下的服务器,方便生产环境下修改项目实时更新启动项目
        //只是在开发环境将项目临时打包到内存中然后运行在开启的服务器上,生产环境还是需要手动打包dist文件夹
        host: 'localhost',
        port: 3000,
        open: false,  //是否自动打开浏览器
        hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)HMR可以修改代码后对应修改那个模块,而不是修改整个项目
        historyApiFallback: true, // 解决vue-router history模式刷新404问题
    },
    //关闭性能分析,加快打包速度
    performance: false,
}

babel.config.js

2、项目根目录创建babel.config.js配置babel规则

// babel.config.js
module.exports = {
    //继承vue的babel规则
    presets: ["@vue/cli-plugin-babel/preset"],
};

.eslintrc.js、.eslintignore

3、项目根目录创建.eslintrc.js配置eslint校验规则、.eslintignore配置eslint忽略文件

// .eslintrc.js
module.exports = {
    root: true,
    env: {
        node: true,
    },
    //继承vue3和eslint的规则
    extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
    parserOptions: {
        parser: "@babel/eslint-parser",
    },
};
# .eslintignore
# 忽略dist目录下所有文件
dist

package.json

4、webpack.config.js中的plugin及vue相关的loader需要install安装,package.json配置如下:

{
  "name": "webpack5-vuecli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack --config ./build/webpack.config.js",
    "dev": "cross-env NODE_ENV=development webpack serve --config ./build/webpack.config.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/eslint-parser": "^7.18.2",
    "@vue/cli-plugin-babel": "^5.0.4",
    "@vue/preload-webpack-plugin": "^2.0.0",
    "babel-loader": "^8.2.5",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.1",
    "css-minimizer-webpack-plugin": "^4.0.0",
    "eslint": "^8.17.0",
    "eslint-plugin-vue": "^9.1.0",
    "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",
    "mini-css-extract-plugin": "^2.6.0",
    "postcss-loader": "^7.0.0",
    "postcss-preset-env": "^7.7.1",
    "sass": "^1.52.2",
    "sass-loader": "^13.0.0",
    "terser-webpack-plugin": "^5.3.3",
    "unplugin-auto-import": "^0.8.7",
    "unplugin-vue-components": "^0.19.6",
    "vue-loader": "^17.0.0",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.6.14",
    "webpack": "^5.73.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.9.2",
    "workbox-webpack-plugin": "^6.5.3"
  },
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ],
  "dependencies": {
    "element-plus": "^2.2.5",
    "vue": "^3.2.37",
    "vue-router": "^4.0.15"
  }
}

配置结束后就可以在项目中使用vue了,如果还需要其他的plugin/loader或者配置,可以自行查阅官网添加