如何将用ES6编写的模块发布到NPM?


143

当我考虑在ES6中重写它时,我打算将其发布给NPM,以适应将来的需求并学习ES6。我已经使用Babel移植到ES5,并运行测试。但是我不确定如何进行:

  1. 我是否要转换并把生成的/ out文件夹发布到NPM?
  2. 我是否将结果文件夹包含在我的Github存储库中?
  3. 还是我要维护2个存储库,一个使用Github的ES6代码+ gulp脚本,一个使用转换后的结果+ NPM测试?

简而言之:我需要采取什么步骤将ES6编写的模块发布到NPM,同时仍然允许人们浏览/分叉原始代码?


我最近一直在为这个决定而苦苦挣扎。我看到约瑟(José)将您标记为正确的答案也视为共识。
talves

这是我的2018年答案,考虑到自2015
。– Dan Dascalescu

1
如果能做相反的事情,我会很乐意。使用ES模块导入NPM模块,但这是我得到的唯一结果。
SeanMC '19

Answers:


80

到目前为止,我所看到的模式是将es6文件保存在src目录中,并在npm的预发布目录中构建您的东西lib

您将需要一个.npmignore文件,类似于.gitignore,但忽略src而不是lib


4
您有示例存储库吗?
艾哈迈德·阿巴斯

2
@JamesAkwuh注意,您可能需要更改package.json中的“ start”和“ build”命令,以使用babel-cli:的相对路径./node_modules/babel-cli/bin/babel.js -s inline -d lib -w src。这应该确保在部署到新环境时安装不会失败。
phazonNinja '17

2
@phazonNinja npm处理它
James Akwuh 17-4-10

4
“如果没有.npmignore文件,但是有.gitignore文件,那么npm将忽略与.gitignore文件匹配的内容。” npm官方文档
Frank Nocke'5

10
而是.npmignore可以使用filespackage.json中字段。它可以让你准确地指定,而不是狩猎为你随机文件要发布的文件,希望发布。
Dan Dascalescu

76

我喜欢何塞的答案。我注意到已经有几个模块遵循该模式。这是使用Babel6轻松实现它的方法。我babel-cli在本地安装,因此如果更改全局babel版本,构建不会中断。

.npmignore

/src/

.gitignore

/lib/
/node_modules/

安装Babel

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

package.json

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

29
中的所有命令scripts都会node_modules/.bin添加到它们中,$PATH并且由于babel-cli安装了二进制文件,node_modules/.bin/babel因此无需按路径引用该命令。
Sukima 2015年

