簡體   English   中英

如何將用 ES6 編寫的模塊發布到 NPM?

[英]How to publish a module written in ES6 to NPM?

我正准備向 NPM 發布一個模塊,當我考慮在 ES6 中重寫它時,既要面向未來,又要學習 ES6。 我已經使用 Babel 轉譯為 ES5,並運行測試。 但我不確定如何繼續:

  1. 我是否轉譯並將生成的 /out 文件夾發布到 NPM?
  2. 我是否將結果文件夾包含在我的 Github 存儲庫中?
  3. 或者我是否維護 2 個存儲庫,一個帶有用於 Github 的 ES6 代碼 + gulp 腳本,另一個帶有用於 NPM 的轉譯結果 + 測試?

簡而言之:我需要采取哪些步驟才能將用 ES6 編寫的模塊發布到 NPM,同時仍然允許人們瀏覽/分叉原始代碼?

到目前為止,我看到的模式是將 es6 文件保存在src目錄中,並在 npm 的 prepublish 中構建你的東西​​到lib目錄。

您將需要一個 .npmignore 文件,類似於 .gitignore 但忽略src而不是lib

我喜歡何塞的回答。 我已經注意到有幾個模塊已經遵循了這種模式。 下面是如何使用 Babel6 輕松實現它。 我在本地安裝babel-cli ,因此如果我更改了全局 babel 版本,構建不會中斷。

.npmignore

/src/

.gitignore

/lib/
/node_modules/

安裝巴別塔

npm install --save-dev babel-core babel-cli babel-preset-es2015

包.json

{
  "main": "lib/index.js",
  "scripts": {
    "prepublish": "babel src --out-dir lib"
  },
  "babel": {
    "presets": ["es2015"]
  }
}

TL;DR - 不要,直到 2019 年 10 月。Node.js模塊團隊已經

請在 [2019 年 10 月] 之前發布任何供 Node.js 使用的 ES 模塊包

2019 年 5 月更新

自 2015 年提出這個問題以來,JavaScript 對模塊的支持已經顯着成熟,有望在 2019 年 10 月正式穩定。所有其他答案現在都已過時或過於復雜。 這是當前的情況和最佳實踐。

ES6 支持

自版本 6 以來, 99% 的 ES6(又名 2015)已​​被 Node 支持。 Node 的當前版本是 12。所有常綠瀏覽器都支持 ES6 的絕大多數功能。 ECMAScript 現在是2019 版本,版本控制方案現在傾向於使用年份。

瀏覽器中的 ES 模塊(又名 ECMAScript 模塊)

自 2017 年以來,所有常青瀏覽器支持import ES6 模塊。 Chrome(+ Opera 和三星 Internet 等分支)和 Safari支持動態導入 Firefox 支持計划用於下一個版本 67。

您不再需要Webpack/rollup/Parcel 等來加載模塊。 它們可能仍用於其他目的,但不是加載代碼所必需的。 您可以直接導入指向 ES 模塊代碼的 URL。

Node 中的 ES 模塊

ES 模塊(帶有import / export .mjs文件)從 Node v8.5.0 開始通過使用--experimental-modules標志調用node來支持。 2019 年 4 月發布的 Node v12 重寫了實驗模塊支持。 最明顯的變化是導入時需要默認指定文件擴展名:

// lib.mjs 

export const hello = 'Hello world!';

// index.mjs:

import { hello } from './lib.mjs';
console.log(hello);

請注意貫穿始終的強制性.mjs擴展名。 運行為:

node --experimental-modules index.mjs

Node 12 版本也是模塊團隊要求開發人員不要發布供 Node.js 使用的ES 模塊包的時間,直到找到通過require('pkg')import 'pkg'使用包的解決方案。 您仍然可以發布用於瀏覽器的原生 ES 模塊。

原生 ES 模塊的生態系統支持

截至 2019 年 5 月,對 ES 模塊的生態系統支持尚不成熟。 例如,像JestAva這樣的測試框架不支持--experimental-modules 您需要使用轉譯器,然后必須在使用命名導入( import { symbol } )語法(它還不適用於大多數 npm 包)和默認導入語法( import Package from 'package' )之間做出決定,這確實有效,但當 Babel在 TypeScript 中編寫(graphql-tools、node-influx、faast 等)解析它時就不行了。然而,有一種解決方法適用於--experimental-modules並且如果 Babel 轉譯你的代碼,那么你可以用 Jest/Ava/Mocha 等測試它:

import * as ApolloServerM from 'apollo-server'; const ApolloServer = ApolloServerM.default || ApolloServerM;

可以說是丑陋的,但通過這種方式,您可以使用import / export編寫自己的 ES 模塊代碼,並使用node --experimental-modules運行它,而無需轉譯器。 如果您有尚未准備好 ESM 的依賴項,請按上述方式導入它們,您將能夠通過 Babel 使用測試框架和其他工具。


該問題的先前答案 - 請記住,在 Node 解決 require/import 問題之前不要這樣做,希望在 2019 年 10 月左右。

將 ES6 模塊發布到 npm,具有向后兼容性

