Skip to content

Commit

Permalink
Use base rather than site to create subpath for links/scripts (#5371)
Browse files Browse the repository at this point in the history
* Use base rather than site to create subpath for links/scripts

* Adding a changeset

* Warn when the site has a pathname but not using base

* fix asset test

* fix asset inlining behavior
  • Loading branch information
matthewp authored Nov 11, 2022
1 parent f47fb99 commit bee8c14
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 62 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-cheetahs-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Prefer the `base` config rather than site config for creating URLs for links and scripts.
4 changes: 2 additions & 2 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export class App {
const url = new URL(request.url);
const manifest = this.#manifest;
const info = this.#routeDataToRouteInfo.get(routeData!)!;
const links = createLinkStylesheetElementSet(info.links, manifest.site);
const links = createLinkStylesheetElementSet(info.links);

let scripts = new Set<SSRElement>();
for (const script of info.scripts) {
Expand All @@ -182,7 +182,7 @@ export class App {
});
}
} else {
scripts.add(createModuleScriptElement(script, manifest.site));
scripts.add(createModuleScriptElement(script));
}
}

Expand Down
10 changes: 2 additions & 8 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,14 +291,8 @@ async function generatePath(

debug('build', `Generating: ${pathname}`);

// If a base path was provided, append it to the site URL. This ensures that
// all injected scripts and links are referenced relative to the site and subpath.
const site =
settings.config.base !== '/'
? joinPaths(settings.config.site?.toString() || 'http://localhost/', settings.config.base)
: settings.config.site;
const links = createLinkStylesheetElementSet(linkIds, site);
const scripts = createModuleScriptsSet(hoistedScripts ? [hoistedScripts] : [], site);
const links = createLinkStylesheetElementSet(linkIds, settings.config.base);
const scripts = createModuleScriptsSet(hoistedScripts ? [hoistedScripts] : [], settings.config.base);

if (settings.scripts.some((script) => script.stage === 'page')) {
const hashedFilePath = internals.entrySpecifierToBundleMap.get(PAGE_SCRIPT_ID);
Expand Down
5 changes: 4 additions & 1 deletion packages/astro/src/core/build/vite-plugin-hoisted-scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export function vitePluginHoistedScripts(
},

async generateBundle(_options, bundle) {
let assetInlineLimit = settings.config.vite?.build?.assetsInlineLimit || 4096;
let assetInlineLimit = 4096;
if(settings.config.vite?.build && settings.config.vite.build.assetsInlineLimit !== undefined) {
assetInlineLimit = settings.config.vite?.build.assetsInlineLimit;
}

// Find all page entry points and create a map of the entry point to the hashed hoisted script.
// This is used when we render so that we can add the script to the head.
Expand Down
11 changes: 8 additions & 3 deletions packages/astro/src/core/build/vite-plugin-ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,22 @@ function buildManifest(
staticFiles.push(entryModules[PAGE_SCRIPT_ID]);
}

const bareBase = removeTrailingForwardSlash(removeLeadingForwardSlash(settings.config.base));
const joinBase = (pth: string) => (bareBase ? bareBase + '/' + pth : pth);

for (const pageData of eachPageData(internals)) {
const scripts: SerializedRouteInfo['scripts'] = [];
if (pageData.hoistedScript) {
scripts.unshift(pageData.hoistedScript);
scripts.unshift(Object.assign({}, pageData.hoistedScript, {
value: joinBase(pageData.hoistedScript.value)
}));
}
if (settings.scripts.some((script) => script.stage === 'page')) {
scripts.push({ type: 'external', value: entryModules[PAGE_SCRIPT_ID] });
}

const bareBase = removeTrailingForwardSlash(removeLeadingForwardSlash(settings.config.base));
const links = sortedCSS(pageData).map((pth) => (bareBase ? bareBase + '/' + pth : pth));

const links = sortedCSS(pageData).map((pth) => joinBase(pth));

routes.push({
file: '',
Expand Down
20 changes: 16 additions & 4 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,23 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
config.build.client = new URL('./dist/client/', config.outDir);
}
const trimmedBase = trimSlashes(config.base);
if (trimmedBase.length && config.trailingSlash === 'never') {
config.base = prependForwardSlash(trimmedBase);
} else {
config.base = prependForwardSlash(appendForwardSlash(trimmedBase));

// If there is no base but there is a base for site config, warn.
const sitePathname = config.site && new URL(config.site).pathname;
if(!trimmedBase.length && sitePathname && sitePathname !== '/') {
config.base = sitePathname;
/* eslint-disable no-console */
console.warn(`The site configuration value includes a pathname of ${sitePathname} but there is no base configuration.
A future version of Astro will stop using the site pathname when producing <link> and <script> tags. Set your site's base with the base configuration.`)
}

if(trimmedBase.length && config.trailingSlash === 'never') {
config.base = prependForwardSlash(trimmedBase);
} else {
config.base = prependForwardSlash(appendForwardSlash(trimmedBase));
}

return config;
});

Expand Down
24 changes: 12 additions & 12 deletions packages/astro/src/core/render/ssr-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,34 @@ import type { SSRElement } from '../../@types/astro';
import npath from 'path-browserify';
import { appendForwardSlash } from '../../core/path.js';

function getRootPath(site?: string): string {
return appendForwardSlash(new URL(site || 'http://localhost/').pathname);
function getRootPath(base?: string): string {
return appendForwardSlash(new URL(base || '/', 'http://localhost/').pathname);
}

function joinToRoot(href: string, site?: string): string {
return npath.posix.join(getRootPath(site), href);
function joinToRoot(href: string, base?: string): string {
return npath.posix.join(getRootPath(base), href);
}

export function createLinkStylesheetElement(href: string, site?: string): SSRElement {
export function createLinkStylesheetElement(href: string, base?: string): SSRElement {
return {
props: {
rel: 'stylesheet',
href: joinToRoot(href, site),
href: joinToRoot(href, base),
},
children: '',
};
}

export function createLinkStylesheetElementSet(hrefs: string[], site?: string) {
return new Set<SSRElement>(hrefs.map((href) => createLinkStylesheetElement(href, site)));
export function createLinkStylesheetElementSet(hrefs: string[], base?: string) {
return new Set<SSRElement>(hrefs.map((href) => createLinkStylesheetElement(href, base)));
}

export function createModuleScriptElement(
script: { type: 'inline' | 'external'; value: string },
site?: string
base?: string
): SSRElement {
if (script.type === 'external') {
return createModuleScriptElementWithSrc(script.value, site);
return createModuleScriptElementWithSrc(script.value, base);
} else {
return {
props: {
Expand Down Expand Up @@ -60,7 +60,7 @@ export function createModuleScriptElementWithSrcSet(

export function createModuleScriptsSet(
scripts: { type: 'inline' | 'external'; value: string }[],
site?: string
base?: string
): Set<SSRElement> {
return new Set<SSRElement>(scripts.map((script) => createModuleScriptElement(script, site)));
return new Set<SSRElement>(scripts.map((script) => createModuleScriptElement(script, base)));
}
52 changes: 52 additions & 0 deletions packages/astro/test/asset-url-base.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';

describe('Asset URL resolution in build', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;

describe('With site and base', async () => {
describe('with site', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/asset-url-base/',
site: 'http://example.com/sub/path/'
});
await fixture.build();
});

it('does not include the site\'s subpath', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
const href = $('link[rel=stylesheet]').attr('href');
expect(href.startsWith('/sub/path/')).to.equal(false);
});
});

describe('with site and base', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/asset-url-base/',
site: 'http://example.com/sub/path/',
base: '/another/base/'
});
await fixture.build();
});

it('does not include the site\'s subpath', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
const href = $('link[rel=stylesheet]').attr('href');
expect(href.startsWith('/sub/path/')).to.equal(false);
});

it('does include the base subpath', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
const href = $('link[rel=stylesheet]').attr('href');
expect(href.startsWith('/another/base/')).to.equal(true);
});
});
});
});
80 changes: 48 additions & 32 deletions packages/astro/test/astro-scripts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@ import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';

describe('Scripts (hoisted and not)', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-scripts/',
vite: {
build: {
assetsInlineLimit: 0,
},
},
});
});

describe('Build', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-scripts/',
vite: {
build: {
assetsInlineLimit: 0,
},
},
});
await fixture.build();
});

Expand Down Expand Up @@ -45,11 +43,8 @@ describe('Scripts (hoisted and not)', () => {
// test 1: Just one entry module
expect($el).to.have.lengthOf(1);

// test 2: attr removed
expect($el.attr('data-astro')).to.equal(undefined);

expect($el.attr('src')).to.equal(undefined);
const inlineEntryJS = $el.text();
const src = $el.attr('src');
const inlineEntryJS = await fixture.readFile(src);

// test 3: the JS exists
expect(inlineEntryJS).to.be.ok;
Expand All @@ -69,21 +64,6 @@ describe('Scripts (hoisted and not)', () => {
expect($('script').attr('src')).to.not.equal(undefined);
});

it('External page builds the hoisted scripts to a single bundle', async () => {
let external = await fixture.readFile('/external/index.html');
let $ = cheerio.load(external);

// test 1: there are two scripts
expect($('script')).to.have.lengthOf(2);

let el = $('script').get(1);
expect($(el).attr('src')).to.equal(undefined, 'This should have been inlined');
let externalEntryJS = $(el).text();

// test 2: the JS exists
expect(externalEntryJS).to.be.ok;
});

it('External page using non-hoist scripts that are modules are built standalone', async () => {
let external = await fixture.readFile('/external-no-hoist/index.html');
let $ = cheerio.load(external);
Expand Down Expand Up @@ -122,12 +102,48 @@ describe('Scripts (hoisted and not)', () => {
// Imported styles + tailwind
expect($('link[rel=stylesheet]')).to.have.a.lengthOf(2);
});

describe('Inlining', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-scripts/',
});
await fixture.build();
});

it('External page builds the hoisted scripts to a single bundle', async () => {
let external = await fixture.readFile('/external/index.html');
let $ = cheerio.load(external);

// test 1: there are two scripts
expect($('script')).to.have.lengthOf(2);

let el = $('script').get(1);
expect($(el).attr('src')).to.equal(undefined, 'This should have been inlined');
let externalEntryJS = $(el).text();

// test 2: the JS exists
expect(externalEntryJS).to.be.ok;
});
})
});

describe('Dev', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
/** @type {import('./test-utils').DevServer} */
let devServer;
before(async () => {
fixture = await loadFixture({
root: './fixtures/astro-scripts/',
vite: {
build: {
assetsInlineLimit: 0,
},
},
});
devServer = await fixture.startDevServer();
});

Expand Down
11 changes: 11 additions & 0 deletions packages/astro/test/fixtures/asset-url-base/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@test/asset-url-base",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
},
"scripts": {
"dev": "astro dev"
}
}
13 changes: 13 additions & 0 deletions packages/astro/test/fixtures/asset-url-base/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<html>
<head>
<title>Testing</title>
<style>
body {
background: blue;
}
</style>
</head>
<body>
<h1>Testing</h1>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const origin = Astro.url.origin;
background: orangered;
}
</style>
<script>
console.log('hello world');
</script>
</head>
<body>
<h1 id="origin">{origin}</h1>
Expand Down
Loading

0 comments on commit bee8c14

Please sign in to comment.