使用webpack配置一个项目(webpack+babel+less+vue+eslint+prettier+commitlint)

1,162 阅读9分钟

背景

身为一个前端开发人员,webpack几乎是我们日常项目开发必用的打包工具,但是因为职位或者不经常配置的原因,小编对其的了解一直模棱两可。在配置webpack的过程中磕磕绊绊,导致小编对配置webpack总是敬而远之,但是自己玩项目又离不开webpack打包工具,愁苦异常。今天痛定思痛,我决定自己研读webpack文档,辅之网上大神的一些文档,自己配置一个webpack的打包项目。 我的预期中项目使用的技术栈是 webpack(多页面打包),babel,less,vue 检测工具:eslint,prettier,husky,lint-stage。话不多说,开始实现。

1.前置工作

  • 先在本地安装个全局yarn?(小编喜欢使用yarn,并准备用这个进行接下来的配置)
  • 去github给自己搞个仓库,同步到本地?(学习和整理是必要的的,小编三年来看过好多资料,做过好多尝试,但是因为长期不用,又没有整理,导致那些知识吃了灰,又还给了搜索引擎😭)

2.webpack多页面项目初始化

文档 :webpack官网guide指南

  1. 拉取github的仓库地址(或者找一个空文件夹)
yarn init -y  // 初始化
yarn add webpack webpack-cli webpack-dev-server -D  // 安装webpack
yarn add webpack-merge html-webpack-plugin webpack-bundle-analyzer -D // 安装webpack配置合并 打包内容嵌入html 打包后文件分析相关
  1. 新建一个.gitignore文件 忽略node_modules dist

  2. 创建目录结构

image.png

  1. 新建src/public/index.html(打包生成的html文件以此为模板)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
</html>
  1. 根目录新建一个webpack.config.js,配置webpack.config.js
const path = require("path");
const { merge } = require("webpack-merge"); // webpack配置合并  需要 yarn add webpack-merge -D

const HtmlWebpackPlugin = require("html-webpack-plugin"); // 打包html  需要 yarn add html-webpack-plugin -D
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin; // 文件体积分析  需要 yarn add webpack-bundle-analyzer -D

const entryObj = {
  home: {
    title: "首页",
  },
  list: {
    title: "列表页",
  },
};

const getEntry = () => {
  const entry = {};
  Object.keys(entryObj).forEach(
    (v) => (entry[v] = `./src/pages/${v}/index.js`)
  );
  return entry;
};

const getHtmlWebpackPluginArr = () => {
  return Object.entries(entryObj).map(([key, val]) => {
    return new HtmlWebpackPlugin({
      title: val.title,
      filename: `${key}/index.html`,
      template: path.resolve(__dirname, "public/index.html"),
      chunks: [key, "common", "vendor"],
    });
  });
};

const commonConfig = {
  entry: getEntry(), // 入口文件配置  等价下方entry
  //   entry: {
  //     home: './src/pages/home/index.js',
  //     list: './src/pages/list/index.js',
  //   },  // 入口文件配置
  output: {
    filename: "[name]/index.js", // 打包文件名
    path: path.resolve(__dirname, "dist"), // 打包文件位置
    clean: true, // 每次打包前清空上次打包文件
  },
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource",
      }, // png等文件的处理
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: "asset/resource",
      }, // 字体文件的处理
    ],
  },
  plugins: [
    ...getHtmlWebpackPluginArr(), // 多页面文件打包 等价下面
    // new HtmlWebpackPlugin({
    //   title: "home", // 打包生成html文件的title
    //   filename: `home/index.html`, // 打包生成html文件的位置和名字
    //   template: path.resolve(__dirname, "public/index.html"), // 打包使用的html模板
    //   chunks: ["home"], // html中引入的文件
    // }),
    // new HtmlWebpackPlugin({
    //   title: "list", // 打包生成html文件的title
    //   filename: `list/index.html`, // 打包生成html文件的位置和名字
    //   template: path.resolve(__dirname, "public/index.html"), // 打包使用的html模板
    //   chunks: ["list"], // html中引入的文件
    // }),
  ],
  optimization: {
    runtimeChunk: "single", // 运行时代码 // 将 runtime 代码拆分为一个单独的 chunk。将其设置为 single 来为所有 chunk 创建一个 runtime bundle
    splitChunks: {
      cacheGroups: {
        common: {
          name: "common",
          chunks: "initial",
          minSize: 1,
          priority: 0,
          minChunks: 2, // 同时引用了2次才打包
        }, // 多页面共用的文件
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all",
        }, // node_modules的打包文件
      },
    },
  },
};

const proConfig = {
  mode: "production",
  devtool: "source-map",
  plugins: [new BundleAnalyzerPlugin()],
};

