引言
该项目会以 React 全家桶 (会使用 16.8 最新 API 及 hooks) 以及 mobx 数据流方案为基础打造的一款高质量的移动端音乐类 WebApp 。 涉及的技术栈主要有:
- react v16.8 全家桶 (react,react-router) : 用于构建用户界面的 MVVM 框架
- mobx 前端数据流方案
- immutable: Facebook 历时三年开发出的进行持久性数据结构处理的库
- axios: 用来请求后端 api 的数据。
这是系列文章,为了大家阅读方便,我会列举出系列文章的目录。
系列文章目录
重要说明
!本项目的仓库为happy-music。其中master分支为最新的所有代码,每次更新文章都会对应一个tag,tag是自增的,本篇文章对应的代码tag为v1.0.3。
本文内容介绍
本篇主要讲述webpack 项目初始化配置。如果是webpack的大拿,就可以跳过这片文章了。这篇文章的主要内容有:
- webpack4构建项目(建议新手不要轻易使用create-react-app等脚手架,前端进阶,webpack基本是绕不开的);
- babel7转译es6、react语法;
- 配置eslint;
- 配置commit-lint,约束提交的信息
- 配置changelog-cli自动生成可读性高的changelog

啰里八嗦的终于来到了正文部分了!!!
项目配置
先给项目取一个响亮的名字,奉行快乐优先原则,就叫happy-music吧。
webpack4配置不细说,具体的直接看官网就可以了,这里只列出一些重点。创建如下文件结构,其中webpack.common.js用于抽离公用配置,webpack.dev.js和webpack.prod.js分别对应开发和线上环境。

yarn add -D webpack webpack-cli webpack-merge
loader
webpack的作用都在官网这张图上,就是将各种模块进行处理和打包。而loader就是处理webpack中的模块。

