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

feat: support autoExternal for dts files #80

Merged
merged 6 commits into from
Aug 12, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/cases/auto-external/default/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "auto-external-default-test",
"dependencies": {
"ora": "8.0.1",
"react": "^18.3.1"
}
}
15 changes: 13 additions & 2 deletions e2e/cases/auto-external/default/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@ import { generateBundleCjsConfig, generateBundleEsmConfig } from '@e2e/helper';
import { defineConfig } from '@rslib/core';

export default defineConfig({
lib: [generateBundleEsmConfig(__dirname), generateBundleCjsConfig(__dirname)],
lib: [
generateBundleEsmConfig(__dirname, {
dts: {
bundle: true,
},
}),
generateBundleCjsConfig(__dirname, {
dts: {
bundle: true,
},
}),
],
source: {
entry: {
main: '../__fixtures__/src/index.ts',
main: './src/index.ts',
},
},
});
7 changes: 7 additions & 0 deletions e2e/cases/auto-external/default/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { oraPromise } from 'ora';
import React from 'react';

export type { oraPromise };
export const foo = () => {
return React.version;
};
8 changes: 8 additions & 0 deletions e2e/cases/auto-external/default/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
1 change: 1 addition & 0 deletions e2e/cases/auto-external/false/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "auto-external-false-test",
"dependencies": {
"ora": "8.0.1",
"react": "^18.3.1"
}
}
18 changes: 11 additions & 7 deletions e2e/cases/auto-external/false/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@ import { defineConfig } from '@rslib/core';

export default defineConfig({
lib: [
{
...generateBundleEsmConfig(__dirname),
generateBundleEsmConfig(__dirname, {
autoExternal: false,
},
{
...generateBundleCjsConfig(__dirname),
dts: {
bundle: true,
},
}),
generateBundleCjsConfig(__dirname, {
autoExternal: false,
},
dts: {
bundle: true,
},
}),
],
source: {
entry: {
main: '../__fixtures__/src/index.ts',
main: './src/index.ts',
},
},
});
7 changes: 7 additions & 0 deletions e2e/cases/auto-external/false/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { oraPromise } from 'ora';
import React from 'react';

export type { oraPromise };
export const foo = () => {
return React.version;
};
8 changes: 8 additions & 0 deletions e2e/cases/auto-external/false/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
21 changes: 15 additions & 6 deletions e2e/cases/auto-external/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import { expect, test } from 'vitest';