要將 ES 模塊發布到 npmjs.org 以便它可以直接導入,而無需 Babel 或其他轉譯器,只需將package.jsonmain字段指向.mjs文件,但省略擴展名:

{
  "name": "mjs-example",
  "main": "index"
}

這是唯一的變化。 通過省略擴展名,如果使用 --experimental-modules 運行,Node 將首先查找 mjs 文件。 否則它將回退到 .js 文件,因此您現有的支持舊 Node 版本的轉譯過程將像以前一樣工作 - 只需確保將 Babel 指向.mjs文件。

這是我發布到 NPM 的具有向后兼容性 Node < 8.5.0 的本機 ES 模塊源代碼 你現在就可以使用它,不需要 Babel 或其他任何東西。

安裝模塊:

npm install local-iso-dt
# or, yarn add local-iso-dt

創建一個測試文件test.mjs

import { localISOdt } from 'local-iso-dt/index.mjs';
console.log(localISOdt(), 'Starting job...');

使用 --experimental-modules 標志運行節點 (v8.5.0+):

node --experimental-modules test.mjs

打字稿

如果你用 TypeScript 開發,你可以生成 ES6 代碼並使用 ES6 模塊:

tsc index.js --target es6 --modules es2015

然后,您需要將*.js輸出重命名為.mjs ,這是一個已知問題,希望很快得到修復,以便tsc可以直接輸出.mjs文件。

@Jose 是對的。 將 ES6/ES2015 發布到 NPM 沒有任何問題,但這可能會導致問題,特別是如果使用您的包的人正在使用 Webpack,例如,因為出於性能原因,通常人們在使用babel進行預處理時會忽略node_modules文件夾。

因此,只需使用gulpgrunt或簡單的 Node.js 來構建一個 ES5 的lib文件夾。

這是我的build-lib.js腳本,我保存在./tools/ (這里沒有gulpgrunt ):

var rimraf = require('rimraf-promise');
var colors = require('colors');
var exec = require('child-process-promise').exec;

console.log('building lib'.green);

rimraf('./lib')
    .then(function (error) {
        let babelCli = 'babel --optional es7.objectRestSpread ./src --out-dir ./lib';
        return exec(babelCli).fail(function (error) {
            console.log(colors.red(error))
        });
    }).then(() => console.log('lib built'.green));

這是最后一個建議:您需要將 .npmignore 添加到您的項目中 如果npm publish沒有找到這個文件,它將使用.gitignore代替,這會給你帶來麻煩,因為通常你的.gitignore文件會排除./lib並包含./src ,這與你想要的完全相反正在發布到 NPM。 .npmignore文件的語法與.gitignore (AFAIK) 基本相同。

遵循 José 和 Marius 的方法,(2019 年更新 Babel 的最新版本):將最新的 JavaScript 文件保存在 src 目錄中,並使用 npm 的prepublish腳本構建並輸出到 lib 目錄。

.npmignore

/src

.gitignore

/lib
/node_modules

安裝 Babel (在我的例子中是 7.5.5 版)

$ npm install @babel/core @babel/cli @babel/preset-env --save-dev

包.json

{
  "name": "latest-js-to-npm",
  "version": "1.0.0",
  "description": "Keep the latest JavaScript files in a src directory and build with npm's prepublish script and output to the lib directory.",
  "main": "lib/index.js",
  "scripts": {
    "prepublish": "babel src -d lib"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.5.5",
    "@babel/core": "^7.5.5",
    "@babel/preset-env": "^7.5.5"
  },
  "babel": {
    "presets": [
      "@babel/preset-env"
    ]
  }
}

我有使用箭頭函數的src/index.js

"use strict";

let NewOneWithParameters = (a, b) => {
  console.log(a + b); // 30
};
NewOneWithParameters(10, 20);

這是GitHub 上的 repo

現在您可以發布包:

$ npm publish
...
> latest-js-to-npm@1.0.0 prepublish .
> babel src -d lib

Successfully compiled 1 file with Babel.
...

在將包發布到 npm 之前,你會看到lib/index.js已經生成,它被轉譯為 es5:

"use strict";

var NewOneWithParameters = function NewOneWithParameters(a, b) {
  console.log(a + b); // 30
};

NewOneWithParameters(10, 20);

[Rollup 打包器的更新]

正如@kyw 所問的,您將如何集成 Rollup 捆綁器?

首先,安裝rolluprollup-plugin-babel

npm install -D rollup rollup-plugin-babel

二、在項目根目錄創建rollup.config.js

import babel from "rollup-plugin-babel";

export default {
  input: "./src/index.js",
  output: {
    file: "./lib/index.js",
    format: "cjs",
    name: "bundle"
  },
  plugins: [
    babel({
      exclude: "node_modules/**"
    })
  ]
};

最后,更新prepublishpackage.json

{
  ...
  "scripts": {
    "prepublish": "rollup -c"
  },
  ...
}

現在可以運行npm publish ,在npm publish包到 npm 之前,你會看到 lib/index.js 已經生成,並被轉譯為 es5:

'use strict';

var NewOneWithParameters = function NewOneWithParameters(a, b) {
  console.log(a + b); // 30
};

