Skip to content

Commit

Permalink
refactor(cli): support for .cts.mts and follows type: module (#…
Browse files Browse the repository at this point in the history
…3222)

* feat: [cli] The configuration file follows the type property of package.json.

* chore: [cli] Remove unnecessary tmp dependencies

* fix: [cli] When building configuration files, do not bundle dependencies.

* chore: [deps] Commit missing information.

* refactor: [cli] Load the configuration file using `jiti`

* pref(cli): jiti use sucrase.transform & remove __dirname in the esm file.

* refactor(cli): support for `.cts`、`.mts` and follows `type: module`

* chore(cli): correct method name

* perf(cli): prevent some edge conditions from going wrong

* chore: fix lock

* chore: remove type-fest

* chore: commit node_modules in monorepo

* chore: support export default in ts config

---------

Co-authored-by: yangjian.fe <yangjian.fe@bytedance.com>
  • Loading branch information
molvqingtai and hardfist committed Jun 23, 2023
1 parent 79fd6dd commit 955d01c
Show file tree
Hide file tree
Showing 47 changed files with 545 additions and 251 deletions.
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"
}
};

0 comments on commit 955d01c

Please sign in to comment.