![](/img/trans.png)
[英]How can I publish an NPM module with both commonjs and es6 versions?
[英]How to publish a module written in ES6 to NPM?
我正准备向 NPM 发布一个模块,当我考虑在 ES6 中重写它时,既要面向未来,又要学习 ES6。 我已经使用 Babel 转译为 ES5,并运行测试。 但我不确定如何继续:
简而言之:我需要采取哪些步骤才能将用 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 模块包
自 2015 年提出这个问题以来,JavaScript 对模块的支持已经显着成熟,有望在 2019 年 10 月正式稳定。所有其他答案现在都已过时或过于复杂。 这是当前的情况和最佳实践。
自版本 6 以来, 99% 的 ES6(又名 2015)已被 Node 支持。 Node 的当前版本是 12。所有常绿浏览器都支持 ES6 的绝大多数功能。 ECMAScript 现在是2019 版本,版本控制方案现在倾向于使用年份。
自 2017 年以来,所有常青浏览器都支持import
ES6 模块。 Chrome(+ Opera 和三星 Internet 等分支)和 Safari支持动态导入。 Firefox 支持计划用于下一个版本 67。
您不再需要Webpack/rollup/Parcel 等来加载模块。 它们可能仍用于其他目的,但不是加载代码所必需的。 您可以直接导入指向 ES 模块代码的 URL。
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 模块。
截至 2019 年 5 月,对 ES 模块的生态系统支持尚不成熟。 例如,像Jest和Ava这样的测试框架不支持--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 月左右。
要将 ES 模块发布到 npmjs.org 以便它可以直接导入,而无需 Babel 或其他转译器,只需将package.json
的main
字段指向.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
文件夹。
因此,只需使用gulp
、 grunt
或简单的 Node.js 来构建一个 ES5 的lib
文件夹。
这是我的build-lib.js
脚本,我保存在./tools/
(这里没有gulp
或grunt
):
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 捆绑器?
首先,安装rollup
和rollup-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 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 支持进行了调查,发现很多库都缺乏支持(因此建议向后兼容):
.mjs
文件的原生支持
mocha@7.0.0-esm1
发布了实验性支持.mjs
文件的问题:
package.json
的主键决定了包发布后的入口点。 所以你可以把 Babel 的输出放在任何你想要的地方,只需要在main
键中提到正确的路径。
"main": "./lib/index.js",
这是一篇关于如何发布 npm 包的好文章
https://codeburst.io/publish-your-own-npm-package-ff918698d450
这是您可以用作参考的示例存储库
NPM 包的两个标准是,它只require( 'package' )
一个require( 'package' )
就可以使用,并且可以做一些类似软件的事情。
如果你满足这两个要求,你就可以为所欲为。 即使模块是用 ES6 编写的,如果最终用户不需要知道这一点,我会暂时转译它以获得最大的支持。
但是,如果像koa一样,您的模块需要与使用 ES6 功能的用户兼容,那么也许两个包的解决方案会更好。
require( 'your-package' )
工作所需的代码。根据模块的结构,此解决方案可能不起作用,但如果您的模块包含在单个文件中,并且没有依赖项(不使用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 存储库,而不是已发布的东西):
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.