Webpack 工程化

Webpack 工程化

搭建一個 react 的環境,並且是基於 typescript 的項目

首先我們需要思考的一個問題是我這個項目需要哪些功能(可以先把能想到的功能配置進去,之後再想到的再配置,畢竟一開始可能想不全是正常的)
1. 能處理樣式 less/css 文件
2. 能處理圖片字體圖標 png/jpg/gif/svg/ttf/… 等
3. 能處理 react 組件代碼 jsx/tsx
4. 能把 ts 轉換成 js
5. 能把 es6 代碼 轉化爲 es5
6. 能處理代碼規範 eslint

一開始我也只想到了這些,so 我們先基於上面的 6 點來配置我們的項目

Step 1 初始化項目

新建一個文件夾 webpack-demo,並且初始化項目

npm init -y

Step 2 新增配置文件

新建一個文件夾 build 來放我們的配置文件,在該目錄中新建一個 webpack 的配置文件,webpack.dev.js
並且我們在該文件中添加如下代碼,webpack 簡單的可以包括如下幾部分
- mode:什麼模式,開發 還是 生產 等
- entry: 文件的入口,可以直接一個 string 路徑
- module: 這裏就是用來幫我們處理各種文件的,比如 css、圖片、ts等
- plugin: 增加各種插件來讓我們的 webpack 功能更加強大
- output:打包輸出到哪裏

const path = require('path');
module.exports = {
    mode: 'development',
    entry: '',
    module: {},
    plugins: [],
    output: {},
};

Step 3 新增配置入口

接下來就是把我們需要的配置往裏加
Webpack 需要一個入口,不然 webpack 也不知道從何下手啊,so 我們新建一個與 build 平級的 src 文件,並且新增文件 index.ts (我們先新建一個 ts 文件,也可以是 js 文件,之後在換成 tsx 或者 jsx )
因此我們的配置就可以修改成如下這樣了

// ...
entry: './src/index.ts',
// ...

Step 4 配置 module

1. webpack 配置 babel
npm install @babel/polyfill core-js@2
npm install babel-loader @babel/core  @babel/preset-env @babel/preset-react  -D

.babelrc

{
    "presets": [
        [
            "@babel/preset-env",
            {
                // 將es6的語法翻譯成es5語法
                "targets": {
                    "chrome": "67"
                },
                "useBuiltIns": "usage", // 做@babel/polyfill補充時,按需補充,用到什麼才補充什麼,
                "corejs": "2"
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": ["@babel/plugin-proposal-class-properties"]
}

在 webpack 中配置

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
        {
            loader: "babel-loader",
        },
    ],
},
2. webpack 配置樣式(less、css)
npm install less less-loader css-loader style-loader -D

在 webpack 中配置

{
    test: /\.less$/,
    exclude: /node_modules/,
    use: [
        "style-loader",
        {
            loader: "css-loader",
            options: {
                importLoaders: 2,
            },
        },
        "less-loader",
    ],
},
{
    test: /\.css$/,
    use: ["style-loader", "css-loader"],
},
3. webpack 配置圖片和字體圖標的處理
npm install file-loader url-loader -D

在 webpack 中配置

url-loader 基本上可以實現 file-loader 的功能,但是有一區別就是經過 url-laoder 打包後的 dist 文件下是不存在 image 文件的,這是因爲 url-loader 會把圖片轉換成 base64 的字符串直接放在 bundle.js 裏面

{
    test: /\.(png|jpg|gif|jpeg)$/,
    use: {
        loader: "file-loader",
        options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
            limit: 200 * 1024, // 小於200kb則打包到js文件裏,大於則使用file-loader的打包方式打包到imgages裏
        },
    },
},
{
    test: /\.(eot|woff2?|ttf|svg)$/,
    use: {
        loader: "url-loader",
        options: {
            name: "[name]_[hash:5].min.[ext]",
            outputPath: "fonts/",
            limit: 500,
        },
    },
},
4. webpack 配置 typescript
npm i typescript -g
npm i typescript ts-loader -D

初始化項目的 tsconfig 文件,然後根據自己的需要選擇相應的配置
通過下面指令生成的文件中所有屬性都有了,根據需要取消註釋就行

tsc --init

這是我的 tsconfig

{
    "compileOnSave": false,
    "compilerOptions": {
        "jsx": "react",
        "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
        "module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
        "declaration": true /* Generates corresponding '.d.ts' file. */,
        "declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */,
        "sourceMap": true /* Generates corresponding '.map' file. */,
        "outDir": "./dist/js" /* Redirect output structure to the directory. */,
        "strict": true /* Enable all strict type-checking options. */,
        "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
        "noUnusedLocals": true /* Report errors on unused locals. */,
        "noUnusedParameters": true /* Report errors on unused parameters. */,
        "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
        "baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
        "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies */
    },
    "include": ["src/**/*.ts", "src/**/*.tsx"],
    "exclude": ["node_modules", "*.test.ts"]
}

