2021年从零开发前端项目指南
之前翻译过一篇 前端工程化发展历史 的文章,Webpack
、Babel
、Eslint
现在基本上就是前端项目的标配了。
但工作以后一般很少接触这些配置,都是在前人配置好的基础上去写业务代码。即使有机会从零配置一个项目,一般也不会自己手动建这些配置文件,直接用 create-react-app
、Ant Design Pro
等自动帮我们生成各个目录和配置文件就可以了,省时省力。
这篇文章的话就从零手动去配置一个前端项目,会涉及到 Webpack
、React
、Babel
、TypeScript
、Ant Design
、Sass
、Eslint
、Prettier
,本文的话就本着「不求甚解」的态度,主要过一下各个模块的使用,适合从零一步一步跟着操作。
前端工程化项目是建立在 node.js
环境下的,之后需要安装各个 npm
包,所以首先电脑必须已经配置好了 node
环境。
新建一个目录然后执行 npm init
来初始化一个项目。
npm init
然后一路回车就可以,只是生成了 package.json
文件,后续想改的话也能改。
Webpack
前端不断发展,但很多特性浏览器不一定会支持,ES6
模块,CommonJs
模块、Scss/less
、jsx
等等,通过 Webpack
我们可以将所有文件进行打包、压缩混淆,最终转换为浏览器识别的代码。
除了安装 Webpack
,我们需要安装对应的命令行工具 webpack-cli
,以及实现了热加载,也就是自动监听我们文件变化然后刷新网页的 webpack-dev-server
。
由于这些工具只在开发阶段使用,所以我们安装的时候可以加上 -D(--save-dev)
命令,这样开发环境就不会打包了。
npm i -D webpack webpack-cli webpack-dev-server
安装之后 package.json
会自动记录我们安装的 node
包,对应版本如下,如果安装的和我不一样的话,后边的一些配置可能略有不同。
{
...
"devDependencies": {
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.0.0"
}
}
接下来在根目录新建 webpack.config.js
进行项目的配置,主要配置入口文件,打包输目录,以及 devServer
的目录。
const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js'
},
devServer: {
static: path.resolve(__dirname, './dist')
}
}
新建一下上边相应的文件。
main.js
文件主要实现在网页写 hello world
。
// /src/main.js
document.write('hello world')
新建 dist
目录,在里边新建 index.html
文件,引入 <script src="bundle.js"></script>
。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>前端工程化</title>
</head>
<body>
<div id="app" />
<script src="bundle.js"></script>
</body>
</html>
最后在 package.json
新建两条命令,默认的 test
命令可以直接删掉了。
...
"scripts": {
"dev": "webpack-dev-server --mode development --open",
"build": "webpack --mode production"
},
...
执行 npm run dev
,此时会自动打开 http://localhost:8080/
。
React
React
可以让我们专注于构建用户界面,而不需要再手动维护 dom
元素的更新,当然还可以用 VUE
。
安装核心库 react
,以及渲染 Web
的 react-dom
。
npm i react react-dom
修改 src/main.js
体验一下。
// /src/main.js
import React from 'react';
import ReactDOM from 'react-dom';
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, { toWhat: 'World by React' }, null),
document.getElementById('app')
);
npm run dev
看下效果:
这里会发现上边都调用了 React.createElement
来创建元素,如果页面复杂的的话,那一层套一层就太繁琐了,React
为我们提供了 JSX
语法来简化写法。
让我们改写一下:
// /src/main.js
import React from 'react';
import ReactDOM from 'react-dom';
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World by jsx" />,
document.getElementById('app')
);
但此时会发现项目跑不起来了
现在,我们就需要 Babel
了。
Babel
babel
可以为我们把各种语法、新功能转换为浏览器所能识别的 js
。这里我们先安装一下 babel
以及在 webpack
中使用的 babel-loader
。
npm i -D @babel/core babel-loader
然后在 webpack
中引入 babel-loader
,用来对 js
进行转换,更改 webpack.config.js
文件。
const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
devServer: {
static: path.resolve(__dirname, './dist')
}
}
然后我们来安装 @babel/preset-react
来转换 jsx
语法。
npm i -D @babel/preset-react
在根目录新建 babel
的配置文件 babel.config.json
。
// babel.config.json
{
"presets": [
"@babel/preset-react"
]
}
此时再运行 npm run dev
就发现项目成功跑起来了!
然后我们还可以安装一些其他 babel
以便使用最新的 ES
语法,比如箭头函数、async await
、问号表达式等等, 需要什么就可以配置什么。当浏览器不支持这些特性时,babel
可以帮我们实现 polyfill
进行降级。
@babel/preset-env
包含了许多 ES
的新特性,core-js
实现 ployfill
,通过这两个 babel
各种 ES
最新的特性就都可以放心使用了,如果有不满足的我们可以单独配置 babel
的插件。
npm i -D @babel/preset-env core-js
然后我们再修改下 babel
的配置文件。
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
],
"@babel/preset-react"
],
"plugins": [
]
}
其中 useBuiltIns": "usage"
代表自动判断每个文件是否引入 ployfill
,corejs: 3
是指定版本。
TypeScript
越来越多的项目引入了 TypeScript
,尤其是规模比较大的项目,通过 ts
可以让一些 bug
提前暴露,平时自己开发的话也可以引入 ts
,提前了解学习。
项目引入 ts
的话有两种方式:
使用
TypeScript Compiler (TSC)
将ts
编译为ES5
以便能够在浏览器中运行。并且使用TSC
进行类型检查。使用
Babel
翻译TS
,使用TSC
进行类型检查。
这里的话使用第二种方式,让 Babel
和 TSC
各司其职。
首先安装 TypeScript
以及 React
的 type
。
npm i -D typescript @types/react @types/react-dom
根目录新建 tsconfig.json
进行 ts
的配置。
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": [
"dom"
],
"jsx": "react",
"noEmit": true,
"sourceMap": true,
/* Strict Type-Checking Options */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
},
"include": [
"src"
]
}
"noEmit": true,
表明 ts
只做类型检查,不进行编译输出。
然后我们将 src/main.js
修改为 src/main.tsx
,并且加上类型。
// /src/main.js
import * as React from 'react';
import * as ReactDOM from 'react-dom';
type Props = {
toWhat: string;
};
type State = {
};
class Hello extends React.Component<Props, State> {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World by jsx" />,
document.getElementById('app')
);
接下来进行 babel
的配置,安装 @babel/preset-typescript
,将我们代码从 ts
转为 js
。
npm i -D @babel/preset-typescript
babel
配置文件中加入。
// babel.config.json
{
"presets": [
"@babel/preset-typescript",
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
],
"@babel/preset-react"
],
"plugins": [
]
}
最后在 webpack.config.js
中 babel
匹配的路径中加入 tsx
。
const path = require('path')
module.exports = {
entry: './src/main.tsx',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|ts)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
resolve: {
// 引入模块的时候可以省略这些后缀
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},
devServer: {
static: path.resolve(__dirname, './dist')
}
}
我们可以全局安装一下 typescript
,便于使用 tsc
命令进行类型检查。
npm install -g typescript
可以运行一下 tsc -w
实时进行类型检查。
Ant Design
引入组件库,方便更快的开发。
npm install antd
顺便可以按照习惯把 main.tsx
中的 hello
组件抽离出来并且命名为 app.tsx
。
// /src/App.tsx
import * as React from 'react';
import { DatePicker } from 'antd';
type Props = {
toWhat: string;
};
type State = {
};
class App extends React.Component<Props, State> {
render(): JSX.Element {
return <div>
Hello {this.props.toWhat}
<div>
<DatePicker></DatePicker>
</div>
</div>;
}
}
export default App;
然后我们在 main.tsx
引入 antd
的 css
文件。
// /src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import App from './App'
ReactDOM.render(
<App toWhat="World by jsx" />,
document.getElementById('app')
);
此时就需要在 webpack.config.js
配置文件中补上 css
的 loader
,先安装一下。
npm i -D style-loader css-loader
css-loader
可以让我们在 js
中引入 css
,style-loader
帮我们将 css
以 style
标签的形式插入到页面。
安装好后进行配置 loader
。
const path = require('path')
module.exports = {
entry: './src/main.tsx',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|ts)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
resolve: {
// 引入模块的时候可以省略这些后缀
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},
devServer: {
static: path.resolve(__dirname, './dist')
}
}
然后就成功引入日期选择器了。
Sass
Sass
是 css
的预编译器,可以让我们写样式更顺手,具体特性可以参考 官网,我用的最多的就是可以嵌套形式写 css
,很方便。
我们安装一下 Sass
以及它的 loader
。
npm install sass-loader sass --save-dev
然后在 webpack.config.js
配置一下
const path = require('path');
module.exports = {
entry: './src/main.tsx',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.(js|ts)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
],
},
resolve: {
// 引入模块的时候可以省略这些后缀
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},
devServer: {
static: path.resolve(__dirname, './dist'),
},
};
在 App.jsx
加几个类名,引入 App.scss
。
// /src/App.tsx
import * as React from 'react';
import { DatePicker } from 'antd';
import './App.scss';
type Props = {
toWhat: string;
};
type State = {};
class App extends React.Component<Props, State> {
render(): JSX.Element {
return (
<div className="app">
<div className="text">Hello</div>
<div>{this.props.toWhat}</div>
<div>
<DatePicker></DatePicker>
</div>
</div>
);
}
}
export default App;
新建 App.scss
,添加颜色实验一下。
.app {
.text {
color: #f00;
}
}
npm run dev
看下效果
Eslint
可以配置 eslint
来进行语法上静态的检查,也可以对 ts
进行检查。
npm i eslint -D
可以全局安装一下 npm i -g npx
命令,能够更方便的运行 node_modules/.bin
目录下的命令.
不然的话我们要执行 eslint
命令的话需要执行 ./node_modules/.bin/eslint --version
才能取到。或者像上边为了执行 tsc
命令,全局安装了 typescript
。或者在 package.json
里边添加一个自定义命令。不过还是 npx
是最方便的。
让我们初始化 eslint
.
npx eslint --init
然后按照项目需要选择对应的选项,最后自动安装相应的依赖。
然后 eslint
就自动为我们生成了 .eslintrc.js
配置文件,顺便补一个 "node": true
,不然的话 module.exports
直接报错。
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node": true,
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
}
};
然后我们在 package.json
中可以添加一个 lint
命令来修复代码。
{
"name": "fe-learn",
"version": "1.0.0",
"description": "前端工程化项目学习",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --mode development --open",
"build": "webpack --mode production",
"lint": "eslint src --fix"
},
"author": "windliang",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"babel-loader": "^8.2.2",
"core-js": "^3.16.2",
"eslint": "^7.32.0",
"eslint-plugin-react": "^7.24.0",
"typescript": "^4.3.5",
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.0.0"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
然后执行 npm run lint
即可进行 eslint
的相关修复。
配合 Vscode
我们也可以做到边写代码边自动检测 eslint
,以及保存的时候自动修复 eslint
相关错误。
可以安装 Eslint
插件,以及在 vscode
的设置中加入以下配置,点击下图的右上角可以直接进行配置的编辑。
{
"eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
}
为了使用更完善的 eslint
配置,我们也可以直接引用腾讯 Alloy
团队的推荐配置,参考 这里。
Prettier
prettier
主要做代码风格上的检查,字符串双引号还是单引号?几个空格?类似这样的。
当然 eslint
也可以配置这些,但为了分离它们各自的职责,最好还是用 prettier
来格式化代码风格,先安装一下。
npm i -D prettier
然后新建一个配置文件 .prettierrc.js
,这里直接引用 腾讯 Alloy 团队推荐的配置。
// .prettierrc.js
module.exports = {
// max 120 characters per line
printWidth: 120,
// use 2 spaces for indentation
tabWidth: 2,
// use spaces instead of indentations
useTabs: false,
// semicolon at the end of the line
semi: true,
// use single quotes
singleQuote: true,
// object's key is quoted only when necessary
quoteProps: 'as-needed',
// use double quotes instead of single quotes in jsx
jsxSingleQuote: false,
// no comma at the end
trailingComma: 'all',
// spaces are required at the beginning and end of the braces
bracketSpacing: true,
// end tag of jsx need to wrap
jsxBracketSameLine: false,
// brackets are required for arrow function parameter, even when there is only one parameter
arrowParens: 'always',
// format the entire contents of the file
rangeStart: 0,
rangeEnd: Infinity,
// no need to write the beginning @prettier of the file
requirePragma: false,
// No need to automatically insert @prettier at the beginning of the file
insertPragma: false,
// use default break criteria
proseWrap: 'preserve',
// decide whether to break the html according to the display style
htmlWhitespaceSensitivity: 'css',
// vue files script and style tags indentation
vueIndentScriptAndStyle: false,
// lf for newline
endOfLine: 'lf',
// formats quoted code embedded
embeddedLanguageFormatting: 'auto',
};
同样的,为了保存的时候自动帮我们格式化,我们可以安装 Vscode
的 Prettier
插件,以及再修改 Vscode
的配置。
{
"files.eol": "\n",
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
总结
通过上边一系列的操作后,就可以开始愉快的开始写项目了,经验有限,上边有问题的地方还请大家指出。
上边的代码都比较零碎,可以在 github 上看整个代码,后台回复「前端配置」即可得到链接。
上边每一块都是一个很大的地方,未来的话会继续边学习边总结,欢迎一起交流。