从入门到入土使用Webpack5搭建一个项目

339 阅读7分钟

引言

在日常开发中,我们开发一个项目用到的都是VueReact等官方出的脚手架;或者团队内部自定义的脚手架。脚手架中封装了许多功能,但是如果没有特别好的文档的话,我们要通过脚手架去自定义的话就会发现有很多功能的定义对我们并不友好,这里本着让大多数人从入门到入土使用Webpack5搭建一个项目,让更多人了解脚手架的内部功能。

技术选型

yarn:包管理器

Webpack:什么还有人不知道Webpack是啥,但针对前端开发者而言,估计工作当中每时每刻都在有意或无意的接触着Webpack。可以看这里哦!!!

ESLint:这是一个提供插件化的javascript代码检测工具。它能让你在团队协作开发的时候,统一代码风格。

Prettier:让你在ESLint的前提下格式化代码的一个插件工具。

Babel:一个用来转译ECMAScript 2015+ 至可兼容浏览器的版本工具。

husky + lint-staged:用来构建代码检查工作流。

Postcss:故名思意是用来处理CSS的工具。

搭建项目

项目出生

新建一个项目webpack-project,进入项目根目录,打开命令行使用yarn init -y创建package.json

image-20210911195520406

安装webpack@5.46.0webpack-cli@4.7.2webpack-merge@5.7.3webpack-dev-server@3.11.2webpack-bundle-analyze

yarn add -D webpack@5.46.0 webpack-cli@4.7.2 webpack-merge@5.7.3 webpack-dev-server@3.11.2 compression-webpack-plugin

开发环境配置

新建配置文件webpack.dev.js

mode

用来指定当前环境

module.exports = merge(common, {
    mode: "development",
});

devtool

用来指定生成sourceMap的格式

module.exports = merge(common, {
    devtool: "eval-cheap-module-source-map",
});

解析图片与字体

配置解析图片与字体(开发与生产环境下publicPath可能会有不同所以分开配置)

module.exports = merge(common, {
    module: {
        rules: [
            // 解析图片
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: "asset",
                generator: {
                    publicPath: "/",
                    filename: "images/[hash][ext][query]",
                },
            },
            // 解析字体
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                type: "asset",
                generator: {
                    publicPath: "/",
                    filename: "fonts/[hash][ext][query]",
                },
            },
        ],
    },
});

优化压缩JS文件

通过terser-webpack-plugin插件来压缩JS文件

const TerserWebpackPlugin = require("terser-webpack-plugin"); // 优化压缩js文件 webpack5内置
module.exports = merge(common, {
    optimization: {
        minimizer: [new TerserWebpackPlugin()],
    },
});

开启一个服务

在开发环境中我们需要时刻观察页面的变化与逻辑,那么开启一个热更新服务是很有必要的,只要你更新了代码,就会实时刷新页面,那么就需要用到webpack-dev-server这个工具啦。

module.exports = merge(common, {
    // 为了解决添加了browserslist文件出现无法热更新的问题(https://github.com/webpack/webpack-dev-server/issues/2758)
    // https://github.com/webpack/webpack-dev-server/issues/2758 已关闭,但webpack并未解决此bug,必须添加该语句
    target: "web",
    devServer: {
        compress: true, // 开启gzip压缩
        open: true,
        port: 9000,
        hot: true,
    },
});

生产环境配置

新建配置文件webpack.prod.js

mode

用来指定当前环境

module.exports = merge(common, {
    mode: "production",
});

devtool

用来指定生成sourceMap的格式

module.exports = merge(common, {
    devtool: "source-map",
});

解析图片与字体

配置解析图片与字体(开发与生产环境下publicPath可能会有不同所以分开配置)

module.exports = merge(common, {
    module: {
        rules: [
            // 解析图片
            {
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                type: "asset",
                generator: {
                    publicPath: "/dist/",
                    filename: "images/[hash][ext][query]",
                },
            },
            // 解析字体
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                type: "asset",
                generator: {
                    publicPath: "/dist/",
                    filename: "fonts/[hash][ext][query]",
                },
            },
        ],
    },
});

优化压缩JS文件

通过terser-webpack-plugin插件来压缩JS文件(删除console)

const TerserWebpackPlugin = require("terser-webpack-plugin"); // 优化压缩js文件 webpack5内置
module.exports = merge(common, {
    optimization: {
        minimizer: [
            new TerserWebpackPlugin({
                terserOptions: {
                    compress: {
                        drop_console: true,
                    },
                },
            }),
        ],
    },
});

开启GZIP压缩,减小请求压力

通过compression-webpack-plugin插件开启gzip压缩

