用webpack寫個現代的JavaScript包

帕奇式發表於2018-11-27

webpack 作為目前主流的構建工具,其較快的版本迭代和複雜的配置方式,使得每次開發前不得不規劃相當部分時間來除錯。這裡將記錄整個環境的搭建過程,為新手提供基礎思路。

就像我在開發vue-sitemap時一樣,構建工具往往需要達到下面幾個需求:

  • 構建生成 CommonJS/UMD/ES Modules 三種模式的程式碼提供給使用者
  • 需執行測試和檢查測試覆蓋的進度
  • 開發時候使用 VS Code 編輯器進行斷點除錯

以上三個作為開發一個元件(package)是基礎中基礎的需求,當然還有更多細節內容需要新增,由於篇幅過長另加文章再說吧。(歡迎各位讀者評論留下你認為需要的功能( • ̀ω•́ )✧)

第一步:構建工具

接下來我們先從最基礎的開始,需要安裝 Node.js(10.x) 作為所有程式碼的執行環境, webpack 也是一樣。

初始化專案

由於我需要把專案釋出至 npm 的,使用命令初始化專案描述檔案 package.json

npm init
複製程式碼

初次化細節各位讀者找其他文章補全吧,這裡不細說

接下來看看目錄結構

│  package.json     //專案描述檔案
│  README.md        //GitHub建立倉庫時預設建立
├─src               //原始碼目錄
│      index.js     //入口檔案
├─tests             //測試程式碼目錄
│
├─dist              //生產檔案的目錄
│
└─docs              //文件目錄
複製程式碼

新增 webpack

npm install -D webpack webpack-cli cross-env
//or
//yarn add webpack webpack-cli cross-env -D
複製程式碼

這裡使用的 webpack v4,後續設定也是基於4來設定,cross-env是幫助在 win 下能正常使用環境變數的包,我開發在 win 環境於是在這加上。

yarn 是一款快速、可靠、安全的依賴管理工具。如果你覺得 npm 安裝時候較慢的話,不妨試試。

等依賴下載解決完畢之後,,在package.json設定構建命令方便之後使用。

//# package.json
{
  //...
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack --propress --hide-modules",
  }
}
複製程式碼

這裡我們可以嘗試執行一下命令npm run build嘗試能否構建成功,成功的情況下在dist目錄下會生成main.js的檔案。

配置 webpack

建立 webpack.config.js檔案來配置 webpack 。為滿足我們的第一個需要生成三種模式的程式碼:

//# webpack.config.js

const package = require('./package.json')
const path = require('path')

const config = {
    entry: "./src/index.js",  //入口檔案
    output: {                 //輸出設定
        path: path.resolve(__dirname, "./dist"),
        filename: `${package.name}.js`
    },
    resolve: {
        alias: {
            "@": path.resolve(__dirname, "./src")
        }
    }
}

if (process.env.NODE_ENV === "umd") {
    config.optimization = { minimize: false };
    config.output.library = package.name;
    config.output.libraryTarget = "umd2";
    config.output.filename = `${package.name}.js`;
}
if (process.env.NODE_ENV === "umd:min") {
    config.output.library = package.name;
    config.output.libraryTarget = 'umd2';
    config.output.filename = `${package.name}.min.js`;
}
if (process.env.NODE_ENV === "es") {
    config.output.library = package.name;
    config.output.libraryTarget = "amd";
    config.output.filename = `${package.name}.es.js`;
}
if (process.env.NODE_ENV === "commonjs") {
    config.output.library = package.name;
    config.output.libraryTarget = "commonjs2";
    config.output.filename = `${package.name}.common.js`;
}

module.exports = config
複製程式碼

新增構建命令

package.json 新增新的執行命令

