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
24 changes: 14 additions & 10 deletions packages/__docs__/src/index.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<base href="/" />
<base href="<%= PUBLIC_PATH %>" />
<!-- GitHub Pages SPA redirect for subdirectory deployments.
When serving this page as 404.html, redirect deep links under
/pr-preview/pr-N/ or /latest/ back to the subdirectory root with
the route encoded as a query parameter. No-op for root paths. -->
When this page is served as 404.html for a deep link inside the
build's base path (webpack's PUBLIC_PATH), bounce back to that base
with the route encoded as a query parameter. The SPA's index.tsx
restores the original URL via history.replaceState. No-op when the
requested path *is* the base directory. -->
<script>
;(function () {
var base = '<%= PUBLIC_PATH %>'
var l = window.location
var m = l.pathname.match(/^(\/pr-preview\/pr-\d+|\/latest)(\/.*)$/)
if (m && m[2] !== '/') {
l.replace(
m[1] + '/?__spa_route=' + encodeURIComponent(m[2] + l.hash)
)
}
if (l.pathname === base) return
if (l.pathname + '/' === base) return
if (l.pathname.indexOf(base) !== 0) return
var rest = l.pathname.slice(base.length)
l.replace(
base + '?__spa_route=' + encodeURIComponent('/' + rest + l.hash)
)
})()
</script>
<meta charset="utf-8" />
Expand Down
71 changes: 48 additions & 23 deletions packages/__docs__/src/navigationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,55 @@
* SOFTWARE.
*/

// Populated at runtime by webpack from output.publicPath, e.g. '/',
// '/pr-preview/pr-123/', or '/instructure-design-tokens/pr-preview/pr-5/'.
declare const __webpack_public_path__: string

const MINOR_VERSION_REGEX = /^v\d+_\d+$/

type ParsedUrl = {
prPrefix: string
basePrefix: string
minorVersion: string | null
page: string
sectionId: string | undefined
}

/**
* Returns the build's deploy base (webpack output.publicPath) with no
* trailing slash. Empty string when served at the domain root.
*
* For PR previews on instructure.design this is '/pr-preview/pr-<n>'.
* For sub-host deployments it can be '/<repo>/pr-preview/pr-<n>'.
*/
function getDeployBase(): string {
if (typeof __webpack_public_path__ === 'string' && __webpack_public_path__) {
return __webpack_public_path__.replace(/\/+$/, '')
}
return ''
}

function parseCurrentUrl(): ParsedUrl {
const { pathname, hash } = window.location
const cleanPath = pathname.replace(/^\/+|\/+$/g, '')

// Strip the deploy base so the rest of the parser can ignore it.
// The PR-preview prefix that the old code sniffed out of the URL is now
// captured in the deploy base itself (webpack's publicPath includes it).
const deployBase = getDeployBase()
let rest = pathname
if (deployBase && rest.startsWith(deployBase)) {
rest = rest.slice(deployBase.length)
}

const cleanPath = rest.replace(/^\/+|\/+$/g, '')
const segments = cleanPath.split('/').filter(Boolean)

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

// Detect PR preview prefix: /pr-preview/pr-123
if (
segments.length >= 2 &&
segments[0] === 'pr-preview' &&
segments[1].startsWith('pr-')
) {
prPrefix = `/${segments[0]}/${segments[1]}`
idx = 2
}

// Detect /latest/ prefix
if (idx === 0 && segments[idx] === 'latest') {
prPrefix = '/latest'
if (segments[idx] === 'latest') {
appPrefix = '/latest'
idx++
}

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

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

type BuildUrlOptions = {
Expand All @@ -80,13 +101,13 @@ type BuildUrlOptions = {
}

function buildUrl(targetPage: string, options?: BuildUrlOptions): string {
const { prPrefix } = parseCurrentUrl()
const parsed = parseCurrentUrl()
const minorVersion =
options?.minorVersion !== undefined
? options.minorVersion
: parseCurrentUrl().minorVersion
: parsed.minorVersion

let url = prPrefix
let url = parsed.basePrefix

if (minorVersion) {
url += `/${minorVersion}`
Expand Down Expand Up @@ -119,11 +140,15 @@ function navigateToVersion(version: string | null): void {
}

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

export {
Expand Down
12 changes: 10 additions & 2 deletions packages/__docs__/webpack.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ const ENV = process.env.NODE_ENV || 'production'
const DEBUG = process.env.DEBUG || ENV === 'development'
const GITHUB_PULL_REQUEST_PREVIEW = process.env.GITHUB_PULL_REQUEST_PREVIEW || 'false'
const PR_NUMBER = process.env.PR_NUMBER
const PUBLIC_PATH = process.env.PUBLIC_PATH
// Source of truth for "where does this build live". Threaded into webpack's
// output.publicPath, HtmlWebpackPlugin's template (for <base> and the 404 SPA
// redirect), and __webpack_public_path__ at runtime (for asset fetches).
const PUBLIC_PATH =
process.env.PUBLIC_PATH ||
(PR_NUMBER ? `/pr-preview/pr-${PR_NUMBER}/` : '/')

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

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