const devConfig = {
  mode: "development",
  devtool: "inline-source-map",
  devServer: {
    hot: true, // 热更新
    open: ["/home"], // 默认打开home/index.html
    client: {
      overlay: {
        errors: true, // 有错误展示弹框
        warnings: false, // 警告不展示
      },
    },
  },
};

module.exports = (env) => {
  return merge(commonConfig, env.production ? proConfig : devConfig);
};

  1. 配置package.json 创建打包和启动服务命令
// package.json
"scripts": {
    "build": "webpack --env production",
    "start": "webpack-dev-server",
    "server": "webpack-dev-server"
  },

到此为止webpack多页面配置就简单完成了。可以去yarn build / yarn serve去尝试一下

3.webpack配置babel

webpack安装babel

  1. 安装babel相关
yarn @babel/core @babel/preset-env babel-loader -D // babel转换器 babel转换预设 webpack配置的babel-loader
  1. 配置bebel
// webpack.config.js  放在commonConfig中吧
{
    test: /\.js$/,
    use: {
      loader: "babel-loader",
    },
    exclude: "/node_modules/",
}, // 使用babel解析文件

新建 babel.config.js

module.exports = {
  presets: ["@babel/preset-env"],
};

package.json添加项目兼容的浏览器版本

"browserslist": [
    "defaults",
    "not ie < 11",
    "last 2 versions",
    "> 1%",
    "last 3 iOS versions"
  ],
 // 这个很多地方都用到,添加在package.json里面做一个统一
  1. 检测babel成果
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(11111);
  }, 1000);
});

promise.then((data) => {
  console.log("data", data);
});

打包后转换
var promise = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve(11111);
  }, 1000);
});
promise.then(function (data) {
  console.log("data", data);
});

本来想查看一下promise转换相关,但是看了一下现在浏览器兼容程度,应该是全兼容了,所以我的@babel-runtime 一直安装不上,估计是废弃了。至此,babel相关就完成了。

4.webpack配置解析less文件

# Webpack——打包CSS / Less / Sass资源相关配置

  1. 安装相关的包
yarn add css-loader less less-loader -D  // 解析css文件,less文件
yarn add mini-css-extract-plugin // 将css提出,压缩
yarn add postcss-loader autoprefixer -D  // 加前缀,兼容浏览器使用
  1. 进行配置
// webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// modules rules
{
    test: /\.less$/i,
    use: [
      MiniCssExtractPlugin.loader,
      "css-loader",
      "less-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [require("autoprefixer")],  // 添加前缀
          },
        },
      },
    ],
 }, // 处理less文件
 
// plugins
new MiniCssExtractPlugin({
  filename: "css/[name].css",
})
  1. 检测效果
div{
    height: 300px;
    display: flex;
    background-color: #ededed;
    border: 1px solid #333;
    border-radius: 5px;
    span{
        color: red;
    }
}

打包后 dist/css/home.css
div {
  height: 300px;
  background-color: #ededed;
  border: 1px solid #333;
  border-radius: 5px;
}
div span {
  color: red;
}

启动yarn start 有增加 display: flex 的前缀 ,less配置完成

5.webpack 配置使用vue

# 基于webpack从0配置vue开发

这个大神写的超级nice,跟着文档做起来还是很丝滑的,有一点需要注意的是 vue和vue-template-compiler的版本要保证一致奥,让我们操练起来。

  1. 装包
 yarn add vue@2.6.14 vue-template-compiler@2.6.14 vue-loader@15.9.8 -D
 // vue   解析vue模板  webpack使用的loder
  1. 配置

(1) 配置vue.config.js

// webpack.config.js

const { VueLoaderPlugin } = require('vue-loader');

// modules.rules
{
    test: /\.vue$/,
    use: [
        {
            loader: 'vue-loader',
            options: {
                compilerOptions: {
                    preserveWhitespace: false,
                },
            },
        },
    ],
},

// plugins
new VueLoaderPlugin()

(2)调整文件结构

src/home 下新建views文件夹 文件夹下面新建 App.vue

//App.vue

<template>
  <div>
    跟着大神 做
    <span>{{ msg }}</span>
    的码农
  </div>
</template>

<script>
export default {
  name: "App",

  data() {
    return {
      msg: "有追求的",
    };
  },
};
</script>
<style lang="less" scoped>
div {
  height: 300px;
  display: flex;
  background-color: #ededed;
  border: 1px solid #333;
  border-radius: 5px;
  span {
    color: red;
  }
}
</style>


(3)src/home文件夹下 删除 index.less, 修改index.js

import Vue from "vue";

import App from "./views/App.vue";

new Vue({
  render: (h) => h(App),
}).$mount("#app");

(4) 修改src/public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    //添加vue文件之后绑定的根元素
    <div id="app"></div> 