配置babel-loader
我们项目主要使用react和ts编写,babel7之后也支持对ts转义了,之前需要使用ts-loader。 安装如下依赖:
yarn add @babel/core @babel/preset-env @babel/preset-typescript @babel/preset-react babel-loader -D
在项目根目录创建.babelrc文件:
{
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
}
根目录创建tsconfig.json文件
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"*": ["types/*"]
},
"target": "es5",
"module": "commonjs",
"sourceMap": true,
"emitDecoratorMetadata": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true, // 为 false 时,如果编译器无法根据变量的使用来判断类型时,将用 any 类型代替。为 true 时,进行强类型检查,会报错
"suppressImplicitAnyIndexErrors": true,
"allowSyntheticDefaultImports": true,
"allowJs": true,
"checkJs": false,
"jsx": "react",
"lib": ["dom", "es2015"],
"outDir": "./dist/",
"typeRoots": ["./node_modules/@types/", "./src/@types/"]
},
"compileOnSave": false,
"exclude": ["node_modules"]
}
配置less-loader
我们项目主要使用less编写,所以需要使用less-loader。使用的loader主要如下:
less-loader: 将less转义成css
css-loader: 用于加载.css文件,并转换成commonjs对象
style-loader: style-loader用于将<style>标签插入到header中
postcss-loader: 作用有两个,第一个就是把 CSS 解析成 JavaScript 可以操作的抽象语法树结构(Abstract Syntax Tree,AST),第二个就是调用插件来处理 AST 并得到结果,用的最多的是autoprefixer插件,可以自动添加浏览器前缀,保证css兼容性的写法。
style-resources-loader: 主要用于将一些公用less,自动插入到所有less文件中。
我们把lessLoader抽象成了一个函数,方便在需要的地方调用。
/*
* @param lessPath 包含的路径
* @param isModules 是否需要添加modules
*/
var lessLoader = function (lessPath, isModules) {
return {
test: /\.less$/,
include: lessPath,
loaders: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: isModules
? '[name]__[local]__[hash:base64:5]'
: '[name]'
}
},
},
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
browsers: ['last 5 versions'],
}),
],
},
},
{
loader: 'less-loader',
options: {
globalVars: globalVars,
},
},
{
loader: 'style-resources-loader',
options: {
patterns: path.resolve(
paths.srcPath,
'style',
'var',
'*.less',
),
injector: 'append',
},
},
],
};
};
这里在啰嗦几句,开启了css-modules后,我们写css类异常恶心。主要缺点如下:
- 必须使用驼峰法来命名css类名
- 当引入到 className 中时必须要使用 styles 对象
- CSS modules 和 全局css类混合在一起会很难管理
- 引用没用定义的CSS modules不会出现警告
import { Component } from 'react';
import styles from './style.less';
export default class Container extends Component {
render() {
return (
<div className={ styles.container }>
</div>
);
}
}
// style.less
.container {
border-width: 2px;
border-style: solid;
border-color: brown;
padding: 0 20px;
margin: 0 6px;
max-width: 400px;
}
这时我们可以使用react-css-modules来解决以上问题,这样就舒服多了。配置babelrc.js
{
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"],
"plugins": [
[
"react-css-modules",
{
"filetypes": {
".less": {
"syntax": "postcss-less"
}
},
"webpackHotModuleReloading": true,
"generateScopedName": "[name]__[local]__[hash:base64:5]",
"exclude": "node_modules"
}
]
]
}
import { Component } from 'react';
import './style.less';
export default class Container extends Component {
render() {
return (
<div styleName="container">
</div>
);
}
}
// style.less
.container {
border-width: 2px;
border-style: solid;
border-color: brown;
padding: 0 20px;
margin: 0 6px;
max-width: 400px;
}
还有其他使用到loader这里就不一一列举了,详情请参考代码happy-music。
插件plugin
html-webpack-plugin
html-webpack-plugin能够根据我们提供的模板自动生成html文件,并引入打包后的内容。 首先安装html-webpack-plugin:
yarn add html-webpack-plugin -D
在根目录创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Happy Music</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
配置html-webpack-plugin:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
file: 'index.html',
template: 'index.html',
inject: "body"
})
],
}
webpack-dev-server启动本地开发服务
在日常的开发过程中,肯定不能每修改一点东西就重新build一次,这样开发效率会受到很大的影响。这时需要启动一个服务,来监听文件的变动。当文件保存时就重新打包,同时帮我们自动刷新浏览器,方便我们及时观察到更新。
虽然webpack提供了webpack --watch的命令来动态监听文件的改变并实时打包,输出新bundle.js文件,这样文件多了之后打包速度会很慢,此外这样的打包的方式不能做到hot replace,即每次webpack编译之后,你还需要手动刷新浏览器。
webpack-dev-server功能可以克服上面的2个问题。webpack-dev-server的原理:启动一个使用express的Http服务器。它的作用主要是用来伺服资源文件。此外这个Http服务器和client使用了websocket通讯协议,原始文件作出改动后,webpack-dev-server会实时的编译,但是最后的编译的文件并没有输出到目标文件夹。
注意:webpack-dev-server后,在目标文件夹中是看不到编译后的文件的,实时编译后的文件都保存到了内存当中。 具体配置可以直接看官方文档
安装webpack-dev-server:
yarn add webpack-dev-server -D
在webpack.development文件配置devServer:
module.exports = {
devServer: {
open: true,
inline: true,
contentBase: path.resolve(paths.appPath, 'public'),
port: config['webapck-dev-server'].port || 8080, // 我们把这些配置抽离到 config目录是为了修改方便
host: config['webapck-dev-server'].host || '0.0.0.0',
proxy: {
[config.appPathname]: {
target: 'http://127.0.0.1:' + config.port,
pathRewrite: { ['^' + config.appPathname]: '' },
},
},// 代理主要用于解决跨域问题
publicPath: '',
hot: true,
disableHostCheck: true,
watchOptions: {
ignored: /node_modules/,
poll: false,
},
}
}
配置eslint
ESLint 可以安装在当前项目中或全局环境下,因为代码检查是项目的重要组成部分,所以我们一般会将它安装在当前项目中。可以运行下面的脚本来安装:
yarn add eslint -D
由于 ESLint 默认使用 Espree 进行语法解析,无法识别 TypeScript 的一些语法,故我们需要安装 @typescript-eslint/parser,替代掉默认的解析器,别忘了同时安装 typescript:
yarn add typescript @typescript-eslint/parser -D
接下来需要安装对应的插件 @typescript-eslint/eslint-plugin 它作为 eslint 默认规则的补充,提供了一些额外的适用于 ts 语法的规则。
yarn add @typescript-eslint/eslint-plugin
根目录创建.eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
// 定义规则
}
}
vscode 集成 ESLint 检查
在编辑器中集成 ESLint 检查,可以在开发过程中就发现错误,甚至可以在保存时自动修复错误,极大的增加了开发效率。 要在 VSCode 中集成 ESLint 检查,我们需要先安装 ESLint 插件。 VSCode 中的 ESLint 插件默认是不会检查 .ts 后缀的,需要在「文件 => 首选项 => 设置 => 工作区」中(也可以在项目根目录下创建一个配置文件 .vscode/settings.json),添加以下配置:
{
"eslint.autoFixOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
}
],
"typescript.tsdk": "node_modules/typescript/lib"
}
常见错误