3
请注意,prepublish是有问题的,因为它可以在安装时(运行github.com/npm/npm/issues/3059),喜欢更地道version脚本挂机(docs.npmjs.com/cli/version
mattecapu

@mattecapu似乎问题prepublish仍然存在。目前,我认为手动编译src目录npm publish是必经之路。
sonlexqt

1
您可以使用prepublishOnly脚本挂钩(请参阅docs.npmjs.com/misc/scripts#prepublish-and-prepare)。请注意,在npm的第5版中,此功能应可正常运行,但目前(假设您使用的是npm v4 +),此功能应该可以使用。
亚历克斯·曼

1
@FrankNocke prepublishpublish(obv 。)之前运行,它将内容推送到npm(或任何您配置的位置)。因此,它是建立在NPM包发生的事情,即使它不是在检查。
西蒙·巴肯

42

TL; DR -不,直到日〜10月2019年Node.js的模块组已经要求

在[2019年10月]之前,请勿发布打算供Node.js使用的任何ES模块软件包。

2019年5月更新

自2015年提出此问题以来,JavaScript对模块的支持已显着成熟,并有望在2019年10月正式稳定。所有其他答案现在已过时或过于复杂。这是当前的情况和最佳实践。

ES6支持

自版本6开始,Node已支持99%的ES6(aka 2015)。Node的当前版本为12。所有常绿浏览器都支持绝大多数ES6功能。ECMAScript现在的版本为2019,而版本控制方案现在倾向于使用多年。

浏览器中的ES模块(也称为ECMAScript模块)

所有常绿浏览器已经支持 import -ing ES6模块自2017年动态进口支持通过浏览器(+叉如Opera和三星上网)和Safari。Firefox的下一个版本是67。

您不再需要 Webpack / rollup / Parcel等来加载模块。它们可能仍可用于其他目的,但不需要加载代码。您可以直接导入指向ES模块代码的URL。

节点中的ES模块

从Node v8.5.0开始,通过使用标志进行调用,已支持ES模块(.mjs带有import/的文件export)。2019年4月发布的Node v12重新编写了实验模块支持。最明显的变化是,导入时默认需要指定文件扩展名:node--experimental-modules

// lib.mjs 

export const hello = 'Hello world!';

// index.mjs:

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

.mjs始终注意强制性扩展。运行方式:

node --experimental-modules index.mjs

也是在模块团队要求开发人员不要发布供Node.js使用的 ES模块软件包之前,Node 12发行直到找到了通过require('pkg')和两者使用软件包的解决方案import 'pkg'。您仍然可以发布供浏览器使用的本机ES模块。

本机ES模块的生态系统支持

截至2019年5月,对ES模块的生态系统支持尚不成熟。例如,诸如JestAva之类的测试框架不支持--experimental-modules。您需要使用转译器,然后必须在使用命名的import(import { symbol })语法(尚不适用于大多数npm软件包)和默认的import语法(import Package from 'package')之间做出决定,该语法有效,但在Babel对其进行解析时无效对于使用TypeScript编写软件包(graphql工具,node-influx,faast等),但是有一种变通办法可以与--experimental-modulesBabel一起使用,如果Babel可以对您的代码进行编译,则可以使用Jest / Ava / Mocha等对其进行测试:

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

可以说很丑陋,但是通过这种方式,您可以使用import/ 编写自己的ES模块代码export然后使用进行运行node --experimental-modules,而无需编译器。如果您的依赖项尚不支持ESM,请按上述方式导入它们,然后就可以通过Babel使用测试框架和其他工具。


该问题的先前答案-请记住,在Node解决需求/导入问题之前,不要这样做,希望在2019年10月左右。

向后兼容发布ES6模块到npm

要发布的ES模块npmjs.org以便它可以直接导入,没有通天或其他transpilers,简单点的main领域在你package.json.mjs文件,但省略扩展名:

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

那是唯一的变化。通过省略扩展名,Node如果与--experimental-modules一起运行,将首先查找mjs文件。否则,它将退回到.js文件,因此您现有的支持较旧Node版本的转译过程将像以前一样工作-只需确保将Babel指向.mjs文件即可。

这是对节点<8.5.0具有向后兼容性的本机ES模块来源我发布到NPM的。您可以立即使用它,而无需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直接输出文件。


3
说:“所有常绿浏览器都支持绝大多数ES6功能。” 当您查看数据并意识到浏览器对es6的支持仅达到所有用户的80%时,这并不意味着什么。
Pedro Pedrosa

3
当前,生态系统肯定还不够成熟。版本为v12的Node.js团队特别询问:“在解决此问题之前,请不要发布打算供Node.js使用的任何ES模块软件包。” 2ality.com/2019/04/nodejs-esm-impl.html#es-modules-on-npm Mocha本身不支持.mjs文件。许多库(例如create-react-app,react-apollo,graphql-js)在包含mjs文件的依赖项方面存在问题。Node.js计划在2019年10月提供正式支持,这是我最早认真地重新审视这一点。
thisismydesign '19

3
@thisismydesign:感谢您提醒您更新此旧答案!刚刚这样做。
丹·达斯卡斯库

17

@乔斯是正确的。将ES6 / ES2015发布到NPM并没有什么问题,但这可能会引起麻烦,特别是如果使用您的软件包的人正在使用Webpack,例如,因为通常人们node_modules在使用babel性能原因。

因此,只需使用gulpgrunt或仅使用Node.js来构建lib文件夹是ES5。

这是我的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)相同。


1
而是.npmignore可以使用filespackage.json中字段。它可以让你准确地指定,而不是狩猎为你随机文件要发布的文件,希望发布。
Dan Dascalescu

难道这不会令人震惊吗?

我不推荐使用.npmignore,尝试package.jsonfiles,而不是,请参阅:github.com/c-hive/guides/blob/master/js/...
thisismydesign

@thisismydesign:这正是我在上面的评论中所建议的..?
丹·达斯卡斯库

我的坏,没注意到:)
thisismydesign '19

8

遵循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

package.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上的仓库

现在,您可以发布该软件包:

$ 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);

[汇总捆绑包的更新]

如@kyw所问,您将如何集成Rollup Bundle?

