2021年從零開發(fā)前端項目指南

之前翻譯過一篇 前端工程化發(fā)展歷史 的文章,Webpack、Babel 、Eslint 現(xiàn)在基本上就是前端項目的標(biāo)配了。
但工作以后一般很少接觸這些配置,都是在前人配置好的基礎(chǔ)上去寫業(yè)務(wù)代碼。即使有機(jī)會從零配置一個項目,一般也不會自己手動建這些配置文件,直接用 create-react-app、Ant Design Pro 等自動幫我們生成各個目錄和配置文件就可以了,省時省力。
這篇文章的話就從零手動去配置一個前端項目,會涉及到 Webpack、React、Babel、TypeScript、Ant Design、Sass、Eslint、Prettier,本文的話就本著「不求甚解」的態(tài)度,主要過一下各個模塊的使用,適合從零一步一步跟著操作。
前端工程化項目是建立在 node.js 環(huán)境下的,之后需要安裝各個 npm 包,所以首先電腦必須已經(jīng)配置好了 node 環(huán)境。
新建一個目錄然后執(zhí)行 npm init 來初始化一個項目。
npm init
然后一路回車就可以,只是生成了 package.json 文件,后續(xù)想改的話也能改。

Webpack
前端不斷發(fā)展,但很多特性瀏覽器不一定會支持,ES6 模塊,CommonJs 模塊、Scss/less 、jsx 等等,通過 Webpack 我們可以將所有文件進(jìn)行打包、壓縮混淆,最終轉(zhuǎn)換為瀏覽器識別的代碼。
除了安裝 Webpack ,我們需要安裝對應(yīng)的命令行工具 webpack-cli,以及實現(xiàn)了熱加載,也就是自動監(jiān)聽我們文件變化然后刷新網(wǎng)頁的 webpack-dev-server。
由于這些工具只在開發(fā)階段使用,所以我們安裝的時候可以加上 -D(--save-dev) 命令,這樣開發(fā)環(huán)境就不會打包了。
npm i -D webpack webpack-cli webpack-dev-server
安裝之后 package.json 會自動記錄我們安裝的 node 包,對應(yīng)版本如下,如果安裝的和我不一樣的話,后邊的一些配置可能略有不同。
{
...
"devDependencies": {
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.0.0"
}
}
接下來在根目錄新建 webpack.config.js 進(jìn)行項目的配置,主要配置入口文件,打包輸目錄,以及 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')
}
}
新建一下上邊相應(yīng)的文件。
main.js 文件主要實現(xiàn)在網(wǎng)頁寫 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 新建兩條命令,默認(rèn)的 test 命令可以直接刪掉了。
...
"scripts": {
"dev": "webpack-dev-server --mode development --open",
"build": "webpack --mode production"
},
...
執(zhí)行 npm run dev ,此時會自動打開 http://localhost:8080/。

React
React 可以讓我們專注于構(gòu)建用戶界面,而不需要再手動維護(hù) dom 元素的更新,當(dāng)然還可以用 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 看下效果:

這里會發(fā)現(xiàn)上邊都調(diào)用了 React.createElement 來創(chuàng)建元素,如果頁面復(fù)雜的的話,那一層套一層就太繁瑣了,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')
);
但此時會發(fā)現(xiàn)項目跑不起來了