</body>
</html>
  1. yarn start 启动服务查看运行效果

image.png

恭喜恭喜,webpack配置vue跑通了,下一个eslint,prettier

6.webpack配置解析 eslint,prettier

# 前端代码规范实践指南(ESLint,Prettier,Husky...)

大佬写的好,看懂了,配成功了

  1. 安装eslint
yarn add eslint -D
  1. 控制台执行
npx eslint --init

17204de4-9e59-450f-bc9f-bf0283035c44.png

  1. 新建.eslintignore
node_modules

dist
  1. 先不用处理报错,先添加prettier
yarn add prettier -D
yarn add eslint-plugin-prettier eslint-config-prettier -D  // 处理prettier 和 eslint冲突的方案

5.根目录创建.prettierrc.js文件

module.exports = {
    printWidth: 100, //一行的字符数,如果超过会进行换行,默认为80
    tabWidth: 4, //一个tab代表几个空格数,默认为80
    useTabs: false, //是否使用tab进行缩进,默认为false,表示用空格进行缩减
    semi: true, //行位是否使用分号,默认为true
    singleQuote: true, //字符串是否使用单引号,默认为false,使用双引号
};

6.创建.prettierignore 内容同.eslintignore

7.配置解决eslint和prettier冲突

// .eslintrc.js

module.exports = {
    env: {
        browser: true,
        commonjs: true,
        es2021: true,
    },
    extends: [
        'plugin:vue/essential',
        'standard',
        // 新增,必须放在最后面
        'plugin:prettier/recommended',  // 使eslint先解析 prettier的相关规则
    ],
    parserOptions: {
        ecmaVersion: 'latest',
    },
    plugins: ['vue'],
    rules: {},
};

8.在vscode设置setting.json使文件在保存的时候自动修复

//setting.json 添加

{
    "eslint.enable": true, //是否开启vscode的eslint
    "eslint.alwaysShowStatus": true,//是否在保存的时候自动fix eslint 
    "eslint.options": { //指定vscode的eslint所处理的文件的后缀
        "extensions": [
            ".js",
          	".vue",
          	".ts",
          	".tsx"
        ]
    },
    "eslint.validate": [ //确定校验准则
        "javascript",
    ],
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    }
}

9.找一个文件ctrl + s 保存一下,嗯嗯,没问题。 package.json添加一个eslint命令

// package.json

"scripts": {
    "build": "webpack --env production",
    "start": "webpack-dev-server",
    "server": "webpack-dev-server",
    "lint": "eslint ./src --fix"  // eslint格式化src下面的所有文件 (存量代码eslint格式化一下,总不能一个一个页面保存吧,哈哈)
},

eslint + prettier配置完成,要添加其他的规则,就在rules额外添加(再次声明大神的文章丝滑,易懂)

7.webpack配置husky和lintstaged

# 前端代码规范实践指南(ESLint,Prettier,Husky...)

  1. 作用和目的 在git提交流程中加入 eslint的相关检测(防止其他的同学的未被eslint格式化的代码提交到代码仓库)
  2. 安装和配置
// 需要在git项目中  不是远端仓库拉下来的代码,需要先 git init一下

1.//安装husky
yarn add husky -D

2.//添加 prepare 命令
npm set-script prepare "husky install"

3.// prepare 创建 bash 脚本,安装 git hooks
npm run prepare

4.// 添加 pre-commit 的 git hook 脚本
npx husky add .husky/pre-commit "npx eslint src --fix"


// 配置只格式化新开发的代码,不去格式化以前的代码

5.//安装 lint-taged
yarn add lint-staged -D

6.// 新建.lintstagedrc.js 配置文件,
module.exports = {
  '**/*.{ts,tsx,js,jsx}': [
    "eslint --cache --fix",
  ],
  "**/*.vue": [
    "eslint --cache --fix",
  ]
}

7.// 刚刚创建的 `./.husky/pre-commit` 里改成执行 `lint-staged` 命令:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

大佬写的好详细,直接借用了(亲测可以)

8.配置git提交流程规范(commitlint)

这个我自己来吧,看的文章太多了,(还把cz-customizable源码看了些),所以自己配吧

  1. 相关npm包介绍
@commitlint/cli
commitlint  // git commit 时对于 commit message 进行规范检查的工具,保证团队的一致性
@commitlint/config-conventional // commitlint 的常规配置
commitizen  // 基于Node.js的 git commit 命令行工具,辅助生成标准化规范化的 commit message
cz-customizable  // 适配器配置commitlint提交的交互  cz-conventional-changelog,git-cz也是,但是配置可能不一样,想用的话可以自己研究一下
  1. 安装相关依赖
