-
Notifications
You must be signed in to change notification settings - Fork 309
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
Jest ESM: Avoiding UMD imports #751
Comments
I just found out about this today. When not using ESM, we can skip processing umd and when using ESM, we can skip processing ESM file. |
I can remove the moduleDirectories: [
'node_modules',
'dist'
],
... (which requires building the lib into |
Oh actually my previous comment was about something else, partially related. Regarding to loading ESM when running Jest with ESM, it is possible to do it via custom Jest resolver. One example is from nrwl nx https://github.com/nrwl/nx/blob/master/packages/jest/plugins/resolver.ts Ideally, a custom resolver should contain the logic to load the correct format, UMD when running in normal mode and ESM when running in ESM mode based on Angular package format, anything else would just fallback to default Jest resolver. |
Thanks for the pointer. Do you think this is something that jest-preset-angular should handle, or should it be separate? Stepping through a bunch of jest code today, it looks like import resolution (ala jest-resolve/defaultResolver) is separate from the decision to load the import as an ESM module. jest-resolve/shouldLoadAsEsm only returns It turns out that my attempt to load FESM files instead of UMDs (via |
I think it should be. If running in ESM mode, this repo at least should have a custom jest resolver or a specific
I don’t get “not loaded as ESM modules”, does it mean the Jest transformer doesn’t receive ESM files ? Regarding to perf, in general the goal to improve is reducing the most amount of processing tasks for the Jest transformer. According to recent experiments, when running in CommonJs mode, it is possible to skip the whole type checking as well as compilation task when dealing with umd files which are built by Angular. That is expected because UMD is compatible with CommonJs which can be a slight performance boost. According to https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm, UMD is a fallback when ESM doesn’t work. This means the potential logic for the custom Jest resolver: if can’t find ESM files when running in ESM mode(1st try with default resolver, if not found then fallback to custom logic to find ESM), simply fallback to load UMD (default Jest resolver I assume) |
Thank you for the info. I agree, I think it would be great if jest-preset-angular made this work nicely. I did some more research/code stepping. Things you may be aware of but are worth pointing out in this issue: As you said @ahnpnl, the resolver can be overridden, which could be done to resolve non UMD files from angular packages (both in node_modules and locally). The default resolver completely relies on the browserify/resolve library. The logic that uses the package.json "main" field to find the UMD file is here: This default resolver is not ES2015 aware, so by default (unless a resolver is configured) Jest is always going to resolve UMD files in Angular packages (and most other packages). IMO this is a Jest bug that should be fixed as part of jestjs/jest#9430 , but I don't know if the Jest team will agree. It would be great if there was an equivalent library that provides similar module resolution logic to browserify/resolve, but is ES2015 aware - given the node support for ESM, I would think there would be a need for such a thing. There is separate logic to determine whether the resolved module should be loaded as an ESM module or not. Here is the top level code: const [path, query] = specifier.split('?');
const resolved = this._resolveModule(referencingIdentifier, path);
if (
this._resolver.isCoreModule(resolved) ||
this.unstable_shouldLoadAsEsm(resolved)
) {
return this.loadEsmModule(resolved, query, isStaticImport);
}
return this.loadCjsAsEsm(referencingIdentifier, resolved, context); The function cachedPkgCheck(cwd: Config.Path): boolean {
const pkgPath = escalade(cwd, (_dir, names) => {
if (names.includes('package.json')) {
// will be resolved into absolute
return 'package.json';
}
return false;
});
if (!pkgPath) {
return false;
}
let hasModuleField = cachedChecks.get(pkgPath);
if (hasModuleField != null) {
return hasModuleField;
}
try {
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
hasModuleField = pkg.type === 'module';
} catch {
hasModuleField = false;
}
cachedChecks.set(pkgPath, hasModuleField);
return hasModuleField;
} As you can see, I don't yet know what the difference in behavior is between I think it's bad news that the resolver can be overridden, but the |
My guess for Anyways from transformer side, the work will be only creating a custom resolver. |
It looks like jestjs/jest#9771 may handle the module resolution for us, though it might take a while - jest is waiting on browserify to implement it. RE the "is this module ESM?" logic, I made a comment here requesting that resolvers gain the ability to specify the module format of the returned file. |
nice, I think let's put this on hold for now while waiting for Jest team's opinion. |
It seems like jest@27.0.0-next.3 module resolution somehow smarter that it doesn't pass UMD files to transformer anymore. Would you please check to see if you observe the same thing ? |
No luck - jest@27.0.0-next.3 and jest@27.0.0-next.4 (with jest-preset-angular@9.0.0-next.10) are still loading + running umd files both for angular libs and internal angular libs. I don't know how to check what is being passed to the transformer, but I'm dumping the call stack from the internal lib using
Same as before, even if I use a module mapper workaround to try to explicitly load the FESM module for internal libs:
When I do this, the internal FESM module loads, but it's still using UMD versions of angular libs, like
I've committed my changes (upgrading dependencies), so you can use the directions above to check out my test repo and see the same. |
Since Jest already takes care of loading the correct module from Regarding to built files which are outside |
Will Jest handle correctly loading existing ESM files from angular package format packages within When I last reviewed the Jest/resolver code, it requires |
Yes Jest does resolving correctly ESM files from
See https://github.com/thymikee/jest-preset-angular/runs/2151739998?check_suite_focus=true Before The same for So now in either CommonJS or ESM mode, Angular packages are no longer required to be compiled. |
By default, Jest loads imports from UMD files. But with new ESM support in node and Jest, that should no longer be the case when using ESM - both external dependencies and internal dependencies should use ESM files, whenever available. This is particularly important since UMD is ES5 only, which can result in errors when running tests that are not errors in the lib or app.
More precisely: When using ESM, imports from angular package format should be loaded from ESM files, whether those angular packages are under
node_modules
or in a local directory.After updating to use Jest 17 and
jest-preset-angular@9.0.0-next.6
, and the newjest-preset-angular/presets/defaults-esm
, I was running into test perf issues (particularly on startup) that are significant relative tojest-preset-angular@8
. I've been trying 2 approaches to improve test perf:moduleNameMapper: { '^@corp/([a-z\\-\/]+)': '<rootDir>/libs/$1/src/public-api.ts' }
to:
moduleNameMapper: { '^@corp/([a-z\\-\/]+)': '<rootDir>/dist/$1' }
where
dist
is the output directory for project builds.isolatedModules
Both of these are helping with perf, but I'm seeing issues with both too.
The problem I'm seeing with using pre-compiled imports is that UMD imports are used. While I have a workaround for this, I also think it's incorrect behavior, which is why I'm raising this issue.
Repro case
I've created a repro case for this. Prerequisites are node 15 and yarn.
This repro case builds a library with 2 submodules, then runs a jest test (with ESM enabled) that uses a language feature that doesn't work in ES5/UMD, but that does work in ES2015.
Note that the identical karma test succeeds (b/c it doesn't use UMD):
Workaround
A workaround for this issue is to change the
moduleNameMapper
injest.config.js
to explicitly load non-UMD js files. Comment this line out:And uncomment these 2 lines:
then:
I don't know if this is an issue when importing files from angular libs in
node_modules
. It appears that when the config is<rootDir>/dist/$1
jest is reading the package.json file in the dist subdirectory, and then resolving that to the UMD js file.🚀 Feature Proposal
I would like to see a way to make Jest smarter about using ESM or FESM files from angular package format references, so that all devs don't have to figure out this workaround. It also should be consistent whether the angular package is in a local directory, or under node_modules.
The text was updated successfully, but these errors were encountered: