Skip to content

Commit 8b45115

Browse files
committed
fix: preserve original extension when extension redirection is disabled
1 parent a6d71dd commit 8b45115

File tree

11 files changed

+138
-77
lines changed

11 files changed

+138
-77
lines changed

packages/core/src/config.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,14 +1448,15 @@ const composeBundlelessExternalConfig = (
14481448
return;
14491449
}
14501450
const { issuer } = contextInfo;
1451+
const originExtension = extname(request);
14511452

14521453
if (!resolver) {
14531454
resolver = getResolve() as RspackResolver;
14541455
}
14551456

14561457
async function redirectPath(
14571458
request: string,
1458-
): Promise<string | undefined> {
1459+
): Promise<{ path?: string; isResolved: boolean }> {
14591460
try {
14601461
let resolvedRequest = request;
14611462
// use resolver to resolve the request
@@ -1485,19 +1486,29 @@ const composeBundlelessExternalConfig = (
14851486
) {
14861487
resolvedRequest = `./${resolvedRequest}`;
14871488
}
1488-
return resolvedRequest;
1489+
return {
1490+
path: resolvedRequest,
1491+
isResolved: true,
1492+
};
14891493
}
14901494
// 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
14911495
// return the original request instead of the resolved request
1492-
return undefined;
1496+
return {
1497+
path: undefined,
1498+
isResolved: true,
1499+
};
14931500
} catch (_e) {
14941501
// catch error when request can not be resolved by resolver
14951502
// e.g. A react component library importing and using 'react' but while not defining
14961503
// it in devDependencies and peerDependencies. Preserve 'react' as-is if so.
14971504
logger.debug(
14981505
`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.`,
14991506
);
1500-
return request;
1507+
// return origin request instead of undefined for cssExternalHandler
1508+
return {
1509+
path: request,
1510+
isResolved: false,
1511+
};
15011512
}
15021513
}
15031514

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

