Skip to content

Commit

Permalink
feat(bundle): support autoExternal configuration (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
9aoy authored Aug 9, 2024
1 parent 3c0501c commit 5e5aeff
Show file tree
Hide file tree
Showing 28 changed files with 525 additions and 101 deletions.
6 changes: 6 additions & 0 deletions e2e/cases/auto-external/default/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@e2e/auto-external-default",
"dependencies": {
"react": "^18.3.1"
}
}
11 changes: 11 additions & 0 deletions e2e/cases/auto-external/default/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { generateBundleCjsConfig, generateBundleEsmConfig } from '@e2e/helper';
import { defineConfig } from '@rslib/core';

export default defineConfig({
lib: [generateBundleEsmConfig(__dirname), generateBundleCjsConfig(__dirname)],
source: {
entry: {
main: '../fixtures/src/index.ts',
},
},
});
6 changes: 6 additions & 0 deletions e2e/cases/auto-external/external-sub-path/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@e2e/auto-external-with-sub-path",
"dependencies": {
"react": "^18.3.1"
}
}
11 changes: 11 additions & 0 deletions e2e/cases/auto-external/external-sub-path/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { generateBundleCjsConfig, generateBundleEsmConfig } from '@e2e/helper';
import { defineConfig } from '@rslib/core';

export default defineConfig({
lib: [generateBundleEsmConfig(__dirname), generateBundleCjsConfig(__dirname)],
source: {
entry: {
main: './src/index.ts',
},
},
});
6 changes: 6 additions & 0 deletions e2e/cases/auto-external/external-sub-path/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import ReactJsx from 'react/jsx-runtime';

export const foo = () => {
return [React.version, ReactJsx.jsx];
};
6 changes: 6 additions & 0 deletions e2e/cases/auto-external/false/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@e2e/auto-external-false",
"dependencies": {
"react": "^18.3.1"
}
}
20 changes: 20 additions & 0 deletions e2e/cases/auto-external/false/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { generateBundleCjsConfig, generateBundleEsmConfig } from '@e2e/helper';
import { defineConfig } from '@rslib/core';

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

export const foo = () => {
return React.version;
};
61 changes: 61 additions & 0 deletions e2e/cases/auto-external/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { join } from 'node:path';
import { buildAndGetResults } from '@e2e/helper';
import { expect, test } from 'vitest';

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

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

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

test('auto external sub path should works', async () => {
const fixturePath = join(__dirname, 'external-sub-path');
const { entries } = await buildAndGetResults(fixturePath);

expect(entries.esm).toContain(
'import * as __WEBPACK_EXTERNAL_MODULE_react__ from "react"',
);
expect(entries.esm).toContain(
'import * as __WEBPACK_EXTERNAL_MODULE_react_jsx_runtime__ from "react/jsx-runtime"',
);

expect(entries.cjs).toContain(
'var external_react_namespaceObject = require("react");',
);
expect(entries.cjs).toContain(
'var jsx_runtime_namespaceObject = require("react/jsx-runtime");',
);
});

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

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

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

test('externals should overrides auto external', async () => {
const fixturePath = join(__dirname, 'with-externals');
const { entries } = await buildAndGetResults(fixturePath);

expect(entries.esm).toContain(
'import * as __WEBPACK_EXTERNAL_MODULE_react1__ from "react1"',
);

expect(entries.cjs).toContain(
'var external_react1_namespaceObject = require("react1");',
);
});
6 changes: 6 additions & 0 deletions e2e/cases/auto-external/with-externals/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@e2e/auto-external-with-externals",
"dependencies": {
"react": "^18.3.1"
}
}
16 changes: 16 additions & 0 deletions e2e/cases/auto-external/with-externals/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { generateBundleCjsConfig, generateBundleEsmConfig } from '@e2e/helper';
import { defineConfig } from '@rslib/core';

export default defineConfig({
lib: [generateBundleEsmConfig(__dirname), generateBundleCjsConfig(__dirname)],
output: {
externals: {
react: 'react1',
},
},
source: {
entry: {
main: '../fixtures/src/index.ts',
},
},
});
3 changes: 0 additions & 3 deletions examples/express-plugin/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ export default defineConfig({
},
},
output: {
externals: {
express: 'express',
},
target: 'node',
},
});
6 changes: 0 additions & 6 deletions examples/react-component/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,5 @@ export default defineConfig({
main: './src/index.tsx',
},
},
output: {
externals: {
react: 'react',
'react/jsx-runtime': 'react/jsx-runtime',
},
},
plugins: [pluginReact()],
});
5 changes: 5 additions & 0 deletions packages/core/__mocks__/rslog.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
logger: {
warn: () => {},
},
};
87 changes: 79 additions & 8 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,24 @@ import {
import glob from 'fast-glob';
import { DEFAULT_CONFIG_NAME, DEFAULT_EXTENSIONS } from './constant';
import type {
AutoExternal,
Format,
LibConfig,
PkgJson,
RslibConfig,
RslibConfigAsyncFn,
RslibConfigExport,
RslibConfigSyncFn,
Syntax,
} from './types/config';
} from './types';
import { getDefaultExtension } from './utils/extension';
import { calcLongestCommonPath } from './utils/helper';
import { color } from './utils/helper';
import { nodeBuiltInModules } from './utils/helper';
import {
calcLongestCommonPath,
color,
isObject,
nodeBuiltInModules,
readPackageJson,
} from './utils/helper';
import { logger } from './utils/logger';
import { transformSyntaxToBrowserslist } from './utils/syntax';

