打造最簡潔的 typescript + react + webpack + eslint 開發環境

打造最簡潔的 typescript + react + webpack + eslint 開發環境

前言

作爲一枚前端開發者來說,與時俱進是少不了的。

近些年來的各種前端開發工具層出不窮,讓人眼花繚亂。

雖然單純的寫一個頁面,用 html、css、JavaScript 三者就夠了,甚至有的簡單的頁面,連 JavaScript 也不需要,單憑 html + css 就足以勝任。

但是對於大型的項目來說,不用前端框架開發,項目簡直就是災難現場。

一般而言,大型項目都不會是由一個人單獨開發完成的,而是需要多人協作開發。每個人的代碼風格都不盡相同,而且每個人的水平都參差不齊,這就導致會碰到各種問題,最後往往搞得大家苦不堪言。

得益於 typescript 的興起以及近些年的蓬勃發展,前端項目的開發也越來越規範化了。

本文的目的就是記錄下,怎麼配置最簡單的 typescript + react + webpack + eslint 前端的開發環境。

1. 初始化項目

這一步是初始化項目必不可少的,也沒什麼高深的地方。打開控制檯,執行以下命令就好了。

mkdir demo && cd demo && git init && npm init -y

echo node_modules/ >> .gitignore

mkdir src && touch src/app.js src/index.html

依次執行完以上命令以後,我們會得以下目錄結構:

$ tree
.
├── package.json
└── src
    ├── app.js
    └── index.html

下面我們來先補充下 index.htmlapp.js 裏面的內容。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>demo</title>
</head>
<body>
  <div id="root"></div>
  <script src="./app.js" type="module"></script>
</body>
</html>

app.js

const root = document.querySelector('#root');

const h1 = document.createElement('h1');
h1.innerText = "Hello, typescript + react + webpack + eslint.";

root.appendChild(h1);

在瀏覽器打開 index.html 頁面,出現以下結果:
在這裏插入圖片描述

2. 添加 webpack

添加 webpack,然後創建配置文件

npm install --save-dev webpack webpack-cli webpack-dev-server webpack-merge clean-webpack-plugin

mkdir build && touch webpack.dev.js build/webpack.prod.js build/webpack.common.js

執行完以上命令以後,目錄結構會變成如下所示
在這裏插入圖片描述

webpack.common.js

基本的配置

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: './src/app.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, '../dist'),
    publicPath: './',
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
};

webpack.dev.js

開發環境的配置

const path = require('path');
const webpackMerge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = webpackMerge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: path.join(__dirname, '../dist'),
    publicPath: '/',
    compress: true,
    port: 9000,
  },
});

webpack.prod.js

生產環境的配置

const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map',
  plugins: [
    new CleanWebpackPlugin(),
  ],
  watchOptions: {
    poll: 1000, // 輪詢間隔時間
    aggregateTimeout: 500, // 防抖(在輸入時間停止刷新計時)
    ignored: /node_modules/,
  },
});

修改 index.html

 </head>
 <body>
   <div id="root"></div>
-  <script src="./app.js" type="module"></script>
 </body>
 </html>