//# package.json
{
  "version": "0.1.0",
  "name": "vscode-mocha-webpack-example",
  "description": "用於管理導航、麵包屑及路由等基於vue的功能整合",
  "main": "./src/index.js",
  "scripts": {
    "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min",
    "build:umd": "cross-env NODE_ENV=umd webpack --mode=production --progress --hide-modules",
    "build:umd:min": "cross-env NODE_ENV=umd:min webpack --mode=production --progress --hide-modules",
    "build:es": "cross-env NODE_ENV=es webpack --mode=production --progress --hide-modules",
    "build:commonjs": "cross-env NODE_ENV=commonjs webpack --mode=production --progress --hide-modules"
  }
  ...
}
複製程式碼

執行npm run build就會 CommonJS/UMD/ES Modules 三種模式生成對應的檔案。

大概是這樣子:

├─dist
│    vscode-mocha-webpack-example.common.js
│    vscode-mocha-webpack-example.es.js
│    vscode-mocha-webpack-example.min.js
│    vscode-mocha-webpack-example.js
複製程式碼

指定終端

為了使你的構建檔案成為最終釋出包的一部分,你必須宣告它們。將以下內容新增到package.json:

"main": "dist/vscode-mocha-webpack-example.common.js",
"module": "dist/vscode-mocha-webpack-example.es.js",
"jsnext:main": "dist/vscode-mocha-webpack-example.es.js",
"files": [
  "dist",
  "src"
],
複製程式碼
  • files部分告訴npm在釋出時打包這些資料夾(否則,它們將被忽略,因為它們列在.gitignore檔案中)
  • main定義CommonJS構建的終端
  • jsnext:mainmodule定義了ES2015構建的終端(我們定義了兩個終端,因為jsnext:main是最早使用的規範,而module則更符合標準規範)。

第二步,設定babel

通過 babel 使得我們使用最新的語法,而不必擔心執行環境不支援的問題。在webpack的下我們需要用到babel-loader來匯入babel支援,關於最新的相容設定還需使用上babel-preset-env

npm install -D babel babel-cli babel-preset-env
//or
//yarn add babel babel-cli babel-preset-env -D
複製程式碼

建立 babel 配置檔案

接著在.babelrc檔案裡設定babel相容的規則:

{
    "presets": [
        [
            "env",
            {
                "useBuiltIns": false,
                "modules": false
            }
        ]
    ]
}
複製程式碼

為 webpack 新增 babel-loader

當我們使用最新語法編寫 JavaScript 時,webpack 會匹配將所有 JS 檔案給 babel 的處理。

const package = require('./package.json')
const path = require('path')

const config = {
    entry: "./src/index.js",
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: `${package.name}.js`
    },
    resolve: {
        alias: {
            "@": path.resolve(__dirname, "./src")
        }
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    }
}

...

module.exports = config
複製程式碼

當執行構建時webpack便會載入babel及其相關的設定將程式碼轉換並生成,到這步構建相關的設定基本完成。

第三步,新增自動化測試

相信對自動化測試有所瞭解的讀者應該對mocha並不陌生,不瞭解的可以先去補補相關知識再往下看。簡單的測試較多使用mocha來進行處理,還有斷言庫chai和提供promise支援的chai-as-promised等等,下面先把這些依賴安裝上:

npm install -D mocha mocha-webpack chai chai-as-promised
//or
//yarn add mocha mocha-webpack chai chai-as-promised -D
複製程式碼

測試程式碼想使用es新特性時可以使用mocha-webpack這個外掛。

然後在package.json新增上測試命令:

{
    //...
    "scripts": {
        "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min",
        "build:umd": "cross-env NODE_ENV=umd webpack --mode=production --progress --hide-modules",
        "build:umd:min": "cross-env NODE_ENV=umd:min webpack --mode=production --progress --hide-modules",
        "build:es": "cross-env NODE_ENV=es webpack --mode=production --progress --hide-modules",
        "test": "cross-env NODE_ENV=test mocha-webpack tests/**/*.spec.js"
    }
    //...
}
複製程式碼

.babelrc 也需要設定一下:

