简体   繁体   English

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

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

I was about to publish a module to NPM, when I thought about rewriting it in ES6, to both future-proof it, and learn ES6.我正准备向 NPM 发布一个模块,当我考虑在 ES6 中重写它时,既要面向未来,又要学习 ES6。 I've used Babel to transpile to ES5, and run tests.我已经使用 Babel 转译为 ES5,并运行测试。 But I'm not sure how to proceed:但我不确定如何继续:

  1. Do I transpile, and publish the resulting /out folder to NPM?我是否转译并将生成的 /out 文件夹发布到 NPM?
  2. Do I include the result folder in my Github repo?我是否将结果文件夹包含在我的 Github 存储库中?
  3. Or do I maintain 2 repos, one with the ES6 code + gulp script for Github, and one with the transpiled results + tests for NPM?或者我是否维护 2 个存储库,一个带有用于 Github 的 ES6 代码 + gulp 脚本,另一个带有用于 NPM 的转译结果 + 测试?

In short: what steps do I need to take to publish a module written in ES6 to NPM, while still allowing people to browse/fork the original code?简而言之:我需要采取哪些步骤才能将用 ES6 编写的模块发布到 NPM,同时仍然允许人们浏览/分叉原始代码?

The pattern I have seen so far is to keep the es6 files in a src directory and build your stuff in npm's prepublish to the lib directory.到目前为止,我看到的模式是将 es6 文件保存在src目录中,并在 npm 的 prepublish 中构建你的东西​​到lib目录。

You will need an .npmignore file, similar to .gitignore but ignoring src instead of lib .您将需要一个 .npmignore 文件,类似于 .gitignore 但忽略src而不是lib

I like José's answer.我喜欢何塞的回答。 I've noticed several modules follow that pattern already.我已经注意到有几个模块已经遵循了这种模式。 Here's how you can easily implement it with Babel6.下面是如何使用 Babel6 轻松实现它。 I install babel-cli locally so the build doesn't break if I ever change my global babel version.我在本地安装babel-cli ,因此如果我更改了全局 babel 版本,构建不会中断。

.npmignore .npmignore

/src/

.gitignore .gitignore

/lib/
/node_modules/

Install Babel安装巴别塔

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

package.json包.json

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

TL;DR - Don't, until ~October 2019. The Node.js Modules Team has asked : TL;DR - 不要,直到 2019 年 10 月。Node.js模块团队已经

Please do not publish any ES module packages intended for use by Node.js until [October 2019]请在 [2019 年 10 月] 之前发布任何供 Node.js 使用的 ES 模块包

2019 May update 2019 年 5 月更新

Since 2015 when this question was asked, JavaScript support for modules has matured significantly, and is hopefully going to be officially stable in October 2019. All other answers are now obsolete or overly complicated.自 2015 年提出这个问题以来,JavaScript 对模块的支持已经显着成熟,有望在 2019 年 10 月正式稳定。所有其他答案现在都已过时或过于复杂。 Here is the current situation and best practice.这是当前的情况和最佳实践。

ES6 support ES6 支持

99% of ES6 (aka 2015) has been supported by Node since version 6 .自版本 6 以来, 99% 的 ES6(又名 2015)已​​被 Node 支持。 The current version of Node is 12. All evergreen browsers support the vast majority of ES6 features. Node 的当前版本是 12。所有常绿浏览器都支持 ES6 的绝大多数功能。 ECMAScript is now at version 2019 , and the versioning scheme now favors using years. ECMAScript 现在是2019 版本,版本控制方案现在倾向于使用年份。

ES Modules (aka ECMAScript modules) in browsers浏览器中的 ES 模块(又名 ECMAScript 模块)

All evergreen browsers have been supporting import -ing ES6 modules since 2017. Dynamic imports are supported by Chrome (+ forks like Opera and Samsung Internet) and Safari.自 2017 年以来,所有常青浏览器支持import ES6 模块。 Chrome(+ Opera 和三星 Internet 等分支)和 Safari支持动态导入 Firefox support is slated for the next version, 67. Firefox 支持计划用于下一个版本 67。

You no longer need Webpack/rollup/Parcel etc. to load modules.您不再需要Webpack/rollup/Parcel 等来加载模块。 They may be still useful for other purposes, but are not required to load your code.它们可能仍用于其他目的,但不是加载代码所必需的。 You can directly import URLs pointing to ES modules code.您可以直接导入指向 ES 模块代码的 URL。

ES modules in Node Node 中的 ES 模块