現(xiàn)在,我們就需要 Babel 了。
Babel
babel 可以為我們把各種語法、新功能轉(zhuǎn)換為瀏覽器所能識別的 js 。這里我們先安裝一下 babel 以及在 webpack 中使用的 babel-loader。
npm i -D @babel/core babel-loader
然后在 webpack 中引入 babel-loader ,用來對 js 進(jìn)行轉(zhuǎn)換,更改 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 來轉(zhuǎn)換 jsx 語法。
npm i -D @babel/preset-react
在根目錄新建 babel 的配置文件 babel.config.json。
// babel.config.json
{
"presets": [
"@babel/preset-react"
]
}
此時再運行 npm run dev 就發(fā)現(xiàn)項目成功跑起來了!
然后我們還可以安裝一些其他 babel 以便使用最新的 ES 語法,比如箭頭函數(shù)、async await、問號表達(dá)式等等, 需要什么就可以配置什么。當(dāng)瀏覽器不支持這些特性時,babel 可以幫我們實現(xiàn) polyfill 進(jìn)行降級。
@babel/preset-env 包含了許多 ES 的新特性,core-js 實現(xiàn) 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 ,尤其是規(guī)模比較大的項目,通過 ts 可以讓一些 bug 提前暴露,平時自己開發(fā)的話也可以引入 ts,提前了解學(xué)習(xí)。
項目引入 ts 的話有兩種方式:
使用
TypeScript Compiler (TSC)將ts編譯為ES5以便能夠在瀏覽器中運行。并且使用TSC進(jìn)行類型檢查。使用
Babel翻譯TS,使用TSC進(jìn)行類型檢查。
這里的話使用第二種方式,讓 Babel 和 TSC 各司其職。
首先安裝 TypeScript 以及 React 的 type 。
npm i -D typescript @types/react @types/react-dom
根目錄新建 tsconfig.json 進(jìn)行 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 只做類型檢查,不進(jìn)行編譯輸出。
然后我們將 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')
);
接下來進(jìn)行 babel 的配置,安裝 @babel/preset-typescript,將我們代碼從 ts 轉(zhuǎn)為 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 命令進(jìn)行類型檢查。
npm install -g typescript
可以運行一下 tsc -w 實時進(jìn)行類型檢查。

Ant Design
引入組件庫,方便更快的開發(fā)。
npm install antd
順便可以按照習(xí)慣把 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 標(biāo)簽的形式插入到頁面。
安裝好后進(jìn)行配置 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 的預(yù)編譯器,可以讓我們寫樣式更順手,具體特性可以參考 官網(wǎng),我用的最多的就是可以嵌套形式寫 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 節(jié)點
'style-loader',
// 將 CSS 轉(zhuǎn)化成 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 來進(jìn)行語法上靜態(tài)的檢查,也可以對 ts 進(jìn)行檢查。
npm i eslint -D
可以全局安裝一下 npm i -g npx 命令,能夠更方便的運行 node_modules/.bin 目錄下的命令.
不然的話我們要執(zhí)行 eslint 命令的話需要執(zhí)行 ./node_modules/.bin/eslint --version 才能取到。或者像上邊為了執(zhí)行 tsc 命令,全局安裝了 typescript。或者在 package.json 里邊添加一個自定義命令。不過還是 npx 是最方便的。
讓我們初始化 eslint.
npx eslint --init
然后按照項目需要選擇對應(yīng)的選項,最后自動安裝相應(yīng)的依賴。

然后 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 命令來修復(fù)代碼。
{
"name": "fe-learn",
"version": "1.0.0",
"description": "前端工程化項目學(xué)習(xí)",
"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"
}
}
然后執(zhí)行 npm run lint 即可進(jìn)行 eslint 的相關(guān)修復(fù)。
配合 Vscode 我們也可以做到邊寫代碼邊自動檢測 eslint,以及保存的時候自動修復(fù) eslint 相關(guān)錯誤。
可以安裝 Eslint 插件,以及在 vscode 的設(shè)置中加入以下配置,點擊下圖的右上角可以直接進(jìn)行配置的編輯。

{
"eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
}
為了使用更完善的 eslint 配置,我們也可以直接引用騰訊 Alloy 團(tuán)隊的推薦配置,參考 這里。
Prettier
prettier 主要做代碼風(fēng)格上的檢查,字符串雙引號還是單引號?幾個空格?類似這樣的。
當(dāng)然 eslint 也可以配置這些,但為了分離它們各自的職責(zé),最好還是用 prettier 來格式化代碼風(fēng)格,先安裝一下。
npm i -D prettier
然后新建一個配置文件 .prettierrc.js,這里直接引用 騰訊 Alloy 團(tuán)隊推薦的配置。
// .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
}
}
總結(jié)
通過上邊一系列的操作后,就可以開始愉快的開始寫項目了,經(jīng)驗有限,上邊有問題的地方還請大家指出。
上邊的代碼都比較零碎,可以在 github 上看整個代碼,后臺回復(fù)「前端配置」即可得到鏈接。
上邊每一塊都是一個很大的地方,未來的話會繼續(xù)邊學(xué)習(xí)邊總結(jié),歡迎一起交流。