yarn add @commitlint/cli @commitlint/config-conventional commitizen commitlint cz-customizable -D
  1. 新建commintlint.config.js
// 配置commintlint的检测规则
module.exports = {
    extends: ['@commitlint/config-conventional'],  // 常规规则配置
    rules: {
        'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert']],
        'subject-full-stop': [0, 'never'],
        'subject-case': [0, 'never'],
    },
};
  1. 新建 .cz-config.js(有个.)
// 配置提交交互流程(主要是替换cz-customizable里面的交互模板,可以看看node_modules源码,就能理解)
module.exports = {
    //可选类型
    types: [
        { value: 'feat ', name: 'feat: 新功能' },
        { value: 'fix  ', name: 'fix: 修复' },
        { value: 'docs ', name: 'docs: 文档变更' },
        { value: 'style', name: 'style: 代码格式(不影响代码运行的变动)' },
        {
            value: 'refactor',
            name: 'refactor: 重构(既不是增加feature),也不是修复bug',
        },
        { value: 'perf ', name: 'perf: 性能优化' },
        { value: 'test ', name: 'test: 增加测试' },
        // { value: 'chore ', name: 'chore: 构建过程或辅助功能的变动' },
        // { value: 'revert', name: 'revert: 回退' },
        // { value: 'build', name: 'build: 打包' },
    ],
    scopes: ['Views', 'Component', 'Style', 'Utils', 'Store', 'Router', 'Other'],
    //消息步骤(这里跳过的步骤,在下面添加)
    messages: {
        type: '请选择提交类型',
        scope: '请选择修改范围(可选)',
        subject: '请简要描述提交(必填)',
        body: '请输入详细描述(可选)',
        // footer: '请输入要关闭的issue(可选)',
        confirmCommit: '确认以上信息提交?(y/n)',
    },
    //跳过问题
    skipQuestions: ['footer'],
    //subject文字长度默认是
    subjectLimit: 72,
};


// 这是源码里面的模板配置,我们的配置就是为了替换他们的这个文件

module.exports = {
  types: [
    { value: 'feat', name: 'feat:     A new feature' },
    { value: 'fix', name: 'fix:      A bug fix' },
    { value: 'docs', name: 'docs:     Documentation only changes' },
    {
      value: 'style',
      name: 'style:    Changes that do not affect the meaning of the code\n            (white-space, formatting, missing semi-colons, etc)',
    },
    {
      value: 'refactor',
      name: 'refactor: A code change that neither fixes a bug nor adds a feature',
    },
    {
      value: 'perf',
      name: 'perf:     A code change that improves performance',
    },
    { value: 'test', name: 'test:     Adding missing tests' },
    {
      value: 'chore',
      name: 'chore:    Changes to the build process or auxiliary tools\n            and libraries such as documentation generation',
    },
    { value: 'revert', name: 'revert:   Revert to a commit' },
    { value: 'WIP', name: 'WIP:      Work in progress' },
  ],

  scopes: [{ name: 'accounts' }, { name: 'admin' }, { name: 'exampleScope' }, { name: 'changeMe' }],

  allowTicketNumber: false,
  isTicketNumberRequired: false,
  ticketNumberPrefix: 'TICKET-',
  ticketNumberRegExp: '\\d{1,5}',

  // it needs to match the value for field type. Eg.: 'fix'
  /*
  scopeOverrides: {
    fix: [

      {name: 'merge'},
      {name: 'style'},
      {name: 'e2eTest'},
      {name: 'unitTest'}
    ]
  },
  */
  // override the messages, defaults are as follows
  messages: {
    type: "Select the type of change that you're committing:",
    scope: '\nDenote the SCOPE of this change (optional):',
    // used if allowCustomScopes is true
    customScope: 'Denote the SCOPE of this change:',
    subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
    body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
    breaking: 'List any BREAKING CHANGES (optional):\n',
    footer: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n',
    confirmCommit: 'Are you sure you want to proceed with the commit above?',
  },

  allowCustomScopes: true,
  allowBreakingChanges: ['feat', 'fix'],
  // skip any questions you want
  skipQuestions: ['body'],

  // limit subject length
  subjectLimit: 100,
  // breaklineChar: '|', // It is supported for fields body and footer.
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true, // default is false
};

  1. package.json 添加代码
"scripts": {
   ...
    "commit": "git add --all && git-cz"
},
"config": {
    "commitizen": {
        "path": "node_modules/cz-customizable"
    }
},
  1. yarn commit 就可以提交了

小结

至此webpack配置项目已经完成了,献上我的代码仓库,大家有兴趣可以下下来看看,有错误的地方希望大家提出指正。

使用vue-cli搭建一个vue2的项目框架 编写完成,大家有兴趣可以看看

文章代码仓库

参考文章