Skip to content

Commit 1ed569c

Browse files
committed
Ensure importmap relative paths start with ./
1 parent 7de4f6f commit 1ed569c

File tree

3 files changed

+70
-3
lines changed

3 files changed

+70
-3
lines changed

packages/core/integration-tests/test/html.js

+54
Original file line numberDiff line numberDiff line change
@@ -3145,6 +3145,60 @@ describe('html', function () {
31453145
assert.equal(value, 7);
31463146
});
31473147

3148+
it('should generate an import map with relative public url', async function () {
3149+
await fsFixture(overlayFS, dir)`
3150+
index.html:
3151+
<body>
3152+
<script src="./main.js" type="module"></script>
3153+
</body>
3154+
main.js:
3155+
globalThis.output = async () => (await import('./main-async')).bar();
3156+
main-async.js:
3157+
import './main-async.css';
3158+
export const bar = async () => (await import('./nested-async')).bar + 3;
3159+
main-async.css:
3160+
.foo { color: red }
3161+
nested-async.js:
3162+
import './nested-async.css';
3163+
export const bar = 4;
3164+
nested-async.css:
3165+
.bar { color: green }
3166+
`;
3167+
3168+
let b = await bundle(path.join(dir, '/index.html'), {
3169+
inputFS: overlayFS,
3170+
mode: 'production',
3171+
defaultTargetOptions: {
3172+
publicUrl: '.',
3173+
},
3174+
});
3175+
3176+
let html = await overlayFS.readFile(b.getBundles()[0].filePath, 'utf8');
3177+
let importMap = JSON.parse(
3178+
html.match(/<script type="importmap">(.*?)<\/script>/)[1],
3179+
);
3180+
assert.deepEqual(importMap, {
3181+
imports: {
3182+
[b.getBundles()[2].publicId]:
3183+
'./' + path.basename(b.getBundles()[2].filePath),
3184+
[b.getBundles()[3].publicId]:
3185+
'./' + path.basename(b.getBundles()[3].filePath),
3186+
[b.getBundles()[4].publicId]:
3187+
'./' + path.basename(b.getBundles()[4].filePath),
3188+
[b.getBundles()[5].publicId]:
3189+
'./' + path.basename(b.getBundles()[5].filePath),
3190+
},
3191+
});
3192+
3193+
assert(
3194+
html.indexOf('<script type="importmap">') < html.indexOf('<script src'),
3195+
);
3196+
3197+
let res = await run(b, null, {require: false});
3198+
let value = await res.output();
3199+
assert.equal(value, 7);
3200+
});
3201+
31483202
it('should merge with an existing import map', async function () {
31493203
await fsFixture(overlayFS, dir)`
31503204
index.html:

packages/core/utils/src/urlJoin.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,26 @@ import path from 'path';
77
* Joins a path onto a URL, and normalizes Windows paths
88
* e.g. from \path\to\res.js to /path/to/res.js.
99
*/
10-
export default function urlJoin(publicURL: string, assetPath: string): string {
10+
export default function urlJoin(
11+
publicURL: string,
12+
assetPath: string,
13+
leadingDotSlash: boolean = false,
14+
): string {
1115
const url = URL.parse(publicURL, false, true);
1216
// Leading / ensures that paths with colons are not parsed as a protocol.
1317
let p = assetPath.startsWith('/') ? assetPath : '/' + assetPath;
1418
const assetUrl = URL.parse(p);
1519
url.pathname = path.posix.join(url.pathname, assetUrl.pathname);
1620
url.search = assetUrl.search;
1721
url.hash = assetUrl.hash;
18-
return URL.format(url);
22+
let result = URL.format(url);
23+
if (
24+
url.host == null &&
25+
result[0] !== '/' &&
26+
result[0] !== '.' &&
27+
leadingDotSlash
28+
) {
29+
result = './' + result;
30+
}
31+
return result;
1932
}

packages/packagers/html/src/HTMLPackager.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ function insertBundleReferences(bundleGraph, htmlBundle, tree) {
218218

219219
if (useImportMap && Object.keys(importMap).length > 0) {
220220
for (let id in importMap) {
221-
importMap[id] = urlJoin(htmlBundle.target.publicUrl, importMap[id]);
221+
importMap[id] = urlJoin(htmlBundle.target.publicUrl, importMap[id], true);
222222
}
223223

224224
// If there is an existing <script type="importmap">, merge with that.

0 commit comments

Comments
 (0)