Skip to content

feat(plugins/url): add url() filter support (options.url) #655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 8, 2018
Merged
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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default `
}
`
```

#### `{Boolean}`

To disable `url()` resolving by `css-loader` set the option to `false`
Expand All @@ -88,6 +89,32 @@ To disable `url()` resolving by `css-loader` set the option to `false`
}
```

#### `{RegExp}`
Copy link

@stereokai stereokai Jan 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few words about what these options do would be smashing!
@d3viant0ne @michael-ciniawsky

Copy link
Member Author

@michael-ciniawsky michael-ciniawsky Jan 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah yeah :) I intend to add appropriate documentation before this goes anywhere, it hasn't even been signed-off by other maintainers nor released yet... 😛

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh man, was already hoping to use it today 😆 😭


**webpack.config.js**
```js
{
loader: 'css-loader',
options: {
url: /filter/
}
}
```

#### `{Function}`

**webpack.config.js**
```js
{
loader: 'css-loader',
options: {
url (url) {
return /filter/.test(url)
}
}
}
```

### `import`

```css
Expand Down
83 changes: 40 additions & 43 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';

import postcss from 'postcss';
// TODO(michael-ciniawsky)
// TODO(michael-ciniawsky)
// replace with postcss-icss-{url, import}
import urls from './plugins/url';
import imports from './plugins/import';
Expand All @@ -26,15 +26,12 @@ const DEFAULTS = {
sourceMap: false,
};

export default function loader (css, map, meta) {
export default function loader(css, map, meta) {
// Loader Options
const options = Object.assign(
{},
DEFAULTS,
getOptions(this)
);
const options = Object.assign({}, DEFAULTS, getOptions(this));

validateOptions(schema, options, 'CSS Loader');

// Loader Mode (Async)
const cb = this.async();
const file = this.resourcePath;
Expand All @@ -47,14 +44,13 @@ export default function loader (css, map, meta) {
map = false;
}

// CSS Plugins
const plugins = [];

// URL Plugin
if (options.url) {
plugins.push(urls());
plugins.push(urls(options));
}

// Import Plugin
if (options.import) {
plugins.push(imports());
Expand All @@ -64,7 +60,7 @@ export default function loader (css, map, meta) {
if (options.minimize) {
plugins.push(minifier());
}

if (meta) {
const { ast } = meta;
// Reuse CSS AST (PostCSS AST e.g 'postcss-loader')
Expand All @@ -73,34 +69,33 @@ export default function loader (css, map, meta) {
css = ast.root;
}
}
map = options.sourceMap

map = options.sourceMap
? {
prev: map || false,
inline: false,
annotation: false,
sourcesContent: true,
}
: false
prev: map || false,
inline: false,
annotation: false,
sourcesContent: true,
}
: false;

return postcss(plugins)
.process(css, {
from: `/css-loader!${file}`,
map,
to: file,
}).then(({ css, map, messages }) => {
})
.then(({ css, map, messages }) => {
if (meta && meta.messages) {
messages = messages.concat(meta.messages)
messages = messages.concat(meta.messages);
}

// CSS Imports
const imports = messages
.filter((msg) => msg.type === 'import' ? msg : false)
.filter((msg) => (msg.type === 'import' ? msg : false))
.reduce((imports, msg) => {
try {
msg = typeof msg.import === 'function'
? msg.import()
: msg.import;
msg = typeof msg.import === 'function' ? msg.import() : msg.import;

imports += msg;
} catch (err) {
Expand All @@ -109,17 +104,15 @@ export default function loader (css, map, meta) {
this.emitError(err);
}

return imports
}, '')
return imports;
}, '');

// CSS Exports
const exports = messages
.filter((msg) => msg.type === 'export' ? msg : false)
.reduce((exports, msg) => {
try {
msg = typeof msg.export === 'function'
? msg.export()
: msg.export;
.filter((msg) => (msg.type === 'export' ? msg : false))
.reduce((exports, msg) => {
try {
msg = typeof msg.export === 'function' ? msg.export() : msg.export;

exports += msg;
} catch (err) {
Expand All @@ -129,23 +122,27 @@ export default function loader (css, map, meta) {
}

return exports;
}, '')
// TODO(michael-ciniawsky)
}, '');

// TODO(michael-ciniawsky)
// triage if and add CSS runtime back
const result = [
imports ? `// CSS Imports\n${imports}\n` : false,
exports ? `// CSS Exports\n${exports}\n` : false,
`// CSS\nexport default \`${css}\``
`// CSS\nexport default \`${css}\``,
]
.filter(Boolean)
.join('\n');

cb(null, result, map ? map.toJSON() : null);