一,安装 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之前,您将看到已生成lib / index.js并将其编译为es5:

'use strict';

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

NewOneWithParameters(10, 20);

注意:顺便说一句,@babel/cli如果您使用汇总捆绑程序,则不再需要。您可以安全地将其卸载:

npm uninstall @babel/cli

您将如何集成汇总捆绑器?
kyw

1
@kyw,关于如何集成汇总捆绑器,请参阅我的最新答案。
尤西


6

如果您想在一个非常简单的小型开源Node模块中看到它的实际效果,那么请看第n天(我开始学习-也有其他贡献者)。查看package.json文件和预发布步骤,它将引导您在何处以及如何执行此操作。如果克隆该模块,则可以在本地运行该模块,并将其用作您的模板。


4

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';

我们在05.2019中对ESM支持进行调查,发现许多库缺乏支持(因此建议向后兼容):


我似乎无法在node_modules文件夹(由npm root -g提供)中导入全局安装的ES6模块。我们真的不应该能够做到这一点吗?我真的很困惑。我知道npm link可以通过将模块链接到我的本地node_modules文件夹来解决问题,但是我想知道为什么不支持导入全局节点模块。
Joakim L. Christiansen,

回答我自己,我想它将永远不会得到支持:github.com/nodejs/node-eps/blob/master/…这是一个非常愚蠢的决定,很容易得到支持……
Joakim L. Christiansen

3

NPM软件包的两个标准是,它仅可用于 require( 'package' )并完成某些类似于软件的工作。

如果满足这两个要求,那么您可以做任何您想做的事情。即使该模块是用ES6编写的,如果最终用户不需要知道这一点,我现在也要对其进行编译以获得最大的支持。

但是,如果像koa一样,您的模块需要与使用ES6功能的用户兼容,那么两个软件包的解决方案可能是一个更好的主意。

带走

  1. 只发布所需的代码来完成require( 'your-package' )工作。
  2. 除非ES5和6之间的问题对用户很重要,否则仅发布1个软件包。如果需要的话,将其转换。

1
这似乎无法回答问题。我认为OP正在设法弄清楚如何构建其Github存储库以及要发布到NPM的内容,您所说的只是他们可以做他们想做的事情。OP希望针对这种情况的良好实践提出具体建议。
jfriend00

@ jfriend00我不同意。我建议他进行转换,并且仅发布require( 'package' )工作所需的文件。我将编辑答案以使其更清楚。也就是说,何塞的答案比我自己的答案要好得多。
JoshWillik 2015年

José的回答非常好,但是我感谢这一点,它明确概述了何时/为什么使用一个与两个软件包的良好经验法则。
乔丹·格雷

3

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


该帖子忽略了以下事实:您可以(并且应该)发布到ES6中编写的NPM模块,并且import可以直接使用而无需Babel或任何其他编译器。
Dan Dascalescu '18

0

依靠模块的解剖结构,此解决方案可能无法工作,但是如果您的模块包含在单个文件中,并且没有依赖项(不使用import),则可以使用以下模式按原样释放代码,并且将能够通过导入(浏览器ES6模块)导入并且需要(节点CommonJS模块)导入

另外,它很适合使用SCRIPT HTML元素导入。

main.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 ;
})()

my-module.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 */
    }

要使用您的模块,您现在可以使用常规语法...

当进口的节点

    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>

-1

给任何人的一些额外说明,直接使用来自github的自己的模块,而不需要发布模块:

广泛使用的)“预发布”挂钩没有做任何事情对您帮助。

一个人可以做的最好的事情(如果计划依靠github仓库,而不是发布的东西):

  • 不公开src从.npmignore(换句话说:允许它)。如果您没有.npmignore,请记住:.gitignore将在安装位置使用的副本,例如ls node_modules/yourProject所示。
  • 确保确实babel-cli是模块中的依赖关系,而不仅仅是devDepenceny,因为您确实是在使用模块的App开发人员计算机上构建消费机器
  • 安装挂钩中执行构建工作,即:

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

(尝试“预安装”没有附加价值,即可能缺少babel-cli)


3
在安装时进行编译非常不礼貌。请不要这样做-npm的安装时间已经很糟糕了!对于要避免使用npm软件包存储库的内部代码,可以执行以下操作:1)使用monorepo,2)上载并依赖npm pack输出,3)检入build输出。
西蒙·布坎
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.