Skip to content
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

refactor(cli): support for .cts.mts and follows type: module #3222

Merged
merged 13 commits into from
Jun 23, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ node_modules/
!scripts/node_modules/
!webpack-examples/*/node_modules
!webpack-test/*/**/node_modules
!packages/rspack-cli/tests/**/node_modules
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
Expand Down
3 changes: 2 additions & 1 deletion packages/rspack-cli/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const config = {
testEnvironment: "../../scripts/test/patch-node-env.cjs",
testTimeout: process.env.CI ? 120000 : 30000,
testMatch: ["<rootDir>/tests/**/*.test.ts", "<rootDir>/tests/**/*.test.js"],
watchPathIgnorePatterns: ["<rootDir>/tests/.*/dist"]
watchPathIgnorePatterns: ["<rootDir>/tests/.*/dist"],
extensionsToTreatAsEsm: [".mts"]
};

module.exports = config;
11 changes: 3 additions & 8 deletions packages/rspack-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,21 @@
"directory": "packages/rspack-cli"
},
"devDependencies": {
"@types/rechoir": "^0.6.1",
"@types/webpack-bundle-analyzer": "^4.6.0",
"concat-stream": "^2.0.0",
"execa": "^5.0.0",
"internal-ip": "6.2.0",
"source-map-support": "^0.5.19",
"ts-node": "10.9.1"
},
"peerDependencies": {
"ts-node": ">= 10"
},
"peerDependenciesMeta": {
"ts-node": {
"optional": true
}
},
"dependencies": {
"@discoveryjs/json-ext": "^0.5.7",
"@rspack/core": "workspace:*",
"@rspack/dev-server": "workspace:*",
"colorette": "2.0.19",
"interpret": "^3.1.1",
"rechoir": "^0.8.0",
"semver": "6.3.0",
"webpack-bundle-analyzer": "4.6.1",
"yargs": "17.6.2"
Expand Down
10 changes: 10 additions & 0 deletions packages/rspack-cli/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const DEFAULT_CONFIG_NAME = "rspack.config" as const;

export const DEFAULT_EXTENSIONS = [
".js",
".ts",
".mjs",
".mts",
".cjs",
".cts"
] as const;
9 changes: 3 additions & 6 deletions packages/rspack-cli/src/rspack-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ import {
MultiStats
} from "@rspack/core";
import { normalizeEnv } from "./utils/options";
import {
loadRspackConfig,
findFileWithSupportedExtensions
} from "./utils/loadConfig";
import { loadRspackConfig } from "./utils/loadConfig";
import findConfig from "./utils/findConfig";
import { Mode } from "@rspack/core/src/config";
import { RspackPluginInstance, RspackPluginFunction } from "@rspack/core";
import path from "path";
Expand Down Expand Up @@ -131,8 +129,7 @@ export class RspackCLI {
} else if (!item.entry) {
const defaultEntryBase = path.resolve(process.cwd(), defaultEntry);
const defaultEntryPath =
findFileWithSupportedExtensions(defaultEntryBase) ||
defaultEntryBase + ".js"; // default entry is js
findConfig(defaultEntryBase) || defaultEntryBase + ".js"; // default entry is js
item.entry = {
main: defaultEntryPath
};
Expand Down
27 changes: 27 additions & 0 deletions packages/rspack-cli/src/utils/crossImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { pathToFileURL } from "url";
import isEsmFile from "./isEsmFile";

/**
* Dynamically import files. It will make sure it's not being compiled away by TS/Rollup.
*/
export const dynamicImport = new Function("path", "return import(path)");

const crossImport = async <T = any>(
path: string,
cwd = process.cwd()
): Promise<T> => {
if (isEsmFile(path, cwd)) {
const url = pathToFileURL(path).href;
const { default: config } = await dynamicImport(url);
return config;
} else {
let result = require(path);
// compatible with export default config in common ts config
if (result && typeof result === "object" && "default" in result) {
result = result.default || {};
}
return result;
}
};

export default crossImport;
12 changes: 12 additions & 0 deletions packages/rspack-cli/src/utils/findConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import fs from "fs";
import { DEFAULT_EXTENSIONS } from "../constants";

/**
* Takes a basePath like `webpack.config`, return `webpack.config.{ext}` if
* exists. returns undefined if none of them exists
*/
const findConfig = (basePath: string): string | undefined => {
return DEFAULT_EXTENSIONS.map(ext => basePath + ext).find(fs.existsSync);
};

export default findConfig;
14 changes: 14 additions & 0 deletions packages/rspack-cli/src/utils/isEsmFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import path from "path";
import readPackageUp from "./readPackageUp";

const isEsmFile = (filePath: string, cwd = process.cwd()) => {
const ext = path.extname(filePath);
if (/\.(mjs|mts)$/.test(ext)) {
return true;
} else {
const packageJson = readPackageUp(cwd);
return packageJson?.type === "module";
}
};

export default isEsmFile;
8 changes: 8 additions & 0 deletions packages/rspack-cli/src/utils/isTsFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import path from "path";

const isTsFile = (configPath: string) => {
const ext = path.extname(configPath);
return /\.(c|m)?ts$/.test(ext);
};

export default isTsFile;
109 changes: 51 additions & 58 deletions packages/rspack-cli/src/utils/loadConfig.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,47 @@
import path from "path";
import { pathToFileURL } from "url";
import fs from "fs";
import { RspackCLIOptions } from "../types";
import { RspackOptions, MultiRspackOptions } from "@rspack/core";
import findConfig from "./findConfig";
import rechoir from "rechoir";
import interpret from "interpret";
import { pathToFileURL } from "url";
import isEsmFile from "./isEsmFile";
import isTsFile from "./isTsFile";
import crossImport from "./crossImport";

interface RechoirError extends Error {
failures: RechoirError[];
error: Error;
}

const DEFAULT_CONFIG_NAME = "rspack.config" as const;

const supportedExtensions = [".js", ".ts", ".mjs", ".cjs"];
const defaultConfig = "rspack.config";
const registerLoader = (configPath: string) => {
const ext = path.extname(configPath);
// TODO implement good `.mts` support after https://github.com/gulpjs/rechoir/issues/43
// For ESM and `.mts` you need to use: 'NODE_OPTIONS="--loader ts-node/esm" rspack build --config ./rspack.config.mts'
if (isEsmFile(configPath) && isTsFile(configPath)) {
return;
}
const extensions = Object.fromEntries(
Object.entries(interpret.extensions).filter(([key]) => key === ext)
);
if (Object.keys(extensions).length === 0) {
throw new Error(`config file "${configPath}" is not supported.`);
}
try {
rechoir.prepare(extensions, configPath);
} catch (error) {
const failures = (error as RechoirError)?.failures;
if (failures) {
const messages = failures.map(failure => failure.error.message);
throw new Error(`${messages.join("\n")}`);
} else {
throw error;
}
}
};

export type LoadedRspackConfig =
| undefined
Expand All @@ -17,66 +53,23 @@ export type LoadedRspackConfig =
) => RspackOptions | MultiRspackOptions);