NewOneWithParameters(10, 20);

注意:順便說一下,如果您使用的是 Rollup 捆綁器,則不再需要@babel/cli 您可以安全地卸載它:

npm uninstall @babel/cli

如果您想在一個非常簡單的小型開源 Node 模塊中看到這一點,那么請查看第 n 天(我開始 - 也是其他貢獻者)。 查看 package.json 文件和 prepublish 步驟,這將引導您到何處以及如何執行此操作。 如果您克隆該模塊,您可以在本地運行它並將其用作您的模板。

Node.js 13.2.0+ 支持沒有實驗標志的 ESM,並且有幾個選項可以發布混合(ESM 和 CommonJS)NPM 包(取決於所需的向后兼容性級別): https : //2ality.com/2019 /10/hybrid-npm-packages.html

我建議采用完全向后兼容的方式,以便更輕松地使用您的包。 這可能如下所示:

混合包具有以下文件:

mypkg/
  package.json
  esm/
    entry.js
  commonjs/
    package.json
    entry.js

mypkg/package.json

{
  "type": "module",
  "main": "./commonjs/entry.js",
  "exports": {
    "./esm": "./esm/entry.js"
  },
  "module": "./esm/entry.js",
  ···
}

mypkg/commonjs/package.json

{
  "type": "commonjs"
}

從 CommonJS 導入:

const {x} = require('mypkg');

從 ESM 導入:

import {x} from 'mypkg/esm';

我們在 2019 年 5 月對 ESM 支持進行調查,發現很多庫都缺乏支持(因此建議向后兼容):

package.json的主鍵決定了包發布后的入口點。 所以你可以把 Babel 的輸出放在任何你想要的地方,只需要在main鍵中提到正確的路徑。

"main": "./lib/index.js",

這是一篇關於如何發布 npm 包的好文章

https://codeburst.io/publish-your-own-npm-package-ff918698d450

這是您可以用作參考的示例存儲庫

https://github.com/flexdinesh/npm-module-boilerplate

NPM 包的兩個標准是,它只require( 'package' )一個require( 'package' )就可以使用,並且可以做一些類似軟件的事情。

如果你滿足這兩個要求,你就可以為所欲為。 即使模塊是用 ES6 編寫的,如果最終用戶不需要知道這一點,我會暫時轉譯它以獲得最大的支持。

但是,如果像koa一樣,您的模塊需要與使用 ES6 功能的用戶兼容,那么也許兩個包的解決方案會更好。

帶走

  1. 只發布使require( 'your-package' )工作所需的代碼。
  2. 除非 ES5 和 6 之間對用戶很重要,否則只發布 1 個包。 如果必須,請轉譯它。

根據模塊的結構,此解決方案可能不起作用,但如果您的模塊包含在單個文件中,並且沒有依賴項(不使用import ),則使用以下模式可以按原樣發布代碼, 並且可以通過import (Browser ES6 Modules) 和require (Node CommonJS Modules)導入

作為獎勵,它適合使用 SCRIPT HTML 元素導入。

主.js :

(function(){
    'use strict';
    const myModule = {
        helloWorld : function(){ console.log('Hello World!' )} 
    };

    // if running in NODE export module using NODEJS syntax
    if(typeof module !== 'undefined') module.exports = myModule ;
    // if running in Browser, set as a global variable.
    else window.myModule = myModule ;
})()

我的模塊.js

    // import main.js (it will declare your Object in the global scope)
    import './main.js';
    // get a copy of your module object reference
    let _myModule = window.myModule;
    // delete the the reference from the global object
    delete window.myModule;
    // export it!
    export {_myModule as myModule};

package.json :`

    {
        "name" : "my-module", // set module name
        "main": "main.js",  // set entry point
        /* ...other package.json stuff here */
    }

要使用您的模塊,您現在可以使用常規語法...

NODE 中導入時...

    let myModule = require('my-module');
    myModule.helloWorld();
    // outputs 'Hello World!'

BROWSER 中導入時...

    import {myModule} from './my-module.js';
    myModule.helloWorld();
    // outputs 'Hello World!'

甚至當使用HTML 腳本元素包含時......

<script src="./main.js"></script>
<script>
     myModule.helloWorld();
    // outputs 'Hello World!'
</script>

對任何人的一些額外說明,直接從 github 使用自己的模塊,而不是通過已發布的模塊:

廣泛使用的)“預發布”鈎子沒有為您做任何事情

可以做的最好的事情(如果計划依賴 github 存儲庫,而不是已發布的東西):

  • 從 .npmignore 中取消src (換句話說:允許它)。 如果您沒有.npmignore ,請記住:將在安裝位置使用.gitignore的副本,因為ls node_modules/yourProject會顯示給您。
  • 確保babel-cli是您模塊中的依賴項,而不僅僅是 devDepenceny,因為您確實是在消費機器上構建,也就是在應用程序開發人員計算機上,誰正在使用您的模塊
  • 安裝鈎子中做構建事情,即:

    "install": "babel src -d lib -s"

(嘗試任何“預安裝”都沒有附加價值,即 babel-cli 可能會丟失)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM