parent
b8b04dc2d3
commit
7a7427e425
7 changed files with 299 additions and 228 deletions
5
.changeset/tame-melons-think.md
Normal file
5
.changeset/tame-melons-think.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix CSS URLs on Windows
|
|
@ -25,7 +25,7 @@ export function getStylesForURL(filePath: URL, viteServer: vite.ViteDevServer):
|
||||||
if (!importedModule.id || scanned.has(importedModule.id)) continue;
|
if (!importedModule.id || scanned.has(importedModule.id)) continue;
|
||||||
const ext = path.extname(importedModule.id.toLowerCase());
|
const ext = path.extname(importedModule.id.toLowerCase());
|
||||||
if (STYLE_EXTENSIONS.has(ext)) {
|
if (STYLE_EXTENSIONS.has(ext)) {
|
||||||
css.add(importedModule.id); // if style file, add to list
|
css.add(importedModule.url || importedModule.id); // if style file, add to list
|
||||||
} else {
|
} else {
|
||||||
crawlCSS(importedModule.id, scanned); // otherwise, crawl file to see if it imports any CSS
|
crawlCSS(importedModule.id, scanned); // otherwise, crawl file to see if it imports any CSS
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import { injectTags } from './html.js';
|
||||||
import { generatePaginateFunction } from './paginate.js';
|
import { generatePaginateFunction } from './paginate.js';
|
||||||
import { getParams, validateGetStaticPathsModule, validateGetStaticPathsResult } from './routing.js';
|
import { getParams, validateGetStaticPathsModule, validateGetStaticPathsResult } from './routing.js';
|
||||||
|
|
||||||
const svelteAndVueStylesRE = /\?[^&]+&type=style&lang/;
|
const svelteStylesRE = /svelte\?svelte&type=style/;
|
||||||
|
|
||||||
interface SSROptions {
|
interface SSROptions {
|
||||||
/** an instance of the AstroConfig */
|
/** an instance of the AstroConfig */
|
||||||
|
@ -247,7 +247,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
|
||||||
|
|
||||||
// inject CSS
|
// inject CSS
|
||||||
[...getStylesForURL(filePath, viteServer)].forEach((href) => {
|
[...getStylesForURL(filePath, viteServer)].forEach((href) => {
|
||||||
if (mode === 'development' && svelteAndVueStylesRE.test(href)) {
|
if (mode === 'development' && svelteStylesRE.test(href)) {
|
||||||
tags.push({
|
tags.push({
|
||||||
tag: 'script',
|
tag: 'script',
|
||||||
attrs: { type: 'module', src: href },
|
attrs: { type: 'module', src: href },
|
||||||
|
|
|
@ -8,31 +8,35 @@ import { expect } from 'chai';
|
||||||
import cheerio from 'cheerio';
|
import cheerio from 'cheerio';
|
||||||
import { loadFixture } from './test-utils.js';
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
describe('Styles SSR', function () {
|
|
||||||
this.timeout(30000); // test needs a little more time in CI
|
|
||||||
|
|
||||||
let fixture;
|
let fixture;
|
||||||
let index$;
|
|
||||||
let bundledCSS;
|
|
||||||
|
|
||||||
|
describe('CSS', function () {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
fixture = await loadFixture({
|
fixture = await loadFixture({
|
||||||
projectRoot: './fixtures/0-css/',
|
projectRoot: './fixtures/0-css/',
|
||||||
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
|
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// test HTML and CSS contents for accuracy
|
||||||
|
describe('build', () => {
|
||||||
|
this.timeout(30000); // test needs a little more time in CI
|
||||||
|
|
||||||
|
let $;
|
||||||
|
let bundledCSS;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
await fixture.build();
|
await fixture.build();
|
||||||
|
|
||||||
// get bundled CSS (will be hashed, hence DOM query)
|
// get bundled CSS (will be hashed, hence DOM query)
|
||||||
const html = await fixture.readFile('/index.html');
|
const html = await fixture.readFile('/index.html');
|
||||||
index$ = cheerio.load(html);
|
$ = cheerio.load(html);
|
||||||
const bundledCSSHREF = index$('link[rel=stylesheet][href^=assets/]').attr('href');
|
const bundledCSSHREF = $('link[rel=stylesheet][href^=assets/]').attr('href');
|
||||||
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
|
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Astro styles', () => {
|
describe('Astro Styles', () => {
|
||||||
it('HTML and CSS scoped correctly', async () => {
|
it('HTML and CSS scoped correctly', async () => {
|
||||||
const $ = index$;
|
|
||||||
|
|
||||||
const el1 = $('#dynamic-class');
|
const el1 = $('#dynamic-class');
|
||||||
const el2 = $('#dynamic-vis');
|
const el2 = $('#dynamic-vis');
|
||||||
const classes = $('#class').attr('class').split(' ');
|
const classes = $('#class').attr('class').split(' ');
|
||||||
|
@ -47,15 +51,11 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('No <style> skips scoping', async () => {
|
it('No <style> skips scoping', async () => {
|
||||||
const $ = index$;
|
|
||||||
|
|
||||||
// Astro component without <style> should not include scoped class
|
// Astro component without <style> should not include scoped class
|
||||||
expect($('#no-scope').attr('class')).to.equal(undefined);
|
expect($('#no-scope').attr('class')).to.equal(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Child inheritance', async () => {
|
it('Child inheritance', async () => {
|
||||||
const $ = index$;
|
|
||||||
|
|
||||||
expect($('#passed-in').attr('class')).to.match(/outer astro-[A-Z0-9]+ astro-[A-Z0-9]+/);
|
expect($('#passed-in').attr('class')).to.match(/outer astro-[A-Z0-9]+ astro-[A-Z0-9]+/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,7 +88,6 @@ describe('Styles SSR', function () {
|
||||||
|
|
||||||
describe('JSX', () => {
|
describe('JSX', () => {
|
||||||
it('.css', async () => {
|
it('.css', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#react-css');
|
const el = $('#react-css');
|
||||||
|
|
||||||
// 1. check HTML
|
// 1. check HTML
|
||||||
|
@ -99,7 +98,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('.module.css', async () => {
|
it('.module.css', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#react-module-css');
|
const el = $('#react-module-css');
|
||||||
const classes = el.attr('class').split(' ');
|
const classes = el.attr('class').split(' ');
|
||||||
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
||||||
|
@ -112,7 +110,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('.sass', async () => {
|
it('.sass', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#react-sass');
|
const el = $('#react-sass');
|
||||||
|
|
||||||
// 1. check HTML
|
// 1. check HTML
|
||||||
|
@ -123,7 +120,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('.scss', async () => {
|
it('.scss', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#react-scss');
|
const el = $('#react-scss');
|
||||||
|
|
||||||
// 1. check HTML
|
// 1. check HTML
|
||||||
|
@ -134,7 +130,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('.module.sass', async () => {
|
it('.module.sass', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#react-module-sass');
|
const el = $('#react-module-sass');
|
||||||
const classes = el.attr('class').split(' ');
|
const classes = el.attr('class').split(' ');
|
||||||
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
||||||
|
@ -147,7 +142,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('.module.scss', async () => {
|
it('.module.scss', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#react-module-scss');
|
const el = $('#react-module-scss');
|
||||||
const classes = el.attr('class').split(' ');
|
const classes = el.attr('class').split(' ');
|
||||||
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
||||||
|
@ -162,7 +156,6 @@ describe('Styles SSR', function () {
|
||||||
|
|
||||||
describe('Vue', () => {
|
describe('Vue', () => {
|
||||||
it('<style>', async () => {
|
it('<style>', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#vue-css');
|
const el = $('#vue-css');
|
||||||
|
|
||||||
// 1. check HTML
|
// 1. check HTML
|
||||||
|
@ -173,7 +166,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('<style scoped>', async () => {
|
it('<style scoped>', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#vue-scoped');
|
const el = $('#vue-scoped');
|
||||||
|
|
||||||
// find data-v-* attribute (how Vue CSS scoping works)
|
// find data-v-* attribute (how Vue CSS scoping works)
|
||||||
|
@ -189,7 +181,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('<style module>', async () => {
|
it('<style module>', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#vue-modules');
|
const el = $('#vue-modules');
|
||||||
const classes = el.attr('class').split(' ');
|
const classes = el.attr('class').split(' ');
|
||||||
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
||||||
|
@ -202,7 +193,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('<style lang="sass">', async () => {
|
it('<style lang="sass">', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#vue-sass');
|
const el = $('#vue-sass');
|
||||||
|
|
||||||
// 1. check HTML
|
// 1. check HTML
|
||||||
|
@ -213,7 +203,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('<style lang="scss">', async () => {
|
it('<style lang="scss">', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#vue-scss');
|
const el = $('#vue-scss');
|
||||||
|
|
||||||
// 1. check HTML
|
// 1. check HTML
|
||||||
|
@ -226,7 +215,6 @@ describe('Styles SSR', function () {
|
||||||
|
|
||||||
describe('Svelte', () => {
|
describe('Svelte', () => {
|
||||||
it('<style>', async () => {
|
it('<style>', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#svelte-css');
|
const el = $('#svelte-css');
|
||||||
const classes = el.attr('class').split(' ');
|
const classes = el.attr('class').split(' ');
|
||||||
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name));
|
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name));
|
||||||
|
@ -239,7 +227,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('<style lang="sass">', async () => {
|
it('<style lang="sass">', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#svelte-sass');
|
const el = $('#svelte-sass');
|
||||||
const classes = el.attr('class').split(' ');
|
const classes = el.attr('class').split(' ');
|
||||||
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name));
|
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name));
|
||||||
|
@ -252,7 +239,6 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('<style lang="scss">', async () => {
|
it('<style lang="scss">', async () => {
|
||||||
const $ = index$;
|
|
||||||
const el = $('#svelte-scss');
|
const el = $('#svelte-scss');
|
||||||
const classes = el.attr('class').split(' ');
|
const classes = el.attr('class').split(' ');
|
||||||
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name));
|
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name));
|
||||||
|
@ -265,3 +251,65 @@ describe('Styles SSR', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// with "build" handling CSS checking, the dev tests are mostly testing the paths resolve in dev
|
||||||
|
describe('dev', () => {
|
||||||
|
let devServer;
|
||||||
|
let $;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
devServer = await fixture.startDevServer();
|
||||||
|
const html = await fixture.fetch('/').then((res) => res.text());
|
||||||
|
$ = cheerio.load(html);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
devServer && (await devServer.stop());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves CSS in public/', async () => {
|
||||||
|
const href = $('link[href="/global.css"]').attr('href');
|
||||||
|
expect((await fixture.fetch(href)).status).to.equal(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves CSS in src/', async () => {
|
||||||
|
const href = $('link[href$="linked.css"]').attr('href');
|
||||||
|
expect((await fixture.fetch(href)).status).to.equal(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves Astro styles', async () => {
|
||||||
|
const style = $('style[astro-style]');
|
||||||
|
expect(style.length).to.not.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves Styles from React', async () => {
|
||||||
|
const styles = ['ReactCSS.css', 'ReactModules.module.css', 'ReactModules.module.scss', 'ReactModules.module.sass', 'ReactSass.sass', 'ReactScss.scss'];
|
||||||
|
for (const style of styles) {
|
||||||
|
const href = $(`link[href$="${style}"]`).attr('href');
|
||||||
|
expect((await fixture.fetch(href)).status, style).to.equal(200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves CSS from Svelte', async () => {
|
||||||
|
const scripts = ['SvelteCSS.svelte?svelte&type=style&lang.css', 'SvelteSass.svelte?svelte&type=style&lang.css', 'SvelteScss.svelte?svelte&type=style&lang.css'];
|
||||||
|
for (const script of scripts) {
|
||||||
|
const src = $(`script[src$="${script}"]`).attr('src');
|
||||||
|
expect((await fixture.fetch(src)).status, script).to.equal(200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves CSS from Vue', async () => {
|
||||||
|
const styles = [
|
||||||
|
'VueCSS.vue?vue&type=style&index=0&lang.css',
|
||||||
|
'VueModules.vue?vue&type=style&index=0&lang.module.scss',
|
||||||
|
'VueSass.vue?vue&type=style&index=0&lang.sass',
|
||||||
|
'VueScoped.vue?vue&type=style&index=0&scoped=true&lang.css',
|
||||||
|
'VueScss.vue?vue&type=style&index=0&lang.scss',
|
||||||
|
];
|
||||||
|
for (const style of styles) {
|
||||||
|
const href = $(`link[href$="${style}"]`).attr('href');
|
||||||
|
expect((await fixture.fetch(href)).status, style).to.equal(200);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
8
packages/astro/test/fixtures/0-css/public/global.css
vendored
Normal file
8
packages/astro/test/fixtures/0-css/public/global.css
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
|
@ -9,14 +9,14 @@ import ReactModulesSass from '../components/ReactModulesSass.jsx';
|
||||||
import ReactModulesScss from '../components/ReactModulesScss.jsx';
|
import ReactModulesScss from '../components/ReactModulesScss.jsx';
|
||||||
import ReactSass from '../components/ReactSass.jsx';
|
import ReactSass from '../components/ReactSass.jsx';
|
||||||
import ReactScss from '../components/ReactScss.jsx';
|
import ReactScss from '../components/ReactScss.jsx';
|
||||||
|
import SvelteCSS from '../components/SvelteCSS.svelte';
|
||||||
|
import SvelteSass from '../components/SvelteSass.svelte';
|
||||||
|
import SvelteScss from '../components/SvelteScss.svelte';
|
||||||
import VueCSS from '../components/VueCSS.vue';
|
import VueCSS from '../components/VueCSS.vue';
|
||||||
import VueModules from '../components/VueModules.vue';
|
import VueModules from '../components/VueModules.vue';
|
||||||
import VueSass from '../components/VueSass.vue';
|
import VueSass from '../components/VueSass.vue';
|
||||||
import VueScoped from '../components/VueScoped.vue';
|
import VueScoped from '../components/VueScoped.vue';
|
||||||
import VueScss from '../components/VueScss.vue';
|
import VueScss from '../components/VueScss.vue';
|
||||||
import SvelteCSS from '../components/SvelteCSS.svelte';
|
|
||||||
import SvelteSass from '../components/SvelteSass.svelte';
|
|
||||||
import SvelteScss from '../components/SvelteScss.svelte';
|
|
||||||
import ReactDynamic from '../components/ReactDynamic.jsx';
|
import ReactDynamic from '../components/ReactDynamic.jsx';
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import ReactDynamic from '../components/ReactDynamic.jsx';
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||||
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.css')}>
|
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.css')}>
|
||||||
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.scss')}>
|
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.scss')}>
|
||||||
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.sass')}>
|
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.sass')}>
|
||||||
|
@ -49,14 +50,14 @@ import ReactDynamic from '../components/ReactDynamic.jsx';
|
||||||
<ReactModulesScss />
|
<ReactModulesScss />
|
||||||
<ReactSass />
|
<ReactSass />
|
||||||
<ReactScss />
|
<ReactScss />
|
||||||
|
<SvelteCSS />
|
||||||
|
<SvelteSass />
|
||||||
|
<SvelteScss />
|
||||||
<VueCSS />
|
<VueCSS />
|
||||||
<VueModules />
|
<VueModules />
|
||||||
<VueSass />
|
<VueSass />
|
||||||
<VueScoped />
|
<VueScoped />
|
||||||
<VueScss />
|
<VueScss />
|
||||||
<SvelteCSS />
|
|
||||||
<SvelteSass />
|
|
||||||
<SvelteScss />
|
|
||||||
<ReactDynamic client:load />
|
<ReactDynamic client:load />
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -27,8 +27,13 @@ import preview from '../dist/core/preview/index.js';
|
||||||
* Build
|
* Build
|
||||||
* .build() - Async. Builds into current folder (will erase previous build)
|
* .build() - Async. Builds into current folder (will erase previous build)
|
||||||
* .readFile(path) - Async. Read a file from the build.
|
* .readFile(path) - Async. Read a file from the build.
|
||||||
* .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit
|
*
|
||||||
|
* Dev
|
||||||
|
* .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit.
|
||||||
* .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before)
|
* .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before)
|
||||||
|
*
|
||||||
|
* Preview
|
||||||
|
* .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit
|
||||||
*/
|
*/
|
||||||
export async function loadFixture(inlineConfig) {
|
export async function loadFixture(inlineConfig) {
|
||||||
if (!inlineConfig || !inlineConfig.projectRoot) throw new Error("Must provide { projectRoot: './fixtures/...' }");
|
if (!inlineConfig || !inlineConfig.projectRoot) throw new Error("Must provide { projectRoot: './fixtures/...' }");
|
||||||
|
@ -52,7 +57,11 @@ export async function loadFixture(inlineConfig) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
build: (opts = {}) => build(config, { mode: 'development', logging: 'error', ...opts }),
|
build: (opts = {}) => build(config, { mode: 'development', logging: 'error', ...opts }),
|
||||||
startDevServer: () => dev(config, { logging: 'error' }),
|
startDevServer: async (opts = {}) => {
|
||||||
|
const devServer = await dev(config, { logging: 'error', ...opts });
|
||||||
|
inlineConfig.devOptions.port = devServer.port; // update port
|
||||||
|
return devServer;
|
||||||
|
},
|
||||||
config,
|
config,
|
||||||
fetch: (url, init) => fetch(`http://${config.devOptions.hostname}:${config.devOptions.port}${url.replace(/^\/?/, '/')}`, init),
|
fetch: (url, init) => fetch(`http://${config.devOptions.hostname}:${config.devOptions.port}${url.replace(/^\/?/, '/')}`, init),
|
||||||
preview: async (opts = {}) => {
|
preview: async (opts = {}) => {
|
||||||
|
|
Loading…
Reference in a new issue