diff --git a/packages/create-remix/cli.ts b/packages/create-remix/cli.ts
index 39c1b895789..538708a9dde 100644
--- a/packages/create-remix/cli.ts
+++ b/packages/create-remix/cli.ts
@@ -3,9 +3,8 @@ import chalkAnimation from "chalk-animation";
import inquirer from "inquirer";
import meow from "meow";
-import type { Lang, Server, Stack } from ".";
-import { appType } from ".";
-import { createApp } from ".";
+import type { Lang, PackageManager, Server, Stack } from ".";
+import { appType, createApp, getInstallCommand } from ".";
const help = `
Usage:
@@ -69,12 +68,14 @@ async function run() {
stack?: never;
server: Server;
lang: Lang;
+ packageManager: PackageManager;
install: boolean;
}
| {
appType: "stack";
stack: Stack;
server?: never;
+ packageManager?: never;
install: boolean;
}
>([
@@ -142,10 +143,31 @@ async function run() {
{ name: "JavaScript", value: "js" }
]
},
+ {
+ name: "packageManager",
+ type: "list",
+ message: "Which package manager?",
+ when(answers) {
+ return answers.appType !== appType.stack;
+ },
+ loop: false,
+ choices: [
+ { name: "npm", value: "npm" },
+ { name: "yarn (without PnP)", value: "yarn" },
+ { name: "yarn (with PnP enabled)", value: "yarn-pnp" }
+ ]
+ },
{
name: "install",
type: "confirm",
- message: "Do you want me to run `npm install`?",
+ message: answers => {
+ const packageManager = typeof answers.packageManager === 'string'
+ ? answers.packageManager
+ : 'npm';
+
+ const command = getInstallCommand(packageManager);
+ return `Do you want me to run \`${command}\`?`;
+ },
default: true
}
]);
@@ -155,14 +177,15 @@ async function run() {
projectDir,
lang: "ts",
stack: answers.stack,
- install: answers.install
+ install: answers.install,
});
} else {
await createApp({
projectDir,
lang: answers.lang,
server: answers.server,
- install: answers.install
+ install: answers.install,
+ packageManager: answers.packageManager,
});
}
}
diff --git a/packages/create-remix/index.ts b/packages/create-remix/index.ts
index 83338ecebaa..07d179895a9 100644
--- a/packages/create-remix/index.ts
+++ b/packages/create-remix/index.ts
@@ -27,6 +27,11 @@ export type AppType = typeof appType[keyof typeof appType];
export type Lang = "ts" | "js";
+export type PackageManager =
+ | "npm"
+ | "yarn"
+ | "yarn-pnp";
+
export type CreateAppArgs =
| {
projectDir: string;
@@ -35,6 +40,7 @@ export type CreateAppArgs =
stack?: never;
install: boolean;
quiet?: boolean;
+ packageManager?: PackageManager;
}
| {
projectDir: string;
@@ -43,13 +49,15 @@ export type CreateAppArgs =
stack: Stack;
install: boolean;
quiet?: boolean;
+ packageManager?: PackageManager;
};
-async function createApp({
+export async function createApp({
projectDir,
lang,
install,
quiet,
+ packageManager = 'npm',
...rest
}: CreateAppArgs) {
let server = rest.stack ? rest.stack : rest.server;
@@ -95,6 +103,16 @@ async function createApp({
await fse.copy(serverLangTemplate, projectDir, { overwrite: true });
}
+ // copy the package manager template
+ const packageManagerLangTemplate = path.resolve(
+ __dirname,
+ "templates",
+ `${packageManager}_${lang}`
+ );
+ if (fse.existsSync(packageManagerLangTemplate)) {
+ await fse.copy(packageManagerLangTemplate, projectDir, { overwrite: true });
+ }
+
// rename dotfiles
let dotfiles = ["gitignore", "github", "dockerignore", "env.example"];
await Promise.all(
@@ -114,8 +132,13 @@ async function createApp({
appPkg.dependencies = appPkg.dependencies || {};
appPkg.devDependencies = appPkg.devDependencies || {};
let serverPkg = require(path.join(serverTemplate, "package.json"));
+ let packageManagerPkgPath = path.join(packageManagerLangTemplate, "package.json");
+ let packageManagerPkg = fse.existsSync(packageManagerPkgPath)
+ ? require(packageManagerPkgPath)
+ : {};
+
["dependencies", "devDependencies", "scripts"].forEach(key => {
- Object.assign(appPkg[key], serverPkg[key]);
+ Object.assign(appPkg[key], serverPkg[key], packageManagerPkg[key]);
});
appPkg.main = serverPkg.main;
@@ -133,16 +156,30 @@ async function createApp({
}
});
+
appPkg = sortPackageJSON(appPkg);
+ if (packageManager === "yarn-pnp") {
+ // yarn PnP does not support the remix package's magic, so we remove it from
+ // the dependencies and delete the postinstall hook
+ delete appPkg.dependencies.remix;
+ delete appPkg.scripts.postinstall;
+ }
+
// write package.json
await fse.writeFile(
path.join(projectDir, "package.json"),
JSON.stringify(appPkg, null, 2)
);
+ // enable yarn PnP if requested
+ if (packageManager === "yarn-pnp") {
+ execSync("yarn set version berry", { stdio: "inherit", cwd: projectDir });
+ }
+
if (install) {
- execSync("npm install", { stdio: "inherit", cwd: projectDir });
+ const command = getInstallCommand(packageManager);
+ execSync(command, { stdio: "inherit", cwd: projectDir });
}
let serverScript = path.resolve(serverTemplate, "scripts/init.js");
@@ -174,4 +211,12 @@ async function createApp({
}
}
-export { createApp };
+export function getInstallCommand(packageManager: PackageManager): string {
+ switch (packageManager) {
+ case "npm":
+ return "npm install";
+ case "yarn":
+ case "yarn-pnp":
+ return "yarn install";
+ }
+}
diff --git a/packages/create-remix/templates/yarn-pnp/gitignore b/packages/create-remix/templates/yarn-pnp/gitignore
new file mode 100644
index 00000000000..77678a7fd7b
--- /dev/null
+++ b/packages/create-remix/templates/yarn-pnp/gitignore
@@ -0,0 +1,14 @@
+node_modules
+
+/.cache
+/server/build
+/public/build
+.env
+
+.yarn/*
+!.yarn/cache
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
diff --git a/packages/create-remix/templates/yarn-pnp_js/app/entry.client.jsx b/packages/create-remix/templates/yarn-pnp_js/app/entry.client.jsx
new file mode 100644
index 00000000000..57dba485b92
--- /dev/null
+++ b/packages/create-remix/templates/yarn-pnp_js/app/entry.client.jsx
@@ -0,0 +1,4 @@
+import { hydrate } from "react-dom";
+import { RemixBrowser } from "@remix-run/react";
+
+hydrate(