在 webpack 中配置

// 處理 ts 文件
{
    test: /\.ts$/,
    exclude: /node_modules/,
    loader: "ts-loader",
},

處理 tsx 文件需要特定的 babel 來進行轉化

{
    test: /\.jsx|tsx?$/,
    use: [
        {
            loader: 'babel-loader',
            options: {
                presets: [
                    '@babel/env',
                    '@babel/react',
                    '@babel/preset-typescript',
                ],
            },
        },
    ],
},
5. webpack 配置 eslint
npm install eslint -D
# 初始化配置 eslintrc,執行命令的時候就會讓你選擇一些配置,根據自己需要來就行
npx eslint --init
npm install eslint-loader -D

.eslintrc.js

module.exports = {
    env: {
        browser: true,
        es2020: true,
        node: true,
    },
    extends: ['plugin:react/recommended', 'standard'],
    parser: '@typescript-eslint/parser',
    parserOptions: {
        ecmaFeatures: {
            jsx: true,
        },
        ecmaVersion: 11,
        sourceType: 'module',
    },
    plugins: ['react', '@typescript-eslint'],
    rules: {
        'prettier/prettier': 1,
    },
};

在 webpack 中配置

// 修改 babel 的那個配置,增加一個 eslint 的 loader
use: ['babel-loader', 'eslint-loader'];

Step 5 配置 output

基本的 loader 已經配置好了,我們接下來配置文件打包輸出到哪裏

output: {
        publicPath: './',
        filename: 'bundle.js',
        path: path.resolve(__dirname, '..', 'dist'),
}

可能對於上面的配置項有些不理解的可以去查看 webpack 官網中的說明,這裏只講配置的過程。

Step 6 測試配置

我們先來看看我們的配置大體上成功了沒有,這裏也僅僅是測一下配置是否有錯而已,像一些樣式什麼的文件並沒有測,你可以根據自己的情況來自測一下
在我們的入口文件裏隨便寫點代碼
在 package.json 文件中 配置 script

// 要想使用 webpack 這個命令需要你在項目裏安裝一下才行
"scripts": {
    "build": "webpack --config ./build/webpack.dev.js"
}

然後 npm run build 執行就會在目錄中出現一個 dist 文件,裏面有一個 bundle.js 文件,這就是我們的輸出文件。自此說明我們的配置大體上沒啥問題(起碼能運行起來,哈哈哈~)

Step 7 增加插件,打包 html 頁面

首先我們在根目錄下新建一個文件夾 public,裏面新增一個 index.html 文件

<!-- -->
<div id = "root"></div>
<!-- -->

使用 html-webpack-plugin 插件幫我們把代碼注入到相應的模板中,並且打包到 dist 目錄中

增加 plugin
先要在項目裏安裝這個插件

npm i html-webpack-plugin -D

修改 webpack.dev.js 文件

const HtmlWebpackPlugin = require('html-webpack-plugin');
// ...

plugin: [
	new HtmlWebpackPlugin({
      template: path.join(__dirname, '..', 'public', 'index.html'),
   }),
]

配置好之後我們再運行一下 npm run build 發現在 dist 目錄裏多了一個 index.html 文件,並且這個文件裏多了一句代碼

<script src='./bundle.js'></script>

該插件幫我們把打包出來的文件引用到了頁面中來,引用的路徑是我們在 output 裏配置的 publicPath ./

離我們的目標又近了一步,接下來我們來實現對於 react 組件的處理

Step 8 處理 react

首先我們得把我們之前的入口文件修改一下後綴名,改爲 tsx,並且裏面的內容改一下

import React from "react";
import ReactDOM from "react-dom";

const App: React.FC = () => {
    return <div>Hello react</div>;
};

ReactDOM.render(<App />, document.getElementById("root"));

我們處理 tsx 文件的配置上面已經加了,所以這裏我們直接這麼寫就行了,再此運行 build 命令,我們可以打開 index.html 文件,發現頁面上出現了 hello react 的字樣,說明我們的項目已經能簡單的支持編譯處理 react 了
但是我們發現,我們每次都需要在 build 後去點開 index.html 文件才能訪問,太傻,太 low 了

Step 9 啓動本地服務訪問頁面

這我們需要在 webpack.dev.js 中新增配置 devServer

const path = require('path');
module.exports = {
    mode: 'development',
    entry: '',
 	  devServer:{},
    module: {},
    plugins: [],
    output: {},
};

