You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TL;DR You should no longer rely on this module for being able to combine loaders, and just write native customization hooks instead.
A bit of history
When Node unflagged ES Modules in v14, they provided an initial set of loader hooks to make it possible to import non-js files, just like you could do with require.extensions. However, it was explicitly stated that this API was experimental and would still change in the future. There was also no way to combine multiple loaders: you could only export one set of hooks, so combining loaders was something you had to do yourself.
The goal of create-esm-loader was to make it possible to combine loaders and to limit the impact of breaking changes in the loaders api. The idea was that if the loaders api changed, only this module had to be updated, without the need for rewriting your loaders.
The module has done this fairly well: when the getFormat(), getSource() en transformSource() hooks were removed in favor of a single load() hook in Node 16.12, you didn't have to rewrite your loaders. Just updating this module and then changing
This means that the two main reasons of why create-esm-loader was conceived are now no longer there: no more breaking changes in the loaders api are expected, and it is possible to natively combine multiple loaders. Therefore, if you've written loaders on top of this module, you should change them to use the native resolve(), load() and initialize()customization hooks instead.
Migrating to native customization hooks
If you've written a loader on top of create-esm-loader, there is some work to migrate to native customization hooks, but it's definitely doable and even makes the loaders more readable. Most loaders do some kind of source transform, which means all you have to do is export a load() hook. For example, a simpe TypeScript loader could look like this
// # typescript-loader.jsimportesbuildfrom'esbuild';exportasyncfunctionload(url,ctx,nextLoad){// If the url does not end with `.ts`, skip to the next loader in the chain.if(!url.endsWith('.ts'))returnnextLoad(url);// Use the next loaders to get the source, which defaults to Node loading// the file from disk, but there can also be a loader that fetches from the// network for example further down the chain. It's important that you// specify the "format" here, because Node doesn't know that `.ts` files// should be treated as esm!let{ source }=awaitnextLoad(url,{format: 'module'});// Strip the types with esbuild and return.let{ code }=awaitesbuild.transform(source,{loader: 'ts'});return{source: code,format: 'module',};}// Use it asimport{register}from'node:module';register('./typescript-loader.js',import.meta.url);
There is no need anymore to implement a custom resolve() hook as we just use Node's default resolution algorithm for .ts files.
If you've written a loader that modifies the resolution algorithm, for example to allow you to do something like
importfnfrom'@/helpers/fn.js';
then all you have to do is export a resolve() hook
// # cwd-loader.jsimportpathfrom'node:path';import{pathToFileURL}from'node:url';exportasyncfunctionresolve(specifier,ctx,nextResolve){// Only handle specifiers that start with `@/`, otherwise let the default // resolution algorithm handle it.if(!specifier.startsWith('@/'))returnnextResolve(specifier);// Treat @ as the current working directory.letrest=specifier.slice(2);letfile=path.join(process.cwd(),rest);leturl=pathToFileURL(file).href;// shortCircuit: true is needed to signal to node that we intentionally // did not use the default resolution algorithm.return{
url,shortCircuit: true,};}
Combining the TypeScript loader and the @-loader can then be done with
Often you want to be able to configure your loaders with options as well, for example to pass in an array of file extensions. This can be done with the initialize() hook:
While you definitely don't need this module anymore to write composable loaders, I still think there's a place for it with a slightly different scope. I'm planning to rewrite the module - as a semver major obviously - so that it can still remove a bit of the boilerplate needed for writing common loaders.
For example, if you just want a source transform, you could write
Alternatively, it could also be used to make accessing options easier:
importcreatefrom'create-esm-loader';// The initialize hook is created automatically and sets `this.options`.exportconst{ load, initialize }=create({// `resolve()` has the exact same signature as the resolve hook, but with// access to this.optionsasyncresolve(specifier,ctx,nextResolve){constalias=this.options;if(specifierinalias){returnnextResolve(alias[specifier]);}returnnextResolve(specifier);},// Same for `load`: same signature as the load hook, but with access// to options.asyncload(url,ctx,nextLoad){const{ extension }=this.options;if(!newURL(url).pathname.endsWith(extension))returnnextLoad(url);return{source: '...',format: 'module',};},});
It might even be possible to export register() as well
The future of
create-esm-loader
TL;DR You should no longer rely on this module for being able to combine loaders, and just write native customization hooks instead.
A bit of history
When Node unflagged ES Modules in v14, they provided an initial set of loader hooks to make it possible to import non-js files, just like you could do with require.extensions. However, it was explicitly stated that this API was experimental and would still change in the future. There was also no way to combine multiple loaders: you could only export one set of hooks, so combining loaders was something you had to do yourself.
The goal of
create-esm-loader
was to make it possible to combine loaders and to limit the impact of breaking changes in the loaders api. The idea was that if the loaders api changed, only this module had to be updated, without the need for rewriting your loaders.The module has done this fairly well: when the
getFormat()
,getSource()
entransformSource()
hooks were removed in favor of a singleload()
hook in Node 16.12, you didn't have to rewrite your loaders. Just updating this module and then changingto
was sufficient. You could even support both Node version before 16.12 and above by exporting
getFormat
,getSource
andtransformSource
along withload
.In the meantime, the loaders api has more or less stabilized and Node has added the possibity to combine loaders with
module.register()
:This means that the two main reasons of why
create-esm-loader
was conceived are now no longer there: no more breaking changes in the loaders api are expected, and it is possible to natively combine multiple loaders. Therefore, if you've written loaders on top of this module, you should change them to use the nativeresolve()
,load()
andinitialize()
customization hooks instead.Migrating to native customization hooks
If you've written a loader on top of
create-esm-loader
, there is some work to migrate to native customization hooks, but it's definitely doable and even makes the loaders more readable. Most loaders do some kind of source transform, which means all you have to do is export aload()
hook. For example, a simpe TypeScript loader could look like thisThere is no need anymore to implement a custom
resolve()
hook as we just use Node's default resolution algorithm for.ts
files.If you've written a loader that modifies the resolution algorithm, for example to allow you to do something like
then all you have to do is export a
resolve()
hookCombining the TypeScript loader and the
@
-loader can then be done withafter which
becomes possible.
Often you want to be able to configure your loaders with options as well, for example to pass in an array of file extensions. This can be done with the
initialize()
hook:which can be used as
The future of this module
While you definitely don't need this module anymore to write composable loaders, I still think there's a place for it with a slightly different scope. I'm planning to rewrite the module - as a semver major obviously - so that it can still remove a bit of the boilerplate needed for writing common loaders.
For example, if you just want a source transform, you could write
Alternatively, it could also be used to make accessing options easier:
It might even be possible to export
register()
as wellso that you can register the loader as
instead of doing
The text was updated successfully, but these errors were encountered: