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;
|
||||
const ext = path.extname(importedModule.id.toLowerCase());
|
||||
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 {
|
||||
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 { getParams, validateGetStaticPathsModule, validateGetStaticPathsResult } from './routing.js';
|
||||
|
||||
const svelteAndVueStylesRE = /\?[^&]+&type=style&lang/;
|
||||
const svelteStylesRE = /svelte\?svelte&type=style/;
|
||||
|
||||
interface SSROptions {
|
||||
/** an instance of the AstroConfig */
|
||||
|
@ -247,7 +247,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
|
|||
|
||||
// inject CSS
|
||||
[...getStylesForURL(filePath, viteServer)].forEach((href) => {
|
||||
if (mode === 'development' && svelteAndVueStylesRE.test(href)) {
|
||||
if (mode === 'development' && svelteStylesRE.test(href)) {
|
||||
tags.push({
|
||||
tag: 'script',
|
||||
attrs: { type: 'module', src: href },
|
||||
|
|
|
@ -8,31 +8,35 @@ import { expect } from 'chai';
|
|||
import cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Styles SSR', function () {
|
||||
this.timeout(30000); // test needs a little more time in CI
|
||||
|
||||
let fixture;
|
||||
let index$;
|
||||
let bundledCSS;
|
||||
let fixture;
|
||||
|
||||
describe('CSS', function () {
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/0-css/',
|
||||
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();
|
||||
|
||||
// get bundled CSS (will be hashed, hence DOM query)
|
||||
const html = await fixture.readFile('/index.html');
|
||||
index$ = cheerio.load(html);
|
||||
const bundledCSSHREF = index$('link[rel=stylesheet][href^=assets/]').attr('href');
|
||||
$ = cheerio.load(html);
|
||||
const bundledCSSHREF = $('link[rel=stylesheet][href^=assets/]').attr('href');
|
||||
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
|
||||
});
|
||||
|
||||
describe('Astro styles', () => {
|
||||
describe('Astro Styles', () => {
|
||||
it('HTML and CSS scoped correctly', async () => {
|
||||
const $ = index$;
|
||||
|
||||
const el1 = $('#dynamic-class');
|
||||
const el2 = $('#dynamic-vis');
|
||||
const classes = $('#class').attr('class').split(' ');
|
||||
|
@ -47,15 +51,11 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('No <style> skips scoping', async () => {
|
||||
const $ = index$;
|
||||
|
||||
// Astro component without <style> should not include scoped class
|
||||
expect($('#no-scope').attr('class')).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('Child inheritance', async () => {
|
||||
const $ = index$;
|
||||
|
||||
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', () => {
|
||||
it('.css', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#react-css');
|
||||
|
||||
// 1. check HTML
|
||||
|
@ -99,7 +98,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('.module.css', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#react-module-css');
|
||||
const classes = el.attr('class').split(' ');
|
||||
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
||||
|
@ -112,7 +110,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('.sass', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#react-sass');
|
||||
|
||||
// 1. check HTML
|
||||
|
@ -123,7 +120,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('.scss', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#react-scss');
|
||||
|
||||
// 1. check HTML
|
||||
|
@ -134,7 +130,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('.module.sass', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#react-module-sass');
|
||||
const classes = el.attr('class').split(' ');
|
||||
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
||||
|
@ -147,7 +142,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('.module.scss', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#react-module-scss');
|
||||
const classes = el.attr('class').split(' ');
|
||||
const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name));
|
||||
|
@ -162,7 +156,6 @@ describe('Styles SSR', function () {
|
|||
|
||||
describe('Vue', () => {
|
||||
it('<style>', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#vue-css');
|
||||
|
||||
// 1. check HTML
|
||||
|
@ -173,7 +166,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('<style scoped>', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#vue-scoped');
|
||||
|
||||
// find data-v-* attribute (how Vue CSS scoping works)
|
||||
|
@ -189,7 +181,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('<style module>', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#vue-modules');
|
||||
const classes = el.attr('class').split(' ');
|
||||
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 () => {
|
||||
const $ = index$;
|
||||
const el = $('#vue-sass');
|
||||
|
||||
// 1. check HTML
|
||||
|
@ -213,7 +203,6 @@ describe('Styles SSR', function () {
|
|||
});
|
||||
|
||||
it('<style lang="scss">', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#vue-scss');
|
||||
|
||||
// 1. check HTML
|
||||
|
@ -226,7 +215,6 @@ describe('Styles SSR', function () {
|
|||
|
||||
describe('Svelte', () => {
|
||||
it('<style>', async () => {
|
||||
const $ = index$;
|
||||
const el = $('#svelte-css');
|
||||
const classes = el.attr('class').split(' ');
|
||||
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 () => {
|
||||
const $ = index$;
|
||||
const el = $('#svelte-sass');
|
||||
const classes = el.attr('class').split(' ');
|
||||
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 () => {
|
||||
const $ = index$;
|
||||
const el = $('#svelte-scss');
|
||||
const classes = el.attr('class').split(' ');
|
||||
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name));
|
||||
|
@ -264,4 +250,66 @@ describe('Styles SSR', function () {
|
|||
expect(bundledCSS).to.match(new RegExp(`.svelte-scss.${scopedClass}[^{]*{font-family:"Comic Sans MS"`));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 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 ReactSass from '../components/ReactSass.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 VueModules from '../components/VueModules.vue';
|
||||
import VueSass from '../components/VueSass.vue';
|
||||
import VueScoped from '../components/VueScoped.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';
|
||||
---
|
||||
|
||||
|
@ -33,6 +33,7 @@ import ReactDynamic from '../components/ReactDynamic.jsx';
|
|||
color: red;
|
||||
}
|
||||
</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.scss')}>
|
||||
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.sass')}>
|
||||
|
@ -49,14 +50,14 @@ import ReactDynamic from '../components/ReactDynamic.jsx';
|
|||
<ReactModulesScss />
|
||||
<ReactSass />
|
||||
<ReactScss />
|
||||
<SvelteCSS />
|
||||
<SvelteSass />
|
||||
<SvelteScss />
|
||||
<VueCSS />
|
||||
<VueModules />
|
||||
<VueSass />
|
||||
<VueScoped />
|
||||
<VueScss />
|
||||
<SvelteCSS />
|
||||
<SvelteSass />
|
||||
<SvelteScss />
|
||||
<ReactDynamic client:load />
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -27,8 +27,13 @@ import preview from '../dist/core/preview/index.js';
|
|||
* Build
|
||||
* .build() - Async. Builds into current folder (will erase previous 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)
|
||||
*
|
||||
* 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) {
|
||||
if (!inlineConfig || !inlineConfig.projectRoot) throw new Error("Must provide { projectRoot: './fixtures/...' }");
|
||||
|
@ -52,7 +57,11 @@ export async function loadFixture(inlineConfig) {
|
|||
|
||||
return {
|
||||
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,
|
||||
fetch: (url, init) => fetch(`http://${config.devOptions.hostname}:${config.devOptions.port}${url.replace(/^\/?/, '/')}`, init),
|
||||
preview: async (opts = {}) => {
|
||||
|
|
Loading…
Reference in a new issue