Skip to content
Permalink
Browse files

fix: ignore URLs in <link href> (#553)

* fix: svelte preprocessor ignores URLs

* test: add test for URL ignoring functionality

* fix: warn when multiple <link> elements are used

* fix: no-op if all <link> are invalid
  • Loading branch information...
tivac committed Jan 24, 2019
1 parent fae0b99 commit 16ec1d95f2ac186d2e12a804d76ec846bab67a1d

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -26,6 +26,8 @@
"dependencies": {
"@modular-css/processor": "file:../processor",
"dedent": "^0.7.0",
"escape-string-regexp": "^1.0.5",
"is-url": "^1.2.4",
"mkdirp": "^0.5.1",
"resolve-from": "^4.0.0",
"rollup-pluginutils": "^2.3.0"
@@ -4,15 +4,19 @@ const path = require("path");

const resolve = require("resolve-from");
const dedent = require("dedent");
const isUrl = require("is-url");
const escape = require("escape-string-regexp");

const Processor = require("@modular-css/processor");

const styleRegex = /<style[\S\s]*?>([\S\s]*?)<\/style>/im;
const scriptRegex = /<script[\S\s]*?>([\S\s]*?)<\/script>/im;
const linkRegex = /<link\b[^<>]*?\bhref=\s*(?:"([^"]+)"|'([^']+)'|([^>\s]+))[^>]*>/im;
const missedRegex = /css\.\w+/gim;

module.exports = (config = false) => {
// Defined here to avoid .lastIndex bugs since /g is set
const linkRegex = /<link\b[^<>]*?\bhref=\s*(?:"([^"]+)"|'([^']+)'|([^>\s]+))[^>]*>/gim;

const processor = new Processor(config);

// eslint-disable-next-line no-console, no-empty-function
@@ -21,19 +25,19 @@ module.exports = (config = false) => {
// This function is hilariously large but it's actually simpler this way
// Mostly because markup() is async so tracking state is painful w/o inlining
// the whole damn thing
// eslint-disable-next-line max-statements
// eslint-disable-next-line max-statements, complexity
const markup = async ({ content, filename }) => {
let source = content;

const link = source.match(linkRegex);
const links = source.match(linkRegex);
const style = source.match(styleRegex);

if(link && style) {
if(links && style) {
throw new Error("@modular-css/svelte: use <style> OR <link>, but not both");
}

// No-op
if(!link && !style) {
if(!links && !style) {
return {
code : source,
};
@@ -55,9 +59,44 @@ module.exports = (config = false) => {
);
}

if(link) {
// This looks weird, but it's to support multiple types of quotation marks
file = link[1] || link[2] || link[3];
if(links) {
const valid = links.reduce((out, link) => {
linkRegex.lastIndex = 0;

const parts = linkRegex.exec(link);

// This looks weird, but it's to support multiple types of quotation marks
const href = parts[1] || parts[2] || parts[3];

// Don't transform URLs
if(isUrl(href)) {
return out;
}

out.push({
link,
href,
});

return out;
}, []);

// No-op
if(!valid.length) {
return {
code : source,
};
}

if(valid.length > 1) {
// eslint-disable-next-line no-console
console.warn("@modular-css/svelte will only use the first local <link> tag");
}

const [{ link, href }] = valid;

// Assign to file for later usage in logging
file = href;

const external = resolve(path.dirname(filename), file);

@@ -74,7 +113,7 @@ module.exports = (config = false) => {
result = await processor.file(external);

// Remove the <link> element from the component to avoid double-loading
source = source.replace(link[0], "");
source = source.replace(new RegExp(`${escape(link)}\r?\n?`), "");

// To get rollup to watch the CSS file we need to inject an import statement
// if a <script> block already exists hijack it otherwise inject a simple one
@@ -2,7 +2,6 @@

exports[`/svelte.js should extract CSS from a <link> tag ("existing script") 1`] = `
"

<div class=\\"flex wrapper\\">
<h1 class=\\"flex fooga hd\\">Head</h1>
<div class=\\"fooga wooga bd\\">
@@ -56,7 +55,6 @@ exports[`/svelte.js should extract CSS from a <link> tag ("existing script") 2`]

exports[`/svelte.js should extract CSS from a <link> tag ("no script") 1`] = `
"

<div class=\\"flex wrapper\\">
<h1 class=\\"flex fooga hd\\">Head</h1>
<div class=\\"fooga wooga bd\\">
@@ -93,7 +91,6 @@ exports[`/svelte.js should extract CSS from a <link> tag ("no script") 2`] = `

exports[`/svelte.js should extract CSS from a <link> tag ("single quotes") 1`] = `
"

<div class=\\"flex wrapper\\">
<h1 class=\\"flex fooga hd\\">Head</h1>
<div class=\\"fooga wooga bd\\">
@@ -130,7 +127,6 @@ exports[`/svelte.js should extract CSS from a <link> tag ("single quotes") 2`] =

exports[`/svelte.js should extract CSS from a <link> tag ("unquoted") 1`] = `
"

<div class=\\"flex wrapper\\">
<h1 class=\\"flex fooga hd\\">Head</h1>
<div class=\\"fooga wooga bd\\">
@@ -167,7 +163,6 @@ exports[`/svelte.js should extract CSS from a <link> tag ("unquoted") 2`] = `

exports[`/svelte.js should extract CSS from a <link> tag ("values") 1`] = `
"

<div
data-simple=\\"color: #BEEFED\\"
data-expression=\\"{\\"color: \\" + \\"#BEEFED\\"}\\"
@@ -270,7 +265,6 @@ Array [

exports[`/svelte.js should handle errors: "empty css file - <link>" 3`] = `
"

<div class=\\"{css.nope}\\">NOPE</div>
<div class=\\"{css.alsonope}\\">STILL NOPE</div>
<script>
@@ -311,7 +305,6 @@ Array [

exports[`/svelte.js should handle errors: "invalid reference <script> - <link>" 3`] = `
"

<h2 class=\\"yup\\">Yup</h2>

<script>
@@ -356,7 +349,6 @@ Array [

exports[`/svelte.js should handle errors: "invalid reference template - <link>" 3`] = `
"

<h1 class=\\"{css.nope}\\">Nope</h1>
<h2 class=\\"yup\\">Yup</h2>
<script>
@@ -382,16 +374,40 @@ exports[`/svelte.js should handle errors: "invalid reference template - <style>"
"
`;

exports[`/svelte.js should ignore <links> that reference a URL 1`] = `
"<link rel=\\"stylesheet\\" href=\\"http://example.com/styles.css\\" />

<div class=\\"fooga\\">fooga</div>
<script>
import css from \\"./simple.css\\";
</script>"
`;

exports[`/svelte.js should ignore <links> that reference a URL 2`] = `
"/* packages/svelte/test/specimens/simple.css */
.fooga {
color: red;
}
"
`;

exports[`/svelte.js should ignore files without <style> blocks 1`] = `
"<h1>Hello</h1>
<script>console.log(\\"output\\")</script>"
`;

exports[`/svelte.js should ignore files without <style> blocks 2`] = `""`;

exports[`/svelte.js should remove files before reprocessing when config.clean is set 1`] = `
exports[`/svelte.js should no-op if all <link>s reference a URL 1`] = `
"<link rel=\\"stylesheet\\" href=\\"http://example.com/styles.css\\" />
<link rel=\\"stylesheet\\" href=\\"http://example.com/styles2.css\\" />

<div class=\\"fooga\\">fooga</div>
"
<div class=\\"source\\">Source</div><script>
`;

exports[`/svelte.js should remove files before reprocessing when config.clean is set 1`] = `
"<div class=\\"source\\">Source</div><script>
import css from \\"./source.css\\";
</script>"
`;
@@ -404,8 +420,7 @@ exports[`/svelte.js should remove files before reprocessing when config.clean is
`;

exports[`/svelte.js should remove files before reprocessing when config.clean is set 3`] = `
"
<div class=\\"source\\">Source</div><script>
"<div class=\\"source\\">Source</div><script>
import css from \\"./source.css\\";
</script>"
`;
@@ -615,3 +630,32 @@ Array [
`;

exports[`/svelte.js should throw on both <style> and <link> in one file 1`] = `"@modular-css/svelte: use <style> OR <link>, but not both"`;

exports[`/svelte.js should warn when multiple <link> elements are in the html 1`] = `
"<link rel=\\"stylesheet\\" href=\\"./simple2.css\\" />

<div class=\\"fooga\\">fooga</div>
<div class=\\"{css.booga}\\">booga</div>
<script>
import css from \\"./simple.css\\";
</script>"
`;

exports[`/svelte.js should warn when multiple <link> elements are in the html 2`] = `
"/* packages/svelte/test/specimens/simple.css */
.fooga {
color: red;
}
"
`;

exports[`/svelte.js should warn when multiple <link> elements are in the html 3`] = `
Array [
Array [
"@modular-css/svelte will only use the first local <link> tag",
],
Array [
"@modular-css/svelte: Unable to find .booga in \\"./simple.css\\"",
],
]
`;
@@ -0,0 +1,5 @@
<link rel="stylesheet" href="./simple.css" />
<link rel="stylesheet" href="./simple2.css" />

<div class="{css.fooga}">fooga</div>
<div class="{css.booga}">booga</div>
@@ -0,0 +1,4 @@
<link rel="stylesheet" href="http://example.com/styles.css" />
<link rel="stylesheet" href="http://example.com/styles2.css" />

<div class="fooga">fooga</div>
@@ -0,0 +1,3 @@
.booga {
color: blue;
}
@@ -0,0 +1,4 @@
<link rel="stylesheet" href="http://example.com/styles.css" />
<link rel="stylesheet" href="./simple.css" />

<div class="{css.fooga}">fooga</div>
@@ -31,6 +31,24 @@ describe("/svelte.js", () => {

expect(output.css).toMatchSnapshot();
});

it("should ignore <links> that reference a URL", async () => {
const filename = require.resolve("./specimens/url.html");
const { preprocess, processor } = plugin({
namer,
});

const processed = await svelte.preprocess(
fs.readFileSync(filename, "utf8"),
Object.assign({}, preprocess, { filename })
);

expect(processed.toString()).toMatchSnapshot();

const output = await processor.output();

expect(output.css).toMatchSnapshot();
});

it.each`
specimen | title
@@ -159,6 +177,47 @@ describe("/svelte.js", () => {
logSnapshot();
});

it("should warn when multiple <link> elements are in the html", async () => {
const spy = jest.spyOn(global.console, "warn");

spy.mockImplementation(() => { /* NO-OP */ });

const filename = require.resolve(`./specimens/multiple-link.html`);

const { processor, preprocess } = plugin({
namer,
});

const processed = await svelte.preprocess(
fs.readFileSync(filename, "utf8"),
Object.assign({}, preprocess, { filename })
);

expect(processed.toString()).toMatchSnapshot();

const output = await processor.output();

expect(output.css).toMatchSnapshot();

expect(spy).toHaveBeenCalled();
expect(spy.mock.calls).toMatchSnapshot();
});

it("should no-op if all <link>s reference a URL", async () => {
const filename = require.resolve("./specimens/multiple-url.html");

const { preprocess } = plugin({
namer,
});

const processed = await svelte.preprocess(
fs.readFileSync(filename, "utf8"),
Object.assign({}, preprocess, { filename })
);

expect(processed.toString()).toMatchSnapshot();
});

it("should remove files before reprocessing when config.clean is set", async () => {
// V1 of files
fs.writeFileSync(path.resolve(__dirname, "./output/source.html"), dedent(`

0 comments on commit 16ec1d9

Please sign in to comment.
You can’t perform that action at this time.