Sources that are in formats Node.js doesn’t understand can be converted into JavaScript using the [transformSource hook][]. Before that hook gets called, however, other hooks need to tell Node.js not to throw an error on unknown file types; and to tell Node.js how to load this new file type.

    This is less performant than transpiling source files before running Node.js; a transpiler loader should only be used for development and testing purposes.

    1. // coffeescript-loader.mjs
    2. import { URL, pathToFileURL } from 'url';
    3. import CoffeeScript from 'coffeescript';
    4. const baseURL = pathToFileURL(`${process.cwd()}/`).href;
    5. // CoffeeScript files end in .coffee, .litcoffee or .coffee.md.
    6. const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/;
    7. export function resolve(specifier, context, defaultResolve) {
    8. const { parentURL = baseURL } = context;
    9. // Node.js normally errors on unknown file extensions, so return a URL for
    10. // specifiers ending in the CoffeeScript file extensions.
    11. if (extensionsRegex.test(specifier)) {
    12. return {
    13. url: new URL(specifier, parentURL).href
    14. };
    15. }
    16. // Let Node.js handle all other specifiers.
    17. return defaultResolve(specifier, context, defaultResolve);
    18. }
    19. export function getFormat(url, context, defaultGetFormat) {
    20. // Now that we patched resolve to let CoffeeScript URLs through, we need to
    21. // tell Node.js what format such URLs should be interpreted as. For the
    22. // purposes of this loader, all CoffeeScript URLs are ES modules.
    23. if (extensionsRegex.test(url)) {
    24. return {
    25. format: 'module'
    26. };
    27. }
    28. // Let Node.js handle all other URLs.
    29. return defaultGetFormat(url, context, defaultGetFormat);
    30. }
    31. export function transformSource(source, context, defaultTransformSource) {
    32. const { url, format } = context;
    33. if (extensionsRegex.test(url)) {
    34. return {
    35. source: CoffeeScript.compile(source, { bare: true })
    36. };
    37. }
    38. // Let Node.js handle all other sources.
    39. return defaultTransformSource(source, context, defaultTransformSource);
    40. }
    1. # main.coffee
    2. import { scream } from './scream.coffee'
    3. console.log scream 'hello, world'
    4. import { version } from 'process'
    5. console.log "Brought to you by Node.js version #{version}"
    1. # scream.coffee
    2. export scream = (str) -> str.toUpperCase()

    With the preceding loader, running node --experimental-loader ./coffeescript-loader.mjs main.coffee causes main.coffee to be turned into JavaScript after its source code is loaded from disk but before Node.js executes it; and so on for any .coffee, .litcoffee or .coffee.md files referenced via import statements of any loaded file.