---
# System prepended metadata

title: Typescript ES6 + webpack 設定
tags: [Node.js]

---

# Typescript ES6 + webpack 設定

## 必要 package
| 名稱 | 連結 | 用途 |
| -- | -- | -- |
| typescript | [link](https://www.npmjs.com/package/typescript) |
| webpack | [link](https://webpack.js.org) |
| webpack-cli | [link](https://www.npmjs.com/package/webpack-cli) |
| @webpack-cli/generators | [link](https://www.npmjs.com/package/@webpack-cli/generators) | webpack 環境設定範本
| ts-loader | [link](https://www.npmjs.com/package/ts-loader) |

```npm
npm install -D webpack webpack-cli @webpack-cli/generators typescript ts-loader
```

:::warning
webpack v5 已經不需要額外安裝和設定 terser-webpack-plugin (用來壓縮發佈後的 js 內容)
:::

## 設定
* 建立 webpack 相關設定檔
    ```npm
    npx webpack-cli init
    ```
    問答簡易選項
    :::info
    ? Which of the following JS solutions do you want to use? **<font color="red">Typescript</font>**
    
    ? Do you want to use webpack-dev-server? **<font color="red">No</font>**
    
    ? Do you want to simplify the creation of HTML files for your bundle? **<font color="red">No</font>**
    
    ? Which of the following CSS solutions do you want to use? **<font color="red">none</font>**
    
    ? Do you like to install prettier to format generated configuration? **<font color="red">Yes</font>**
    
    ? Overwrite package.json? **<font color="red">overwrite</font>**
    
    ? Overwrite tsconfig.json? **<font color="red">檔案已存在才出現</font>**
    :::
    會產生 tsconfig.json, webpack.config.js, 複寫 package.json
    
* 修改 webpack-cli 產生的 tsconfig.json
    ```json
    "target": "es6",
    "module": "ESNEXT",
    "moduleResolution": "node",
    "esModuleInterop": true
    ```
    :::warning
    此欄位可移除, 或是修改成入口檔案
    ```json
    "files": ["src/index.ts"]
    ```
    
    :::spoiler **延伸-測試&nbsp;tsconfig include 欄位是否有作用**
    1. root 資料夾新增檔案 abc.ts
    ```typescript
    export class ABC {}
    ```
    
    2. 在入口檔案 src/index.ts 建立 ABC 實體
    ```typescript
    import { ABC } from "../abc";
    
    export class Index{
        abc: ABC;
        constructor(){
            this.abc = new ABC();
        }
    }

    ```
    * 測試1: 設定 **include** 指定資料夾 **src**, 測試放在外層的 abc.ts 能否正常讀取
    ```json
    "include": ["src"]
    ```
    ```
    通過編譯, 不會出現找不到 abc.ts 的錯誤, runtime 也正常運作
    ```
    <br>

    * 測試2: 移除 **include** 比對差異
    ```
    通過編譯, runtime 正常運作
    ```
    *官方文件指出在沒設定 files 和 include, 預設值是 **["\*\*/\*"]** 會編譯專案目錄底下所有相關檔案* [link](https://www.typescriptlang.org/tsconfig#include)
    <br>
    
    * 測試3: **include** 指定一個不存在的資料夾 aaa
    ```json
    "include": ["aaa"]
    ```
    ```
    無法通過編譯 ERROR
      TS18003: No inputs were found in config file 'tsconfig.json'. Specified 'include' paths were '["aaa"]' and 'exclude' paths were '[]'
    ```
    <br>
    
    * 測試4: 建立 aaa 資料夾, 裡面無任何檔案
    ```
    一樣出現 ERROR TS18003
    ```
    <br>
    
    * 測試5: 在 aaa 資料夾內隨意新增一個 ts 檔, 並加上 class
    ```
    通過編譯
    ```
    <br>

    結論: tsconfig.json 即使不設定 include 資料夾, 也不影響 webpack 發佈
    :::
    
    :::spoiler **tsconfig.json&nbsp;範例**
    ```json
    {
      "compilerOptions": {
        "allowSyntheticDefaultImports": true,
        "noImplicitAny": true,
        "module": "ESNEXT",
        "target": "es6",
        "allowJs": true
      }
    }
    ```   
    :::
    
* 修改 webpack.config.js
    ```javascript
    entry: "./src/入口預設檔案.ts",
    output: {
    path: path.resolve(__dirname, "輸出資料夾"),
    filename: "輸出檔名.js"
  },
  ```
    :::spoiler **webpack.config.js&nbsp;範例**
    ```javascript
    const path = require("path");
    const isProduction = process.env.NODE_ENV == "production";

    const config = {
      entry: "./src/index.ts",
      output: {
        path: path.resolve(__dirname, "output"),
        filename: "index.js"
      },
      plugins: [],
      module: {
        rules: [
          {
            test: /\.(ts|tsx)$/i,
            loader: "ts-loader",
            exclude: ["/node_modules/"],
          },
          {
            test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
            type: "asset",
          },
        ],
      },
      resolve: {
        extensions: [".tsx", ".ts", ".js"],
      },
    };

    module.exports = () => {
      if (isProduction) {
        config.mode = "production";
      } else {
        config.mode = "development";
      }
      return config;
    };
    ```
    :::
    
* 修改 package.json
    ```json
    "description": "My webpack project",
    "name": "my-webpack-project"
    ```
    :::spoiler **package.json&nbsp;範例**
    ```json
    {
      "devDependencies": {
        "@webpack-cli/generators": "^2.1.0",
        "prettier": "^2.3.0",
        "ts-loader": "^9.2.2",
        "typescript": "^4.3.2",
        "webpack": "^5.38.1",
        "webpack-cli": "^4.7.0"
      },
      "version": "1.0.0",
      "description": "My webpack project",
      "name": "my-webpack-project",
      "scripts": {
        "build": "webpack --mode=production --node-env=production",
        "build:dev": "webpack --mode=development",
        "build:prod": "webpack --mode=production --node-env=production",
        "watch": "webpack --watch"
      }
    }
    ```
    :::
    
---
## ES6 module
* 假設 src 資料夾有 3 個 class, 其中入口是 Main
    ```typescript
    export class A {}
    ```
    ```typescript
    export class B {}
    ```
    ```typescript
    export class Main {}
    ```
* 在未使用 webpack 之前
<font color="red">匯入 module 需要含副檔名</font>
    ```typescript
    import { A } from "./A.js";
    import { B } from "./B.js";
    
    export class Main {
        a: A;
        b: B;
        
        constructor(){
            this.a = new A();
            this.b = new B();
        }
    }
    ```
* 導入 webpack 之後
<font color="red">import module 不能加上副檔名, 否則會找不到</font>
    ```typescript
    import { A } from "./A";
    import { B } from "./B";
    
    export class Main {
        a: A;
        b: B;
        
        constructor(){
            this.a = new A();
            this.b = new B();
        }
    }
    ```
* 使用 VS Code 總是自動加上 module 副檔名的解決方法
    1. 開啟 **Preference**
    2. 搜尋 **Import Module Specifier Ending**
    3. 不要選 **js**, 選 **auto**
    ![](https://i.imgur.com/vddLMPa.png)

---
    
## 發佈
```npm
npm run build
```
---

## 選用 package
| 名稱 | 連結 | 用途 |
| -- | -- | -- |
| copy-webpack-plugin | [link](https://webpack.js.org/plugins/copy-webpack-plugin/) | 複製特定檔案到發佈資料夾 |
| clean-webpack-plugin | [link](https://github.com/johnagan/clean-webpack-plugin) | 用來清空發佈資料夾 |

* copy-webpack-plugin + clean-webpack-plugin
    :::success
    webpack.config.js 簡易設定
    ```javascript
    const CopyPlugin = require('copy-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    const config = {
    ...略
    plugins: [
        new HtmlWebpackPlugin({
          template: "index.html"
        }),
        new CopyPlugin({
          patterns: [{
            from: path.resolve(__dirname, "來源相對路徑"),
            to: path.resolve(__dirname, "目標相對路徑"),
          }]
        }),
        new CleanWebpackPlugin()
  ],
    ...略
    }
    ```
    更多用法請參考文件 [link](https://webpack.js.org/plugins/copy-webpack-plugin/)
    :::
    
    錯誤排解
    :::danger
    HookWebpackError: Only file and data URLs are supported by the default ESM loader
    :::success
    copy-webpack-plugin 版本不相容, 嘗試升級或降版本
    :::
---
## 導入SASS
需要安裝的 package
| 名稱 | 連結 | 用途 |
| -- | -- | -- |
| sass-loader | [link](https://www.npmjs.com/package/sass-loader) | |
| node-sass | [link](https://www.npmjs.com/package/node-sass) | |
| css-loader | [link](https://www.npmjs.com/package/css-loader) | |
| mini-css-extract-plugin | [link](https://www.webpack.org.cn/plugins/mini-css-extract-plugin/) | 產生css |

#### 修改 webpack.config.js
 ```js
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  ...
  const config = {
      plugins: [
          new MiniCssExtractPlugin({
          filename: '[name].css',
        })
      ],
      module: {
          rules: [
              {
                test: /\.s[ac]ss$/i,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
              },
          ]
      }
  }
  ```
  
#### 在入口檔案 import scss
發佈的時候才會將 scss 編譯成 css
```typescript
import "相對路徑/來源檔案.scss";
```

#### 使用 jquery
* 安裝
    ```nodejs
    npm install --save-dev @types/jquery
    npm install jquery --save
    ```
* webpack 設定
    ```js
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
        }),
    ]
    ```



#### 疑難排解
* 每次都固定發佈成 main.css, 想更換檔名
    :::success
    webpack entry 預設名稱是 main
    需要修改 webpack.config.js 的 entry 欄位
    ```js
    entry: "./src/index.ts"
    ```
    改成
    ```js
    entry: { entry自訂名稱: "./src/index.ts" }
    ```
    :::

#### 參考文件
* [mini-css-extract-plugin說明](https://www.webpack.org.cn/plugins/mini-css-extract-plugin/)
* [webpack前端打包工具](https://awdr74100.github.io/2020-03-04-webpack-sassloader/)
* [Why is [name] always main in MiniCssExtractPlugin for webpack?](https://stackoverflow.com/a/60177523)

---
###### tags: `Node.js`