朝 package.json 添加腳本

  "description": "",
   "main": "index.js",
   "scripts": {
+    "start": "webpack-dev-server --open --config ./build/webpack.dev.js",
+    "watch": "webpack --watch --config ./build/webpack.prod.js",
+    "build": "webpack --config ./build/webpack.prod.js",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "keywords": [],

運行腳本

npm start

$ npm start

> [email protected] start /Users/root1/Desktop/demo
> webpack-dev-server --open --config ./build/webpack.dev.js

ℹ 「wds」: Project is running at http://localhost:9000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/root1/Desktop/demo/dist
ℹ 「wdm」: wait until bundle finished: /
ℹ 「wdm」: Hash: e989ba9f2efc6598e907
Version: webpack 4.43.0
Time: 540ms
Built at: 2020/06/21 下午2:26:14
     Asset       Size  Chunks             Chunk Names
 bundle.js    876 KiB    main  [emitted]  main
index.html  249 bytes          [emitted]  
Entrypoint main = bundle.js
[0] multi (webpack)-dev-server/client?http://localhost:9000 ./src/app.js 40 bytes {main} [built]
[./node_modules/ansi-html/index.js] 4.16 KiB {main} [built]
[./node_modules/html-entities/lib/index.js] 449 bytes {main} [built]
[./node_modules/loglevel/lib/loglevel.js] 8.41 KiB {main} [built]
[./node_modules/url/url.js] 22.8 KiB {main} [built]
[./node_modules/webpack-dev-server/client/index.js?http://localhost:9000] (webpack)-dev-server/client?http://localhost:9000 4.29 KiB {main} [built]
[./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {main} [built]
[./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2.91 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {main} [built]
[./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.59 KiB {main} [built]
[./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {main} [built]
[./node_modules/webpack-dev-server/node_modules/strip-ansi/index.js] (webpack)-dev-server/node_modules/strip-ansi/index.js 161 bytes {main} [built]
[./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
[./src/app.js] 174 bytes {main} [built]
    + 18 hidden modules
Child HtmlWebpackCompiler:
     1 asset
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html] 455 bytes {HtmlWebpackPlugin_0} [built]
ℹ 「wdm」: Compiled successfully.

npm run build

$ npm run build

> [email protected] build /Users/root1/Desktop/demo
> webpack --config ./build/webpack.prod.js

Hash: 82d149378677fd82d8b9
Version: webpack 4.43.0
Time: 337ms
Built at: 2020/06/21 下午2:27:55
        Asset       Size  Chunks                   Chunk Names
    bundle.js   1.09 KiB       0  [emitted]        main
bundle.js.map   4.89 KiB       0  [emitted] [dev]  main
   index.html  228 bytes          [emitted]        
Entrypoint main = bundle.js bundle.js.map
[0] ./src/app.js 174 bytes {0} [built]
Child HtmlWebpackCompiler:
     1 asset
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 455 bytes {0} [built]

npm run watch

$ npm run watch

> [email protected] watch /Users/root1/Desktop/demo
> webpack --watch --config ./build/webpack.prod.js


webpack is watching the files…

Hash: 82d149378677fd82d8b9
Version: webpack 4.43.0
Time: 130ms
Built at: 2020/06/21 下午2:31:00
        Asset       Size  Chunks                   Chunk Names
    bundle.js   1.09 KiB       0  [emitted]        main
bundle.js.map   4.89 KiB       0  [emitted] [dev]  main
   index.html  228 bytes          [emitted]        
Entrypoint main = bundle.js bundle.js.map
[0] ./src/app.js 174 bytes {0} [built]
Child HtmlWebpackCompiler:
     1 asset
    Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html 455 bytes {0} [built]

2. 添加 react

添加 react 開發依賴以及類型文件。

npm install --save react react-dom
npm install --save-dev @types/react @types/react-dom

單純只是添加了這些的話,我們暫時還無法編譯運行。

因爲 jsx 語法,需要我們配置 babel 才能正確的編譯 js 代碼。

我們知道的是,babel 配置一直是 webpack 配置裏面的“玄學”,特別是對於初學者而言,着實不友好。

但是如果我們往下看,當我們朝項目中配置了 typescript 以後,就不需要配置 babel 就能直接編譯運行了。

因爲 typescript 不能直接運行在瀏覽器上,需要編譯爲 js 才能運行,如果我們寫的是 tsx 文件,那麼在由 typescript 編譯爲 js 的過程中,jsx 語法直接就被編譯好了,不需要我們自己操心了。

3. 添加 typescript

添加 typescript 依賴以及配置文件。

npm install --save-dev typescript ts-loader source-map-loader

touch tsconfig.json

下面是 tsconfig.json 配置文件裏面的內容:

{
  "compilerOptions": {
      "outDir": "./dist/",
      "sourceMap": true,
      "noImplicitAny": true,
      "module": "commonjs",
      "target": "es5",
      "jsx": "react",
      "allowJs": true,
      "baseUrl": ".",
      "esModuleInterop": true,
  },
  "include": [
      "./src/**/*"
  ],
  "exclude": [
    "dist",
    "node_modules"
  ]
}

接下來,我們就修改 webpack.common.js 文件,讓其支持 typescript 的編譯

       template: "./src/index.html",
     }),
   ],
+
+  resolve: {
+    extensions: ['.ts', '.tsx', '.js', '.json'],
+  },
+
+  module: {
+    rules: [
+      {
+        test: /\.ts(x?)$/,
+        exclude: /node_modules/,
+        use: [
+          {
+            loader: 'ts-loader',
+          },
+        ],
+      },
+      {enforce: 'pre', test: /\.js$/, loader: 'source-map-loader'},
+    ],
+  },
 };

接下來,我們講 app.js 文件重命名爲 app.tsx ,然後改一下里面的代碼,改成 react 的寫法

import React, { FC } from "react";
import ReactDom from "react-dom";

const Hello: FC<{title: string}> = ({ title }) => {
  return <h1>{title}</h1>;
};

ReactDom.render(
  <Hello title="Hello, typescript + react + webpack + eslint." />,
  document.querySelector("#root"),
);

然後改一下 webpack.common.js 裏面的入口文件

 const HtmlWebpackPlugin = require("html-webpack-plugin");
 
 module.exports = {
-  entry: './src/app.js',
+  entry: './src/app.tsx',
   output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, '../dist'),

然後運行 npm start ,發現項目能正常運行
在這裏插入圖片描述

4. 添加 eslint

輸入下列命令,爲項目添加 eslint 規則約束,這一項是可選的,但是爲了項目代碼的統一,建議還是用上。

唯一不好的地方是,用了以後,在寫代碼的過程中,經常會發現,自己寫的不符合規範,還得花時間研究下,爲啥自己寫的不符合規範。

運行以下命令

npm install eslint -g && eslint --init

之後會出現讓你進行選擇的選項,類似下面的情況:

$ eslint --init
? How would you like to use ESLint? (Use arrow keys)
  To check syntax only 
❯ To check syntax and find problems 
  To check syntax, find problems, and enforce code style 

不要着急,一路選擇下去

$ eslint --init
? How would you like to use ESLint? To check syntax, find problems, and enforce 
code style
? What type of modules does your project use? JavaScript modules (import/export)


? Which framework does your project use? React
? Does your project use TypeScript? Yes
? Where does your code run? (Press <space> to select, <a> to toggle all, <i> to 
invert selection)Browser
? How would you like to define a style for your project? Use a popular style gui
de
? Which style guide do you want to follow? Google (https://github.com/google/esl
int-config-google)
? What format do you want your config file to be in? JavaScript
Checking peerDependencies of eslint-config-google@latest
Local ESLint installation not found.
The config that you've selected requires the following dependencies:

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest eslint-config-google@latest eslint@>=5.16.0 @typescript-eslint/parser@latest
? Would you like to install them now with npm? (Y/n) y

最後輸入 y 回車,eslint 相關配置就會自動安裝到項目中去了

最後朝 package.json 中新增一個 fix 腳本

     "start": "webpack-dev-server --open --config ./build/webpack.dev.js",
     "watch": "webpack --watch --config ./build/webpack.prod.js",
     "build": "webpack --config ./build/webpack.prod.js",
+    "lint": "eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx src/ ",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "keywords": [],

運行 npm run lint 開始自動修復我們的 src 文件裏的代碼格式

$ npm run lint

> [email protected] lint /Users/root1/Desktop/demo
> eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx src/ 


/Users/root1/Desktop/demo/src/app.tsx
  1:8   error  'React' is defined but never used           no-unused-vars
  1:16  error  'FC' is defined but never used              no-unused-vars
  4:7   error  'Hello' is assigned a value but never used  no-unused-vars

✖ 3 problems (3 errors, 0 warnings)

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] lint: `eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx src/ `
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/root1/.npm/_logs/2020-06-21T07_23_07_803Z-debug.log

可知道,fix 失敗,這說明,有些錯誤,是不能通過此命令爲我們自動修復的,我們必須手動修復或者忽略掉這個約束規則。

從報錯,我們可知,是因爲我們的代碼不符合 no-unused-vars 這個規則,那麼我們可以去掉這個約束或者修改我們的代碼。

分析代碼可以,這幾個變量 我們都不能去掉,否則代碼就不能正常運行了,那麼我們只需要在 .eslintrc.js 文件裏面的 rules 里加一行規則就行了,忽略掉這個規則
在這裏插入圖片描述
再次運行 npm run lint

$ npm run lint

> [email protected] lint /Users/root1/Desktop/demo
> eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx src/ 

發現,沒有報錯,根據 Unix 的 no news is good news 的設計哲學,我們的代碼全部符合 eslint 的規則。
在這裏插入圖片描述

到此,我們的目標基本實現,最簡潔的 typescript + react + webpack + eslint 開發環境已然搭建完成了。

最後來看下我們的項目目錄:
在這裏插入圖片描述

5. 可選 webpack 插件

在實際項目中,只是添加以上一些模塊是不夠的,因爲我們暫時還無法處理圖片、css 等資源,爲了讓我們的開發環境更完善點,我們接下來需要朝項目中添加一些 webpack loader,讓我們的開發環境更加的強大一些。

處理圖片的 loader

添加 file loader

npm install --save-dev file-loader

修改 webpack.common.js

         ],
       },
       {enforce: 'pre', test: /\.js$/, loader: 'source-map-loader'},
