Skip to content

Commit 55461b4

Browse files
authored
fix(html): externalize rollup.external scripts correctly (#18618)
1 parent 2a88f71 commit 55461b4

File tree

4 files changed

+79
-22
lines changed

4 files changed

+79
-22
lines changed

packages/vite/src/node/plugins/html.ts

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -466,17 +466,22 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
466466
inlineModuleIndex++
467467
if (url && !isExcludedUrl(url) && !isPublicFile) {
468468
setModuleSideEffectPromises.push(
469-
this.resolve(url, id)
470-
.then((resolved) => {
471-
if (!resolved) {
472-
return Promise.reject()
473-
}
474-
return this.load(resolved)
475-
})
476-
.then((mod) => {
477-
// set this to keep the module even if `treeshake.moduleSideEffects=false` is set
478-
mod.moduleSideEffects = true
479-
}),
469+
this.resolve(url, id).then((resolved) => {
470+
if (!resolved) {
471+
return Promise.reject(
472+
new Error(`Failed to resolve ${url} from ${id}`),
473+
)
474+
}
475+
// set moduleSideEffects to keep the module even if `treeshake.moduleSideEffects=false` is set
476+
const moduleInfo = this.getModuleInfo(resolved.id)
477+
if (moduleInfo) {
478+
moduleInfo.moduleSideEffects = true
479+
} else if (!resolved.external) {
480+
return this.load(resolved).then((mod) => {
481+
mod.moduleSideEffects = true
482+
})
483+
}
484+
}),
480485
)
481486
// <script type="module" src="..."/>
482487
// add it as an import
@@ -715,23 +720,28 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
715720
const getImportedChunks = (
716721
chunk: OutputChunk,
717722
seen: Set<string> = new Set(),
718-
): OutputChunk[] => {
719-
const chunks: OutputChunk[] = []
723+
): (OutputChunk | string)[] => {
724+
const chunks: (OutputChunk | string)[] = []
720725
chunk.imports.forEach((file) => {
721726
const importee = bundle[file]
722-
if (importee?.type === 'chunk' && !seen.has(file)) {
723-
seen.add(file)
727+
if (importee) {
728+
if (importee.type === 'chunk' && !seen.has(file)) {
729+
seen.add(file)
724730

725-
// post-order traversal
726-
chunks.push(...getImportedChunks(importee, seen))
727-
chunks.push(importee)
731+
// post-order traversal
732+
chunks.push(...getImportedChunks(importee, seen))
733+
chunks.push(importee)
734+
}
735+
} else {
736+
// external imports
737+
chunks.push(file)
728738
}
729739
})
730740
return chunks
731741
}
732742

733743
const toScriptTag = (
734-
chunk: OutputChunk,
744+
chunkOrUrl: OutputChunk | string,
735745
toOutputPath: (filename: string) => string,
736746
isAsync: boolean,
737747
): HtmlTagDescriptor => ({
@@ -746,7 +756,10 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
746756
// https://developer.chrome.com/blog/modulepreload/#ok-so-why-doesnt-link-relpreload-work-for-modules:~:text=For%20%3Cscript%3E,of%20other%20modules.
747757
// Now `<script type="module">` uses `same origin`: https://github.com/whatwg/html/pull/3656#:~:text=Module%20scripts%20are%20always%20fetched%20with%20credentials%20mode%20%22same%2Dorigin%22%20by%20default%20and%20can%20no%20longer%0Ause%20%22omit%22
748758
crossorigin: true,
749-
src: toOutputPath(chunk.fileName),
759+
src:
760+
typeof chunkOrUrl === 'string'
761+
? chunkOrUrl
762+
: toOutputPath(chunkOrUrl.fileName),
750763
},
751764
})
752765

@@ -863,7 +876,9 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
863876
const resolveDependencies =
864877
typeof modulePreload === 'object' &&
865878
modulePreload.resolveDependencies
866-
const importsFileNames = imports.map((chunk) => chunk.fileName)
879+
const importsFileNames = imports
880+
.filter((chunkOrUrl) => typeof chunkOrUrl !== 'string')
881+
.map((chunk) => chunk.fileName)
867882
const resolvedDeps = resolveDependencies
868883
? resolveDependencies(chunk.fileName, importsFileNames, {
869884
hostId: relativeUrlPath,

playground/html/__tests__/html.spec.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,24 @@ describe('main', () => {
117117
expect(serverLogs).not.toEqual(
118118
expect.arrayContaining([
119119
expect.stringMatching(
120-
'can\'t be bundled without type="module" attribute',
120+
/"\/external-path\.js".*can't be bundled without type="module" attribute/,
121121
),
122122
]),
123123
)
124124
}
125125
})
126+
127+
test.runIf(isBuild)(
128+
'external paths by rollupOptions.external works',
129+
async () => {
130+
expect(await page.textContent('.external-path-by-rollup-options')).toBe(
131+
'works',
132+
)
133+
expect(serverLogs).not.toEqual(
134+
expect.arrayContaining([expect.stringContaining('Could not load')]),
135+
)
136+
},
137+
)
126138
})
127139

128140
describe('nested', () => {

playground/html/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ <h1>Hello</h1>
1313
<div>External path: <span class="external-path"></span></div>
1414
<script type="module" src="/external-path.js" vite-ignore></script>
1515
<link rel="stylesheet" href="/external-path.css" vite-ignore />
16+
<div>
17+
External path by rollupOptions.external (build only):
18+
<span class="external-path-by-rollup-options"></span>
19+
</div>

playground/html/vite.config.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default defineConfig({
4040
resolve(__dirname, 'relative-input.html'),
4141
),
4242
},
43+
external: ['/external-path-by-rollup-options.js'],
4344
},
4445
},
4546

@@ -228,6 +229,26 @@ ${
228229
},
229230
},
230231
},
232+
{
233+
name: 'append-external-path-by-rollup-options',
234+
apply: 'build', // this does not work in serve
235+
transformIndexHtml: {
236+
order: 'pre',
237+
handler(_, ctx) {
238+
if (!ctx.filename.endsWith('html/index.html')) return
239+
return [
240+
{
241+
tag: 'script',
242+
attrs: {
243+
type: 'module',
244+
src: '/external-path-by-rollup-options.js',
245+
},
246+
injectTo: 'body',
247+
},
248+
]
249+
},
250+
},
251+
},
231252
serveExternalPathPlugin(),
232253
],
233254
})
@@ -241,6 +262,11 @@ function serveExternalPathPlugin() {
241262
} else if (req.url === '/external-path.css') {
242263
res.setHeader('Content-Type', 'text/css')
243264
res.end('.external-path{color:red}')
265+
} else if (req.url === '/external-path-by-rollup-options.js') {
266+
res.setHeader('Content-Type', 'application/javascript')
267+
res.end(
268+
'document.querySelector(".external-path-by-rollup-options").textContent = "works"',
269+
)
244270
} else {
245271
next()
246272
}

0 commit comments

Comments
 (0)