Skip to content
Open
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
40 changes: 27 additions & 13 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1448,14 +1448,15 @@ const composeBundlelessExternalConfig = (
return;
}
const { issuer } = contextInfo;
const originExtension = extname(request);

if (!resolver) {
resolver = getResolve() as RspackResolver;
}

async function redirectPath(
request: string,
): Promise<string | undefined> {
): Promise<{ path?: string; isResolved: boolean }> {
try {
let resolvedRequest = request;
// use resolver to resolve the request
Expand Down Expand Up @@ -1485,19 +1486,29 @@ const composeBundlelessExternalConfig = (
) {
resolvedRequest = `./${resolvedRequest}`;
}
return resolvedRequest;
return {
path: resolvedRequest,
isResolved: true,
};
}
// NOTE: If request is a phantom dependency, which means it can be resolved but not specified in dependencies or peerDependencies in package.json, the output will be incorrect to use when the package is published
// return the original request instead of the resolved request
return undefined;
return {
path: undefined,
isResolved: true,
};
} catch (_e) {
// catch error when request can not be resolved by resolver
// e.g. A react component library importing and using 'react' but while not defining
// it in devDependencies and peerDependencies. Preserve 'react' as-is if so.
logger.debug(
`Failed to resolve module ${color.green(`"${request}"`)} from ${color.green(issuer)}. If it's an npm package, consider adding it to dependencies or peerDependencies in package.json to make it externalized.`,
);
return request;
// return origin request instead of undefined for cssExternalHandler
return {
path: request,
isResolved: false,
};
}
}

Expand All @@ -1506,7 +1517,8 @@ const composeBundlelessExternalConfig = (
if (issuer) {
let resolvedRequest: string = request;

const redirectedPath = await redirectPath(resolvedRequest);
const { path: redirectedPath, isResolved } =
await redirectPath(resolvedRequest);
const cssExternal = await cssExternalHandler(
resolvedRequest,
callback,
Expand Down Expand Up @@ -1537,18 +1549,20 @@ const composeBundlelessExternalConfig = (
// If data.request already have an extension, we replace it with new extension
// This may result in a change in semantics,
// user should use copy to keep origin file or use another separate entry to deal this
if (resolvedRequest.startsWith('.')) {
if (resolvedRequest.startsWith('.') && isResolved) {
const ext = extname(resolvedRequest);

if (ext) {
// 1. js files hit JS_EXTENSIONS_PATTERN, ./foo.ts -> ./foo.mjs
if (JS_EXTENSIONS_PATTERN.test(resolvedRequest)) {
if (jsRedirectExtension) {
resolvedRequest = resolvedRequest.replace(
/\.[^.]+$/,
jsExtension,
);
}
resolvedRequest = resolvedRequest.replace(
/\.[^.]+$/,
jsRedirectExtension
? jsExtension
: JS_EXTENSIONS_PATTERN.test(originExtension)
? originExtension
: '',
);
} else {
// 2. asset files, does not match jsExtensionsPattern, eg: ./foo.png -> ./foo.mjs
// non-js && non-css files
Expand All @@ -1569,7 +1583,7 @@ const composeBundlelessExternalConfig = (
// If the import path refers to a directory,
// it most likely actually refers to a `index.*` file due to Node's module resolution.
// When redirect.js.path is set to false, index should still be added before adding extension.
// When redirect.js.path is true, the resolver directly generate correct resolvedRequest with index appended.
// When redirect.js.path is set to true, the resolver directly generates correct resolvedRequest with index appended.
if (
!jsRedirectPath &&
(await isDirectory(
Expand Down
8 changes: 4 additions & 4 deletions packages/plugin-dts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ const defaultRedirect = {
};
```

Controls the redirect of the import paths of output TypeScript declaration files.
Controls the redirect of the import paths of TypeScript declaration output files.

```js
pluginDts({
Expand Down Expand Up @@ -269,9 +269,9 @@ import { foo } from '../foo'; // expected output './dist/utils/index.d.ts'
- **Type:** `boolean`
- **Default:** `false`

Whether to automatically redirect the file extension to import paths based on the TypeScript declaration output files.
Whether to automatically redirect the file extension of import paths based on the TypeScript declaration output files.

- When set to `true`, the import paths in declaration files will be redirected to the corresponding JavaScript extension which can be resolved to corresponding declaration file. The extension of the declaration output file is related to the `dtsExtension` configuration.
- When set to `true`, the file extension of the import path in the declaration file will be automatically completed or replaced with the corresponding JavaScript file extension that can be resolved to the corresponding declaration file. The extension of the declaration output file is related to the `dtsExtension` configuration.

```ts
// `dtsExtension` is set to `.d.mts`
Expand All @@ -282,7 +282,7 @@ import { foo } from './foo.ts'; // source code of './src/bar.ts' ↓
import { foo } from './foo.mjs'; // expected output of './dist/bar.d.mts'
```

- When set to `false`, the file extension will remain unchanged from the original import path in the rewritten import path of the output file (regardless of whether it is specified or specified as any value).
- When set to `false`, import paths will retain their original file extensions.

### tsgo

Expand Down
60 changes: 30 additions & 30 deletions tests/integration/preserve-jsx/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ __webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
default: ()=>Root
});
const external_App1_cjs_namespaceObject = require("./App1.cjs");
const external_App2_cjs_namespaceObject = require("./App2.cjs");
const external_App1_namespaceObject = require("./App1");
const external_App2_namespaceObject = require("./App2");
const DynamicComponent = ()=>{
const Component = Math.random() > 0.5 ? external_App1_cjs_namespaceObject.App1A : external_App1_cjs_namespaceObject.App1C;
return import("./App1.cjs").then((mod)=>{
const Dynamic = mod[Component === external_App1_cjs_namespaceObject.App1A ? 'App1A' : 'App1C'];
const Component = Math.random() > 0.5 ? external_App1_namespaceObject.App1A : external_App1_namespaceObject.App1C;
return import("./App1").then((mod)=>{
const Dynamic = mod[Component === external_App1_namespaceObject.App1A ? 'App1A' : 'App1C'];
return <Dynamic/>;
});
};
Expand All @@ -47,51 +47,51 @@ function Root() {
return <>
<React.Fragment>
<>
<external_App1_cjs_namespaceObject.App1A/>
<external_App1_namespaceObject.App1A/>
<React.Suspense fallback={<div>Loading...</div>}>
<DynamicComponent/>
</React.Suspense>
</>
</React.Fragment>
<external_App1_cjs_namespaceObject.App1C>
{x ? <external_App1_cjs_namespaceObject.App1A>
<external_App1_cjs_namespaceObject.App1B>
<external_App2_cjs_namespaceObject.App2 props={external_App2_cjs_namespaceObject.app2Props}>
<external_App1_cjs_namespaceObject.App1C props={external_App1_cjs_namespaceObject.app1cProps}/>
</external_App2_cjs_namespaceObject.App2>
</external_App1_cjs_namespaceObject.App1B>
</external_App1_cjs_namespaceObject.App1A> : <external_App1_cjs_namespaceObject.App1B/>}
</external_App1_cjs_namespaceObject.App1C>
<external_App1_cjs_namespaceObject.App1C className="c"/>
<external_App1_cjs_namespaceObject.App1C className="app1c"></external_App1_cjs_namespaceObject.App1C>
<external_App1_cjs_namespaceObject.App1B/>
<external_App1_namespaceObject.App1C>
{x ? <external_App1_namespaceObject.App1A>
<external_App1_namespaceObject.App1B>
<external_App2_namespaceObject.App2 props={external_App2_namespaceObject.app2Props}>
<external_App1_namespaceObject.App1C props={external_App1_namespaceObject.app1cProps}/>
</external_App2_namespaceObject.App2>
</external_App1_namespaceObject.App1B>
</external_App1_namespaceObject.App1A> : <external_App1_namespaceObject.App1B/>}
</external_App1_namespaceObject.App1C>
<external_App1_namespaceObject.App1C className="c"/>
<external_App1_namespaceObject.App1C className="app1c"></external_App1_namespaceObject.App1C>
<external_App1_namespaceObject.App1B/>
<NamespaceComponents.Button label="Namespace button" {...{
title: 'extra',
['data-role']: 'primary'
}} data-count={3} icon={<external_App1_cjs_namespaceObject.App1A/>} fragmentContent={<>
}} data-count={3} icon={<external_App1_namespaceObject.App1A/>} fragmentContent={<>
<span>Nested</span>
<span>Fragment</span>
</>}/>
<div className="wrapper">
{[
<section key="namespace-import" data-index="0">
<external_App1_cjs_namespaceObject.App data-dynamic="registry" data-item="one"/>
<external_App1_namespaceObject.App data-dynamic="registry" data-item="one"/>
<foo:bar value="namespaced"/>
<svg:path d="M0,0 L10,10" xlink:href="#one"/>
<span>{'item-one'.toUpperCase()}</span>
{}
</section>,
<section key="legacy-widget" data-index="1">
<external_App2_cjs_namespaceObject.App2 data-dynamic="registry" data-item="two" {...external_App2_cjs_namespaceObject.app2Props}/>
<external_App2_namespaceObject.App2 data-dynamic="registry" data-item="two" {...external_App2_namespaceObject.app2Props}/>
app2
<external_App2_cjs_namespaceObject.App2/>
<external_App2_namespaceObject.App2/>
<foo:bar value="namespaced-two"/>
<svg:path d="M10,10 L20,20" xlink:href="#two"/>
<external_App1_cjs_namespaceObject.App1A/>
<external_App1_namespaceObject.App1A/>
{}
</section>,
<section key="external-app" data-index="2">
<external_App1_cjs_namespaceObject.App1A data-dynamic="registry" data-item="fallback"/>
<external_App1_namespaceObject.App1A data-dynamic="registry" data-item="fallback"/>
<foo:bar value="namespaced-three"/>
<svg:path d="M20,20 L30,30" xlink:href="#three"/>
{(()=><NamespaceComponents.Button label="Inline child"/>)()}
Expand Down Expand Up @@ -120,11 +120,11 @@ Object.defineProperty(exports, '__esModule', {
`;

exports[`JSX syntax should be preserved 2`] = `
"import { App, App1A, App1B, App1C, app1cProps } from "./App1.js";
import { App2, app2Props } from "./App2.js";
"import { App, App1A, App1B, App1C, app1cProps } from "./App1";
import { App2, app2Props } from "./App2";
const DynamicComponent = ()=>{
const Component = Math.random() > 0.5 ? App1A : App1C;
return import("./App1.js").then((mod)=>{
return import("./App1").then((mod)=>{
const Dynamic = mod[Component === App1A ? 'App1A' : 'App1C'];
return <Dynamic/>;
});
Expand Down Expand Up @@ -205,11 +205,11 @@ export { Root as default };
`;

exports[`JSX syntax should be preserved 3`] = `
"import { App, App1A, App1B, App1C, app1cProps } from "./App1.jsx";
import { App2, app2Props } from "./App2.jsx";
"import { App, App1A, App1B, App1C, app1cProps } from "./App1";
import { App2, app2Props } from "./App2";
const DynamicComponent = ()=>{
const Component = Math.random() > 0.5 ? App1A : App1C;
return import("./App1.jsx").then((mod)=>{
return import("./App1").then((mod)=>{
const Dynamic = mod[Component === App1A ? 'App1A' : 'App1C'];
return <Dynamic/>;
});
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/redirect/js-not-resolve/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import lodash from 'lodash';
// can be resolved but not specified -- phantom dependency
import prettier from 'prettier';
import bar from './bar.js';
import baz from './baz.cjs';
import foo from './foo';
import foo_node from './foo.node';
import foo_node_js from './foo.node.js';

export default lodash.toUpper(foo + bar + typeof prettier.version);
export default lodash.toUpper(
foo + foo_node + foo_node_js + bar + baz + typeof prettier.version,
);
36 changes: 31 additions & 5 deletions tests/integration/redirect/js.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ test('redirect.js default', async () => {
import { baz } from "./baz.js";
export * from "./.hidden.js";
export * from "./.hidden-folder/index.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./foo.js";
export * from "./foo.js";
const src = lodash.toUpper(lodash_merge(foo) + bar + foo + bar + baz + typeof prettier.version);
export { src as default };
"
Expand Down Expand Up @@ -57,6 +62,11 @@ test('redirect.js.path false', async () => {
import { foo as external_foo_js_foo } from "./foo.js";
export * from "./.hidden.js";
export * from "./.hidden-folder/index.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./foo.js";
export * from "./foo.js";
const src = lodash.toUpper(lodash_merge(external_foo_js_foo) + index_js_bar + foo + bar + baz + typeof prettier.version);
export { src as default };
"
Expand Down Expand Up @@ -84,6 +94,11 @@ test('redirect.js.path with user override externals', async () => {
import { foo as external_foo_js_foo } from "./foo.js";
export * from "./.hidden.js";
export * from "./.hidden-folder/index.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./foo.js";
export * from "./foo.js";
const src = lodash.toUpper(lodash_merge(external_foo_js_foo) + index_js_bar + foo + bar + baz + typeof prettier.version);
export { src as default };
"
Expand Down Expand Up @@ -119,6 +134,11 @@ test('redirect.js.path with user override alias', async () => {
import { foo as external_foo_js_foo } from "./foo.js";
export * from "./.hidden.js";
export * from "./.hidden-folder/index.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./bar.node.js";
export * from "./foo.js";
export * from "./foo.js";
const src = lodash.toUpper(lodash_merge(external_foo_js_foo) + index_js_bar + foo + bar + baz + typeof prettier.version);
export { src as default };
"
Expand All @@ -138,15 +158,21 @@ test('redirect.js.extension: false', async () => {
contents.esm4!,
/esm\/index\.js/,
);

expect(indexContent).toMatchInlineSnapshot(`
"import lodash from "lodash";
import lodash_merge from "lodash.merge";
import prettier from "prettier";
import { bar } from "./bar/index.ts";
import { foo } from "./foo.ts";
import { baz } from "./baz.ts";
export * from "./.hidden.ts";
export * from "./.hidden-folder/index.ts";
import { bar } from "./bar/index";
import { foo } from "./foo";
import { baz } from "./baz";
export * from "./.hidden";
export * from "./.hidden-folder/index";
export * from "./bar.node";
export * from "./bar.node.js";
export * from "./bar.node.ts";
export * from "./foo.js";
export * from "./foo.ts";
const src = lodash.toUpper(lodash_merge(foo) + bar + foo + bar + baz + typeof prettier.version);
export { src as default };
"
Expand Down
1 change: 1 addition & 0 deletions tests/integration/redirect/js/src/bar.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const bar_node = 'bar node';
8 changes: 6 additions & 2 deletions tests/integration/redirect/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// can not be resolved
import lodash from 'lodash';
// can be resolved, 3rd party packages
import lodash from 'lodash';
import merge from 'lodash.merge';
// can be resolved but not specified -- phantom dependency
import prettier from 'prettier';
Expand All @@ -14,6 +13,11 @@ import { foo } from './foo';

export * from './.hidden';
export * from './.hidden-folder';
export * from './bar.node';
export * from './bar.node.js';
export * from './bar.node.ts';
export * from './foo.js';
export * from './foo.ts';

export default lodash.toUpper(
merge(foo) + bar + foo2 + bar2 + baz + typeof prettier.version,
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/redirect/js/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"extends": "@rslib/tsconfig/base",
"compilerOptions": {
"baseUrl": "./",
"noEmit": true,
"allowImportingTsExtensions": true,
"paths": {
"@/*": ["./src/*"]
}
Expand Down
Loading
Loading