const CompressionWebpackPlugin = require("compression-webpack-plugin"); // gzip压缩
module.exports = merge(common, {
    plugins: [
        /* 开启gzip压缩 */
        new CompressionWebpackPlugin({
            test: /\.js$|\.html$|\.css/,
            include: undefined,
            exclude: undefined,
            algorithm: "gzip",
            compressionOptions: { level: 9 },
            threshold: 0,
            minRatio: 0.8,
            filename: "[path][base].gz",
            deleteOriginalAssets: false, // 项目上线时改为true,为了开启打包文件分析工具必须改为false
        }),
    ],
});

接下来就是一个项目的主要配置,在webpack.common.js中我们需要配置entryoutput、通用module.rulesresolve、通用optimization、通用plugins

通用配置

entry

配置页面的入口文件。

const path = require("path");
module.exports = {
    entry: {
        page1: {
            import: path.resolve(__dirname, "src/pages/page1/index.js"),
        },
        page2: {
            import: path.resolve(__dirname, "src/pages/page2/index.js"),
        },
    },
};

output

配置项目打包后js文件的输出目录

const path = require("path");
module.exports = {
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "js/[name].js",
        // webpack5.20+ 功能:CleanWebpackPlugin
        clean: true,
    },
};

通用module.rules

配置通用rules。

const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 将css文件提取出来
// 使用 mini-css-extract-plugin 需要在 plugins 中引入
module.exports = {
    module: {
        rules: [
            // 解析html,需要安装 html-loader,通过 yarn add -D html-loader安装, 无需引入
            {
                test: /\.(html|htm)$/i,
                use: ["html-loader"],
            },
            // 解析css,需要安装 postcss-loader、css-loader,通过 yarn add -D html-loader css-loader 安装, 无需引入
            {
                test: /\.css$/i,
                use: [
                    // "style-loader",
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    "postcss-loader",
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // "style-loader",
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    "postcss-loader",
                    "sass-loader",
                ],
            },
            {
                test: /\.less$/i,
                use: [
                    // "style-loader",
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    "postcss-loader",
                    "less-loader",
                ],
            },
            {
                test: /\.styl$/i,
                use: [
                    // "style-loader",
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    "postcss-loader",
                    "stylus-loader",
                ],
            },
            // 解析js
            {
                test: /\.js$/,
                exclude: /(node_modules|lib)/,
                use: ["babel-loader"],
            },
        ],
    },
};

resolve

解析路径。

const path = require("path");

module.exports = {
    resolve: {
        alias: {
            "@": path.resolve(__dirname, "src"),
            vue$: "vue/dist/vue.min.js",
        },
        extensions: [".js", ".json", ".wasm"],
        modules: ["node_modules"],
        symlinks: false, // 减少 npm link 或 yarn link 解析工作量
    },
};

通用optimization

const HtmlMinimizerWebpackPlugin = require("html-minimizer-webpack-plugin"); // 优化压缩html文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin"); // 优化压缩css文件

module.exports = {
    optimization: {
        minimize: true, // 最小化bundle
        minimizer: [new HtmlMinimizerWebpackPlugin(), new CssMinimizerWebpackPlugin()],
        splitChunks: { // 分包,拆分模块
            chunks: "initial",
            cacheGroups: {
                vue: {
                    test: /[\\/]node_modules[\\/](vue)[\\/]/,
                    name: "vue",
                    priority: 0,
                    reuseExistingChunk: true,
                },
                defaultVendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10,
                    reuseExistingChunk: true,
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true,
                },
            },
        },
        runtimeChunk: "single", // 值single将创建一个运行时文件,用于共享所有生成的块。值multiple向每个入口点创建一个运行时文件
    },
};

通用plugins

const glob = require("glob");
const path = require("path");
const chalk = require("chalk");

const ProgressBarWebpackPlugin = require("progress-bar-webpack-plugin"); // 进度条

const HtmlWebpackPlugin = require("html-webpack-plugin"); // 配置html
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // 将css文件提取出来
const PurgecssWebpackPlugin = require("purgecss-webpack-plugin"); // css摇树,只打包实际用到的代码

const HtmlMinimizerWebpackPlugin = require("html-minimizer-webpack-plugin"); // 优化压缩html文件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin"); // 优化压缩css文件

const ESLintWebpackPlugin = require("eslint-webpack-plugin"); // 用于报告不符合规范的代码

