2021 年從零開發前端項目指南

之前翻譯過一篇 前端工程化發展歷史 的文章,WebpackBabelEslint 現在基本上就是前端項目的標配了。

但工作以後一般很少接觸這些配置,都是在前人配置好的基礎上去寫業務代碼。即使有機會從零配置一個項目,一般也不會自己手動建這些配置文件,直接用 create-react-appAnt Design Pro 等自動幫我們生成各個目錄和配置文件就可以了,省時省力。

這篇文章的話就從零手動去配置一個前端項目,會涉及到 WebpackReactBabelTypeScriptAnt DesignSassEslintPrettier,本文的話就本着「不求甚解」的態度,主要過一下各個模塊的使用,適合從零一步一步跟着操作。

前端工程化項目是建立在 node.js 環境下的,之後需要安裝各個 npm 包,所以首先電腦必須已經配置好了 node 環境。

新建一個目錄然後執行 npm init 來初始化一個項目。

npm init

然後一路回車就可以,只是生成了 package.json 文件,後續想改的話也能改。

img

Webpack

前端不斷髮展,但很多特性瀏覽器不一定會支持,ES6 模塊,CommonJs 模塊、Scss/lessjsx 等等,通過 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 >
    <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 ,以及渲染 Webreact-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" 代表自動判斷每個文件是否引入 ployfillcorejs: 3 是指定版本。

TypeScript

越來越多的項目引入了 TypeScript ,尤其是規模比較大的項目,通過 ts 可以讓一些 bug 提前暴露,平時自己開發的話也可以引入 ts,提前瞭解學習。

項目引入 ts 的話有兩種方式:

  1. 使用  TypeScript Compiler (TSC)ts 編譯爲 ES5 以便能夠在瀏覽器中運行。並且使用 TSC 進行類型檢查。

  2. 使用 Babel 翻譯 TS,使用 TSC 進行類型檢查。

這裏的話使用第二種方式,讓 BabelTSC 各司其職。

首先安裝 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.jsbabel 匹配的路徑中加入 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 引入 antdcss 文件。

// /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 配置文件中補上 cssloader ,先安裝一下。

npm i -D style-loader css-loader

css-loader 可以讓我們在 js 中引入 cssstyle-loader 幫我們將 cssstyle 標籤的形式插入到頁面。

安裝好後進行配置 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

Sasscss 的預編譯器,可以讓我們寫樣式更順手,具體特性可以參考 官網,我用的最多的就是可以嵌套形式寫 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 class>
        <div class>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',
};

同樣的,爲了保存的時候自動幫我們格式化,我們可以安裝 VscodePrettier 插件,以及再修改 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 上看整個代碼,後臺回覆「前端配置」即可得到鏈接。

上邊每一塊都是一個很大的地方,未來的話會繼續邊學習邊總結,歡迎一起交流。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_xAT8D9O-O6Jz1cQ3sUhnA