Skip to content

Commit 587849a

Browse files
committed
refactor: unify deploy base path handling
1 parent d5c1198 commit 587849a

3 files changed

Lines changed: 72 additions & 35 deletions

File tree

packages/__docs__/src/index.html

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
<!DOCTYPE html>
22
<html lang="en" dir="ltr">
33
<head>
4-
<base href="/" />
4+
<base href="<%= PUBLIC_PATH %>" />
55
<!-- GitHub Pages SPA redirect for subdirectory deployments.
6-
When serving this page as 404.html, redirect deep links under
7-
/pr-preview/pr-N/ or /latest/ back to the subdirectory root with
8-
the route encoded as a query parameter. No-op for root paths. -->
6+
When this page is served as 404.html for a deep link inside the
7+
build's base path (webpack's PUBLIC_PATH), bounce back to that base
8+
with the route encoded as a query parameter. The SPA's index.tsx
9+
restores the original URL via history.replaceState. No-op when the
10+
requested path *is* the base directory. -->
911
<script>
1012
;(function () {
13+
var base = '<%= PUBLIC_PATH %>'
1114
var l = window.location
12-
var m = l.pathname.match(/^(\/pr-preview\/pr-\d+|\/latest)(\/.*)$/)
13-
if (m && m[2] !== '/') {
14-
l.replace(
15-
m[1] + '/?__spa_route=' + encodeURIComponent(m[2] + l.hash)
16-
)
17-
}
15+
if (l.pathname === base) return
16+
if (l.pathname + '/' === base) return
17+
if (l.pathname.indexOf(base) !== 0) return
18+
var rest = l.pathname.slice(base.length)
19+
l.replace(
20+
base + '?__spa_route=' + encodeURIComponent('/' + rest + l.hash)
21+
)
1822
})()
1923
</script>
2024
<meta charset="utf-8" />

packages/__docs__/src/navigationUtils.ts

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,55 @@
2222
* SOFTWARE.
2323
*/
2424

25+
// Populated at runtime by webpack from output.publicPath, e.g. '/',
26+
// '/pr-preview/pr-123/', or '/instructure-design-tokens/pr-preview/pr-5/'.
27+
declare const __webpack_public_path__: string
28+
2529
const MINOR_VERSION_REGEX = /^v\d+_\d+$/
2630

2731
type ParsedUrl = {
28-
prPrefix: string
32+
basePrefix: string
2933
minorVersion: string | null
3034
page: string
3135
sectionId: string | undefined
3236
}
3337