我們要想用這東西還得需要安裝一下 webpack-dev-server

npm i webpack-dev-server -D

同時修改我們的 package.json 文件

"scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --config ./build/webpack.dev.js",
        "server": "webpack-dev-server --open",
        "start": "npm run build && npm run server"
},

然後我們去修改我們的 webpack.dev.js 文件

devServer: {
        contentBase: path.resolve(__dirname, '../dist'),
},

然後運行npm run server 結果發現居然報錯了

> [email protected] server /Users/mac/Desktop/webpack-demo
> webpack-dev-server --open

ℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mac/Desktop/webpack-demo
✖ 「wdm」: Hash: 9741ca9c1aa6b6e1ab1d
Version: webpack 4.43.0
Time: 310ms
Built at: 2020/06/14 下午3:33:28
  Asset     Size  Chunks             Chunk Names
main.js  361 KiB    main  [emitted]  main
Entrypoint main = main.js
[0] multi (webpack)-dev-server/client?http://localhost:8080 ./src 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/clients/SockJSClient.js] (webpack)-dev-server/client/clients/SockJSClient.js 4.06 KiB {main} [built]
[./node_modules/webpack-dev-server/client/index.js?http://localhost:8080] (webpack)-dev-server/client?http://localhost:8080 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]
    + 17 hidden modules

ERROR in Entry module not found: Error: Can't resolve './src' in '/Users/mac/Desktop/webpack-demo'

ERROR in multi (webpack)-dev-server/client?http://localhost:8080 ./src
Module not found: Error: Can't resolve './src' in '/Users/mac/Desktop/webpack-demo'
 @ multi (webpack)-dev-server/client?http://localhost:8080 ./src main[1]
ℹ 「wdm」: Failed to compile.

一開始我以爲是我的 output 中的 publicPath 這個路徑配置影響到了我的 server,因爲啓動 server 的時候顯示

「wds」: webpack output is served from /
「wds」: Content not from webpack is served from /Users/mac/Desktop/webpack-demo

這明顯不是我想要的 dist 目錄啊,所以就增加了 publicPath ,devServer 裏的 publicPath 和 output 裏的其實作用一樣,只是如果 devServer 裏如果不加就默認是 output 中的,我在 devServer 里加了

// ...
publicPath: './index.html'
// ...

結果再次運行發現還是報一樣的錯,而且路徑也沒有改變,貌似沒起作用
看了網上很多說的,有說是因爲 html-webpack-plugin 這個插件與 webpack-dev-sever 配置衝突,需要 publicPath 裏 ”dist/index.html“ 這樣寫,結果說的基本上都試了,結果都不行,後來在 stack overflow 中看了一個同樣問題的,在評論裏有人說,修改我們的 script,增加一個 —config

I also just get this error like you got and its was solved when i specify webpack config file. Try to add --config when you run webpack-dev-server. Ex. webpack-dev-server --config webpack.dev.js

於是乎,我也修改了我的 script

"scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack --config ./build/webpack.dev.js",
        "server": "webpack-dev-server --config ./build/webpack.dev.js --open",
        "start": "npm run build && npm run server"
}

然後發現可以了,具體的原因其實我也不知道爲什麼, 尷尬啊~~

自此,我們的項目基本上也可以正常的運行起來了,還不錯的樣子吼~~

總結

Webpack 的運行流程是一個串行的過程,從啓動到結束會依次執行以下流程:(網上找來的,具體哪裏忘記了,尷尬~~)

  1. 初始化參數:從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數;
  2. 開始編譯:用上一步得到的參數初始化 Compiler 對象,加載所有配置的插件,執行對象的 run 方法開始執行編譯;
  3. 確定入口:根據配置中的 entry 找出所有的入口文件;
  4. 編譯模塊:從入口文件出發,調用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經過了本步驟的處理;
  5. 完成模塊編譯:在經過第4步使用 Loader 翻譯完所有模塊後,得到了每個模塊被翻譯後的最終內容以及它們之間的依賴關係;
  6. 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk 轉換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內容的最後機會;
  7. 輸出完成:在確定好輸出內容後,根據配置確定輸出的路徑和文件名,把文件內容寫入到文件系統。
    在以上過程中,Webpack 會在特定的時間點廣播出特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯,並且插件可以調用 Webpack 提供的 API 改變 Webpack 的運行結果。

總的來說,我們需要工程化一個項目,是基於我們的目的來說的,你希望這個工程具備什麼樣的功能,你就往裏加,一開始誰都無法考慮的很周全,一點點來,先把基本的架子搭出來,然後往裏填充,讓這個架子更加豐滿。

也許我上面的配置還有隱藏的一些錯誤或的地方,如果又啥問題歡迎指出來~~

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