Skip to content

Commit

Permalink
Make esm support optional
Browse files Browse the repository at this point in the history
 - requires loading the require hook the old way via a --require
 - we `--import` the ESM hook still but it only does ESM things
 - we don't load the ESM hook at all if its turned off
  • Loading branch information
airhorns committed Nov 15, 2024
1 parent 326beb6 commit caff11e
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup-test-env
- run: pnpm build
- run: pnpm zx integration-test/test.js
- run: pnpm test
- run: pnpm integration-test

lint:
runs-on: ubuntu-latest
Expand Down
15 changes: 15 additions & 0 deletions integration-test/cjs-with-esm-disabled/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": true,
"dynamicImport": true
},
"target": "esnext"
},
"module": {
"type": "commonjs",
"lazy": true
}
}
11 changes: 11 additions & 0 deletions integration-test/cjs-with-esm-disabled/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "simple",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "commonjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "ISC"
}
3 changes: 3 additions & 0 deletions integration-test/cjs-with-esm-disabled/run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { utility } from "./utils";

console.log(utility("It worked!"));
5 changes: 5 additions & 0 deletions integration-test/cjs-with-esm-disabled/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
set -ex

$DIR/../../pkg/wds.bin.js $@ $DIR/run.ts | grep "IT WORKED"
1 change: 1 addition & 0 deletions integration-test/cjs-with-esm-disabled/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const utility = (str: string) => str.toUpperCase();
3 changes: 3 additions & 0 deletions integration-test/cjs-with-esm-disabled/wds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
esm: false,
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"lint:fix": "NODE_OPTIONS=\"--max-old-space-size=4096\" prettier --write --check \"src/**/*.{js,ts,tsx}\" && eslint --ext ts,tsx --fix src",
"prerelease": "gitpkg publish",
"test": "vitest run",
"integration-test": "pnpm run build && zx integration-test/test.js",
"test:watch": "vitest"
},
"engines": {
Expand Down
1 change: 1 addition & 0 deletions src/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface RunOptions {
export interface ProjectConfig {
ignore: string[];
swc?: SwcConfig;
esm?: boolean;
extensions: string[];
cacheDir: string;
}
1 change: 1 addition & 0 deletions src/Supervisor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export class Supervisor extends EventEmitter {
...process.env,
WDS_SOCKET_PATH: this.socketPath,
WDS_EXTENSIONS: this.project.config.extensions.join(","),
WDS_ESM_ENABLED: this.project.config.esm ? "true" : "false",
},
stdio: stdio,
detached: true,
Expand Down
7 changes: 6 additions & 1 deletion src/hooks/child-process-cjs-hook.cts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ if (!workerData || !(workerData as SyncWorkerData).isWDSSyncWorker) {
}
> = {};

// enable source maps
process.setSourceMapsEnabled(true);

// Compile a given file by sending it into our async-to-sync wrapper worker js file
// The leader process returns us a list of all the files it just compiled, so that we don't have to pay the IPC boundary cost for each file after this one
// So, we keep a map of all the files it's compiled so far, and check it first.
Expand All @@ -37,7 +40,9 @@ if (!workerData || !(workerData as SyncWorkerData).isWDSSyncWorker) {

// Register our compiler for typescript files.
// We don't do the best practice of chaining module._compile calls because esbuild won't know about any of the stuff any of the other extensions might do, so running them wouldn't do anything. wds must then be the first registered extension.
for (const extension of process.env["WDS_EXTENSIONS"]!.split(",")) {
const extensions = process.env["WDS_EXTENSIONS"]!.split(",");
log.debug("registering cjs hook for extensions", extensions);
for (const extension of extensions) {
require.extensions[extension] = (module: any, filename: string) => {
const compiledFilename = compileOffThread(filename);
if (typeof compiledFilename === "string") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@
* Entrypoint file passed as --import to all child processes started by wds
*/
import { register } from "node:module";
import { log } from "./utils.cjs";

if (!register) {
throw new Error(
`This version of Node.js (${process.version}) does not support module.register(). Please upgrade to Node v18.19 or v20.6 and above.`
);
}

// enable source maps
process.setSourceMapsEnabled(true);

// register the CJS hook to intercept require calls the old way
import "./child-process-cjs-hook.cjs";

log.debug("registering wds ESM loader");
// register the ESM loader the new way
register("./child-process-esm-loader.js", import.meta.url);
30 changes: 23 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { fileURLToPath } from "url";
import Watcher from "watcher";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import type { RunOptions } from "./Options.js";
import type { ProjectConfig, RunOptions } from "./Options.js";
import { Project } from "./Project.js";
import { Supervisor } from "./Supervisor.js";
import { MissingDestinationError, SwcCompiler } from "./SwcCompiler.js";
Expand Down Expand Up @@ -142,14 +142,28 @@ const startIPCServer = async (socketPath: string, project: Project) => {
return server;
};

const childProcessArgs = () => {
return ["--import", path.join(dirname, "hooks", "child-process-register.js")];
const childProcessArgs = (config: ProjectConfig) => {
const args = ["--require", path.join(dirname, "hooks", "child-process-cjs-hook.cjs")];
if (config.esm) {
args.push("--import", path.join(dirname, "hooks", "child-process-esm-hook.js"));
}
return args;
};

export const wds = async (options: RunOptions) => {
const workspaceRoot = findWorkspaceRoot(process.cwd()) || process.cwd();
let workspaceRoot: string;
let projectRoot: string;
const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "wds"));
log.debug(`starting wds for workspace root ${workspaceRoot} and workdir ${workDir}`);

const firstNonOptionArg = options.argv.find((arg) => !arg.startsWith("-"));
if (firstNonOptionArg && fs.existsSync(firstNonOptionArg)) {
const absolutePath = path.resolve(firstNonOptionArg);
projectRoot = findRoot(path.dirname(absolutePath));
workspaceRoot = findWorkspaceRoot(projectRoot) || projectRoot;
} else {
projectRoot = findRoot(process.cwd());
workspaceRoot = findWorkspaceRoot(process.cwd()) || process.cwd();
}

let serverSocketPath: string;
if (os.platform() === "win32") {
Expand All @@ -158,11 +172,13 @@ export const wds = async (options: RunOptions) => {
serverSocketPath = path.join(workDir, "ipc.sock");
}

const config = await projectConfig(findRoot(process.cwd()));
const config = await projectConfig(projectRoot);
log.debug(`starting wds for workspace root ${workspaceRoot} and workdir ${workDir}`, config);

const compiler = await SwcCompiler.create(workspaceRoot, config.cacheDir);
const project = new Project(workspaceRoot, config, compiler);

project.supervisor = new Supervisor([...childProcessArgs(), ...options.argv], serverSocketPath, options, project);
project.supervisor = new Supervisor([...childProcessArgs(config), ...options.argv], serverSocketPath, options, project);

if (options.reloadOnChanges) startFilesystemWatcher(project);
if (options.terminalCommands) startTerminalCommandListener(project);
Expand Down
1 change: 1 addition & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const projectConfig = async (root: string): Promise<ProjectConfig> => {
ignore: [],
extensions: [".ts", ".tsx", ".jsx"],
cacheDir: path.join(root, "node_modules/.cache/wds"),
esm: true,
};

try {
Expand Down

0 comments on commit caff11e

Please sign in to comment.