Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow assigning external modules to global variables #337

Closed
zanona opened this issue Aug 19, 2020 · 8 comments
Closed

allow assigning external modules to global variables #337

zanona opened this issue Aug 19, 2020 · 8 comments

Comments

@zanona
Copy link

zanona commented Aug 19, 2020

It would be great if we could reference external modules to their global counterpart as I currently do on rollup like:

export default {
	external: [
		'@wordpress/blocks',
		'@wordpress/block-editor',
		'@wordpress/components',
		'@wordpress/compose',
		'@wordpress/data',
		'@wordpress/date',
		'@wordpress/edit-post',
		'@wordpress/element',
		'@wordpress/plugins',
	],
	output: {
		format: 'iife',
		globals: {
			'@wordpress/blocks': 'wp.blocks',
			'@wordpress/block-editor': 'wp.blockEditor',
			'@wordpress/components': 'wp.components',
			'@wordpress/compose': 'wp.compose',
			'@wordpress/data': 'wp.data',
			'@wordpress/date': 'wp.date',
			'@wordpress/edit-post': 'wp.editPost',
			'@wordpress/element': 'wp.element',
			'@wordpress/plugins': 'wp.plugins',
		},
	},
	plugins,
};

a possible interface for this, could be something like:

esbuild --bundle foo.tsx --external:@wordpress/blocks=wp.blocks --external:@wordpress/plugins=wp.plugins
@zanona zanona changed the title allow assining external modules to global variables allow assigning external modules to global variables Aug 19, 2020
@evanw
Copy link
Owner

evanw commented Aug 20, 2020

I think this feature would be most appropriately addressed by a plugin instead of adding it to esbuild's core. There is a plugin API in development that I believe should be able to implement this feature: #111.

@remorses

This comment has been minimized.

@evanw
Copy link
Owner

evanw commented Oct 11, 2020

@evanw the current plugin api cannot implement this feature without parsing the module

I was thinking about a plugin like this:

let examplePlugin = plugin => {
  plugin.setName('wp-global')
  plugin.addResolver({ filter: /^@wordpress\// }, args => ({
    path: args.path, namespace: 'wp-global',
  }))
  plugin.addLoader({ filter: /.*/, namespace: 'wp-global' }, args => {
    let name = args.path.slice(11).replace(/-(.)/g, (_, x) => x.toUpperCase())
    let contents = `export default wp[${JSON.stringify(name)}]`
    return { contents }
  })
}

When you build this file with that plugin:

import blocks from '@wordpress/blocks'
console.log(blocks)

you get something like this:

// wp-global:@wordpress/blocks
var blocks_default = wp["blocks"];

// <stdin>
console.log(blocks_default);

I'm not familiar with Rollup so I'm not sure what your use case is, but that's what I thought you were trying to do. Is that what you were looking for?

@remorses
Copy link
Contributor

remorses commented Oct 11, 2020

I think i misunderstood this feature request, i created #451 with my use case


I want to do the opposite, replace global objects with external modules, this way you can for example replace the “process” global object with a polyfill

Example

Building with a setting like --globals=proces:process-polyfill.js

The code below

console.log(`This platform is ${process.platform}`);

Would become

import process from './process-polyfill.js'
console.log(`This platform is ${process.platform}`);

@evanw
Copy link
Owner

evanw commented Oct 18, 2020

I think i misunderstood this feature request, i created #451 with my use case

#415 has now been implemented.

It would be great if we could reference external modules to their global counterpart as I currently do on rollup

Actually this is already possible without plugins by using paths in tsconfig.json (or jsconfig.json if you prefer) and stub files for each global:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@wordpress/blocks": "file-that-exports-wp.blocks.js",
      "@wordpress/block-editor": "file-that-exports-wp.blockEditor.js",
      "@wordpress/components": "file-that-exports-wp.components.js",
      "@wordpress/compose": "file-that-exports-wp.compose.js",
      "@wordpress/data": "file-that-exports-wp.data.js",
      "@wordpress/date": "file-that-exports-wp.date.js",
      "@wordpress/edit-post": "file-that-exports-wp.editPost.js",
      "@wordpress/element": "file-that-exports-wp.element.js",
      "@wordpress/plugins": "file-that-exports-wp.plugins.js"
    }
  }
}

For example, file-that-exports-wp.blocks.js could look like this:

