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

Support for ESM #106868

Open
tylersmalley opened this issue Jul 27, 2021 · 17 comments
Open

Support for ESM #106868

tylersmalley opened this issue Jul 27, 2021 · 17 comments
Labels
impact:needs-assessment Product and/or Engineering needs to evaluate the impact of the change. loe:small Small Level of Effort Team:Operations Team label for Operations Team

Comments

@tylersmalley
Copy link
Contributor

Since v12, Node.js has supported ESM. We should investigate the best way to support ESM in the Kibana project.

Our node Babel preset current transpiles down to commonjs. Can we instead use ESM globally?
Does APM support ESM? @watson mentioned there might still be an issue hooking into the loader.

@tylersmalley tylersmalley added the Team:Operations Team label for Operations Team label Jul 27, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-operations (Team:Operations)

@watson
Copy link
Contributor

watson commented Jul 27, 2021

After just having a chat with @trentm, I think it should be possible to "hack" our implementation of the APM agent to work despite running as ESM (which officially isn't supported by the agent):

  1. Since the agent currently doesn't have a way to hook into the ESM loader, we would have to manually call require() for the modules we want to instrument (and that needs to be instrumented in order for context propagation to not break) before they are imported elsewhere in the source code. I think this is doable.
  2. Those modules that we want to instrument must not have separate CJS and ESM code. Else we're instrumenting the wrong code.

I think (besides core modules) that we only care about @elastic/elasticsearch and hapi - possibly also bluebird.

@trentm
Copy link
Member

trentm commented Jul 27, 2021

Possibly also the following. (I'm reading through Kibana's current "dependencies" set and comparing to modules for which our APM has some sort of instrumentation.)

  • elasticsearch
  • handlerbars

@tylersmalley tylersmalley added 1 and removed 1 labels Oct 11, 2021
@exalate-issue-sync exalate-issue-sync bot added impact:low Addressing this issue will have a low level of impact on the quality/strength of our product. loe:small Small Level of Effort labels Nov 5, 2021
@tylersmalley tylersmalley removed loe:small Small Level of Effort impact:low Addressing this issue will have a low level of impact on the quality/strength of our product. EnableJiraSync labels Mar 16, 2022
@exalate-issue-sync exalate-issue-sync bot added impact:needs-assessment Product and/or Engineering needs to evaluate the impact of the change. loe:small Small Level of Effort labels Mar 31, 2022
@watson
Copy link
Contributor

watson commented Aug 3, 2022

Getting support for ESM in Kibana is becoming exceedingly important as more and more modules that we rely upon from npm has switched to being ESM only. So we're essentially stuck on older non-maintained versions of many of our dependencies which might contain bugs and security vulnerabilities.

@elastic/kibana-operations Is APM the only thing holding us back, or are there other issues?

@trentm any updates on the APM front, or is our best best still to "hack" the implementation as you suggested previously?

@spalger
Copy link
Contributor

spalger commented Aug 3, 2022

I agree with you @watson, we really need to figure out how to at least support using ESM only packages from NPM.

@trentm
Copy link
Member

trentm commented Aug 3, 2022

any updates on the APM front

We are currently investigating ESM support (elastic/apm-agent-nodejs#2343) but this doesn't promise full ESM support for the 8.5 timeframe. So in the near term the best bet is still the "hack", unfortunately.

On the "hack": In addition to @elastic/elasticsearch and @hapi/hapi, we will need to make sure that core modules like http, https, maybe http2 get instrumented.

@watson
Copy link
Contributor

watson commented Aug 4, 2022

@spalger I would suggest that we create a proof-of-concept that uses the hack @trentm described. The actual hack part of the implementation shouldn't take that much longer compared to the time it takes to do the other required changes. That would give us an idea if this is shippable or if we need to wait for full APM support.

One other ask I have is that we afterwards investigate how much work it would take to backport this to 7.17. Otherwise it will make it almost impossible to properly maintain that branch.

I'll happily help with this if it's needed.

@spalger
Copy link
Contributor

spalger commented Aug 4, 2022

I'm unclear what the "hack" we're talking about is.

I agree that whatever we do here will probably need to be backported or re-implemented on 7.17.

My knowledge about what is necessary on a technical level is pretty low. I've set the "type" property in a package.json file and used import() statements instead of require() a couple times, but I would really need to do a good deal of research to figure out how to apply this to the Kibana repository. Maybe we could get together sometime and you can share your knowledge of the options we have @watson? My primary goal is ensuring that Kibana keeps working as is but can also import ESM-only modules from NPM.

Do you have an idea of what types of changes would be required in the repository besides this APM "hack" which I'm assuming would be mostly invisible to most people working in the repository?

@trentm
Copy link
Member

trentm commented Aug 4, 2022

The "hack" is to:

  1. Manually have the Kibana JS code do require('foo') for each 'foo' module that you want/need the Node.js APM agent to instrument before any other Kibana JS code does import ... from 'foo'.
  2. Also, ensure that for the 'foo' modules in question that the use of require('foo') and import ... from 'foo' results in loading the same files on disk. There are some packages that ship separate CommonJS and ESM files. This hack won't work on packages that do that.

It is possible this could all be contained to https://github.com/elastic/kibana/blob/main/packages/kbn-apm-config-loader/src/init_apm.ts but I'm not positive. The hack would be to explicitly add the following after the apm.start(...):

require('http');
require('https');
require('@hapi/hapi');
require('@elastic/elasticsearch');
// ... possibly others like bluebird (ew), handlebars

When I played with that hack a while back (notes here: https://gist.github.com/trentm/ceb76cd5b1763811fc6290dd4040819e#another-workaround-user-side-only), I had put this file in a ".cjs" file so that require is actually defined. Is that possible with the Kibana build system?

Possibly another path is to get "require" back in an ESM module file (https://nodejs.org/api/module.html#modulecreaterequirefilename), notes here: https://gist.github.com/trentm/ceb76cd5b1763811fc6290dd4040819e#a-user-workaround-use-require-for-your-esm-files

@spalger
Copy link
Contributor

spalger commented Aug 4, 2022

Thanks for the info @trentm, I'm still unclear on the rules around what can load what when it comes to ESM and commonjs, but that idea of using commonjs to instrument the important modules before they're loaded via ESM makes sense to me.

I can imagine that hack might work today and break when we upgrade these services without people really noticing, and possibly in a way that prevents us from using this hack...

Have we considered an alternate approach that would allow us to consume ESM-only packages from commonjs... what if we wrote a commonjs wrapper around ESM-only packages, which can async-load the package with import() statements in the commonjs exports... Not a great solution but seems slightly more reliable until APM can figure out how it's going to support ESM going forward.

One such package I'm thinking of is execa. If we need to get to v6 then we could wrap it with @kbn/execa and only load the actual execa module when you call one of the methods exported from @kbn/execa... Seems like it would work for that specific example but there are likely libraries which export things we need synchronously where this wouldn't work...

@trentm
Copy link
Member

trentm commented Aug 4, 2022

Have we considered an alternate approach ...

Currently the APM agent hooks into require('foo') and will only do its instrumentation if that 'foo' string is one of its instrumented module names. So any commonjs wrapper would have to take that over that module name. I'm not sure if that could easily be made to work. I might be misunderstanding your idea, however.

What about having a test file that ensures expected module instrumentations are working:

This might also guard against other ways that APM instrumentation can stop: For example if Kibana updates to a newer version of Hapi or @elastic/elasticsearch that the current APM agent doesn't yet support.

@spalger
Copy link
Contributor

spalger commented Aug 4, 2022

Sorry, yeah, my idea was more about sticking with commonjs so that APM continues to work but allowing Kibana to consume ESM-only dependencies until APM is ready to instrument native ESM

@trentm
Copy link
Member

trentm commented Aug 4, 2022

Sorry, yeah, my idea was more about sticking with commonjs so that APM continues ...

Ah, I understand now. Yah, that works, but perhaps sucks for you having to write those wrappers and training Kibana devs to use the wrapped versions.

@spalger
Copy link
Contributor

spalger commented Aug 4, 2022

Yeah, we have the ESLint powers to ensure people use the wrappers, but it's certainly not ideal. Though it should be relatively limited to packages which both moved to ESM-only, and have unfixed security issues in the pre-ESM-only versions.

@watson
Copy link
Contributor

watson commented Sep 26, 2023

@trentm It looks like the APM agent now has ESM support: elastic/apm-agent-nodejs#3381

Does that mean that we don't need to do any of the workarounds mentioned above for the APM agent to work if Kibana switches to use ESM directly?

Also, I'm not sure if this is relevant for ESM compatibility with the APM agent, but Kibana is currently running Node.js 18 but will soon be upgrading to Node.js 20 (most likely within the next few months)

@trentm
Copy link
Member

trentm commented Sep 26, 2023

@watson The APM agent's ESM support is documented here: https://www.elastic.co/guide/en/apm/agent/nodejs/current/esm.html
The ESM support has some limitations to consider (and perhaps prioritize some work for Kibana if it is seriously going to switch to running ESM):

Aside: What are the advantages of moving Kibana to using ESM?

@watson
Copy link
Contributor

watson commented Sep 26, 2023

Thanks for the very thorough explanation of the current state!

What are the advantages of moving Kibana to using ESM?

Every day more and more 3rd party packages we depend on via npm switch to be pure ESM. We can't upgrade to those as long as we don't support ESM. This becomes especially critical once a bug or security issue is only fixed in the ESM-only version of the package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
impact:needs-assessment Product and/or Engineering needs to evaluate the impact of the change. loe:small Small Level of Effort Team:Operations Team label for Operations Team
Projects
None yet
Development

No branches or pull requests

5 participants