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

时间:2015-04-20 02:25:07

标签: javascript node.js npm ecmascript-6 babeljs

当我考虑在ES6中重写它时,我正准备向NPM发布一个模块,以及面向未来的模型,并学习ES6。我使用Babel转换到ES5,然后运行测试。但我不确定如何继续:

  1. 我是否需要进行转换,并将结果/输出文件夹发布到NPM?
  2. 我是否将结果文件夹包含在我的Github仓库中?
  3. 或者我维护2个回购,一个用于Github的ES6代码+ gulp脚本,另一个用于NPM的转换结果+测试?
  4. 简而言之:我需要采取哪些步骤将用ES6编写的模块发布到NPM,同时仍然允许人们浏览/分叉原始代码?

10 个答案:

答案 0 :(得分:63)

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

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

答案 1 :(得分:61)

我喜欢José的回答。我已经注意到几个模块已经遵循这种模式。以下是使用Babel6轻松实现它的方法。我在本地安装/src/ ,这样如果我改变了我的全局babel版本,构建就不会中断。

<强> .npmignore

/lib/
/node_modules/

<强>的.gitignore

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

安装Babel

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

<强>的package.json

item

答案 2 :(得分:17)

TL; DR -直到2019年10月为止。Node.jsModules Team具有asked

  

请不要发布任何打算供Node.js使用的ES模块软件包,直到[October 2019]

2019年5月更新

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

ES6支持

节点since version 6已支持

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

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

自2017年以来,

All evergreen browsers已成为supporting import的ES6模块。Dynamic importssupported的Chrome浏览器(包括Opera和Samsung Internet之类的叉子)和苹果浏览器。 Firefox的下一个版本为67。

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

节点中的ES模块

自节点v8.5.0起,通过使用带有.mjs标志的import来支持

ES模块(带有export / node的{​​{1}}文件)。 2019年4月发布的Node v12重新编写了实验模块支持。最明显的变化是导入时默认情况下需要指定文件扩展名:

--experimental-modules

请注意所有必填的// lib.mjs export const hello = 'Hello world!'; // index.mjs: import { hello } from './lib.mjs'; console.log(hello); 扩展名。运行方式:

.mjs

Node 12版本也是模块团队asked开发人员在解决方案发布之前不发布打算由Node.js使用的 ES模块包 的时候。通过node --experimental-modules index.mjs require('pkg')找到了使用软件包的方法。您仍然可以发布供浏览器使用的本机ES模块。

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

截至2019年5月,对ES模块的生态系统支持尚不成熟。例如,JestAva之类的测试框架不支持import 'pkg'。您需要使用翻译器,然后必须在使用命名的import(--experimental-modules)语法(目前尚不适用于大多数npm软件包)和默认的导入语法(import { symbol })之间做出决定,确实有效,但是not when Babel parses it适用于packages authored in TypeScript(graphql-tools,node-influx,fast等)。但是有一种变通办法可以与import Package from 'package'一起使用,并且如果Babel将您的代码进行转换您可以使用Jest / Ava / Mocha等进行测试:

--experimental-modules

可能很难看,但是通过这种方式,您可以使用import * as ApolloServerM from 'apollo-server'; const ApolloServer = ApolloServerM.default || ApolloServerM; / import编写自己的ES模块代码,并使用export运行它,而无需编译器。如果您还没有依赖ESM的依赖项,请按上述方式导入它们,然后就可以通过Babel使用测试框架和其他工具。


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

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

要将ES模块发布到npmjs.org,以便可以直接导入而无需Babel或其他编译器,只需将node --experimental-modules中的main字段指向package.json文件,但省略扩展名:

.mjs

那是唯一的变化。通过省略扩展名,Node如果与--experimental-modules一起运行,将首先查找mjs文件。否则,它将退回到.js文件,因此您现有的transpilation process支持旧版本的Node将像以前一样工作— —确保将Babel指向{ "name": "mjs-example", "main": "index" } 文件。

这是我发布给NPM的source for a native ES module with backwards compatibility for Node < 8.5.0。您可以立即使用它,而无需Babel或其他任何东西。

安装模块

.mjs

创建测试文件 test.mjs

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

使用--experimental-modules标志运行节点(v8.5.0 +):

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

TypeScript

如果您使用TypeScript开发,则可以生成ES6代码并使用ES6模块:

node --experimental-modules test.mjs

然后,您需要将tsc index.js --target es6 --modules es2015 的输出重命名为*.js,这是一个已知的issue,有望很快得到修复,以便.mjs可以直接输出tsc文件

答案 3 :(得分:16)

@Jose是对的。将ES6 / ES2015发布到NPM没有任何问题,但这可能会带来麻烦,特别是如果使用您的软件包的人正在使用Webpack,例如,通常人们在使用node_modules进行预处理时会忽略babel文件夹表现原因。

因此,只需使用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)的语法基本相同。

答案 4 :(得分:5)

如果你想在一个非常简单的小型开源节点模块中看到这个,那么看看nth-day(我开始 - 也是其他贡献者)。查看package.json文件和预发布步骤,它将引导您到达何处以及如何执行此操作。如果您克隆该模块,则可以在本地运行它并将其用作模板。

答案 5 :(得分:4)

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

这里是the repo on 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。您可以安全地将其卸载:

npm uninstall @babel/cli

答案 6 :(得分: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

答案 7 :(得分:2)

NPM包的两个标准是它只能使用require( 'package' )并且可以使用软件。

如果您满足这两个要求,您可以随心所欲。 即使模块是用ES6编写的,如果最终用户不需要知道,我现在也会将其转换为获得最大支持。

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

外卖

  1. 只发布使require( 'your-package' )工作所需的代码。
  2. 除非ES5与...之间6对用户很重要,只发布1个包。如果必须,请透明它。

答案 8 :(得分:0)

取决于模块的结构,此解决方案可能无法使用,但是如果您的模块包含在单个文件中,并且没有依赖项(不使用导入< / strong>),您可以使用以下模式按原样发布代码,并可以通过 import (浏览器ES6模块)和 require (节点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>

答案 9 :(得分:0)

Node.js 13.2.0+支持ESM,而没有实验性标志,并且有一些发布混合(ESM和CommonJS)NPM软件包的选项(取决于所需的向后兼容性级别):https://2ality.com/2019/10/hybrid-npm-packages.html < / p>

我建议采用完全向后兼容的方式,以简化软件包的使用。可能如下所示:

混合程序包具有以下文件:

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

我们做了一个investigation into ESM support in 05.2019,发现很多库都缺乏支持(因此建议向后兼容)