Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/compiler-secondary-passes-any-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/compiler": patch
---

The secondary passes (`transformDirectives`, the lazy and refresh passes, source names) accept a filename with any extension. They run on code that is already lowered to JavaScript, so the filename is metadata rather than a parser choice: a known extension still picks its dialect, and any other (`App.rx`, from another front end) is parsed as TSX instead of failing with "Unknown file extension". This matters for server functions, whose IDs hash the filename: a front end can now pass the module's real path, so the ID matches the one `moduleForFunctionId` computes for that module. The JSX transform itself is unchanged.
71 changes: 71 additions & 0 deletions packages/compiler/__tests__/secondary-passes-frontend.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const path = require("path");

const compilerDir = path.resolve(__dirname, "..");
const { transformDirectives, transformLazy, transformRefresh } = require(compilerDir);

// Another front end (ReactiveScript's `.rx`, through a bundler's frontends hook)
// lowers its own modules to Solid's output, then hands that ordinary
// JavaScript to the secondary passes under the module's own filename — the
// path server-function ids hash, as the bundler's manifest keys it.
const ROOT = "/project";
const FILENAME = `${ROOT}/src/routes/App.rx`;
const generated = `
export async function save(value) {
"use server";
return value;
}
`;

describe("secondary passes for another front end's modules", () => {
it.each(["server", "client"])(
"transforms the generated JavaScript under the original path in %s mode",
mode => {
const result = transformDirectives(generated, {
filename: FILENAME,
root: ROOT,
mode,
env: "production"
});
expect(result.valid).toBe(true);
expect(result.functions).toHaveLength(1);
expect(result.functions[0].name).toBe("save");
}
);

it("hashes the module's own path, not a borrowed extension", () => {
const own = transformDirectives(generated, {
filename: FILENAME,
root: ROOT,
mode: "server",
env: "production"
});
const client = transformDirectives(generated, {
filename: FILENAME,
root: ROOT,
mode: "client",
env: "production"
});
const borrowed = transformDirectives(generated, {
filename: `${FILENAME}.tsx`,
root: ROOT,
mode: "server",
env: "production"
});
expect(client.functions[0].id).toBe(own.functions[0].id);
expect(borrowed.functions[0].id).not.toBe(own.functions[0].id);
});

it("runs the lazy and refresh passes under that filename", () => {
const lazy = transformLazy(
`import { lazy } from "solid-js";\nexport const Page = lazy(() => import("./Page.rx"));\n`,
{ filename: FILENAME }
);
expect(lazy.code).toContain("lazy(");
const refresh = transformRefresh(`export function App() { return null; }\n`, {
filename: FILENAME,
bundler: "vite",
jsx: false
});
expect(typeof refresh.code).toBe("string");
});
});
13 changes: 13 additions & 0 deletions packages/compiler/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ pub struct TransformResult {
pub css_hash: Option<String>,
}

/// The dialect a secondary pass (`transformDirectives`, `transformLazy`,
/// `transformRefresh`, `transformSourceNames`) parses its input in. Those
/// passes run on what a JSX transform — this crate's, or another front end's —
/// already produced, and keep the authored filename only for path-derived
/// metadata (server-function ids hash it). So a filename Oxc knows picks its
/// dialect, and any other (`.tsrx`, another front end's `.rx`) is ordinary
/// code, parsed as TSX.
pub(crate) fn secondary_source_type(filename: Option<&str>) -> SourceType {
filename
.and_then(|filename| SourceType::from_path(filename).ok())
.unwrap_or_else(SourceType::tsx)
}

pub(crate) fn source_type_for_filename(filename: Option<&str>) -> Result<SourceType> {
if filename.is_some_and(|filename| filename.ends_with(".tsrx")) {
// Secondary passes receive already-projected ordinary code but retain
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler/src/directives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::{ParseOptions, Parser};

use crate::config::source_type_for_filename;
use crate::config::secondary_source_type;
use transform::{DirectivesTransform, Env, ImportDef, ImportKind, Mode, UnsupportedExport};

const DEFAULT_RUNTIME: &str = "@solidjs/web/server-functions";
Expand Down Expand Up @@ -118,7 +118,7 @@ pub fn transform_directives(
.clone()
.unwrap_or_else(|| "use server".to_string());

let source_type = source_type_for_filename(Some(filename))?;
let source_type = secondary_source_type(Some(filename));
let allocator = Allocator::default();
let parsed = Parser::new(&allocator, &code, source_type)
.with_options(ParseOptions {
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler/src/lazy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use oxc_parser::{ParseOptions, Parser};
use oxc_semantic::SemanticBuilder;
use oxc_span::{GetSpan, Span};

use crate::config::{TransformResult, source_type_for_filename};
use crate::config::{TransformResult, secondary_source_type};

pub const LAZY_PLACEHOLDER_PREFIX: &str = "__SOLID_LAZY_MODULE__:";

Expand Down Expand Up @@ -60,7 +60,7 @@ pub fn transform_lazy(
});
};

let source_type = source_type_for_filename(Some(filename))?;
let source_type = secondary_source_type(Some(filename));
let allocator = Allocator::default();
let parsed = Parser::new(&allocator, &code, source_type)
.with_options(ParseOptions {
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler/src/refresh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::{ParseOptions, Parser};

use crate::config::{TransformResult, source_type_for_filename};
use crate::config::{TransformResult, secondary_source_type};
use transform::{Bundler, RefreshConfig, RefreshTransform};

pub const DEFAULT_IMPORT_SOURCE: &str = "solid-refresh";
Expand Down Expand Up @@ -90,7 +90,7 @@ pub fn transform_refresh(
.unwrap_or_else(|| DEFAULT_IMPORT_SOURCE.to_string()),
};

let source_type = source_type_for_filename(options.filename.as_deref())?;
let source_type = secondary_source_type(options.filename.as_deref());
let allocator = Allocator::default();
let parsed = Parser::new(&allocator, &code, source_type)
.with_options(ParseOptions {
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler/src/source_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ use oxc_semantic::{Scoping, SemanticBuilder, SymbolId};
use oxc_span::{GetSpan, Span};
use oxc_syntax::scope::ScopeFlags;

use crate::config::{TransformResult, source_type_for_filename};
use crate::config::{TransformResult, secondary_source_type};
use crate::shared::ast::object_property;
use crate::shared::ast_builder::AstBuilder;

Expand Down Expand Up @@ -93,7 +93,7 @@ pub fn transform_source_names(
return Ok(untouched(code));
}

let source_type = source_type_for_filename(filename)?;
let source_type = secondary_source_type(filename);
let allocator = Allocator::default();
let parsed = Parser::new(&allocator, &code, source_type)
.with_options(ParseOptions {
Expand Down
Loading