How to publish a module written in ES6 to NPM?

前端 未结 11 559
自闭症患者
自闭症患者 2020-11-29 15:22

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. I\'ve used Babel to transpile to ES5, and run tests

相关标签:
11条回答
  • 2020-11-29 15:43

    I like José's answer. I've noticed several modules follow that pattern already. Here's how you can easily implement it with Babel6. I install babel-cli locally so the build doesn't break if I ever change my global babel version.

    .npmignore

    /src/
    

    .gitignore

    /lib/
    /node_modules/
    

    Install 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"]
      }
    }
    
    0 讨论(0)
  • 2020-11-29 15:43

    TL;DR - Don't, until ~October 2019. The Node.js Modules Team has asked:

    Please do not publish any ES module packages intended for use by Node.js until [October 2019]

    2019 May update

    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. Here is the current situation and best practice.

    ES6 support

    99% of ES6 (aka 2015) has been supported by Node since version 6. The current version of Node is 12. All evergreen browsers support the vast majority of ES6 features. ECMAScript is now at version 2019, and the versioning scheme now favors using years.

    ES Modules (aka ECMAScript modules) in browsers

    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. Firefox support is slated for the next version, 67.

    You no longer need Webpack/rollup/Parcel etc. to load modules. 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 modules in Node

    ES modules (.mjs files with import/export) have been supported since Node v8.5.0 by calling node with the --experimental-modules flag. Node v12, released in April 2019, rewrote the experimental modules support. 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. 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'. You can still publish native ES modules intended for browsers.

    Ecosystem support of native ES modules

    As of May 2019, ecosystem support for ES Modules is immature. For example, test frameworks like Jest and Ava don't support --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 * 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. 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.


    Previous answer to the question - remember, don't do this until Node solves the require/import issue, hopefully around October 2019.

    Publishing ES6 modules to npm, with backwards compatibility

    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:

    {
      "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. 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).

    Here's the source for a native ES module with backwards compatibility for Node < 8.5.0 that I published to NPM. You can use it right now, without Babel or anything else.

    Install the module:

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

    Create a test file 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:

    node --experimental-modules test.mjs
    

    TypeScript

    If you develop in TypeScript, you can generate ES6 code and use ES6 modules:

    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.

    0 讨论(0)
  • 2020-11-29 15:46

    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). Look in the package.json file and at the prepublish step which will lead you to where and how to do this. If you clone that module you can run it locally and use it as a template for yous.

    0 讨论(0)
  • 2020-11-29 15:50

    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)

    As a bonus, it will be suittable to be imported using a SCRIPT HTML Element.

    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 */
        }
    

    To use your module, you can now use the regular syntax ...

    When imported in NODE ...

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

    When imported in BROWSER ...

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

    Or even when included using an HTML Script Element...

    <script src="./main.js"></script>
    <script>
         myModule.helloWorld();
        // outputs 'Hello World!'
    </script>
    
    0 讨论(0)
  • 2020-11-29 15:56

    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.

    .npmignore

    /src
    

    .gitignore

    /lib
    /node_modules
    

    Install Babel (version 7.5.5 in my case)

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

    And I have src/index.js which uses the arrow function:

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

    Here is the repo on GitHub.

    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:

    "use strict";
    
    var NewOneWithParameters = function NewOneWithParameters(a, b) {
      console.log(a + b); // 30
    };
    
    NewOneWithParameters(10, 20);
    

    [Update for Rollup bundler]

    As asked by @kyw, how would you integrate Rollup bundler?

    First, install rollup and rollup-plugin-babel

    npm install -D rollup rollup-plugin-babel
    

    Second, create rollup.config.js in the project root directory

    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

    {
      ...
      "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:

    '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. You can safely uninstall it:

    npm uninstall @babel/cli
    
    0 讨论(0)
  • 2020-11-29 15:58

    The main key in package.json decides the entry point to the package once it's published. So you can put your Babel's output wherever you want and just have to mention the right path in main key.

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

    Here's a well written article on how to publish an npm package

    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

    0 讨论(0)
提交回复
热议问题