module.exports = {
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
ecmaFeatures: {
modules: true
}
}
}
使用prettier格式化代码
Prettier 聚焦于代码的格式化,通过语法分析,重新整理代码的格式,让所有人的代码都保持同样的风格。根目录创建.prettierrc.js
module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 使用 4 个空格缩进
tabWidth: 4,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: 'none',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// 换行符使用 lf
endOfLine: 'lf'
};
接下来安装 VSCode 中的 Prettier 插件,然后修改 .vscode/settings.json:
{
"files.eol": "\n",
"editor.tabSize": 4,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.autoFixOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
}
],
"typescript.tsdk": "node_modules/typescript/lib"
}
提交前自动 eslint 校验和 commit 信息的规范校验
涉及的插件主要有: husky: 一个 Git Hook 工具 lint-staged: 用于实现每次提交只检查本次提交所修改的文件 @commitlint/cli: commit msg 检查 @commitlint/config-conventional: 配置commit规则
yarn add husky lint-staged @commitlint/cli @commitlint/config-conventional -D
根目录创建.huskyrc文件,当然这个也可以直接配置在package.json文件:
{
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
根目录创建 .lintstagedrc文件
{
"*.tsx": ["eslint --fix", "git add"],
"*.ts": ["eslint --fix", "git add"]
}
根目录创建 commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // 新功能
'modify', // 修改
'fix', // 修复bug
'docs', // 文档
'style', // 格式
'refactor', // 重构
'test', // 增加测试
'chore', // 构建过程或辅助工具的变动
'revert', //回滚
'upgrade' // 第三方库升级
],
],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never'],
},
};
自动changelog生成
安装conventional-changelog-cli插件,conventional-changelog-cli 默认推荐的 commit 标准是来自angular项目,除了 angular 标准以外,目前集成了包括 atom, codemirror, ember, eslint, express, jquery 等项目的标准,具体可以根据自己口味来选用。
yarn add conventional-changelog-cli -D
在根目录创建release.sh,这个命令主要用于需要正式发布时,在本地运行,可以自动生成新的版本号,已经根据commit信息生成changelog。
#!/bin/bash
# 可以直接设置为开发分支,origin为远程地址
master="dev"
origin=""
git fetch $origin
echo "Current fetch all Tags"
git pull $origin $master
echo "Current pull origin $master."
# 自动生成tag和修改版本号
npm version patch
conventional-changelog -p eslint -i CHANGELOG.md -s -r 0
git add CHANGELOG.md
git commit -m "docs: update changelog"
git push --follow-tags origin $master
echo "Git push origin $master"
echo "Release finished."
配置package.json,增加执行命令,在发布前可以在本地运行npm run beforepublish,就可以自动修改版本号、自动打tag及生成changelog。
"scripts": {
"beforepublish": "./release.sh"
}
完成了上述步骤,我们项目的初始化配置也基本告一段落了,基本可以正常跑起来了。
!本项目的仓库为happy-music。其中master分支为最新的所有代码,每次更新文章都会对应一个tag,tag是自增的,本篇文章对应的代码tag为v1.0.3。
结语
本篇文章主要讲述了整个项目的初始化过程,里面涉及的内容都很基础,但是是搭建系统不可或缺的一部分。大家可以关注我的微信公众号,会定期分享前端干货,共同成长。

@Author WaterMan