ES modules ( .mjs files with import / export ) have been supported since Node v8.5.0 by calling node with the --experimental-modules flag. ES 模块(带有import / export .mjs文件)从 Node v8.5.0 开始通过使用--experimental-modules标志调用node来支持。 Node v12, released in April 2019, rewrote the experimental modules support. 2019 年 4 月发布的 Node v12 重写了实验模块支持。 The most visible change is that the file extension needs to be specified by default when importing:最明显的变化是导入时需要默认指定文件扩展名:

// lib.mjs 

export const hello = 'Hello world!';

// index.mjs:

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

Note the mandatory .mjs extensions throughout.请注意贯穿始终的强制性.mjs扩展名。 Run as:运行为:

node --experimental-modules index.mjs

The Node 12 release is also when the Modules Team asked developers to not publish ES module packages intended for use by Node.js until a solution is found for using packages via both require('pkg') and import 'pkg' . Node 12 版本也是模块团队要求开发人员不要发布供 Node.js 使用的ES 模块包的时间,直到找到通过require('pkg')import 'pkg'使用包的解决方案。 You can still publish native ES modules intended for browsers.您仍然可以发布用于浏览器的原生 ES 模块。

Ecosystem support of native ES modules原生 ES 模块的生态系统支持

As of May 2019, ecosystem support for ES Modules is immature.截至 2019 年 5 月,对 ES 模块的生态系统支持尚不成熟。 For example, test frameworks like Jest andAva don't support --experimental-modules .例如,像JestAva这样的测试框架不支持--experimental-modules You need to use a transpiler, and must then decide between using the named import ( import { symbol } ) syntax (which won't work with most npm packages yet), and the default import syntax ( import Package from 'package' ), which does work, but not when Babel parses it for packages authored in TypeScript (graphql-tools, node-influx, faast etc.) There is however a workaround that works both with --experimental-modules and if Babel transpiles your code so you can test it with Jest/Ava/Mocha etc:您需要使用转译器,然后必须在使用命名导入( 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;

Arguably ugly, but this way you can write your own ES modules code with import / export and run it with node --experimental-modules , without transpilers.可以说是丑陋的,但通过这种方式,您可以使用import / export编写自己的 ES 模块代码,并使用node --experimental-modules运行它,而无需转译器。 If you have dependencies that aren't ESM-ready yet, import them as above, and you'll be able to use test frameworks and other tooling via Babel.如果您有尚未准备好 ESM 的依赖项,请按上述方式导入它们,您将能够通过 Babel 使用测试框架和其他工具。


Previous answer to the question - remember, don't do this until Node solves the require/import issue, hopefully around October 2019.该问题的先前答案 - 请记住,在 Node 解决 require/import 问题之前不要这样做,希望在 2019 年 10 月左右。

Publishing ES6 modules to npm, with backwards compatibility将 ES6 模块发布到 npm,具有向后兼容性

To publish an ES module to npmjs.org so that it can be imported directly, without Babel or other transpilers, simply point the main field in your package.json to the .mjs file, but omit the extension:要将 ES 模块发布到 npmjs.org 以便它可以直接导入,而无需 Babel 或其他转译器,只需将package.jsonmain字段指向.mjs文件,但省略扩展名:

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

That's the only change.这是唯一的变化。 By omitting the extension, Node will look first for an mjs file if run with --experimental-modules.通过省略扩展名,如果使用 --experimental-modules 运行,Node 将首先查找 mjs 文件。 Otherwise it will fall back to the .js file, so your existing transpilation process to support older Node versions will work as before — just make sure to point Babel to the .mjs file(s).否则它将回退到 .js 文件,因此您现有的支持旧 Node 版本的转译过程将像以前一样工作 - 只需确保将 Babel 指向.mjs文件。

Here's the source for a native ES module with backwards compatibility for Node < 8.5.0 that I published to NPM.这是我发布到 NPM 的具有向后兼容性 Node < 8.5.0 的本机 ES 模块源代码 You can use it right now, without Babel or anything else.你现在就可以使用它,不需要 Babel 或其他任何东西。

Install the module:安装模块:

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

Create a test file test.mjs :创建一个测试文件test.mjs

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

Run node (v8.5.0+) with the --experimental-modules flag:使用 --experimental-modules 标志运行节点 (v8.5.0+):

node --experimental-modules test.mjs

TypeScript打字稿

If you develop in TypeScript, you can generate ES6 code and use ES6 modules:如果你用 TypeScript 开发,你可以生成 ES6 代码并使用 ES6 模块:

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

Then, you need to rename *.js output to .mjs , a known issue that will hopefully get fixed soon so tsc can output .mjs files directly.然后,您需要将*.js输出重命名为.mjs ,这是一个已知问题,希望很快得到修复,以便tsc可以直接输出.mjs文件。

@Jose is right. @Jose 是对的。 There's nothing wrong with publishing ES6/ES2015 to NPM but that may cause trouble, specially if the person using your package is using Webpack, for instance, because normally people ignore the node_modules folder while preprocessing with babel for performance reasons.将 ES6/ES2015 发布到 NPM 没有任何问题,但这可能会导致问题,特别是如果使用您的包的人正在使用 Webpack,例如,因为出于性能原因,通常人们在使用babel进行预处理时会忽略node_modules文件夹。

So, just use gulp , grunt or simply Node.js to build a lib folder that is ES5.因此,只需使用gulpgrunt或简单的 Node.js 来构建一个 ES5 的lib文件夹。

Here's my build-lib.js script, which I keep in ./tools/ (no gulp or grunt here):这是我的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));

Here's a last advice: You need to add a .npmignore to your project .这是最后一个建议:您需要将 .npmignore 添加到您的项目中 If npm publish doesn't find this file, it will use .gitignore instead, which will cause you trouble because normally your .gitignore file will exclude ./lib and include ./src , which is exactly the opposite of what you want when you are publishing to NPM.如果npm publish没有找到这个文件,它将使用.gitignore代替,这会给你带来麻烦,因为通常你的.gitignore文件会排除./lib并包含./src ,这与你想要的完全相反正在发布到 NPM。 The .npmignore file has basically the same syntax of .gitignore (AFAIK). .npmignore文件的语法与.gitignore (AFAIK) 基本相同。

Following José and Marius's approach, (with update of Babel's latest version in 2019): Keep the latest JavaScript files in a src directory, and build with npm's prepublish script and output to the lib directory.遵循 José 和 Marius 的方法,(2019 年更新 Babel 的最新版本):将最新的 JavaScript 文件保存在 src 目录中,并使用 npm 的prepublish脚本构建并输出到 lib 目录。

