一文搞懂前端組件發佈 npm 庫
前端的組件都是基於 javascript 開發,然後用 node.js 打包,發佈到 npm 的。所以我們要做組件發佈,首先要了解 npm 包的開發與發佈。
npm 包的開發與發佈
項目初始化
我們常常用 npm init 命令來初始化 node 項目;如果使用默認的設置,則可加入參數 -y。下面,我們新建一個 npm-components 文件夾,然後初始化項目:
$ mkdir npm-components
$ cd npm-components
$ npm init -y
此時,會在項目文件夾中出現一個 package.json 文件:
{
"name": "npm-components", // 組件名
"version": "1.0.0", // 組件版本
"description": "", // 組件描述
"main": "index.js", // 組件入口
"scripts": { // 組件腳本
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "", // 組件作者
"license": "ISC" // 開源協議
}
package.json 文件中的 name、version、main、script 是常用的幾個配置。
name 和 version 顧名思義是包名和版本。name 命名要注意最好是小寫加中劃線(-)的寫法,version 規範可以參考下一小節。
script 則是可以運行在命令行工具的腳本。對 linux 腳本不熟的同學,可以先在命令行工具中調試完成,再複製到 script 裏。
main 會作爲整個組件的入口,默認是 index.js,可以根據實際需要進行修改。
我們先按默認設置,在項目文件夾中,新建一個 index.js:
// index.js
const hello = 'hello';
const world = 'world';
module.exports.log = function() {
console.log(hello + world);
};
一個最簡單的 npm 項目就完成了,這裏僅包含 index.js
和 package.json
兩個文件。
package.json 除了以上配置之外,我們還可能會配置到
-
private 屬性,是否私有項目,私有項目一般不對外公佈。我們現在要把 npm 發佈,所以要把這個 private 刪除,或者設爲 false。非開源項目,加上
private: false
可以避免將其發佈到公網上。 -
files,需要發佈到 npm 的文件。
-
dependencies 項目依賴庫,這裏會顯示通過
npm install
安裝到項目中的庫; -
devDependencies 項目開發環境依賴庫,這裏會顯示通過
npm install --save-dev
安裝到項目中的庫。
npm version 與版本規範
開發完成就可以給組件打上版本號了。
按語義化版本控制規範 SemVer,版本格式爲:major.minor.patch,版本號遞增規則如下:
-
主版本號 major:當你做了不兼容的 API 修改,
-
次版本號 minor:當你做了向下兼容的功能性新增,
-
修訂號 patch:當你做了向下兼容的問題修正。
先行版本號及版本編譯信息可以加到 “major.minor.patch” 的後面,作爲延伸。如:1.0.0-alpha.0
每次發佈前,需要確定一下更新的內容,選擇版本號,使用 npm version 打上版本號
$ npm version [patch|minor|major]
此時,package.json 中的 version 會在相應的版本上加 1,有的命令行工具中也會看到版本號的變化。
~/npm-components (npm-components@1.0.0)
$ npm version patch
v1.0.1
~/code/npm-components (npm-components@1.0.1)
$
發佈到 npm
開發完成,標記版本號之後,發佈到 npm 只需要按部就班即可:
-
如果是公網的開源項目,需要去 npm 官網註冊個賬號;
-
如果貴公司有內部的 npm 庫,只需要找到 npm 管理人員給你添加即可;
-
如果平時使用其他源,需要切換到 npm(可安裝 nrm 來管理多個源):
$ nrm ls npm -------- https://registry.npmjs.org/ yarn ------- https://registry.yarnpkg.com/ cnpm ------- http://r.cnpmjs.org/ taobao ----- https://registry.npm.taobao.org/ $ nrm use npm
-
在項目根目錄下的命令行工具,運行
npm login
,會提示輸入個人信息,完成登錄。 -
運行,會進行上傳;
$ npm publish
-
如果上傳過程順利,沒報出紅色錯誤信息,在 https://www.npmjs.com/ 就可以看到你發佈的包了。
-
發佈的包在 72 小時內是可以刪除的,過了 72 小時就永遠無法刪除了,所以記得不要隨意發一些沒有意義的包。如果需要卸載,在發佈後 72 小時內執行:
# npm unpublish <pkg>[@<version>] $ npm unpublish npm-components@1.0.1
至此,一個簡單項目的 npm 包發佈完成。
模塊化
真實的項目中,我們往往不可能僅僅寫一個 js,一個 js 也不可能寫太長,多了就要拆分到多個 js。這個時候,就需要用到封裝了。
我們把常用的內容封裝在一起,就是一個簡單的模塊。這整個過程可以成爲是一個模塊化的過程。
模塊化幫我們實現了代碼複用,也幫我們做到了模塊內的數據、方法私有。
前端模塊化演進
前端的模塊化是一個演進的過程,經歷了 4 個階段:
-
全局 function 模式 : 將不同的功能封裝成不同的全局函數
-
namespace 模式 : 簡單對象封裝
-
IIFE 模式:匿名函數自調用 (閉包)
-
IIFE 模式增強: 引入依賴
基本原理是將模塊掛載在 window 屬性下。到 IIFE 增強階段,現在的模塊化規範基本已經成型,有了明顯的引入導出。如下代碼的引入 jQuery 和暴露 myModule:
// module.js文件
(function(window, $) {
let data = 'www.baidu.com';
// 操作數據的函數
function foo() {
// 用於暴露有函數
console.log(`foo() ${data}`);
$('body').css('background', 'red');
otherFun(); // 內部調用
}
function otherFun() {
// 內部私有的函數
console.log('otherFun()');
}
// 暴露行爲
window.myModule = { foo };
})(window, jQuery) // jQuery 作爲參數引入
在此基礎上,逐漸演化出了 AMD、CommonJS、CMD、UMD 等規範。
AMD(Asynchromous Module Definition - 異步模塊定義)
AMD 更多用於瀏覽器端,需要異步加載各模塊,然後再去執行內部代碼。是 RequireJS 在推廣過程中對模塊定義的規範化產出,推崇依賴前置。
// 定義沒有依賴的模塊
define(function(){ return 模塊 });
// 定義有依賴的模塊
define(['module1', 'module2'], function(m1, m2){ return 模塊 });
// 引入使用模塊
require(['module1', 'module2'], function(m1, m2){ 使用m1/m2 });
CommonJS 規範
CommonJS 是服務端模塊的規範,由於 Node.js 被廣泛認知。根據 CommonJS 規範,一個單獨的文件就是一個模塊。加載模塊使用 require 方法,該方法讀取一個文件並執行,最後返回文件內部的 module.exports 對象。
//module1.js
moudle.exports = { value: 1 };
//module2.js
var module1 = require('./module1');
var value2 = module1.value + 2;
module.exports ={ value: value2 };
CommonJS 加載模塊是同步的,所以只有加載完成才能執行後面的操作。
CMD(Common Module Definition - 公共模塊定義)
CMD 是 SeaJS 在推廣過程中對模塊定義的規範化產出,同時 CMD 也是延自 CommonJS Modules/2.0 規範。對於模塊的依賴,CMD 是延遲執行,推崇依賴就近。
define((require, exports, module) => {
module.exports = {
fun1: () => {
var $ = require('jquery'); // 執行 fun1 時,再加載
return $('#test');
}
};
});
如上代碼,只有當真正執行到 fun1 方法時,纔回去執行 jquery。
UMD 規範
那有沒有一種規範同時同時兼容 AMD 和 CommonJS,既可以適用瀏覽器端,有可以適用於服務器端?
有的,就是 UMD 規範。UMD 規範甚至都不能稱作一個規範,它是 AMD 和 CommonJS 的一個糅合。是一段固定的代碼寫法。如下的工廠模式:
((root, factory) => {
if (typeof define === 'function' && define.amd) {
//AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
//CommonJS
var $ = requie('jquery');
module.exports = factory($);
} else {
//都不是,瀏覽器全局定義
root.testModule = factory(root.jQuery);
}
})(this, ($) => {
//do something... 這裏是真正的函數體
});
ES module 規範
另一種支持服務端和瀏覽器端的規範就是 ES module 了,即我們現在最常用的import
和 export
:
export default ...;
import xxx from '';
export ...;
import { xxx } from '';
但目前也僅是大於 13.2 的 Node.js 版本才支持 ES 模塊化,還需要等待很長的時間全面使用。
所以,出於兼容性考慮,我們仍然選擇 UMD 規範進行開發。(處於淘汰邊緣的 UMD、CMD、AMD 這些規範,大家不需要理解,只需要知道有這麼一回事即可)
Webpack 打包
寫 UMD 的過程重複且繁瑣,這時候就需要用工具來完成了,Webpack 也就出現了。Webpack 配置相當簡單,以我們的項目爲例。
先安裝 webpack
$ npm install --save-dev webpack webpack-cli
新建 webpack 配置文件,webpack.config.js。用 webpack 給組件庫打包輸出符合 UMD 規範的代碼,只需要在基本配置稍作修改即可:
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'production',
entry: './index.js',
externals: 'lodash', // library包中有引入lodash,打包時不將lodash打進去,用戶在引入該library時,需自己再引入lodash,避免用戶重複引入lodash,導致文件過大。
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'library.js',
library: 'library', // 全局掛載包的引用名
libraryTarget: 'umd', //通用模式:支持用戶通過es、common.js、AMD的方式引入npm包
globalObject: 'this' // node 等環境運行時需要設置爲 this
}
}
要修改的地方如下:
-
filename:打包產物 library 的名稱;
-
externals: ‘lodash’, library 包中有引入 lodash,打包時不將 lodash 打進去,用戶在引入該 library 時,需自己再引入 lodash,避免用戶重複引入 lodash,導致文件過大;
-
libraryTarget: ‘umd’ 使用
UMD規範
支持用戶通過 es、common.js、AMD 的方式引入 npm 包; -
library: ‘library’ 會在全局變量中增加一個 liabray 的變量,用於掛載該包,主要用於通過腳本形式全局引入時的配置;
-
globalObject: 'this',爲 webpack 4 新增屬性,需要指定 global 的值爲 ’this‘,否則會爲默認值 ’self‘,無法在 nodejs 環境中使用。
在 package.json 中加上腳本
{
...
script: {
"build": "webpack"
}
}
運行腳本
npm run build
此時,webpack 就會開始打包。我們打開打包後的文件library.js
,就會發現我們前面 10 行 UMD 規範的代碼。以下是不壓縮不混淆(Webpack.config.js 設置optimization: { minimize: false }
)打包後的代碼:
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["library"] = factory();
else
root["library"] = factory();
})(this, function() {
return /******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 10:
/***/ ((module) => {
const hello = 'hello';
const world = 'world';
module.exports.log = function() {
console.log(hello + world);
};
/***/ })
...
至此,一個支持瀏覽器端和服務端調用的組件打包完成,可以再次打個版本號,發佈到 npm 庫了。
本地測試
我們可以使用 npm 的包進行測試,也可以在開發階段引入本地打好的包,進行簡單的測試。我們遵守了 UMD 規範,因此可以在 node 環境和瀏覽器環境都驗證一下:
node 環境驗證
項目根目錄新建一個 index.test.js
let library = require('./dist/library.js');
library.log();
本地執行
$ node index.test.js
helloworld
瀏覽器環境驗證
項目根目錄新建 public 文件夾,再在此文件夾下新建一個 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta >
<title>測試頁面</title>
</head>
<body>
<script src='../dist/library.js'></script>
<script>
library.log(); // 控制檯輸出 helloworld
</script>
</body>
</html>
此時,打開此文件夾,即可以看到 控制檯輸出 helloworld。
如果還要更真實的環境,還可以使用本地服務器做驗證。
首先,安裝 webpack 開發服務器:
$ npm install --save-dev webpack-dev-server
然後,修改 package.json,新增 webpack 服務器腳本:
{
...
"script":{
"serve": "webpack serve --open",
...
}
}
修改 webpack.config.js
const path = require('path');
module.exports = {
devServer: {
static: path.join(__dirname, "./") // 將項目根目錄設爲靜態目錄
},
...
}
運行服務器
$ npm run serve
webpack 會默認打開瀏覽器。可以看到瀏覽器控制檯輸出 helloworld。
驗證通過。大功告成!!!
參考:
-
認識 AMD、CMD、UMD、CommonJS
-
你真的知道 NPM 版本管理規範嗎
-
語義化版本 2.0.0
-
webpack 打包自己開發的 npm 包的相關配置
-
使用 Webpack 打包 Web 端和 Node 服務端通用的依賴庫
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/CVTRRnc6YfBJwz7kFF-z2Q