# 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 **延伸-測試 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 範例**
```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 範例**
```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 範例**
```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**

---
## 發佈
```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`