-
Notifications
You must be signed in to change notification settings - Fork 182
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
Add an ability to import and run JS modules in server-side components #234
Comments
Can't we use |
I don't think you can import modules with eval. |
ie. I'm open to any option that works. Let's tinker with the possible security issues / other concerns later. |
In the example you've shared, we have
Can't we replace it with
|
I'm not sure how eval can mimic sentences like |
Why would it be tricky? Wouldn't eval parse it normally? |
@tipiirai I came up with a fairly simple, but very primitive solution to transpile the source by hand. We could look up all import statements and transform them to dynamic imports, which are allowed in non-module environments, like Here is my sample implementation. It uses a regex for the pattern lookup, so it's very error-prone. It works, but I'm not convinced anyone should use this: const BunTranspiler = new Bun.Transpiler({
loader: 'js',
// minifyWhitespace: true, // TEMP: Commented out for debugging reasons
})
function transpileImports(source) {
const imports = BunTranspiler
.scanImports(source)
.filter(imp => imp.kind == 'import-statement')
.map(imp => imp.path)
// Slightly modified version of a regex made by Antón Kryukov Chinaev @antonkc
// https://github.com/antonkc/MOR/blob/main/matchJsImports.md
const regex = /(?<=(?:[\s\n;])|^)(?:import[\s\n]*)((?:(?:[_\$\w][_\$\w0-9]*)(?:[\s\n]+(?:as[\s\n]+(?:[_\$\w][_\$\w0-9]*)))?(?=(?:[\n\s]*,[\n\s]*[\{\*])|(?:[\n\s]+from)))?)[\s\n,]*((?:\*[\n\s]*(?:as[\s\n]+(?:[_\$\w][_\$\w0-9]*))(?=[\n\s]+from))?)[\s\n,]*((?:\{[n\s]*(?:(?:[_\$\w][_\$\w0-9]*)(?:[\s\n]+(?:as[\s\n]+(?:[_\$\w][_\$\w0-9]*)))?[\s\n]*,?[\s\n]*)*\}(?=[\n\s]*from))?)(?:[\s\n]*((?:from)?))[\s\n]*(?:["']([^"']*)(["']))[\s\n]*?;?/gm
return BunTranspiler
.transformSync(source) // Strips comments, minifies whitespaces, etc.
.replaceAll(regex, (input, defaultImport, wildcardImport, namedImports, from, moduleName, quote) => {
// Just to be more confident, that the import statement is indeed an import statement,
// though it's incorrect in some rare cases, but more on that later on
if (!imports.includes(moduleName)) return input
// TODO: Resolve the path to run the script in the right context
const path = quote + moduleName + quote
if (wildcardImport)
if (defaultImport)
return `const ${wildcardImport.split(' ').at(-1)} = await import(${path}), ${defaultImport} = ${wildcardImport.split(' ').at(-1)}.default;`
else return `const ${wildcardImport.split(' ').at(-1)} = await import(${path});`
else if (namedImports)
if (defaultImport)
return `const ${namedImports.replaceAll(' as ', ': ').slice(0, -1)}, default: ${defaultImport} } = await import(${path});`
else return `const ${namedImports.replaceAll(' as ', ': ')} = await import(${path});`
else if (defaultImport) return `const { default: ${defaultImport} } = await import(${path});`
return `await import (${path});`
})
} Given the source: import defaultImport1 from "./module";
import * as wildcardImport1 from "./module";
import { namedImport1 } from "./module";
import { namedImport1 as alias1 } from "./module";
import { default as alias2 } from "./module";
import { namedImport2, namedImport3 } from "./module";
import defaultImport2, * as wildcardImport2 from "./module";
import { namedImport4, namedImport1 as alias3, /* ... */ } from "./module";
import defaultImport3, { namedImport5, /* ... */ } from "./module";
import "./module"; It transpiles to: const { default: defaultImport1 } = await import("./module");
const wildcardImport1 = await import("./module");
const {namedImport1} = await import("./module");
const {namedImport1: alias1} = await import("./module");
const {default: alias2} = await import("./module");
const {namedImport2, namedImport3} = await import("./module");
const wildcardImport2 = await import("./module"), defaultImport2 = wildcardImport2.default;
const {namedImport4, namedImport1: alias3} = await import("./module");
const {namedImport5, default: defaultImport3 } = await import("./module");
await import ("./module"); And in action: // module.js
export default 42
export const namedImport1 = 42
export const namedImport2 = 42
export const namedImport3 = 42
export const namedImport4 = 42
export const namedImport5 = 42 const source = `
import defaultImport1 from "./module";
import * as wildcardImport1 from "./module";
import { namedImport1 } from "./module";
import { namedImport1 as alias1 } from "./module";
import { default as alias2 } from "./module";
import { namedImport2, namedImport3 } from "./module";
import defaultImport2, * as wildcardImport2 from "./module";
import { namedImport4, namedImport1 as alias3, /* ... */ } from "./module";
import defaultImport3, { namedImport5, /* ... */ } from "./module";
import "./module";
console.log({
alias1,
alias2,
alias3,
namedImport1,
namedImport2,
namedImport3,
namedImport4,
namedImport5,
defaultImport1,
defaultImport2,
defaultImport3,
wildcardImport1,
wildcardImport2,
})
`
const transpiled = transpileImports(source)
const AsyncFunction = async function () {}.constructor
AsyncFunction(transpiled)() /* =>
{
alias1: 42,
alias2: 42,
alias3: 42,
namedImport1: 42,
namedImport2: 42,
namedImport3: 42,
namedImport4: 42,
namedImport5: 42,
defaultImport1: 42,
defaultImport2: 42,
defaultImport3: 42,
wildcardImport1: Module {
default: 42,
namedImport1: 42,
namedImport2: 42,
namedImport3: 42,
namedImport4: 42,
namedImport5: 42,
},
wildcardImport2: Module {
default: 42,
namedImport1: 42,
namedImport2: 42,
namedImport3: 42,
namedImport4: 42,
namedImport5: 42,
},
}
*/ Issues that I've already found:
|
This is a highly desirable feature, and I'm looking forward to it. I think there are 3-level component life time:
|
This should be possible:
Currently Nue uses a new Function() call to execute server side scripts, which does not allow import statements an is limited in many ways. A better way is to use dynamic imports.
Unfortunately Bun does not yet support imports via plain string so we need to figure out something clever. Maybe use Bun.build or something. I don't know.
What is the best way to implement this? I know this is a rather heavy-duty question and requires deep understanding of both Bun/Node and Nue, but yeah... I'll ask anyway.
The text was updated successfully, but these errors were encountered: