GoogleChromeLabs / native-url

Node's url module implemented using the built-in URL API.
https://npm.im/native-url
Apache License 2.0
284 stars 11 forks source link

Named exports as default export #26

Open niksy opened 4 years ago

niksy commented 4 years ago

native-url exposes methods thorugh named exports. To get default Node url behavior you would need to import entire module contents

import * as url from 'native-url'; // or 'url' if aliased`

This is fine for your own code, but dependencies will throw error since they can’t find default export by default, and most 3rd party code using Node url depend on that default export to be available.

To fix this, it’s best to make changes to code at compile time to expose every named export as property of object which should be default export.

Here is a Babel plugin code which achieves that:

const babel = require('@babel/core');

const plugin = babel.createConfigItem(({ types: t }) => {
    return {
        visitor: {
            ExportNamedDeclaration(path, parent) {
                const properties = path.node.specifiers.map((node) => ({
                    exported: node.exported.name,
                    local: node.local.name
                }));
                path.insertAfter(
                    t.exportDefaultDeclaration(
                        t.objectExpression(
                            properties.map((prop) =>
                                t.objectProperty(
                                    t.identifier(prop.exported),
                                    t.identifier(prop.local)
                                )
                            )
                        )
                    )
                );
            }
        }
    };
});

And here is how you apply it with Webpack:

{
    test: /\.m?js$/,
    include: /node_modules\/native-url/,
    use: [
        {
            loader: 'babel-loader',
            options: {
                plugins: [plugin]
            }
        }
    ]
};

After that you can use both modules’ named exports as default export.

Is this something which should be documented for both Webpack and Rollup, or should native-url expose default export by default?

janicklas-ralph commented 4 years ago

The exports are the same as the default node-url library which webpack uses when bundling for client

I'm assuming everyone developed their libraries based on node-url's export interface (which is the same as url) coupled with babel and Webpack/rollup to handle the resolution? If thats the case this won't be an issue.

I'm curious to know cases when people have used the default import, since both url and node-url don't support it?

Please let me know

niksy commented 4 years ago

@janicklas-ralph

This doesn’t throw warning:

const url = require('url');

This does throw warning:

import url from `url`;
Hash: 30643d8377069131206c
Version: webpack 4.43.0
Time: 113ms
Built at: 2020-05-20 11:16:05
 Asset      Size  Chunks             Chunk Names
out.js  14.4 KiB       0  [emitted]  main
Entrypoint main = out.js
[./index.js] 125 bytes {0} [built]
    + 5 hidden modules

WARNING in ./index.js 4:0-3
"export 'default' (imported as 'url') was not found in 'url'

WARNING in ./node_modules/dependancy/index.js 3:0-3
"export 'default' (imported as 'url') was not found in 'url'
 @ ./index.js

Documentation is misleading in saying this:

With this in place, import url from 'url' will use native-url and keep your bundle small.

developit commented 4 years ago

This happens because the module is being imported as ESM. node-url is only published as CJS, so it always triggers emulated exports.default in Webpack.

@janicklas-ralph - It might be worth removing the module entry to make it exactly the same as node-url? This would cause import * as and import to work identically.

niksy commented 4 years ago

@developit maybe export object (with all named exports as properties of that object) as default export and rely on tree-shaking from bundlers for any unwanted exports?