.npmignore .npmignore

/src

.gitignore .gitignore

/lib
/node_modules

Install Babel (version 7.5.5 in my case)安装 Babel (在我的例子中是 7.5.5 版)

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

package.json包.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"
    ]
  }
}

And I have src/index.js which uses the arrow function:我有使用箭头函数的src/index.js

"use strict";

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

Here is the repo on GitHub .这是GitHub 上的 repo

Now you can publish the package:现在您可以发布包:

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

Successfully compiled 1 file with Babel.
...

Before the package is published to npm, you will see that lib/index.js has been generated, which is transpiled to es5:在将包发布到 npm 之前,你会看到lib/index.js已经生成,它被转译为 es5:

"use strict";

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

NewOneWithParameters(10, 20);

[Update for Rollup bundler] [Rollup 打包器的更新]

As asked by @kyw, how would you integrate Rollup bundler?正如@kyw 所问的,您将如何集成 Rollup 捆绑器?

First, install rollup and rollup-plugin-babel首先,安装rolluprollup-plugin-babel

npm install -D rollup rollup-plugin-babel

Second, create rollup.config.js in the project root directory二、在项目根目录创建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/**"
    })
  ]
};

Lastly, update prepublish in package.json最后,更新prepublishpackage.json

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

Now you can run npm publish , and before the package is published to npm, you will see that lib/index.js has been generated, which is transpiled to es5:现在可以运行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);

Note: by the way, you no longer need @babel/cli if you are using the Rollup bundler.注意:顺便说一下,如果您使用的是 Rollup 捆绑器,则不再需要@babel/cli You can safely uninstall it:您可以安全地卸载它:

npm uninstall @babel/cli

If you want to see this in action in a very simple small open source Node module then take a look at nth-day (which I started - also other contributors).如果您想在一个非常简单的小型开源 Node 模块中看到这一点,那么请查看第 n 天(我开始 - 也是其他贡献者)。 Look in the package.json file and at the prepublish step which will lead you to where and how to do this.查看 package.json 文件和 prepublish 步骤,这将引导您到何处以及如何执行此操作。 If you clone that module you can run it locally and use it as a template for yous.如果您克隆该模块,您可以在本地运行它并将其用作您的模板。

Node.js 13.2.0+ supports ESM without the experimental flag and there're a few options to publish hybrid (ESM and CommonJS) NPM packages (depending on the level of backward compatibility needed): https://2ality.com/2019/10/hybrid-npm-packages.html Node.js 13.2.0+ 支持没有实验标志的 ESM,并且有几个选项可以发布混合(ESM 和 CommonJS)NPM 包(取决于所需的向后兼容性级别): https : //2ality.com/2019 /10/hybrid-npm-packages.html

I recommend going the full backward compatibility way to make the usage of your package easier.我建议采用完全向后兼容的方式,以便更轻松地使用您的包。 This could look as follows:这可能如下所示:

The hybrid package has the following files:混合包具有以下文件:

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"
}

Importing from CommonJS:从 CommonJS 导入:

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

Importing from ESM:从 ESM 导入:

import {x} from 'mypkg/esm';

We did an investigation into ESM support in 05.2019 and found that a lot of libraries were lacking support (hence the recommendation for backward compatibility):我们在 2019 年 5 月对 ESM 支持进行调查,发现很多库都缺乏支持(因此建议向后兼容):

The main key in package.json decides the entry point to the package once it's published. package.json的主键决定了包发布后的入口点。 So you can put your Babel's output wherever you want and just have to mention the right path in main key.所以你可以把 Babel 的输出放在任何你想要的地方,只需要在main键中提到正确的路径。

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

Here's a well written article on how to publish an npm package这是一篇关于如何发布 npm 包的好文章

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

Here's a sample repo you can use for reference这是您可以用作参考的示例存储库

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

The two criteria of an NPM package is that it is usable with nothing more than a require( 'package' ) and does something software-ish. NPM 包的两个标准是,它只require( 'package' )一个require( 'package' )就可以使用,并且可以做一些类似软件的事情。

If you fulfill those two requirements, you can do whatever you wish.如果你满足这两个要求,你就可以为所欲为。 Even if the module is written in ES6, if the end user doesn't need to know that, I would transpile it for now to get maximum support.即使模块是用 ES6 编写的,如果最终用户不需要知道这一点,我会暂时转译它以获得最大的支持。

However, if like koa , your module requires compatibility with users using ES6 features, then perhaps the two package solution would be a better idea.但是,如果像koa一样,您的模块需要与使用 ES6 功能的用户兼容,那么也许两个包的解决方案会更好。

Takeaway带走

  1. Only publish as much code as you need to make require( 'your-package' ) work.只发布使require( 'your-package' )工作所需的代码。
  2. Unless the between ES5 & 6 matters to the user, only publish 1 package.除非 ES5 和 6 之间对用户很重要,否则只发布 1 个包。 Transpile it if you must.如果必须,请转译它。

Deppending on the anatomy of your module, this solution may not work, but if your module is contained inside a single file, and has no dependencies (does not make use of import ), using the following pattern you can release your code as it is, and will be able to be imported with import (Browser ES6 Modules) and require (Node CommonJS Modules)根据模块的结构,此解决方案可能不起作用,但如果您的模块包含在单个文件中,并且没有依赖项(不使用import ),则使用以下模式可以按原样发布代码, 并且可以通过import (Browser ES6 Modules) 和require (Node CommonJS Modules)导入

As a bonus, it will be suittable to be imported using a SCRIPT HTML Element.作为奖励,它适合使用 SCRIPT HTML 元素导入。

main.js :主.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 :我的模块.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 :` package.json :`

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

To use your module, you can now use the regular syntax ...要使用您的模块,您现在可以使用常规语法...

When imported in NODE ...NODE 中导入时...

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

When imported in BROWSER ...BROWSER 中导入时...

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

Or even when included using an HTML Script Element ...甚至当使用HTML 脚本元素包含时......

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

A few extra notes for anyone, using own modules directly from github, not going through published modules:对任何人的一些额外说明,直接从 github 使用自己的模块,而不是通过已发布的模块:

The ( widely used ) "prepublish" hook is not doing anything for you. 广泛使用的)“预发布”钩子没有为您做任何事情

Best thing one can do (if plans to rely on github repos, not published stuff):可以做的最好的事情(如果计划依赖 github 存储库,而不是已发布的东西):

  • unlist src from .npmignore (in other words: allow it).从 .npmignore 中取消src (换句话说:允许它)。 If you don't have an .npmignore , remember: A copy of .gitignore will be used instead in the installed location, as ls node_modules/yourProject will show you.如果您没有.npmignore ,请记住:将在安装位置使用.gitignore的副本,因为ls node_modules/yourProject会显示给您。
  • make sure, babel-cli is a depenency in your module, not just a devDepenceny since you are indeed building on the consuming machine aka at the App developers computer, who is using your module确保babel-cli是您模块中的依赖项,而不仅仅是 devDepenceny,因为您确实是在消费机器上构建,也就是在应用程序开发人员计算机上,谁正在使用您的模块
  • do the build thing, in the install hook ie:安装钩子中做构建事情,即:

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

(no added value in trying anything "preinstall", ie babel-cli might be missing) (尝试任何“预安装”都没有附加价值,即 babel-cli 可能会丢失)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM