背景
身为一个前端开发人员,webpack几乎是我们日常项目开发必用的打包工具,但是因为职位或者不经常配置的原因,小编对其的了解一直模棱两可。在配置webpack的过程中磕磕绊绊,导致小编对配置webpack总是敬而远之,但是自己玩项目又离不开webpack打包工具,愁苦异常。今天痛定思痛,我决定自己研读webpack文档,辅之网上大神的一些文档,自己配置一个webpack的打包项目。 我的预期中项目使用的技术栈是 webpack(多页面打包),babel,less,vue 检测工具:eslint,prettier,husky,lint-stage。话不多说,开始实现。
1.前置工作
- 先在本地安装个全局yarn?(小编喜欢使用yarn,并准备用这个进行接下来的配置)
- 去github给自己搞个仓库,同步到本地?(学习和整理是必要的的,小编三年来看过好多资料,做过好多尝试,但是因为长期不用,又没有整理,导致那些知识吃了灰,又还给了搜索引擎😭)
2.webpack多页面项目初始化
- 拉取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 打包后文件分析相关
-
新建一个.gitignore文件 忽略node_modules dist
-
创建目录结构
- 新建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>
- 根目录新建一个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);
};
- 配置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
- 安装babel相关
yarn @babel/core @babel/preset-env babel-loader -D // babel转换器 babel转换预设 webpack配置的babel-loader
- 配置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里面做一个统一
- 检测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资源相关配置
- 安装相关的包
yarn add css-loader less less-loader -D // 解析css文件,less文件
yarn add mini-css-extract-plugin // 将css提出,压缩
yarn add postcss-loader autoprefixer -D // 加前缀,兼容浏览器使用
- 进行配置
// 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",
})
- 检测效果
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
这个大神写的超级nice,跟着文档做起来还是很丝滑的,有一点需要注意的是 vue和vue-template-compiler的版本要保证一致奥,让我们操练起来。
- 装包
yarn add vue@2.6.14 vue-template-compiler@2.6.14 vue-loader@15.9.8 -D
// vue 解析vue模板 webpack使用的loder
- 配置
(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>
- yarn start 启动服务查看运行效果
恭喜恭喜,webpack配置vue跑通了,下一个eslint,prettier
6.webpack配置解析 eslint,prettier
# 前端代码规范实践指南(ESLint,Prettier,Husky...)
大佬写的好,看懂了,配成功了
- 安装eslint
yarn add eslint -D
- 控制台执行
npx eslint --init
- 新建.eslintignore
node_modules
dist
- 先不用处理报错,先添加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...)
- 作用和目的 在git提交流程中加入 eslint的相关检测(防止其他的同学的未被eslint格式化的代码提交到代码仓库)
- 安装和配置
// 需要在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源码看了些),所以自己配吧
- 相关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也是,但是配置可能不一样,想用的话可以自己研究一下
- 安装相关依赖
yarn add @commitlint/cli @commitlint/config-conventional commitizen commitlint cz-customizable -D
- 新建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'],
},
};
- 新建 .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
};
- package.json 添加代码
"scripts": {
...
"commit": "git add --all && git-cz"
},
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
},
- yarn commit 就可以提交了
小结
至此webpack配置项目已经完成了,献上我的代码仓库,大家有兴趣可以下下来看看,有错误的地方希望大家提出指正。
使用vue-cli搭建一个vue2的项目框架 编写完成,大家有兴趣可以看看