+      {
+        test: /\.(png|jpg|gif)$/,
+        use: [
+          {
+            loader: 'file-loader',
+            options: {},
+          },
+        ],
+      },
     ],
   },
 };

然後我們嘗試着,朝項目中引入一個 logo 圖片。
在這裏插入圖片描述

然後改一下 app.tsx 裏面的代碼:

 import React, {FC} from 'react';
 import ReactDom from 'react-dom';
+import logo from './logo.jpg';
 
 const Hello: FC<{title: string}> = ({title}) => {
-  return <h1>{title}</h1>;
+  return <h1><img src={logo} />{title}</h1>;
 };
 
 ReactDom.render(

然後準備,運行的時候,卻發現運行不了
在這裏插入圖片描述

別慌,原來是我們在 typescript 中引入圖片的時候,需要自己定義以下其類型聲明。

我們創建一個 typings 文件夾,然後新建一個 custom.d.ts 文件,自定義自己的類型聲明。

mkdir typings && touch typings/custom.d.ts

custom.d.ts 內容

declare module '*.png' {
  const content: any;
  export = content;
}

declare module '*.jpg' {
  const content: any;
  export = content;
}

寫到這裏,還沒完,還需要改下 tsconfig.json ,讓我們這個文件可以正常加載出來。

       "esModuleInterop": true,
   },
   "include": [
-      "./src/**/*"
+      "./src/**/*",
+      "typings/*"
   ],
   "exclude": [
     "dist",

改完以後,重新 npm start 一下:

在這裏插入圖片描述

再次打開頁面,可以看到,我們的 logo 也加載出來了。

處理 css 的 loader

添加 style-loader 和 css-loader

npm install --save-dev style-loader css-loader

修改 webpack.common.js

         ],
       },
       {enforce: 'pre', test: /\.js$/, loader: 'source-map-loader'},
+      {
+        test: /\.css$/,
+        use: [
+          'style-loader',
+          {loader: 'css-loader', options: {url: false}},
+        ],
+      },
       {
         test: /\.(png|jpg|gif)$/,
         use: [

添加一個 app.css 文件
在這裏插入圖片描述

app.tsx 裏 import 一下

 import React, {FC} from 'react';
 import ReactDom from 'react-dom';
 import logo from './logo.jpg';
+import './app.css';
 
 const Hello: FC<{title: string}> = ({title}) => {
   return <h1><img src={logo} />{title}</h1>;

重新 npm start 一下
在這裏插入圖片描述
打開頁面,發現我們的 css 樣式應用上去了。

其它有用的 loader

webpack 提供了很多 loader,處理各種不同的情景,如果你還有更多的需求,請參考頁面:https://webpack.js.org/loaders/

6. 倉庫地址

如果你想直接用我配置好的倉庫,請訪問地址:https://github.com/lovefengruoqing/demo,克隆或者下載下來以後,修改下 package.json 裏面的信息即可使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章