test('auto external default should works', async () => {
const fixturePath = join(__dirname, 'default');
const { entries } = await buildAndGetResults(fixturePath);
const { js, dts } = await buildAndGetResults(fixturePath, 'all');

expect(entries.esm).toContain(
expect(js.entries.esm).toContain(
'import * as __WEBPACK_EXTERNAL_MODULE_react__ from "react"',
);

expect(entries.cjs).toContain(
expect(js!.entries.cjs).toContain(
'var external_react_namespaceObject = require("react");',
);

// dts should externalized
expect(dts.entries.esm).toContain("import type { oraPromise } from 'ora';");
expect(dts.entries.cjs).toContain("import type { oraPromise } from 'ora';");
});

test('auto external sub path should works', async () => {
Expand All @@ -36,15 +40,20 @@ test('auto external sub path should works', async () => {

test('auto external false should works', async () => {
const fixturePath = join(__dirname, 'false');
const { entries } = await buildAndGetResults(fixturePath);
const { js, dts } = await buildAndGetResults(fixturePath, 'all');

expect(entries.esm).not.toContain(
expect(js.entries.esm).not.toContain(
'import * as __WEBPACK_EXTERNAL_MODULE_react__ from "react"',
);

expect(entries.cjs).not.toContain(
expect(js.entries.cjs).not.toContain(
'var external_react_namespaceObject = require("react");',
);

// dts should bundled
expect(dts.entries.esm).toContain('export declare function oraPromise');

expect(dts.entries.cjs).toContain('export declare function oraPromise');
});

test('externals should overrides auto external', async () => {
Expand Down
49 changes: 43 additions & 6 deletions e2e/scripts/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,24 +102,61 @@ export async function getResults(
};
}

export const buildAndGetResults = async (
fixturePath: string,
type: 'js' | 'dts' = 'js',
): Promise<{
type BuildResult = {
contents: Record<string, Record<string, string>>;
files: Record<string, string[]>;
entries: Record<string, string>;
entryFiles: Record<string, string>;
rspackConfig: InspectConfigResult['origin']['bundlerConfigs'];
rsbuildConfig: InspectConfigResult['origin']['rsbuildConfig'];
isSuccess: boolean;
}> => {
};

export async function buildAndGetResults(
fixturePath: string,
type: 'all',
): Promise<{
js: BuildResult;
dts: BuildResult;
}>;
export async function buildAndGetResults(
fixturePath: string,
type?: 'js' | 'dts',
): Promise<BuildResult>;
export async function buildAndGetResults(
fixturePath: string,
type: 'js' | 'dts' | 'all' = 'js',
) {
const rslibConfig = await loadConfig(join(fixturePath, 'rslib.config.ts'));
process.chdir(fixturePath);
const rsbuildInstance = await build(rslibConfig);
const {
origin: { bundlerConfigs, rsbuildConfig },
} = await rsbuildInstance.inspectConfig({ verbose: true });
if (type === 'all') {
const jsResults = await getResults(rslibConfig, fixturePath, 'js');
const dtsResults = await getResults(rslibConfig, fixturePath, 'dts');
return {
js: {
contents: jsResults.contents,
files: jsResults.files,
entries: jsResults.entries,
entryFiles: jsResults.entryFiles,
rspackConfig: bundlerConfigs,
rsbuildConfig: rsbuildConfig,
isSuccess: Boolean(rsbuildInstance),
},
dts: {
contents: dtsResults.contents,
files: dtsResults.files,
entries: dtsResults.entries,
entryFiles: dtsResults.entryFiles,
rspackConfig: bundlerConfigs,
rsbuildConfig: rsbuildConfig,
isSuccess: Boolean(rsbuildInstance),
},
};
}

const results = await getResults(rslibConfig, fixturePath, type);
return {
Expand All @@ -131,4 +168,4 @@ export const buildAndGetResults = async (
rsbuildConfig: rsbuildConfig,
isSuccess: Boolean(rsbuildInstance),
};
};
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"test:artifact": "vitest run --project artifact",
"test:artifact:watch": "vitest --project artifact",
"test:e2e": "cd e2e && pnpm run test",
"test:unit": "vitest run --project unit",
"test:unit:watch": "vitest --project unit",
"test:unit": "vitest run --project unit*",
"test:unit:watch": "vitest --project unit*",
"watch": "pnpm build --watch"
},
"simple-git-hooks": {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ const composeDtsConfig = async (
libConfig: LibConfig,
dtsExtension: string,
): Promise<RsbuildConfig> => {
const { dts, bundle, output } = libConfig;
const { dts, bundle, output, autoExternal } = libConfig;

if (dts === false || dts === undefined) return {};

Expand All @@ -408,6 +408,7 @@ const composeDtsConfig = async (
distPath: dts?.distPath ?? output?.distPath?.root ?? './dist',
abortOnError: dts?.abortOnError ?? true,
dtsExtension,
autoExternal,
}),
],
};
Expand Down
5 changes: 3 additions & 2 deletions packages/plugin-dts/src/apiExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type BundleOptions = {
dtsExtension: string;
dtsEntry: DtsEntry;
tsconfigPath?: string;
bundledPackages?: string[];
};

export async function bundleDts(options: BundleOptions): Promise<void> {
Expand All @@ -29,6 +30,7 @@ export async function bundleDts(options: BundleOptions): Promise<void> {
path: 'index.d.ts',
},
tsconfigPath = 'tsconfig.json',
bundledPackages = [],
} = options;
try {
const start = Date.now();
Expand All @@ -40,8 +42,7 @@ export async function bundleDts(options: BundleOptions): Promise<void> {
const mainEntryPointFilePath = dtsEntry.path!;
const internalConfig = {
mainEntryPointFilePath,
// TODO: use !externals
// bundledPackages: [],
bundledPackages,
dtsRollup: {
enabled: true,
untrimmedFilePath,
Expand Down
97 changes: 97 additions & 0 deletions packages/plugin-dts/src/dts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'node:fs';
import { basename, dirname, join, relative } from 'node:path';
import { logger } from '@rsbuild/core';
import color from 'picocolors';
Expand All @@ -6,6 +7,95 @@ import * as ts from 'typescript';
import { emitDts } from './tsc';
import { ensureTempDeclarationDir, loadTsconfig } from './utils';

const isObject = (obj: unknown): obj is Record<string, any> =>
Object.prototype.toString.call(obj) === '[object Object]';

// use !externals
export const calcBundledPackages = (options: {
autoExternal: DtsGenOptions['autoExternal'];
cwd: string;
userExternals?: DtsGenOptions['userExternals'];
}): string[] => {
const { autoExternal, cwd, userExternals } = options;

let pkgJson: {
dependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};

try {
const content = fs.readFileSync(join(cwd, 'package.json'), 'utf-8');
pkgJson = JSON.parse(content);
} catch (err) {
logger.warn(
'The type of third-party packages will not be bundled due to read package.json failed',
);
return [];
}

const externalOptions = autoExternal
? {
dependencies: true,
peerDependencies: true,
devDependencies: false,
...(autoExternal === true ? {} : autoExternal),
}
: {
dependencies: false,
peerDependencies: false,
devDependencies: false,
};

// User externals should not bundled
// Only handle the case where the externals type is string / (string | RegExp)[] / plain object, function type is too complex.
const getUserExternalsKeys = (
value: typeof userExternals,
): (string | RegExp)[] => {
if (!value) {
return [];
}

if (typeof value === 'string' || value instanceof RegExp) {
return [value];
}

if (Array.isArray(value)) {
return value.flatMap((v) => getUserExternalsKeys(v));
}

if (isObject(userExternals)) {
return Object.keys(userExternals);
}
return [];
};

const externals: (string | RegExp)[] = getUserExternalsKeys(userExternals);

const allDeps: string[] = [];

for (const type of [
'dependencies',
'peerDependencies',
'devDependencies',
] as const) {
const deps = pkgJson[type] && Object.keys(pkgJson[type]);
if (deps) {
if (externalOptions[type]) {
externals.push(...deps);
}
allDeps.push(...deps);
}
}

const bundledPackages = allDeps.filter(
(d) =>
!externals.some((e) => (typeof e === 'string' ? d === e : e.test(d))),
);

return Array.from(new Set(bundledPackages));
};

export async function generateDts(data: DtsGenOptions): Promise<void> {
const {
bundle,
Expand All @@ -16,6 +106,8 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
cwd,
isWatch,
dtsExtension = '.d.ts',
autoExternal = true,
userExternals,
} = data;
logger.start(`Generating DTS... ${color.gray(`(${name})`)}`);
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, tsconfigPath);
Expand Down Expand Up @@ -66,6 +158,11 @@ export async function generateDts(data: DtsGenOptions): Promise<void> {
},
tsconfigPath,
dtsExtension,
bundledPackages: calcBundledPackages({
autoExternal,
cwd,
userExternals,
}),
});
}
};
Expand Down
Loading
Loading