return null;
})
.catch((err) => {
err.name === 'CssSyntaxError' ? cb(new SyntaxError(err)) : cb(err);
err = err.name === 'CssSyntaxError' ? new SyntaxError(err) : err;

cb(err);

return null;
});
};
}
7 changes: 6 additions & 1 deletion src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
"type": "object",
"properties": {
"url": {
"type": "boolean"
"anyOf": [
{ "type": "string" },
{ "type": "boolean" },
{ "instanceof": "RegExp" },
{ "instanceof": "Function" }
]
},
"import": {
"type": "boolean"
Expand Down
73 changes: 51 additions & 22 deletions src/plugins/url.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
/* eslint-disable */
import postcss from 'postcss';
import valueParser from 'postcss-value-parser';
// ICSS {String}
// import { createICSSRules } from "icss-utils";

const walkUrls = (parsed, callback) => {
const walkUrls = (parsed, cb) => {
parsed.walk((node) => {
if (node.type === 'function' && node.value === 'url') {
const content = node.nodes.length !== 0 && node.nodes[0].type === 'string'
? node.nodes[0].value
: valueParser.stringify(node.nodes);

if (content.trim().length !== 0) {
callback(node, content);
cb(node, content);
}

// do not traverse inside url
Expand All @@ -22,30 +20,32 @@ const walkUrls = (parsed, callback) => {
};

const mapUrls = (parsed, map) => {
walkUrls(parsed, (node, content) => {
node.nodes = [{ type: 'word', value: map(content) }];
walkUrls(parsed, (node, url) => {
node.nodes = [{ type: 'word', value: map(url) }];
});
};

const filterUrls = (parsed, filter) => {
const filterUrls = (parsed, filter, options) => {
const result = [];

walkUrls(parsed, (node, content) => {
if (filter(content)) {
result.push(content);
walkUrls(parsed, (node, url) => {
if (filter(url, options)) {
return false
}

return result.push(url);
});

return result;
};

const walkDeclsWithUrl = (css, filter) => {
const walkDeclsWithUrl = (css, filter, options) => {
const result = [];

css.walkDecls((decl) => {
if (decl.value.includes('url(')) {
const parsed = valueParser(decl.value);
const values = filterUrls(parsed, filter);
const values = filterUrls(parsed, filter, options);

if (values.length) {
result.push({
Expand All @@ -60,10 +60,39 @@ const walkDeclsWithUrl = (css, filter) => {
return result;
};

const filterValues = value => !/^\w+:\/\//.test(value) &&
!value.startsWith('//') &&
!value.startsWith('#') &&
!value.startsWith('data:');
const URL = /^\w+:\/\//;

const filter = (url, options) => {
if (URL.test(url)) {
return true;
}

if (url.startsWith('//')) {
return true;
}

if (url.startsWith('//')) {
return true;
}

if (url.startsWith('#')) {
return true;
}

if (url.startsWith('data:')) {
return true;
}

if (options.url instanceof RegExp) {
return options.url.test(url);
}

if (typeof options.url === 'function') {
return options.url(url);
}

return false;
}

const flatten = arr => arr.reduce((acc, d) => [...acc, ...d], []);

Expand All @@ -72,24 +101,23 @@ const uniq = arr => arr.reduce(
[],
);

module.exports = postcss.plugin('postcss-icss-url', () => (css, result) => {
const traversed = walkDeclsWithUrl(css, filterValues);
module.exports = postcss.plugin('postcss-icss-url', (options) => (css, result) => {
const traversed = walkDeclsWithUrl(css, filter, options);
const paths = uniq(flatten(traversed.map(item => item.values)));

// ICSS imports {String}
const aliases = {};

paths.forEach((url, idx) => {
// ICSS Placeholder
// CSS Content Placeholder
const alias = '${' + `CSS__URL__${idx}` + '}';

aliases[url] = alias;

result.messages.push({
type: 'import',
plugin: 'postcss-icss-url',
import: `import CSS__URL__${idx} from '${url}';\n`
})
});
});

traversed.forEach((item) => {
Expand All @@ -98,3 +126,4 @@ module.exports = postcss.plugin('postcss-icss-url', () => (css, result) => {
item.decl.value = item.parsed.toString();
});
});

2 changes: 2 additions & 0 deletions test/fixtures/imports/fixture.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import css from './fixture.css';

export default css;
3 changes: 3 additions & 0 deletions test/fixtures/urls/file.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.url {
background: url('./file.png')
}
4 changes: 4 additions & 0 deletions test/fixtures/urls/filter/fixture.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.url {
background: url('./file.png');
background: url('./filter/file.png');
}
3 changes: 3 additions & 0 deletions test/fixtures/urls/filter/fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import css from './fixture.css';

export default css;
2 changes: 2 additions & 0 deletions test/fixtures/urls/fixture.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import css from './fixture.css';

export default css;
2 changes: 1 addition & 1 deletion test/loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Loader', () => {

const stats = await webpack('fixture.js', config);
const { source } = stats.toJson().modules[1];

expect(source).toMatchSnapshot();
});
});
Loading