{
    //...
    "env": {
        "test": {
            "presets": [
                [
                    "env",
                    {
                        "modules": false,
                        "targets": {
                            "node": "current"
                        }
                    }
                ]
            ]
        }
    }
}
複製程式碼

為了能測試新增tests/unit/example.spec.jssrc/index.js兩個檔案,程式碼如下:

├─src
│      index.js
└─tests
    └─unit
            example.spec.js

複製程式碼
//# src/index.js
export function getRole(user){
    switch(user){
        case "Packy":
            return "admin"
        case "Joan":
            return "reader"
    }
}

//# tests/unit/example.spec.js
import { assert } from "chai";
import { getRole } from "@/index";

describe('Testing', ()=>{
  it('Packy is admin', () => { assert.equal(getRole('Packy'), 'admin') })
  it("Joan is reader", () => { assert.equal(getRole("Joan"), "reader") });
})
複製程式碼

現在執行測試命令就能得出測試結果了:

npm run test
複製程式碼

大概輸出是這個樣子:

 WEBPACK  Compiling...

  [=======================  ] 91% (additional chunk assets processing)
 WEBPACK  Compiled successfully in 5893ms

 MOCHA  Testing...



  Testing
    √ Packy is admin
    √ Joan is reader


  2 passing (39ms)

 MOCHA  Tests completed successfully
複製程式碼

關於測試覆蓋率的問題

有了測試還得知道測試是否都覆蓋了所有程式碼(聽說基本要到80%,有些團隊可能要求更高90~95%),那如何得知?

nyc這個包就能幫助到我去檢驗測試覆蓋率,首先先安裝依賴:

npm install -D nyc babel-plugin-istanbul
複製程式碼

再設定檢查範圍和新增命令:

//# package.json
{
    ...
    "scripts": {
        "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min",
        "build:umd": "cross-env NODE_ENV=umd webpack --mode=production --progress --hide-modules",
        "build:umd:min": "cross-env NODE_ENV=umd:min webpack --mode=production --progress --hide-modules",
        "build:es": "cross-env NODE_ENV=es webpack --mode=production --progress --hide-modules",
        "build:commonjs": "cross-env NODE_ENV=commonjs webpack --mode=production --progress --hide-modules",
        "test": "cross-env NODE_ENV=test nyc mocha-webpack tests/**/*.spec.js"
    },
    ...
    "nyc": {
        "include": [
            "src/**"
        ],
        "instrument": false,
        "sourceMap": false
    }
    ...
}
複製程式碼

安裝依賴中也看到babel也需要新增相關的設定:

//# .babelrc
{
    ...
    "env": {
        "test": {
            "presets": [
                [
                    "env",
                    {
                        "modules": false,
                        "targets": {
                            "node": "current"
                        }
                    }
                ]
            ],
            "plugins": [
                "istanbul"
            ]
        }
    }
}
複製程式碼

執行npm run test將會得到以下內容:

 WEBPACK  Compiling...

  [=======================  ] 91% (additional chunk assets processing)
 WEBPACK  Compiled successfully in 5893ms

 MOCHA  Testing...



  Testing
    √ Packy is admin
    √ Joan is reader


  2 passing (39ms)

 MOCHA  Tests completed successfully

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.js |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
複製程式碼

簡單說一下這四欄東西代表什麼意思:

  • Stmts : Statement coverage 宣告覆蓋率,程式中的每個語句都已執行嗎?
  • Branch: Branch coverage 分支覆蓋率,是否已執行每個控制結構的每個分支(也稱為DD路徑)(例如if和case語句)?例如,給定if語句,是否已執行true和false分支?
  • Funcs: Function coverage 方法覆蓋率,是否已呼叫程式中的每個函式(或子例程)?
  • Lines: Line coverage 行程式碼覆蓋,是否已執行原始檔中的每個可執行的行?

