Skip to content

Commit

Permalink
feat(remix-dev): add ability to use tsconfig paths aliases other th…
Browse files Browse the repository at this point in the history
…an `~` (remix-run#2412)

Co-authored-by: Jacob Ebey <jacob.ebey@live.com>
Co-authored-by: Logan McAnsh <logan@mcan.sh>
  • Loading branch information
3 people authored and aaronpowell committed May 3, 2022
1 parent f75866f commit 43ebd00
Show file tree
Hide file tree
Showing 11 changed files with 912 additions and 513 deletions.
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
- eps1lon
- evanwinter
- exegeteio
- F3n67u
- fergusmeiklejohn
- fgiuliani
- fishel-feng
Expand Down
5 changes: 4 additions & 1 deletion integration/helpers/create-fixture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import express from "express";
import cheerio from "cheerio";
import prettier from "prettier";
import getPort from "get-port";
import stripIndent from "strip-indent";

import type {
ServerBuild,
Expand Down Expand Up @@ -43,6 +44,8 @@ export type Fixture = Awaited<ReturnType<typeof createFixture>>;
export type AppFixture = Awaited<ReturnType<typeof createAppFixture>>;

export const js = String.raw;
export const json = String.raw;
export const mdx = String.raw;

export async function createFixture(init: FixtureInit) {
let projectDir = await createFixtureProject(init);
Expand Down Expand Up @@ -432,7 +435,7 @@ async function writeTestFiles(init: FixtureInit, dir: string) {
Object.keys(init.files).map(async (filename) => {
let filePath = path.join(dir, filename);
await fse.ensureDir(path.dirname(filePath));
await fs.writeFile(filePath, init.files[filename]);
await fs.writeFile(filePath, stripIndent(init.files[filename]));
})
);
await renamePkgJsonApp(dir);
Expand Down
126 changes: 126 additions & 0 deletions integration/path-mapping-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
createAppFixture,
createFixture,
js,
json,
mdx,
} from "./helpers/create-fixture";
import type { Fixture, AppFixture } from "./helpers/create-fixture";

let fixture: Fixture;
let app: AppFixture;

beforeAll(async () => {
fixture = await createFixture({
files: {
"app/components/my-lib/index.ts": js`
export const pizza = "this is a pizza";
`,

"app/routes/index.tsx": js`
import { pizza } from "@mylib";
import { json, useLoaderData, Link } from "remix";
export function loader() {
return json(pizza);
}
export default function Index() {
let data = useLoaderData();
return (
<div>
{data}
</div>
)
}
`,

"app/routes/tilde-alias.tsx": js`
import { pizza } from "~/components/my-lib";
import { json, useLoaderData, Link } from "remix";
export function loader() {
return json(pizza);
}
export default function Index() {
let data = useLoaderData();
return (
<div>
{data}
</div>
)
}
`,

"app/components/component.jsx": js`
export function PizzaComponent() {
return <span>this is a pizza</span>
}
`,

"app/routes/mdx.mdx": mdx`
---
meta:
title: My First Post
description: Isn't this awesome?
headers:
Cache-Control: no-cache
---
import { PizzaComponent } from "@component";
# Hello MDX!
This is my first post.
<PizzaComponent />
`,

"tsconfig.json": json`
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2019"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"],
"@mylib": ["./app/components/my-lib/index"],
"@component": ["./app/components/component.jsx"],
},
// Remix takes care of building everything in \`remix build\`.
"noEmit": true
}
}
`,
},
});

app = await createAppFixture(fixture);
});

afterAll(async () => app.close());

it("import internal library via alias other than ~", async () => {
// test for https://github.com/remix-run/remix/issues/2298
let response = await fixture.requestDocument("/");
expect(await response.text()).toMatch("this is a pizza");
});

it("import internal library via ~ alias", async () => {
let response = await fixture.requestDocument("/tilde-alias");
expect(await response.text()).toMatch("this is a pizza");
});

it("works for mdx files", async () => {
let response = await fixture.requestDocument("/mdx");
expect(await response.text()).toMatch("this is a pizza");
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"simple-git": "^3.2.4",
"sort-package-json": "^1.54.0",
"strip-ansi": "^6.0.1",
"strip-indent": "^3.0.0",
"type-fest": "^2.11.1",
"typescript": "^4.5.5",
"unified": "^9.2.0"
Expand Down
32 changes: 26 additions & 6 deletions packages/remix-dev/compiler/plugins/mdx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { remarkMdxFrontmatter } from "remark-mdx-frontmatter";

import type { RemixConfig } from "../../config";
import { getLoaderForFile } from "../loaders";
import { createMatchPath } from "../utils/tsconfig";

export function mdxPlugin(config: RemixConfig): esbuild.Plugin {
return {
Expand All @@ -16,17 +17,36 @@ export function mdxPlugin(config: RemixConfig): esbuild.Plugin {
]);

build.onResolve({ filter: /\.mdx?$/ }, (args) => {
let matchPath = createMatchPath();
// Resolve paths according to tsconfig paths property
function resolvePath(id: string) {
if (!matchPath) {
return id;
}
return (
matchPath(id, undefined, undefined, [
".ts",
".tsx",
".js",
".jsx",
".mdx",
".md",
]) || id
);
}

let resolvedPath = resolvePath(args.path);
let resolved = path.resolve(args.resolveDir, resolvedPath);

return {
path: args.path.startsWith("~/")
? path.resolve(config.appDirectory, args.path.replace(/^~\//, ""))
: path.resolve(args.resolveDir, args.path),
path: resolved,
namespace: "mdx",
};
});

build.onLoad({ filter: /\.mdx?$/ }, async (args) => {
try {
let contents = await fsp.readFile(args.path, "utf-8");
let fileContents = await fsp.readFile(args.path, "utf-8");

let rehypePlugins = [];
let remarkPlugins = [
Expand Down Expand Up @@ -54,7 +74,7 @@ export const meta = typeof attributes !== "undefined" && attributes.meta;
export const links = undefined;
`;

let compiled = await xdm.compile(contents, {
let compiled = await xdm.compile(fileContents, {
jsx: true,
jsxRuntime: "classic",
pragma: "React.createElement",
Expand All @@ -63,7 +83,7 @@ export const links = undefined;
remarkPlugins,
});

contents = `
let contents = `
${compiled.value}
${remixExports}`;

Expand Down
21 changes: 14 additions & 7 deletions packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
serverBuildVirtualModule,
assetsManifestVirtualModule,
} from "../virtualModules";
import { createMatchPath } from "../utils/tsconfig";

/**
* A plugin responsible for resolving bare module ids based on server target.
Expand All @@ -19,12 +20,23 @@ export function serverBareModulesPlugin(
dependencies: Record<string, string>,
onWarning?: (warning: string, key: string) => void
): Plugin {
let matchPath = createMatchPath();
// Resolve paths according to tsconfig paths property
function resolvePath(id: string) {
if (!matchPath) {
return id;
}
return (
matchPath(id, undefined, undefined, [".ts", ".tsx", ".js", ".jsx"]) || id
);
}

return {
name: "server-bare-modules",
setup(build) {
build.onResolve({ filter: /.*/ }, ({ importer, path }) => {
// If it's not a bare module ID, bundle it.
if (!isBareModuleId(path)) {
if (!isBareModuleId(resolvePath(path))) {
return undefined;
}

Expand Down Expand Up @@ -114,12 +126,7 @@ function getNpmPackageName(id: string): string {
}

function isBareModuleId(id: string): boolean {
return (
!id.startsWith("node:") &&
!id.startsWith(".") &&
!id.startsWith("~") &&
!isAbsolute(id)
);
return !id.startsWith("node:") && !id.startsWith(".") && !isAbsolute(id);
}

function warnOnceIfEsmOnlyPackage(
Expand Down
65 changes: 65 additions & 0 deletions packages/remix-dev/compiler/utils/tsconfig/configLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as path from "path";

import { tsConfigLoader } from "./tsConfigLoader";

export interface ConfigLoaderParams {
cwd: string;
}

export interface ConfigLoaderSuccessResult {
resultType: "success";
configFileAbsolutePath: string;
baseUrl: string;
absoluteBaseUrl: string;
paths: { [key: string]: Array<string> };
mainFields?: Array<string>;
addMatchAll?: boolean;
}

export interface ConfigLoaderFailResult {
resultType: "failed";
message: string;
}

export type ConfigLoaderResult =
| ConfigLoaderSuccessResult
| ConfigLoaderFailResult;

export function loadTsConfig(cwd: string = process.cwd()): ConfigLoaderResult {
return configLoader({ cwd: cwd });
}

export function configLoader({
cwd,
}: ConfigLoaderParams): ConfigLoaderResult {
// Load tsconfig and create path matching function
let loadResult = tsConfigLoader({
cwd,
getEnv: (key: string) => process.env[key],
});

if (!loadResult.tsConfigPath) {
return {
resultType: "failed",
message: "Couldn't find tsconfig.json",
};
}

if (!loadResult.baseUrl) {
return {
resultType: "failed",
message: "Missing baseUrl in compilerOptions",
};
}

let tsConfigDir = path.dirname(loadResult.tsConfigPath);
let absoluteBaseUrl = path.join(tsConfigDir, loadResult.baseUrl);

return {
resultType: "success",
configFileAbsolutePath: loadResult.tsConfigPath,
baseUrl: loadResult.baseUrl,
absoluteBaseUrl,
paths: loadResult.paths || {},
};
}
20 changes: 20 additions & 0 deletions packages/remix-dev/compiler/utils/tsconfig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import tsConfigPaths from "tsconfig-paths";

import { loadTsConfig } from "./configLoader";
export { loadTsConfig } from "./configLoader";

export function createMatchPath() {
let configLoaderResult = loadTsConfig();
if (configLoaderResult.resultType === "failed") {
return undefined;
}

let matchPath = tsConfigPaths.createMatchPath(
configLoaderResult.absoluteBaseUrl,
configLoaderResult.paths,
configLoaderResult.mainFields,
configLoaderResult.addMatchAll
);

return matchPath;
}

0 comments on commit 43ebd00

Please sign in to comment.