Skip to content

feat: external node_modules packages by default #93

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
56 changes: 52 additions & 4 deletions packages/core/src/core/rsbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,47 @@ const isMultiCompiler = <
return 'compilers' in compiler && Array.isArray(compiler.compilers);
};

const autoExternalNodeModules: (
data: Rspack.ExternalItemFunctionData,
callback: (
err?: Error,
result?: Rspack.ExternalItemValue,
type?: Rspack.ExternalsType,
) => void,
) => void = ({ context, request, dependencyType, getResolve }, callback) => {
if (!request || request.startsWith('node:')) {
return callback();
}

const doExternal = () => {
callback(
undefined,
`${dependencyType === 'commonjs' ? 'commonjs' : 'module-import'} ${request}`,
);
};
if (/node_modules/.test(request)) {
return doExternal();
}

const resolver = getResolve?.();

if (!resolver) {
return callback();
}

resolver(context!, request!, (err, resolvePath) => {
if (err) {
// ignore resolve error
return callback();
}

if (resolvePath && /node_modules/.test(resolvePath!)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could put /node_modules/.test logic into doExternal to convergence the logic.

return doExternal();
}
return callback();
});
};

class TestFileWatchPlugin {
private contextToWatch: string | null = null;

Expand Down Expand Up @@ -49,6 +90,8 @@ export const prepareRsbuild = async (
setupFiles: Record<string, string>,
): Promise<RsbuildInstance> => {
RsbuildLogger.level = isDebug() ? 'verbose' : 'error';
// TODO: find a better way to test outputs
const writeToDisk = process.env.DEBUG_RSTEST_OUTPUTS === 'true';

const rsbuildInstance = await createRsbuild({
rsbuildConfig: {
Expand All @@ -60,20 +103,25 @@ export const prepareRsbuild = async (
environments: {
[name]: {
dev: {
writeToDisk: false,
writeToDisk,
},
output: {
sourceMap: {
js: 'source-map',
},
externals: {
'@rstest/core': 'global @rstest/core',
},
externals: [
{
'@rstest/core': 'global @rstest/core',
},
autoExternalNodeModules,
],
target: 'node',
},
tools: {
rspack: (config) => {
config.output ??= {};
config.output.iife = false;
config.externalsPresets = { node: true };
config.output.devtoolModuleFilenameTemplate =
'[absolute-resource-path]';

Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/pool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ export const runInPool = async ({
isolate,
maxWorkers,
minWorkers,
execArgv: [...(poolOptions?.execArgv ?? []), ...execArgv],
execArgv: [
...(poolOptions?.execArgv ?? []),
...execArgv,
'--experimental-vm-modules',
'--experimental-import-meta-resolve',
],
env: {
NODE_ENV: 'test',
// enable diff color by default
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/runtime/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const runInPool = async ({
for (const { filePath, originPath } of setupEntries) {
const setupCodeContent = assetFiles[filePath]!;

loadModule({
await loadModule({
codeContent: setupCodeContent,
distPath: filePath,
originPath: originPath,
Expand All @@ -73,7 +73,7 @@ const runInPool = async ({
});
}

loadModule({
await loadModule({
codeContent,
distPath: filePath,
originPath,
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/runtime/worker/loadModule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createRequire as createNativeRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
import vm from 'node:vm';
import path from 'pathe';
import { logger } from '../../utils/logger';
Expand Down Expand Up @@ -87,6 +88,20 @@ export const loadModule = ({
filename: distPath,
lineOffset: 0,
columnOffset: -codeDefinition.length,
importModuleDynamically: async (
specifier,
_referencer,
importAttributes,
) => {
const dependencyAsset = import.meta.resolve(
specifier,
pathToFileURL(originPath),
);

// @ts-expect-error
const res = await import(dependencyAsset, importAttributes);
return res;
},
});
fn(...Object.values(context));

Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions tests/externals/fixtures/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { expect, it } from '@rstest/core';
import stripAnsi from 'strip-ansi';

it('should load esm correctly', () => {
expect(stripAnsi('\u001B[4mUnicorn\u001B[0m')).toBe('Unicorn');
});

it('should load esm dynamic correctly', async () => {
const { default: stripAnsi } = await import('strip-ansi');
expect(stripAnsi('\u001B[4mUnicorn\u001B[0m')).toBe('Unicorn');
});

it('should load cjs with require correctly', () => {
const picocolors = require('picocolors');
expect(picocolors.green).toBeDefined();
});
34 changes: 34 additions & 0 deletions tests/externals/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import fs from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from '@rstest/core';
import { runRstestCli } from '../scripts/';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

describe('test externals', () => {
it('should external node_modules by default', async () => {
process.env.DEBUG_RSTEST_OUTPUTS = 'true';
const { cli } = await runRstestCli({
command: 'rstest',
args: ['run', './fixtures/index.test.ts'],
options: {
nodeOptions: {
cwd: __dirname,
},
},
});

await cli.exec;
expect(cli.exec.process?.exitCode).toBe(0);

const outputPath = join(__dirname, 'dist/fixtures/index.test.ts.js');

expect(fs.existsSync(outputPath)).toBeTruthy();
const content = fs.readFileSync(outputPath, 'utf-8');

expect(content).toContain('require("picocolors")');
expect(content).toContain('import("strip-ansi")');
});
});
10 changes: 10 additions & 0 deletions tests/externals/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"private": true,
"name": "@rstest/tests-externals",
"version": "1.0.0",
"devDependencies": {
"@rstest/core": "workspace:*",
"picocolors": "^1.1.1",
"strip-ansi": "^7.1.0"
}
}
Loading