export default wp.blocks

@evanw
Copy link
Owner

evanw commented Nov 11, 2020

Closing this. It should now be possible to implement things like this using plugins and/or inject.

@evanw evanw closed this as completed Nov 11, 2020
@a-b-r-o-w-n
Copy link

a-b-r-o-w-n commented Jan 5, 2021

I am trying to implement something similar to webpack's external config but am not having any luck. This module will be executed in an iframe with some global variables in scope so I don't want to bundle those. In my source I have something like:

import { foo } from 'my-package';

In webpack I would do:

{
  externals: {
    'my-package': 'MyPackage'
  }
}

Where MyPackage is the name of the global variable defined on window. When running esbuild, I get errors like

No matching export for import "foo"

I have implemented the plugin as suggested above. Is this possible to do with esbuild?

EDIT:

Some background: I am working on ui extensions that are to be embedded in our app. I need to ensure that the same module is used in the hosting app and in the ui extension. For instance, using different versions of react causes runtime errors when using useState, etc.

DOUBLE EDIT:

I got this working just by using commonjs exports instead.

const myPlugin = {
  name: 'my-plugin',
  setup(build) {
    build.onResolve({ filter: /^my-package$/ }, (args) => ({
      path: args.path,
      namespace: 'my-plugin',
    }));

    build.onLoad({ filter: /.*/, namespace: 'my-plugin' }, () => {
      const contents = `module.exports = MyPackage`;
      return { contents };
    });
  }
}

@esamattis
Copy link

esamattis commented Oct 29, 2021

The tsconfig method works but at least with WordPRess you'll need to use commonjs exports like a-b-r-o-w-n mentions.

module.exports = wp.plugins;

If you use export default wp.plugins imports like

import { registerPlugin } from "@wordpress/plugins";

it will cause import error:

 > src/admin-script/editor-extensions.tsx:6:9: error: No matching export in "wp-admin-globals/plugins.js" for import "registerPlugin"
    6 │ import { registerPlugin } from "@wordpress/plugins";
      ╵       

UPDATE: I ended up creating a generic plugin for this. Sharing here as it might help others too.

function importAsGlobals(mapping) {
    // https://stackoverflow.com/a/3561711/153718
    const escRe = (s) => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
    const filter = new RegExp(
        Object.keys(mapping)
            .map((mod) => `^${escRe(mod)}$`)
            .join("|"),
    );

    return {
        name: "global-imports",
        setup(build) {
            build.onResolve({ filter }, (args) => {
                if (!mapping[args.path]) {
                    throw new Error("Unknown global: " + args.path);
                }
                return {
                    path: args.path,
                    namespace: "external-global",
                };
            });

            build.onLoad(
                {
                    filter,
                    namespace: "external-global",
                },
                async (args) => {
                    const global = mapping[args.path];
                    return {
                        contents: `module.exports = ${global};`,
                        loader: "js",
                    };
                },
            );
        },
    };
}

and pass the mapping to esbuild:

        plugins: [
            importAsGlobals({
                "@wordpress/components": "wp.components",
                "@wordpress/api-fetch": "wp.apiFetch",
                "@wordpress/edit-post": "wp.editPost",
                "@wordpress/element": "wp.element",
                "@wordpress/plugins": "wp.plugins",
                "@wordpress/editor": "wp.editor",
                "@wordpress/block-editor": "wp.blockEditor",
                "@wordpress/blocks": "wp.blocks",
                "@wordpress/hooks": "wp.hooks",
                "@wordpress/utils": "wp.utils",
                "@wordpress/date": "wp.date",
                "@wordpress/data": "wp.data",
                react: "React",
                "react-dom": "ReactDOM",
            }),
        ],

This probably already exists but my google-fu fails me atm.

luwes added a commit to luwes/react-player that referenced this issue Oct 6, 2023
luwes added a commit to cookpete/react-player that referenced this issue Oct 10, 2023
* fix: modernize build using esbuild

* example: build & serve demo w/ esbuild
move to examples folder so more apps could be added

* tools: add livereload, fix demo target folder

* tools: remove Webpack tooling

* tools: remove babel-eslint, upgrade standard

* fix: robust minimal test stack esbuild/zora/c8

* fix: add esbuild global externals plugin
evanw/esbuild#337
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants