Files
51b878f5a3 Switch to postcss.config.mjs, add type (#63380)
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide

### Adding or Updating Examples

- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md


## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change

-->

### What?

Now that @phanect added support for ESM PostCSS config files in PR
#63109 (original issue #34448), PostCSS can use ESM config by default.

It needs to use an `.mjs` extension by default because `create-next-app`
scaffolds CommonJS apps by default.

This will also work with ESM projects which have added `"type":
"module"` in their `package.json`

### Why?

1. To convert one more file to ESM
2. To use the modern format
3. To follow other similar migrations that have taken place in the
Next.js codebase (eg. `next.config.mjs`)

### How?

- Change file extensions from `.cjs` to `.mjs` (change similar to PR
#58380)
- Change module format from CommonJS to ESM
- Add type for the config, for users who enable `checkJs: true` in
`tsconfig.json`

Co-authored-by: Sam Ko <[email protected]>
2024-03-18 16:57:55 +00:00

250 lines
6.4 KiB
TypeScript

import { install } from "../helpers/install";
import { copy } from "../helpers/copy";
import { async as glob } from "fast-glob";
import os from "os";
import fs from "fs/promises";
import path from "path";
import { cyan, bold } from "picocolors";
import { Sema } from "async-sema";
import pkg from "../package.json";
import { GetTemplateFileArgs, InstallTemplateArgs } from "./types";
/**
* Get the file path for a given file in a template, e.g. "next.config.js".
*/
export const getTemplateFile = ({
template,
mode,
file,
}: GetTemplateFileArgs): string => {
return path.join(__dirname, template, mode, file);
};
export const SRC_DIR_NAMES = ["app", "pages", "styles"];
/**
* Install a Next.js internal template to a given `root` directory.
*/
export const installTemplate = async ({
appName,
root,
packageManager,
isOnline,
template,
mode,
tailwind,
eslint,
srcDir,
importAlias,
}: InstallTemplateArgs) => {
console.log(bold(`Using ${packageManager}.`));
/**
* Copy the template files to the target directory.
*/
console.log("\nInitializing project with template:", template, "\n");
const templatePath = path.join(__dirname, template, mode);
const copySource = ["**"];
if (!eslint) copySource.push("!eslintrc.json");
if (!tailwind)
copySource.push(
mode == "ts" ? "tailwind.config.ts" : "!tailwind.config.js",
"!postcss.config.mjs",
);
await copy(copySource, root, {
parents: true,
cwd: templatePath,
rename(name) {
switch (name) {
case "gitignore":
case "eslintrc.json": {
return `.${name}`;
}
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
case "README-template.md": {
return "README.md";
}
default: {
return name;
}
}
},
});
const tsconfigFile = path.join(
root,
mode === "js" ? "jsconfig.json" : "tsconfig.json",
);
await fs.writeFile(
tsconfigFile,
(await fs.readFile(tsconfigFile, "utf8"))
.replace(
`"@/*": ["./*"]`,
srcDir ? `"@/*": ["./src/*"]` : `"@/*": ["./*"]`,
)
.replace(`"@/*":`, `"${importAlias}":`),
);
// update import alias in any files if not using the default
if (importAlias !== "@/*") {
const files = await glob("**/*", {
cwd: root,
dot: true,
stats: false,
// We don't want to modify compiler options in [ts/js]config.json
// and none of the files in the .git folder
ignore: ["tsconfig.json", "jsconfig.json", ".git/**/*"],
});
const writeSema = new Sema(8, { capacity: files.length });
await Promise.all(
files.map(async (file) => {
await writeSema.acquire();
const filePath = path.join(root, file);
if ((await fs.stat(filePath)).isFile()) {
await fs.writeFile(
filePath,
(
await fs.readFile(filePath, "utf8")
).replace(`@/`, `${importAlias.replace(/\*/g, "")}`),
);
}
writeSema.release();
}),
);
}
if (srcDir) {
await fs.mkdir(path.join(root, "src"), { recursive: true });
await Promise.all(
SRC_DIR_NAMES.map(async (file) => {
await fs
.rename(path.join(root, file), path.join(root, "src", file))
.catch((err) => {
if (err.code !== "ENOENT") {
throw err;
}
});
}),
);
const isAppTemplate = template.startsWith("app");
// Change the `Get started by editing pages/index` / `app/page` to include `src`
const indexPageFile = path.join(
"src",
isAppTemplate ? "app" : "pages",
`${isAppTemplate ? "page" : "index"}.${mode === "ts" ? "tsx" : "js"}`,
);
await fs.writeFile(
indexPageFile,
(
await fs.readFile(indexPageFile, "utf8")
).replace(
isAppTemplate ? "app/page" : "pages/index",
isAppTemplate ? "src/app/page" : "src/pages/index",
),
);
if (tailwind) {
const tailwindConfigFile = path.join(
root,
mode === "ts" ? "tailwind.config.ts" : "tailwind.config.js",
);
await fs.writeFile(
tailwindConfigFile,
(
await fs.readFile(tailwindConfigFile, "utf8")
).replace(
/\.\/(\w+)\/\*\*\/\*\.\{js,ts,jsx,tsx,mdx\}/g,
"./src/$1/**/*.{js,ts,jsx,tsx,mdx}",
),
);
}
}
/** Copy the version from package.json or override for tests. */
const version = process.env.NEXT_PRIVATE_TEST_VERSION ?? pkg.version;
/** Create a package.json for the new project and write it to disk. */
const packageJson: any = {
name: appName,
version: "0.1.0",
private: true,
scripts: {
dev: "next dev",
build: "next build",
start: "next start",
lint: "next lint",
},
/**
* Default dependencies.
*/
dependencies: {
react: "^18",
"react-dom": "^18",
next: version,
},
devDependencies: {},
};
/**
* TypeScript projects will have type definitions and other devDependencies.
*/
if (mode === "ts") {
packageJson.devDependencies = {
...packageJson.devDependencies,
typescript: "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
};
}
/* Add Tailwind CSS dependencies. */
if (tailwind) {
packageJson.devDependencies = {
...packageJson.devDependencies,
postcss: "^8",
tailwindcss: "^3.4.1",
};
}
/* Default ESLint dependencies. */
if (eslint) {
packageJson.devDependencies = {
...packageJson.devDependencies,
eslint: "^8",
"eslint-config-next": version,
};
}
const devDeps = Object.keys(packageJson.devDependencies).length;
if (!devDeps) delete packageJson.devDependencies;
await fs.writeFile(
path.join(root, "package.json"),
JSON.stringify(packageJson, null, 2) + os.EOL,
);
console.log("\nInstalling dependencies:");
for (const dependency in packageJson.dependencies)
console.log(`- ${cyan(dependency)}`);
if (devDeps) {
console.log("\nInstalling devDependencies:");
for (const dependency in packageJson.devDependencies)
console.log(`- ${cyan(dependency)}`);
}
console.log();
await install(packageManager, isOnline);
};
export * from "./types";