-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
An alternative to relative imports for local source code #40446
Comments
Pinging @elastic/kibana-platform |
cc @elastic/kibana-operations |
I should mention, these sorts of imports do not really solve the portability problem, they just make the portability problem significantly easier to deal with since you can universally use find+replace for access to code within plugins |
I'm generally +1 on anything that removes We could use |
@jasonrhodes Unfortunately, we're limited by the implementation here. For client-side code and for typescript, we can specify any common root we want, but for server-side javascript, all we can do is specify a root path that is literally part of the file path. In other words, to support that, we'd need to move all of our source code under a common top level directory within the project. I think in practice we'll be OK here for Kibana's case, but it's a fair point. Edit: The vast majority of imports in this case would be into one of three directories, |
I really like your suggestion @jasonrhodes. I really wish we could solve this by just going full monorepo and making everything a package, but I realize how much work that is. What if instead of webpack aliases and
I've also created a POC for this behavior here #40451 |
That’s a cool idea if we can get it to behave consistently for js/ts, server/client, and tests. I’ll check out your POC next week |
Thanks for having that discussion. I would in general really appreciate having shorter path for the relative imports. My two cents: I would create some Given the actual import paths, I would suggest using a different prefix than Given the concrete entry points I would suggest having thing that are really commonly imported from as a prefix, and not only the
|
I like that idea @timroes, especially the part about finding a prefix that isn't a usable npm org prefix. In my POC I was able to get the following to work by editing the config.
Choosing how to define the prefix is now going to be the hardest part of this 😅 |
I would vote for either |
I'm also not a fan of importing |
@spalger I would totally support that approach. I think we should only export places we know there is a high chance users commonly use them. So I think |
Oh, something I forgot to test is how this works in filesystems when creating links to the imports from the |
As long as it's clear and easy to add new directories to the white list, locking it down specifically is probably fine. In practice, I find that usually leads to people deciding to put something in the core or legacy directories even though the new thing doesn't really belong there, just to get the easy sharing. So having a single top-level alias that lets you import from anywhere below it seems okay to me, cause engineers gonna engineer anyway. :D |
I'm OK with a solution that doesn't rely on NODE_PATH, but I don't support the level of customization that's been proposed since last week. To start, preventing people from importing certain things is not part of the problem I described here. Today people can import from root in typescript and from relative paths in javascript/typescript, and we don't have a widespread problem of people importing code into the distribution from outside src/x-pack. If restricting imports was part of the problem I was trying to solve here, I'd strongly encourage the use of linting instead of customizations to our build tooling. We're already using linting rules to help enforce some of the emerging best practices for importing with success, and having all such rules handled via linting allows for more consistent development. Plus, linting rules can be disabled for exceptional cases we didn't consider and don't necessarily want to change our build system to support. My preference is not to use a prefix. Paths are a universal language, and we've embraced that with the new platform's goal of having the file system reflect the architecture of the project. Importing from project root is already a feature used in the wild with NODE_PATH as well as the basic configuration for typescript projects. We have hundreds of these imports in Kibana already, and unlike the magic paths built into our webpack config, I've never heard nor seen any developer questioning which files those imports reference. I don't even know if we intended to introduce this behavior for typescript, I suspect it was an accidental side effect of introducing typescript and people embraced it because it feels pretty natural. I'm strongly opposed to customized import behaviors that proxy to a nested place in the repository. We've been working for years to try to make how Kibana functions as a piece of software less unique. It's been a monumental expense of developer time, company focus, community pains as we break things, etc. |
Alright, I miss understood "Unfortunately, we're limited by the implementation here." as "We'd do it if we could"... Do people generally think that importing from There are so many |
You didn't misunderstand me, I just formed a more concrete opinion of it once you showed it was possible.
I can't say for certain, but people have already done it 200-something times since in the last few months since it became possible when there wasn't any history of it in the repo for years before. I think people pick up pretty quickly on whatever directory scheme we have, and since
I think |
I must say I agree with Court on that part about not using the pathes for import prevention. We should rather use linting for that. Given the prefixes I share a different opinion. Despite paths are universal, I would want to argue that also using prefixes in web development has become quiet universal. You'll find quiet some "standard presets", e.g. like the famous electron-webpack build system, that uses aliases to improve developer experience. And tools that try to prevent that improve of developer experience and requiring full paths, usually get quiet some pushback on that opinion, e.g. see the create react app PR, where the denial of the very similar request got 51 👎 and 0 👍 reactions (at the time of writing). Thus I think many developers are actually used to having prefixes as part of their developer experience to improve long relative paths, so I wouldn't agree with having prefixes makes "Kibana as a piece of software more unique". I agree with Spencer regarding the naming of |
FWIW I was imagining that whatever the single magic token is ( |
Can we have double
|
I'm not sure what problem this aims to address, can you clarify?
It's not the existence of aliased imports that make Kibana unique, it's the aliases themselves. Even the concept of "core" is unique to Kibana, requiring an understanding of "what is 'core' in the context of the Kibana project" to understand where the import statement maps to in the code base. And our plugin imports make this even more confusing since plugins can exist in multiple locations, which either means we have some magic
Can you elaborate on this? I don't think I think import from 'src/core/public';
import from 'src/plugins/foo/public';
import from 'src/test_utils'; I understand the concern in principle about the generic-ness of |
This is what I was thinking. I don't intend to trivialize any of the concerns raised here, but I guess I don't see how someone reading an import that starts with But perhaps I am oversimplifying; I could get on board with using a special token if others find that more intuitive, I'm just reticent about seeing more magic aliases added to Kibana 😉 |
Will we abandon |
@restrry I'm not sure how the babel approach would work in that case, so I'll defer to @spalger on that. If we went the NODE_PATH approach, then we'd have three mechanisms that dealt with parsing the import paths:
As far as I know, that's about as "standard" of a set of mechanisms available to us. |
As a person who will be responsible for maintaining this, I would certainly prefer that it was implemented with a babel plugin rather than webpack aliases/NODE_PATH. I mostly prefer that because I know the scope of babel plugins, they aren't applied to node_modules, they aren't automatically inherited by child processes, and we can further customize their behavior if we need that down the road. Configuration options for webpack aliases and NODE_PATH haven't changed in years and are very simple, but also applies way more widely than I think is necessary and I'd suspect have downsides we're not seeing.
I agree, but I expect this to be a drawback of adding support for
My concern is mostly for third-party devs. |
@spalger Works for me |
@spalger Thanks for clarifying. @brianseeders I'll give that a go, thanks! I do like leaning on |
Blast, I was doing a bit more research on NODE_PATH, and I think we may want to avoid it after all. While it hasn't made its way to the node docs as far as I can tell, node team members have indicated in GitHub that NODE_PATH is "deprecated", incompatible with the new ES module flag, and will not likely have a direct replacement. In fact, the ESM proposal explicitly rejects all mechanisms to change path resolution behavior. This means that relying on it heavily in Kibana could one day hold us back from upgrading node, and that is not really acceptable. So to recap:
I think there are two remaining options that could work for us:
Thoughts? |
We could definitely write the Babel plugin ourselves. Seems like relatively simple babel plugin to write. But I'm also fine with only supporting this type of resolution in TypeScript, except that I think since we use babel to transpile our TS we're going to need to support this in babel anyway right? |
I'm not necessarily sure I agree with this, but I haven't dug into it enough to say for sure. But the babel plugin we were going to use was 1700+ lines of JS, and it is broken in ways that require uses to wipe out their babel cache under certain conditions. We'd likely start stepping into the weeds of a definition of "simple" if we go down this route at the least.
TypeScript compiles to a relative path, so AFAIK there wouldn't be any sort of requirements on babel for that approach. |
Yeah, cache invalidation when configuration changes would certainly be the tricky bit here, but since we don't want multiple rules and we will plan to "never" change the configuration,
Are you implying that typescript is currently compiling to a relative path? Typescript already works? |
Do we know if the TypeScript compiler approach also works for Jest test files? I know Jest does it's own module resolution for its mocking purposes, but I'm not sure how this interacts with the tsconfig. |
As far as I know, there is no automatic way to make this work. Regardless of which approach we take, jest will need to be configured with the proper modulePaths.
Config changes weren't the most serious issue - moving files like
I was implying that, but you prompted me to go check on this, and it turns out I'm wrong. I had seen TS project-level -> relative path compilation happen, but it must have been during my effort with the babel plugin. I just tested to see if project-level imports worked for distributed code, and it does not. It looks like all the existing imports using project-level resolution are for types. So this doesn't work anywhere today, but it can through symlinks or babel plugin (mostly from scratch). On principle I like the idea of symlinks more because they're the "blessed" approach, but it could also cause a cascade of issues that I haven't thought of since we're doing things like bundling up a vendor dll for node modules. Perhaps we just need to throw together working examples of both to compare. |
@epixa what would you say is the status on this ATM? What style of paths should developers migrating to NP use? |
@lizozom The latest comments on here are an accurate reflection of the status. I'm planning to put together a POC of the symlink approach to see if that shakes loose any horrible surprises, but I haven't started on it. I'm open to anyone else giving that a go as well, but I'm not sure who has time. At this point, relative paths are still the only option that works almost everywhere. |
I've got a POC of a babel plugin that replaces imports at build time with relative imports, and it's really simple: const Path = require('path');
const t = require('babel-types');
module.exports = () => ({
name: '@kbn/babel-preset/transform/rewrite_absolute_imports',
visitor: {
ImportDeclaration(path) {
const source = path.get('source');
const importPath = t.isStringLiteral(source.node) && source.node.value;
if (!importPath || !(importPath.startsWith('src/') || importPath.startsWith('x-pack/'))) {
return;
}
const absPath = Path.resolve(path.hub.file.opts.root, path.hub.file.opts.sourceFileName);
const targetPath = Path.resolve(path.hub.file.opts.root, source.node.value);
source.replaceWith(t.stringLiteral(Path.relative(Path.dirname(absPath), targetPath)));
}
}
}); This only applies to |
Same-name variables differentiated by capitalization. You animal. |
I've sort of given up on |
So, after discussion with @epixa, I opened #52995, which is based on @spalger work and adds the (very few) missing bits. PR focuses on actually implementing the absolute path imports handling. I would like to keep the eslint rules to enforce their usage and the associated big-diff auto-fixes in a separate PR if that's alright with everyone. There are still a few questions open in the PR on some specific Comments/Reviews are welcome. |
Hi, My 2 cents on the topic, as a community plugin developer: Currently, some JS/TS Kibana-core components are not easy to import in a Kibana plugin. This is not very elegant, and is strongly based on physical plugin installation directory: $KIBANA_HOME/plugins/my-plugin So it is not portable. |
The problem
Today, we import local source code with many different path formats. Client-side code can use webpack aliases, TypeScript can import from
kibana
,ui
, orplugins
(technically on either server or client), server-side JavaScript can only do relative imports.This is a burden for us, so we agreed to fix it. Since relative imports are usable anywhere (in theory), we decided all imports should be done through relative imports. This solves the consistency problem, but it introduces others.
Relative imports across module/package/plugin boundaries inherently make those modules less portable if the system around them changes. A great example of this is the recent move of the legacy plugins, which involved updating hundreds of import paths despite the intention of those import statements remaining unchanged.
Relative imports create ambiguity with similarly named files and directories. e.g. At a glance, does
import from '../../../../../../tests'
import from the root leveltests
directory or from thetests
directory inside your plugin? The deeper your code is nested, the harder that question is to answer. It's not purely visual as well, as you can't really use find+replace to identify this either.Relative imports make many developers unhappy. There has been strong push back on relative imports for sharing code across plugins, especially from folks that do most of their development in the browser where they're used to convenient webpack aliases.
Finally, the assumption we made when adopting a relative imports strategy that they are available in all contexts is not accurate. Despite them seeming like the standard import mechanism, developers have run into issues in various places (e.g. mocks in tests) when trying to do relative imports.
Proposed solution
Code should be imported from outside the current package/system/plugin using imports from Kibana root.
Within a package/system/plugin, it's appropriate to use relative imports, though absolute imports are still acceptable when the relative import is still crazy. Most developers will do this anyway given the option.
So for example, any of these options would be OK from within a plugin called "foo":
I have a proof of concept for supporting this import behavior in #40066. More work is required, like updating the webpack configuration for client-side code and getting CI passing, but overall I think this is very doable.
The text was updated successfully, but these errors were encountered: