163

我正要向 NPM 发布一个模块,当时我考虑在 ES6 中重写它,既要面向未来,又要学习 ES6。我使用 Babel 转译为 ES5,并运行测试。但我不确定如何进行:

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

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

4

11 回答 11

90

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

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

于 2015-04-20T02:32:51.250 回答
85

我喜欢何塞的回答。我已经注意到几个模块已经遵循了这种模式。这是您可以使用 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"]
  }
}
于 2015-11-28T20:46:09.623 回答
54

TL;DR - 不要,直到 2019 年 10 月。Node.js模块团队已经过:

请不要在 [2019 年 10 月] 之前发布任何打算供 Node.js 使用的 ES 模块包

2019年5月更新

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

ES6 支持

从版本 6开始,Node 支持 99% 的 ES6(又名 2015)。当前的 Node 版本是 12。所有常青浏览器都支持绝大多数 ES6 特性。ECMAScript 现在是2019 版本,版本控制方案现在有利于使用年份。

浏览器中的 ES 模块(又名 ECMAScript 模块)

自 2017 年以来,所有常青浏览器支持 importES6 模块。Chrome(Opera 和 Samsung Internet 等 + 分支)和 Safari支持动态导入。下一个版本 67 将支持 Firefox。

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

Node 中的 ES 模块

ES 模块(.mjs带有import/的文件)从 Node v8.5.0 开始通过使用标志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 12 版本也是模块团队要求开发人员不要发布打算供 Node.js 使用的ES 模块包,直到找到通过require('pkg')import 'pkg'. 你仍然可以发布用于浏览器的原生 ES 模块。

原生 ES 模块的生态系统支持

截至 2019 年 5 月,对 ES 模块的生态系统支持还不成熟。例如,JestAva等测试框架不支持--experimental-modules. 您需要使用转译器,然后必须在使用命名的 import ( import { symbol }) 语法(目前还不适用于大多数 npm 包)和默认的 import 语法 ( 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/编写你自己的 ES 模块代码export并用 运行它node --experimental-modules,而无需转译器。如果您有尚未 ESM 就绪的依赖项,请按上述方式导入它们,您将能够通过 Babel 使用测试框架和其他工具。


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

将 ES6 模块发布到 npm,具有向后兼容性

要将 ES 模块发布到 npmjs.org 以便可以直接导入,无需 Babel 或其他转译器,只需将main您的字段package.json指向.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

然后,您需要将*.jsoutput 重命名为.mjs,这个已知问题有望很快得到解决,以便tsc可以直接输出.mjs文件。

于 2018-09-26T04:30:36.653 回答
18

@Jose 是对的。将 ES6/ES2015 发布到 NPM 并没有什么问题,但这可能会导致麻烦,特别是如果使用你的包的人正在使用 Webpack,例如,因为通常人们出于性能原因node_modules在预处理时会忽略该文件夹。babel

因此,只需使用gulpgrunt或简单地使用 Node.js 来构建一个libES5 文件夹。

这是我的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文件将 exclude./lib和 include ./src,这与您发布到 NPM 时想要的完全相反。该.npmignore文件具有与.gitignore(AFAIK) 基本相同的语法。

于 2015-09-19T15:04:37.907 回答
11

按照 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 上的存储库

现在你可以发布包了:

$ 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 捆绑器?

首先,安装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/**"
    })
  ]
};

最后,prepublish更新package.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注意:顺便说一下,如果您使用的是 Rollup 捆绑器,则不再需要。您可以安全地卸载它:

npm uninstall @babel/cli
于 2019-08-29T22:27:35.123 回答
6

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 支持进行了调查,发现很多库都缺乏支持(因此建议向后兼容):

于 2019-12-22T01:59:27.273 回答
6

如果您想在一个非常简单的小型开源 Node 模块中看到这一点,那么请查看第 n 天(我开始的 - 也是其他贡献者)。查看 package.json 文件和 prepublish 步骤,这将引导您找到执行此操作的位置和方法。如果您克隆该模块,您可以在本地运行它并将其用作您的模板。

于 2016-06-17T14:19:40.467 回答
4

主键package.json决定包发布后的入口点。main所以你可以把你的 Babel 的输出放在任何你想要的地方,只需要在key中提到正确的路径。

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

这是一篇关于如何发布 npm 包的好文章

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

这是一个示例存储库,您可以用作参考

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

于 2018-03-06T17:02:46.800 回答
3

NPM 包的两个标准是它只需要 a 就可以使用,require( 'package' )并且可以做一些类似于软件的事情。

如果你满足这两个要求,你就可以为所欲为。即使模块是用 ES6 编写的,如果最终用户不需要知道这一点,我现在会对其进行转换以获得最大的支持。

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

带走

  1. 只发布require( 'your-package' )工作所需的代码。
  2. 除非 ES5 和 6 之间对用户很重要,否则只发布 1 个包。如果必须,请转译它。
于 2015-04-20T02:33:28.447 回答
0

根据您的模块的解剖结构,此解决方案可能不起作用,但如果您的模块包含在单个文件中,并且没有依赖项(不使用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};

包.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!'

浏览器中导入时...

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

甚至当使用 HTML 脚本元素包含在内时...

<script src="./main.js"></script>
<script>
     myModule.helloWorld();
    // outputs 'Hello World!'
</script>
于 2019-05-16T22:51:44.670 回答
0

给任何人的一些额外说明,直接从 github 使用自己的模块,而不是通过已发布的模块:

广泛使用的)“预发布”钩子并没有为您做任何事情

可以做的最好的事情(如果计划依赖 github repos,而不是发布的东西):

  • 从.npmignoresrc中取消列出(换句话说:允许它)。如果您没有,请记住:将在安装位置使用.npmignore的副本,如下所示。.gitignorels node_modules/yourProject
  • 确保,babel-cli是您的模块的依赖,而不仅仅是 devDepenceny,因为您确实是在使用您的模块的 App 开发人员计算机上的消费机器上构建的
  • 在安装钩子中进行构建,即:

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

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

于 2017-05-08T13:00:16.237 回答