Last active
October 21, 2023 02:28
-
-
Save NathanaelA/1d834a5967e502570279cad610f82d30 to your computer and use it in GitHub Desktop.
Svelte Node.JS loader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// (c) 2021-2022, Nathanael Anderson @ Master Technology | |
// License: MIT | |
// Please NOTE the compiler settings on Line 49 & 89 is VERY VERY specific to my use case, | |
// the compiler OPTIONS probably will be different for your a normal use case. | |
// Flags | |
const DEBUGGING = false; | |
import { writeFileSync } from 'node:fs'; | |
import { readFile } from 'node:fs/promises'; | |
import { dirname, extname, resolve as resolvePath } from 'node:path'; | |
import { cwd } from 'node:process'; | |
import { fileURLToPath, pathToFileURL } from 'node:url'; | |
import * as svelte from 'svelte/compiler'; | |
const baseURL = pathToFileURL(`${cwd}/`).href; | |
export async function resolve(specifier, context, defaultResolve) { | |
const { parentURL = baseURL } = context; | |
if (specifier.endsWith(".svelte")) { | |
return { | |
url: new URL(specifier, parentURL).href | |
}; | |
} | |
// Let Node.js handle all other specifiers. | |
return defaultResolve(specifier, context, defaultResolve); | |
} | |
export function getFormat(url, context, defaultGetFormat) { | |
// Handle our Svelte file types | |
if (url.endsWith(".svelte")) { | |
return { | |
format: 'module' | |
}; | |
} | |
// Let Node.js handle all other URLs. | |
return defaultGetFormat(url, context, defaultGetFormat); | |
} | |
export function transformSource(source, context, defaultTransformSource) { | |
const { url, format } = context; | |
if (url.endsWith(".svelte")) { | |
const transformedSource = svelte.compile(source.toString(), { | |
namespace: "foreign", // Needs to be this, otherwise we won't have all elements created... | |
dev: false, | |
enableSourcemap: {js: true, css: false}, | |
filename: url | |
}); | |
// We have to transform all manual imports of "svelte" to "svelte/internal" because the default import | |
// on NODE points to the server side script (SSR) which doesn't support some events. | |
let code = transformedSource.js.code.replace(/['"](svelte)['"]/g, "'svelte/internal'") + "\r\n//# sourceMappingURL="+transformedSource.js.map.toUrl(); | |
if (DEBUGGING) { | |
let output = url.substr(7) + ".mjs"; | |
writeFileSync(output, code); | |
} | |
return { | |
source: code | |
}; | |
} | |
// Let Node.js handle all other sources. | |
return defaultTransformSource(source, context, defaultTransformSource); | |
} | |
// Later versions of Node... | |
export async function load(url, context, defaultLoad) { | |
if (url.endsWith(".svelte")) { | |
let format = await getPackageType(url); | |
if (format === false) { format = "module"; } | |
if (format === 'commonjs') { | |
return { format }; | |
} | |
const { source: rawSource } = await defaultLoad(url, { format }); | |
const transformedSource = svelte.compile(rawSource.toString(), { | |
namespace: "foreign", // Needs to be this, otherwise we won't have all elements created... | |
enableSourcemap: {js: true, css: false}, | |
filename: url | |
}); | |
// We have to transform all manual imports of "svelte" to "svelte/internal" because the default import | |
// on NODE points to the server side script which doesn't support some events. | |
let code = transformedSource.js.code.replace(/['"](svelte)['"]/g, "'svelte/internal'") + "\r\n//# sourceMappingURL="+transformedSource.js.map.toUrl(); | |
if (DEBUGGING) { | |
let output = url.substr(7) + ".mjs"; | |
writeFileSync(output, code); | |
} | |
return { | |
format, | |
source: code, | |
}; | |
} | |
// Let Node.js handle all other URLs. | |
return defaultLoad(url, context, defaultLoad); | |
} | |
async function getPackageType(url) { | |
const isFilePath = !!extname(url); | |
// If it is a file path, get the directory it's in | |
const dir = isFilePath ? | |
dirname(fileURLToPath(url)) : | |
url; | |
// Compose a file path to a package.json in the same directory, | |
// which may or may not exist | |
const packagePath = resolvePath(dir, 'package.json'); | |
// Try to read the possibly nonexistent package.json | |
const type = await readFile(packagePath, { encoding: 'utf8' }) | |
.then((filestring) => JSON.parse(filestring).type) | |
.catch((err) => { | |
if (err?.code !== 'ENOENT') console.error(err); | |
}); | |
// Ff package.json existed and contained a `type` field with a value, voila | |
if (type) return type; | |
// Otherwise, (if not at the root) continue checking the next directory up | |
// If at the root, stop and return false | |
return dir.length > 1 && getPackageType(resolvePath(dir, '..')); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some notes:
namespace: "foreign"
set as a compiler option, most peoples problem will want to remove this line from both places it is set.svelte-loader.mjs
in my project root directory.To use:
node --experimental-loader ../svelte-loader.mjs entry-point.mjs