1509-
const redirectedPath = await redirectPath(resolvedRequest);
1520+
const { path: redirectedPath, isResolved } =
1521+
await redirectPath(resolvedRequest);
15101522
const cssExternal = await cssExternalHandler(
15111523
resolvedRequest,
15121524
callback,
@@ -1543,11 +1555,17 @@ const composeBundlelessExternalConfig = (
15431555
if (ext) {
15441556
// 1. js files hit JS_EXTENSIONS_PATTERN, ./foo.ts -> ./foo.mjs
15451557
if (JS_EXTENSIONS_PATTERN.test(resolvedRequest)) {
1546-
if (jsRedirectExtension) {
1558+
if (isResolved) {
15471559
resolvedRequest = resolvedRequest.replace(
15481560
/\.[^.]+$/,
1549-
jsExtension,
1561+
jsRedirectExtension
1562+
? jsExtension
1563+
: JS_EXTENSIONS_PATTERN.test(originExtension)
1564+
? originExtension
1565+
: '',
15501566
);
1567+
} else {
1568+
resolvedRequest = request;
15511569
}
15521570
} else {
15531571
// 2. asset files, does not match jsExtensionsPattern, eg: ./foo.png -> ./foo.mjs
@@ -1556,7 +1574,7 @@ const composeBundlelessExternalConfig = (
15561574
? redirectedPath
15571575
: request;
15581576

1559-
if (assetRedirectExtension) {
1577+
if (assetRedirectExtension && isResolved) {
15601578
resolvedRequest = resolvedRequest.replace(
15611579
/\.[^.]+$/,
15621580
jsExtension,
@@ -1565,11 +1583,11 @@ const composeBundlelessExternalConfig = (
15651583
}
15661584
} else {
15671585
// 1. js files hit JS_EXTENSIONS_PATTERN,./foo ->./foo.mjs
1568-
if (jsRedirectExtension) {
1586+
if (jsRedirectExtension && isResolved) {
15691587
// If the import path refers to a directory,
15701588
// it most likely actually refers to a `index.*` file due to Node's module resolution.
15711589
// When redirect.js.path is set to false, index should still be added before adding extension.
1572-
// When redirect.js.path is true, the resolver directly generate correct resolvedRequest with index appended.
1590+
// When redirect.js.path is set to true, the resolver directly generates correct resolvedRequest with index appended.
15731591
if (
15741592
!jsRedirectPath &&
15751593
(await isDirectory(

packages/plugin-dts/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ const defaultRedirect = {
233233
};
234234
```
235235

236-
Controls the redirect of the import paths of output TypeScript declaration files.
236+
Controls the redirect of the import paths of TypeScript declaration output files.
237237

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

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

274-
- 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.
274+
- 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.
275275

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

285-
- 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).
285+
- When set to `false`, import paths will retain their original file extensions.
286286

287287
### tsgo
288288

tests/integration/preserve-jsx/__snapshots__/index.test.ts.snap

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ __webpack_require__.r(__webpack_exports__);
2929
__webpack_require__.d(__webpack_exports__, {
3030
default: ()=>Root
3131
});
32-
const external_App1_cjs_namespaceObject = require("./App1.cjs");
33-
const external_App2_cjs_namespaceObject = require("./App2.cjs");
32+
const external_App1_namespaceObject = require("./App1");
33+
const external_App2_namespaceObject = require("./App2");
3434
const DynamicComponent = ()=>{
35-
const Component = Math.random() > 0.5 ? external_App1_cjs_namespaceObject.App1A : external_App1_cjs_namespaceObject.App1C;
36-
return import("./App1.cjs").then((mod)=>{
37-
const Dynamic = mod[Component === external_App1_cjs_namespaceObject.App1A ? 'App1A' : 'App1C'];
35+
const Component = Math.random() > 0.5 ? external_App1_namespaceObject.App1A : external_App1_namespaceObject.App1C;
36+
return import("./App1").then((mod)=>{
37+
const Dynamic = mod[Component === external_App1_namespaceObject.App1A ? 'App1A' : 'App1C'];
3838
return <Dynamic/>;
3939
});
4040
};
@@ -47,51 +47,51 @@ function Root() {
4747
return <>
4848
<React.Fragment>
4949
<>
50-
<external_App1_cjs_namespaceObject.App1A/>
50+
<external_App1_namespaceObject.App1A/>
5151
<React.Suspense fallback={<div>Loading...</div>}>
5252
<DynamicComponent/>
5353
</React.Suspense>
5454
</>
5555
</React.Fragment>
56-
<external_App1_cjs_namespaceObject.App1C>
57-
{x ? <external_App1_cjs_namespaceObject.App1A>
58-
<external_App1_cjs_namespaceObject.App1B>
59-
<external_App2_cjs_namespaceObject.App2 props={external_App2_cjs_namespaceObject.app2Props}>
60-
<external_App1_cjs_namespaceObject.App1C props={external_App1_cjs_namespaceObject.app1cProps}/>
61-
</external_App2_cjs_namespaceObject.App2>
62-
</external_App1_cjs_namespaceObject.App1B>
63-
</external_App1_cjs_namespaceObject.App1A> : <external_App1_cjs_namespaceObject.App1B/>}
64-
</external_App1_cjs_namespaceObject.App1C>
65-
<external_App1_cjs_namespaceObject.App1C className="c"/>
66-
<external_App1_cjs_namespaceObject.App1C className="app1c"></external_App1_cjs_namespaceObject.App1C>
67-
<external_App1_cjs_namespaceObject.App1B/>
56+
<external_App1_namespaceObject.App1C>
57+
{x ? <external_App1_namespaceObject.App1A>
58+
<external_App1_namespaceObject.App1B>
59+
<external_App2_namespaceObject.App2 props={external_App2_namespaceObject.app2Props}>
60+
<external_App1_namespaceObject.App1C props={external_App1_namespaceObject.app1cProps}/>
61+
</external_App2_namespaceObject.App2>
62+
</external_App1_namespaceObject.App1B>
63+
</external_App1_namespaceObject.App1A> : <external_App1_namespaceObject.App1B/>}
64+
</external_App1_namespaceObject.App1C>
65+
<external_App1_namespaceObject.App1C className="c"/>
66+
<external_App1_namespaceObject.App1C className="app1c"></external_App1_namespaceObject.App1C>
67+
<external_App1_namespaceObject.App1B/>
6868
<NamespaceComponents.Button label="Namespace button" {...{
6969
title: 'extra',
7070
['data-role']: 'primary'
71-
}} data-count={3} icon={<external_App1_cjs_namespaceObject.App1A/>} fragmentContent={<>
71+
}} data-count={3} icon={<external_App1_namespaceObject.App1A/>} fragmentContent={<>
7272
<span>Nested</span>
7373
<span>Fragment</span>
7474
</>}/>
7575
<div className="wrapper">
7676
{[
7777
<section key="namespace-import" data-index="0">
78-
<external_App1_cjs_namespaceObject.App data-dynamic="registry" data-item="one"/>
78+
<external_App1_namespaceObject.App data-dynamic="registry" data-item="one"/>
7979
<foo:bar value="namespaced"/>
8080
<svg:path d="M0,0 L10,10" xlink:href="#one"/>
8181
<span>{'item-one'.toUpperCase()}</span>
8282
{}
8383
</section>,
8484
<section key="legacy-widget" data-index="1">
85-
<external_App2_cjs_namespaceObject.App2 data-dynamic="registry" data-item="two" {...external_App2_cjs_namespaceObject.app2Props}/>
85+
<external_App2_namespaceObject.App2 data-dynamic="registry" data-item="two" {...external_App2_namespaceObject.app2Props}/>
8686
app2
87-
<external_App2_cjs_namespaceObject.App2/>
87+
<external_App2_namespaceObject.App2/>
8888
<foo:bar value="namespaced-two"/>
8989
<svg:path d="M10,10 L20,20" xlink:href="#two"/>
90-
<external_App1_cjs_namespaceObject.App1A/>
90+
<external_App1_namespaceObject.App1A/>
9191
{}
9292
</section>,
9393
<section key="external-app" data-index="2">
94-
<external_App1_cjs_namespaceObject.App1A data-dynamic="registry" data-item="fallback"/>
94+
<external_App1_namespaceObject.App1A data-dynamic="registry" data-item="fallback"/>
9595
<foo:bar value="namespaced-three"/>
9696
<svg:path d="M20,20 L30,30" xlink:href="#three"/>
9797
{(()=><NamespaceComponents.Button label="Inline child"/>)()}
@@ -120,11 +120,11 @@ Object.defineProperty(exports, '__esModule', {
120120
`;
121121
122122
exports[`JSX syntax should be preserved 2`] = `
123-
"import { App, App1A, App1B, App1C, app1cProps } from "./App1.js";
124-
import { App2, app2Props } from "./App2.js";
123+
"import { App, App1A, App1B, App1C, app1cProps } from "./App1";
124+
import { App2, app2Props } from "./App2";
125125
const DynamicComponent = ()=>{
126126
const Component = Math.random() > 0.5 ? App1A : App1C;
127-
return import("./App1.js").then((mod)=>{
127+
return import("./App1").then((mod)=>{
128128
const Dynamic = mod[Component === App1A ? 'App1A' : 'App1C'];
129129
return <Dynamic/>;
130130
});
@@ -205,11 +205,11 @@ export { Root as default };
205205
`;
206206
207207
exports[`JSX syntax should be preserved 3`] = `
208-
"import { App, App1A, App1B, App1C, app1cProps } from "./App1.jsx";
209-
import { App2, app2Props } from "./App2.jsx";
208+
"import { App, App1A, App1B, App1C, app1cProps } from "./App1";
209+
import { App2, app2Props } from "./App2";
210210
const DynamicComponent = ()=>{
211211
const Component = Math.random() > 0.5 ? App1A : App1C;
212-
return import("./App1.jsx").then((mod)=>{
212+
return import("./App1").then((mod)=>{
213213
const Dynamic = mod[Component === App1A ? 'App1A' : 'App1C'];
214214
return <Dynamic/>;
215215
});

tests/integration/redirect/js-not-resolve/src/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ import lodash from 'lodash';
44
import prettier from 'prettier';
55
import bar from './bar.js';
66
import foo from './foo';
7+
import foo_node from './foo.node';
8+
import foo_node_js from './foo.node.js';
79

8-
export default lodash.toUpper(foo + bar + typeof prettier.version);
10+
export default lodash.toUpper(
11+
foo + foo_node + foo_node_js + bar + typeof prettier.version,
12+
);

tests/integration/redirect/js.test.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ test('redirect.js default', async () => {
2828
import { baz } from "./baz.js";
2929
export * from "./.hidden.js";
3030
export * from "./.hidden-folder/index.js";
31+
export * from "./bar.node.js";
32+
export * from "./bar.node.js";
33+
export * from "./bar.node.js";
34+
export * from "./foo.js";
35+
export * from "./foo.js";
3136
const src = lodash.toUpper(lodash_merge(foo) + bar + foo + bar + baz + typeof prettier.version);
3237
export { src as default };
3338
"
@@ -57,6 +62,11 @@ test('redirect.js.path false', async () => {
5762
import { foo as external_foo_js_foo } from "./foo.js";
5863
export * from "./.hidden.js";
5964
export * from "./.hidden-folder/index.js";
65+
export * from "./bar.node.js";
66+
export * from "./bar.node.js";
67+
export * from "./bar.node.js";
68+
export * from "./foo.js";
69+
export * from "./foo.js";
6070
const src = lodash.toUpper(lodash_merge(external_foo_js_foo) + index_js_bar + foo + bar + baz + typeof prettier.version);
6171
export { src as default };
6272
"
@@ -84,6 +94,11 @@ test('redirect.js.path with user override externals', async () => {
8494
import { foo as external_foo_js_foo } from "./foo.js";
8595
export * from "./.hidden.js";
8696
export * from "./.hidden-folder/index.js";
97+
export * from "./bar.node.js";
98+
export * from "./bar.node.js";
99+
export * from "./bar.node.js";
100+
export * from "./foo.js";
101+
export * from "./foo.js";
87102
const src = lodash.toUpper(lodash_merge(external_foo_js_foo) + index_js_bar + foo + bar + baz + typeof prettier.version);
88103
export { src as default };
89104
"
@@ -119,6 +134,11 @@ test('redirect.js.path with user override alias', async () => {
119134
import { foo as external_foo_js_foo } from "./foo.js";
120135
export * from "./.hidden.js";
121136
export * from "./.hidden-folder/index.js";
137+
export * from "./bar.node.js";
138+
export * from "./bar.node.js";
139+
export * from "./bar.node.js";
140+
export * from "./foo.js";
141+
export * from "./foo.js";
122142
const src = lodash.toUpper(lodash_merge(external_foo_js_foo) + index_js_bar + foo + bar + baz + typeof prettier.version);
123143
export { src as default };
124144
"
@@ -138,15 +158,21 @@ test('redirect.js.extension: false', async () => {
138158
contents.esm4!,
139159
/esm\/index\.js/,
140160
);
161+
141162
expect(indexContent).toMatchInlineSnapshot(`
142163
"import lodash from "lodash";
143164
import lodash_merge from "lodash.merge";
144165
import prettier from "prettier";
145-
import { bar } from "./bar/index.ts";
146-
import { foo } from "./foo.ts";
147-
import { baz } from "./baz.ts";
148-
export * from "./.hidden.ts";
149-
export * from "./.hidden-folder/index.ts";
166+
import { bar } from "./bar/index";
167+
import { foo } from "./foo";
168+
import { baz } from "./baz";
169+
export * from "./.hidden";
170+
export * from "./.hidden-folder/index";
171+
export * from "./bar.node";
172+
export * from "./bar.node.js";
173+
export * from "./bar.node.ts";
174+
export * from "./foo.js";
175+
export * from "./foo.ts";
150176
const src = lodash.toUpper(lodash_merge(foo) + bar + foo + bar + baz + typeof prettier.version);
151177
export { src as default };
152178
"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const bar_node = 'bar node';

tests/integration/redirect/js/src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
// can not be resolved
2-
import lodash from 'lodash';
31
// can be resolved, 3rd party packages
2+
import lodash from 'lodash';
43
import merge from 'lodash.merge';
54
// can be resolved but not specified -- phantom dependency
65
import prettier from 'prettier';
@@ -14,6 +13,11 @@ import { foo } from './foo';
1413

1514
export * from './.hidden';
1615
export * from './.hidden-folder';
16+
export * from './bar.node';
17+
export * from './bar.node.js';
18+
export * from './bar.node.ts';
19+
export * from './foo.js';
20+
export * from './foo.ts';
1721

1822
export default lodash.toUpper(
1923
merge(foo) + bar + foo2 + bar2 + baz + typeof prettier.version,

tests/integration/redirect/js/tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"extends": "@rslib/tsconfig/base",
33
"compilerOptions": {
44
"baseUrl": "./",
5+
"noEmit": true,
6+
"allowImportingTsExtensions": true,
57
"paths": {
68
"@/*": ["./src/*"]
79
}

0 commit comments

Comments
 (0)