不在覆蓋範圍內的程式碼的行數會在Uncovered Line這欄顯示。

為測試提供async/await支援

在測試中想使用async/await語法,需新增setup.js檔案並在入口處新增babel-polyfill

require("babel-polyfill");
複製程式碼

並在.babelrc修改useBuiltInsentry

{
    ...
    "env": {
        "test": {
            "presets": [
                [
                    "env",
                    {
                        "useBuiltIns": "entry",
                        "modules": false,
                        "targets": {
                            "node": "current"
                        }
                    }
                ]
            ],
            "plugins": [
                "istanbul"
            ]
        }
    }
}
複製程式碼

接下來在src/index.jstests/example.spec.js兩個檔案新增新的程式碼:

//# src/index.js
export function getUsers(){
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            console.log('123')
            resolve(['Packy', 'Joan'])
        }, 1000)
    })
}

//# tests/unit/example.spec.js
describe('GetUsers', ()=>{
  it('get result is Array', async ()=>{
    const users = await getUsers();
    assert.isArray(users, "[message]");
  })
})
複製程式碼

執行測試就能看到效果!

讓測試更進一步,在 VS Code 中除錯

想在VS Code斷點除錯程式碼需要額外增加一些設定,新增以下程式碼至 webpack.config.js

//# webpack.config.js

//...

if (process.env.NODE_ENV === "test") {
    config.devtool = "eval-source-map";
    config.output = Object.assign(config.output, {
        devtoolModuleFilenameTemplate: "[absolute-resource-path]",
        devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
    });
}

module.exports = config;
複製程式碼

在VS Code 新增除錯程式碼

開啟 VS Code 除錯皮膚在下拉選項中選擇新增配置(或者直接建立並開啟.vscode/launch.json檔案):


// 使用 IntelliSense 瞭解相關屬性。
// 懸停以檢視現有屬性的描述。
// 欲瞭解更多資訊,請訪問: https://go.microsoft.com/fwlink/?linkid=830387
{
    "version": "0.2.0",
    "configurations": [
    {
        "type": "node",
        "request": "launch",
        "name": "Mocha-webpack Tests",
        "program": "${workspaceFolder}/node_modules/mocha-webpack/bin/mocha-webpack",
        "args": [
            "--full-trace",
            "--timeout",
            "999999",
            "--colors",
            "tests/**/*.js"
        ],
        "sourceMaps": true,
        "env": {
            "NODE_ENV": "test"
        },
        "internalConsoleOptions": "openOnSessionStart"
    }]
}
複製程式碼

src目錄下的原始碼或是tests目錄下的測試程式碼都能獲得斷點效果,想馬上嘗試可以下載本文例子vscode-mocha-webpack-example,安裝依賴後就能嘗試斷點除錯了。

設定參考源自vscode-ts-webpack-node-debug-example

值得一提的是,上面參考例子原文說devtool使用eval相關的設定並不能斷點,但是在使用mocha-webpack除錯時上面例子並不能實現斷點。在我和公司小夥伴多番尋找vscodemocha-webpack的issue後,經過各種嘗試下發現設定成eval-source-map便能實現最佳斷點效果(eval也能實現但是由於斷點sourcemap指向的源是生成後的檔案導致在斷點時多少會產生偏移)。

吐槽:在使用nvm切換nodejs環境時發現npm下載不了,開啟github的下載連結直接404了,驚悚地發現npm整個搬走 (`Д´*)9 ┴┴,為解決這個問題請下載最新版本v1.1.7nvm

最後:

我的動力來自你的指頭,請用你的指頭使勁給我個贊吧!d(´ω` )

覺得本文有幫助的話不要忘記點一下收藏φ(>ω<*) 哦!

同時歡迎各路新手、大神在本文下方吐槽留言,謝謝參與討論的各位仁兄!( • ̀ω•́ )✧

下面是本文完整例子,記得star一下!

同時非常感謝Mather協同編輯!

相關文章