Expand Down Expand Up @@ -77,6 +83,63 @@ export async function loadConfig(
return content as RslibConfig;
}

export const composeAutoExternalConfig = (options: {
autoExternal: AutoExternal;
pkgJson?: PkgJson;
userExternals?: NonNullable<RsbuildConfig['output']>['externals'];
}): RsbuildConfig => {
const { autoExternal, pkgJson, userExternals } = options;

if (!autoExternal) {
return {};
}

if (!pkgJson) {
logger.warn(
'autoExternal configuration will not be applied due to read package.json failed',
);
return {};
}

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

// User externals configuration has higher priority than autoExternal
// eg: autoExternal: ['react'], user: output: { externals: { react: 'react-1' } }
// Only handle the case where the externals type is object, string / string[] does not need to be processed, other types are too complex.
const userExternalKeys =
userExternals && isObject(userExternals) ? Object.keys(userExternals) : [];

const externals = (
['dependencies', 'peerDependencies', 'devDependencies'] as const
)
.reduce<string[]>((prev, type) => {
if (externalOptions[type]) {
return pkgJson[type] ? prev.concat(Object.keys(pkgJson[type]!)) : prev;
}
return prev;
}, [])
.filter((name) => !userExternalKeys.includes(name));

const uniqueExternals = Array.from(new Set(externals));

return externals.length
? {
output: {
externals: [
// Exclude dependencies, e.g. `react`, `react/jsx-runtime`
...uniqueExternals.map((dep) => new RegExp(`^${dep}($|\\/|\\\\)`)),
...uniqueExternals,
],
},
}
: {};
};

export async function createInternalRsbuildConfig(): Promise<RsbuildConfig> {
return defineRsbuildConfig({
dev: {
Expand Down Expand Up @@ -166,15 +229,15 @@ const composeFormatConfig = (format: Format): RsbuildConfig => {

const composeAutoExtensionConfig = (
format: Format,
root: string,
autoExtension: boolean,
pkgJson?: PkgJson,
): {
config: RsbuildConfig;
dtsExtension: string;
} => {
const { jsExtension, dtsExtension } = getDefaultExtension({
format,
root,
pkgJson,
autoExtension,
});

Expand Down Expand Up @@ -394,17 +457,24 @@ async function composeLibRsbuildConfig(
configPath: string,
) {
const config = mergeRsbuildConfig<LibConfig>(rsbuildConfig, libConfig);
const rootPath = dirname(configPath);
const pkgJson = readPackageJson(rootPath);

const { format, autoExtension = true } = config;
const { format, autoExtension = true, autoExternal = true } = config;
const formatConfig = composeFormatConfig(format!);
const { config: autoExtensionConfig, dtsExtension } =
composeAutoExtensionConfig(format!, dirname(configPath), autoExtension);
composeAutoExtensionConfig(format!, autoExtension, pkgJson);
const bundleConfig = composeBundleConfig(config.bundle);
const targetConfig = composeTargetConfig(config.output?.target);
const syntaxConfig = composeSyntaxConfig(
config.output?.syntax,
config.output?.target,
);
const autoExternalConfig = composeAutoExternalConfig({
autoExternal,
pkgJson,
userExternals: rsbuildConfig.output?.externals,
});
const entryConfig = await composeEntryConfig(
config.source?.entry,
config.bundle,
Expand All @@ -415,6 +485,7 @@ async function composeLibRsbuildConfig(
return mergeRsbuildConfig(
formatConfig,
autoExtensionConfig,
autoExternalConfig,
syntaxConfig,
bundleConfig,
targetConfig,
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/types/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,19 @@ export type Dts =
}
| false;

export type AutoExternal =
| boolean
| {
dependencies?: boolean;
devDependencies?: boolean;
peerDependencies?: boolean;
};

export interface LibConfig extends RsbuildConfig {
bundle?: boolean;
format?: Format;
autoExtension?: boolean;
autoExternal?: AutoExternal;
output?: RsbuildConfig['output'] & {
/** Support esX and browserslist query */
syntax?: Syntax;
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './config';
export * from './utils';
6 changes: 6 additions & 0 deletions packages/core/src/types/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type PkgJson = {
dependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
[key: string]: unknown;
};
Loading

0 comments on commit 5e5aeff

Please sign in to comment.