module.exports = {
    plugins: [
        new ESLintWebpackPlugin({}),
        new HtmlWebpackPlugin({
            title: "页面1", // 使用了 html-loader 无效 (<%= htmlWebpackPlugin.options.title %>)
            filename: "index.html", // 打包后的文件名
            template: path.resolve(__dirname, "src/pages/page1/index.html"), // 打包的 html
            chunks: ["page1"], // 对于 entry 配置
        }),
        new HtmlWebpackPlugin({
            title: "页面2",
            filename: "page2.html",
            template: path.resolve(__dirname, "src/pages/page2/index.html"),
            chunks: ["page2"],
        }),
        new MiniCssExtractPlugin({
            filename: "css/[name].css",
        }),
        new PurgecssWebpackPlugin({
            paths: glob.sync(`${path.resolve(__dirname, "src")}/**/*`, { nodir: true }),
        }),
        new ProgressBarWebpackPlugin({
            format: `:msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
        }),
    ],
};

ESLint + Prettier

这个组合相信大家在熟悉不过了吧,通过这个可以让你的代码变得优雅,像诗一般,接下来我就带大家了解它的魅力吧。

首先我们先安装ESLint,我们来到ESLint官网使用指南,根据官网指南安装并使用ESLint。

# 我们打开webpack-project的命令行窗口,安装ESLint
yarn add eslint --dev
# 使用ESLint,会在项目中自动生成一个配置文件
yarn run eslint --init

选择你想如何使用ESLint,一般选择第二个,语法检查与问题检查,代码风格则交给我们的Prettier啦

image-20210912123025476

选择你项目中使用的类型模块,在ES6以前肯定选择CommonJS,但是在今天我们的选择肯定是选择JS modules啦

image-20210912123409474

选择你项目使用什么框架,ESLint会针对框架给出最优的配置,在这里我选择第三种。

image-20210912123657438

选择是否使用TS编程,这里选择No,有兴趣的同学可以试试配置TS。

image-20210912124105574

选择你项目中代码运行的环境,一般Browser和Node都需要选择,如果不选择Node在使用module.exports时会出现ESLint的未定义变量错误。

image-20210912124227413

选择配置文件的类型一般选择js文件类型。

image-20210912124528242

这样我们就通过ESLint,生成了我们的配置文件啦,这里附上配置文件截图:

image-20210912124910032

我们可以看到extends中有一个plugin:prettier/recommended的选项,它是什么意思呢,它是在安装了eslint-config-prettiereslint-plugin-prettier后的配置ESLint+Prettier共同协作的简便写法,具体可以在这里查看。

你以为到这里结束了吗,可别忘了你还没有安装prettier呢,我们需要通过yarn add -D prettier进行安装,安装之后我们就可以通过yarn eslint --fix来解决代码格式化问题啦。

当然我们也可以对prettier进行配置,我们在项目根目录中新建.prettierrc.js配置文件,在这里就不过多说明啦,这里附上我常用的配置:

image-20210912130716209

详细说明可以到这里

当然你还可以添加代码给是忽略文件,.eslintignore与.prettierignore它们的用法度大同小异,与.gitignore也相似。所以就不做过多说明啦。

配置完ESLint+Prettier了,那应该配置什么呢,从上面我们可以知道我们需要执行yarn eslint --fix才能帮助我们解决代码格式化问题,但是这多多少少会有些麻烦,这就引出了我们下面的主角啦!

husky + lint-staged

使用husky + lint-staged可以在我们提交代码之前,做一些事情,比如代码风格检查啊、代码格式化啊,这样就可以省去我们少敲命令行的时间啦。

# 安装 husky + lint-staged
yarn add husky lint-staged -D

配置lint-staged,我们进入package.json文件在文件中键入,如下配置:

{
    "lint-staged": {
        "*.js": [
            "eslint --fix"
        ]
    }
}

这样它就会在提交代码时检查js文件,并对js文件执行代码风格检查啊、代码格式化。

配置husky,我们需要在命令行中键入yarn husky install,执行成功后你的项目中会出现.husky文件夹。如下所示:

image-20210912154218143

Babel

配置Babel,新建Babel文件babel.config.js,如下图所示:

image-20210912154509569

详细的配置内容我们可以去看官方文档。

PostCSS

配置PostCSS,新建PostCSS文件postcss.config.js,如下图所示:

image-20210912154705422

详细配置插件可以看官方文档。有关与postcss的插件可以到这里查看。

总结

image-20210912155155275

最终搭建出来一个比较完善的项目结构。在写公司项目的时候我们关注的是src下面的目录与文件,往往却忽视了src以外的文件目录,而这些往往是相当重要的,这些涉及了各种性能的调优,制定团队协作的规范,所以我觉得我们有必要去从零搭建一个项目,希望对大家有所帮助。