Search code examples
javascripttypescriptnpmprecompilees6-modules

Do we still have to make all this effort to precompile packages for NPM?


I am currently working on a project where there will be many packages for npm. I want to take a more radical approach on this project. So, no files should be pre-compiled anymore. In the src folders of the packages there will be just only d.ts, ts, js, mjs files (as i said, no one of them should be precompiled anymore). I do this out of laziness and because I think that the time is ready to stop pre-compiling files!

I mean how many variations should I create? ESModules, AMD, CommonJs, SystemJS?

My simple thought is: leave it as it is (import x from 'x', export foo = 123) and the developer that using the package will already have the right tool (Babel, Typescript)! or not?

The second question is: up to which level should the package be compiled down? ES3, ES6? What do package users who only use modern browsers and only support them? Would differential loading be the right approach here? I have only seen differential loading in the environment of HTML files. So as a starting point! This is not the case for me if the packages are used selectively by developers.

Specifically, my question is: do we still have to make all this effort? What is currently the lowest common denominator? I don't know the statistical numbers, but I have the feeling that everybody uses a compiler / preprocessor (Babel, PostCss) in his project?

What do you think about this?


Solution

  • From my experience, CommonJS and ES modules are the relevant technologies these days. You might decide to skip CommonJS, then you would get along without transpilation. However, many frameworks and other libraries still rely on CommonJS, and they cannot use ES modules.

    What you could do, as a lightweight approach, is the following:

    First: Write everything as ES modules by default, and lay out your package to be used as ES module(s) by default (e.g. set "type":"module" in your package.json and use .mjs as file ending).

    Then: Use Babel, but only to create a CommonJS fallback of your modules. This could look like this:

    Add the following dev dependencies to your project. (There are other Babel plugins for special cases of syntax; Babel will usually tell you which ones it needs):

    npm install --save-dev @babel/cli @babel/core @babel/plugin-syntax-import-meta @babel/plugin-transform-modules-commonjs
    

    Then add the following block to your package.json:

    "babel": {
        "plugins": [
            [ "@babel/plugin-transform-modules-commonjs" ],
            [ "@babel/plugin-syntax-import-meta" ]
        ]
    }
    

    Now you can transpile your ES modules to CommonJS with a simple line of shell code:

    # assuming your ES modules are in the lib/ directory
    for script in $(find lib/ -iname '*.mjs' | grep -v test.mjs | sed -e 's|.mjs||g'); do
        npx babel $script.mjs > $script.cjs
    done
    

    You can also add this as a script to your package.json.

    "scripts": {
        "build": "for script in $(find lib/ -iname '*.mjs' | grep -v test.mjs | sed -e 's|.mjs||g'); do npx babel $script.mjs > $script.cjs; done"
    }
    

    Now you can run this with npm run build, add it as a commit hook, or run it before pushing to the NPM registry. You could also built to a different folder than where your ES modules live, and exclude that folder via .gitignore.

    This is how I created several libraries of mine (for the exact same reasons), and it works perfectly – both in CommonJS and in ES module environments.

    By the way, NodeJS has some documentation on shipping packages as both ES modules and CommonJS: https://nodejs.org/api/esm.html#esm_dual_commonjs_es_module_packages