export async function loadRspackConfig(
options: RspackCLIOptions
options: RspackCLIOptions,
cwd = process.cwd()
): Promise<LoadedRspackConfig> {
let loadedConfig: LoadedRspackConfig;
// if we pass config paras
if (options.config) {
const resolvedConfigPath = path.resolve(process.cwd(), options.config);
if (!fs.existsSync(resolvedConfigPath)) {
throw new Error(`config file "${resolvedConfigPath}" not exists`);
const configPath = path.resolve(cwd, options.config);
if (!fs.existsSync(configPath)) {
throw new Error(`config file "${configPath}" not found.`);
}
loadedConfig = await requireWithAdditionalExtension(resolvedConfigPath);
isTsFile(configPath) && registerLoader(configPath);
return crossImport(configPath, cwd);
} else {
let defaultConfigPath = findFileWithSupportedExtensions(
path.resolve(process.cwd(), defaultConfig)
);
if (defaultConfigPath != null) {
loadedConfig = await requireWithAdditionalExtension(defaultConfigPath);
const defaultConfig = findConfig(path.resolve(cwd, DEFAULT_CONFIG_NAME));
if (defaultConfig) {
isTsFile(defaultConfig) && registerLoader(defaultConfig);
return crossImport(defaultConfig, cwd);
} else {
loadedConfig = {};
return {};
}
}
return loadedConfig;
}

// takes a basePath like `webpack.config`, return `webpack.config.{js,ts}` if
// exists. returns null if none of them exists
export function findFileWithSupportedExtensions(
basePath: string
): string | null {
for (const extension of supportedExtensions) {
if (fs.existsSync(basePath + extension)) {
return basePath + extension;
}
}
return null;
}

let hasRegisteredTS = false;
async function requireWithAdditionalExtension(resolvedPath: string) {
if (resolvedPath.endsWith("ts") && !hasRegisteredTS) {
hasRegisteredTS = true;
let tsNode: any;
try {
tsNode = require("ts-node");
} catch (e) {
throw new Error("`ts-node` is required to use TypeScript configuration.");
}
tsNode.register({ transpileOnly: true });
}
let loadedConfig;
if (resolvedPath.endsWith("ts")) {
loadedConfig = require(resolvedPath);
} else if (resolvedPath.endsWith(".cjs")) {
/**
* this is a dirty hack to help us test rsapck-cli because we can't dynamic import js config due to [nodejs bug](https://github.com/nodejs/node/issues/35889) in jest
*/
loadedConfig = require(resolvedPath);
} else {
// dynamic import can handle both cjs & mjs
const fileUrl = pathToFileURL(resolvedPath).href;
loadedConfig = (await import(fileUrl)).default;
}
return loadedConfig;
}
23 changes: 23 additions & 0 deletions packages/rspack-cli/src/utils/readPackageUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from "fs";
import path from "path";

const readPackageUp = (cwd = process.cwd()): { type?: "module" } | null => {
let currentDir = path.resolve(cwd);
let packageJsonPath = path.join(currentDir, "package.json");

while (!fs.existsSync(packageJsonPath)) {
let parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
return null;
}
currentDir = parentDir;
packageJsonPath = path.join(currentDir, "package.json");
}
try {
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
} catch (error) {
return null;
}
};

export default readPackageUp;
1 change: 1 addition & 0 deletions packages/rspack-cli/tests/build/config/cjs/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Main cjs file");
10 changes: 10 additions & 0 deletions packages/rspack-cli/tests/build/config/cjs/rspack.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const path = require("path");

module.exports = {
mode: "production",
entry: path.resolve(__dirname, "main.ts"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "cjs.bundle.js"
}
};
10 changes: 10 additions & 0 deletions packages/rspack-cli/tests/build/config/cjs/rspack.config.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const path = require("path");

module.exports = {
mode: "production",
entry: path.resolve(__dirname, "main.ts"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "cts.bundle.js"
}
};
11 changes: 11 additions & 0 deletions packages/rspack-cli/tests/build/config/cjs/rspack.config.export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const path = require("path");

// @ts-ignore
export = {
mode: "production",
entry: path.resolve(__dirname, "main.ts"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "ts.bundle.js"
}
};
10 changes: 10 additions & 0 deletions packages/rspack-cli/tests/build/config/cjs/rspack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const path = require("path");

module.exports = {
mode: "production",
entry: path.resolve(__dirname, "main.ts"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "js.bundle.js"
}
};
10 changes: 10 additions & 0 deletions packages/rspack-cli/tests/build/config/cjs/rspack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const path = require("path");

export default {
mode: "production",
entry: path.resolve(__dirname, "main.ts"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "ts.bundle.js"
}
};