38+
/**
39+
* Returns the build's deploy base (webpack output.publicPath) with no
40+
* trailing slash. Empty string when served at the domain root.
41+
*
42+
* For PR previews on instructure.design this is '/pr-preview/pr-<n>'.
43+
* For sub-host deployments it can be '/<repo>/pr-preview/pr-<n>'.
44+
*/
45+
function getDeployBase(): string {
46+
if (typeof __webpack_public_path__ === 'string' && __webpack_public_path__) {
47+
return __webpack_public_path__.replace(/\/+$/, '')
48+
}
49+
return ''
50+
}
51+
3452
function parseCurrentUrl(): ParsedUrl {
3553
const { pathname, hash } = window.location
36-
const cleanPath = pathname.replace(/^\/+|\/+$/g, '')
54+
55+
// Strip the deploy base so the rest of the parser can ignore it.
56+
// The PR-preview prefix that the old code sniffed out of the URL is now
57+
// captured in the deploy base itself (webpack's publicPath includes it).
58+
const deployBase = getDeployBase()
59+
let rest = pathname
60+
if (deployBase && rest.startsWith(deployBase)) {
61+
rest = rest.slice(deployBase.length)
62+
}
63+
64+
const cleanPath = rest.replace(/^\/+|\/+$/g, '')
3765
const segments = cleanPath.split('/').filter(Boolean)
3866

39-
let prPrefix = ''
67+
// In-app namespace prefix (within a single deploy): /latest or empty.
68+
// Versioned routes (/vM_N) and pages follow.
69+
let appPrefix = ''
4070
let idx = 0
4171

42-
// Detect PR preview prefix: /pr-preview/pr-123
43-
if (
44-
segments.length >= 2 &&
45-
segments[0] === 'pr-preview' &&
46-
segments[1].startsWith('pr-')
47-
) {
48-
prPrefix = `/${segments[0]}/${segments[1]}`
49-
idx = 2
50-
}
51-
52-
// Detect /latest/ prefix
53-
if (idx === 0 && segments[idx] === 'latest') {
54-
prPrefix = '/latest'
72+
if (segments[idx] === 'latest') {
73+
appPrefix = '/latest'
5574
idx++
5675
}
5776

@@ -71,7 +90,9 @@ function parseCurrentUrl(): ParsedUrl {
7190
sectionId = decodeURI(hash.replace(/^#+/, ''))
7291
}
7392

74-
return { prPrefix, minorVersion, page, sectionId }
93+
// basePrefix spans the deploy base + in-app namespace, so buildUrl
94+
// produces correct outbound links under any deploy host.
95+
return { basePrefix: deployBase + appPrefix, minorVersion, page, sectionId }
7596
}
7697

7798
type BuildUrlOptions = {
@@ -80,13 +101,13 @@ type BuildUrlOptions = {
80101
}
81102

82103
function buildUrl(targetPage: string, options?: BuildUrlOptions): string {
83-
const { prPrefix } = parseCurrentUrl()
104+
const parsed = parseCurrentUrl()
84105
const minorVersion =
85106
options?.minorVersion !== undefined
86107
? options.minorVersion
87-
: parseCurrentUrl().minorVersion
108+
: parsed.minorVersion
88109

89-
let url = prPrefix
110+
let url = parsed.basePrefix
90111

91112
if (minorVersion) {
92113
url += `/${minorVersion}`
@@ -119,11 +140,15 @@ function navigateToVersion(version: string | null): void {
119140
}
120141

121142
/**
122-
* Returns the base path prefix for fetching static assets.
123-
* On PR previews this is e.g. `/pr-preview/pr-2425`, otherwise empty string.
143+
* Returns the base path for fetching this build's static assets
144+
* (legacy-icons-data.json, markdown-and-sources-data.json, etc.). These
145+
* always live next to main.js — i.e. at the deploy base, regardless of any
146+
* /latest or /vM_N in-app namespace.
147+
*
148+
* For PR previews this is e.g. `/pr-preview/pr-2425`. Empty at the root.
124149
*/
125150
function getAssetBasePath(): string {
126-
return parseCurrentUrl().prPrefix
151+
return getDeployBase()
127152
}
128153

129154
export {

packages/__docs__/webpack.config.mjs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ const ENV = process.env.NODE_ENV || 'production'
3232
const DEBUG = process.env.DEBUG || ENV === 'development'
3333
const GITHUB_PULL_REQUEST_PREVIEW = process.env.GITHUB_PULL_REQUEST_PREVIEW || 'false'
3434
const PR_NUMBER = process.env.PR_NUMBER
35-
const PUBLIC_PATH = process.env.PUBLIC_PATH
35+
// Source of truth for "where does this build live". Threaded into webpack's
36+
// output.publicPath, HtmlWebpackPlugin's template (for <base> and the 404 SPA
37+
// redirect), and __webpack_public_path__ at runtime (for asset fetches).
38+
const PUBLIC_PATH =
39+
process.env.PUBLIC_PATH ||
40+
(PR_NUMBER ? `/pr-preview/pr-${PR_NUMBER}/` : '/')
3641

3742
const outputPath = resolvePath(import.meta.dirname, '__build__')
3843

@@ -53,7 +58,7 @@ const config = merge(baseConfig, {
5358
filename: '[name].js',
5459
// Builds deployed to subdirectories on GitHub Pages (e.g. /pr-preview/pr-123/
5560
// or /latest/) need a matching publicPath so script tags resolve correctly.
56-
publicPath: PUBLIC_PATH || (PR_NUMBER ? `/pr-preview/pr-${PR_NUMBER}/` : '/'),
61+
publicPath: PUBLIC_PATH,
5762
},
5863
devServer: {
5964
static: {
@@ -69,6 +74,9 @@ const config = merge(baseConfig, {
6974
new HtmlWebpackPlugin({
7075
template: './src/index.html',
7176
chunks: ['main'],
77+
// Expose the deploy base to the template so <base href> and the inline
78+
// 404 SPA-redirect script stay aligned with the deployment location.
79+
templateParameters: { PUBLIC_PATH },
7280
}),
7381
new webpack.DefinePlugin({
7482
'process.env.GITHUB_PULL_REQUEST_PREVIEW': JSON.stringify(GITHUB_PULL_REQUEST_PREVIEW),

0 commit comments

Comments
 (0)