Skip to content

Commit

Permalink
Merge pull request #97 from posthtml/unbuild
Browse files Browse the repository at this point in the history
  • Loading branch information
cossssmin authored Jul 12, 2024
2 parents cb9628c + 4ecfd5a commit 61722f1
Show file tree
Hide file tree
Showing 9 changed files with 5,628 additions and 427 deletions.
11 changes: 11 additions & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
rollup: {
emitCJS: true,
},
rootDir: './lib',
outDir: '../dist',
entries: ['index.js'],
declaration: true,
})
166 changes: 166 additions & 0 deletions dist/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
'use strict';

const postcss = require('postcss');
const isUrl = require('is-url-superb');
const defu = require('defu');
const api_js = require('posthtml/lib/api.js');
const srcset = require('srcset');

function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }

const postcss__default = /*#__PURE__*/_interopDefaultCompat(postcss);
const isUrl__default = /*#__PURE__*/_interopDefaultCompat(isUrl);

const urlPattern = /(url\(["']?)(.*?)(["']?\))/g;
function postcssBaseurl(options = {}) {
const { base } = options;
return {
postcssPlugin: "postcss-baseurl",
Once(root) {
root.walkAtRules((rule) => {
if (rule.name === "font-face") {
rule.walkDecls((decl) => {
const { value } = decl;
decl.value = value.replace(urlPattern, ($0, $1, $2, $3) => {
return isUrl__default($2) ? $1 + $2 + $3 : $1 + base + $2 + $3;
});
});
}
});
root.walkRules((rule) => {
rule.walkDecls((decl) => {
const { value } = decl;
decl.value = value.replace(urlPattern, ($0, $1, $2, $3) => {
return isUrl__default($2) ? $1 + $2 + $3 : $1 + base + $2 + $3;
});
});
});
}
};
}
postcssBaseurl.postcss = true;

const defaultTags = {
a: {
href: true
},
area: {
href: true
},
audio: {
src: true
},
base: {
href: true
},
body: {
background: true
},
embed: {
src: true
},
iframe: {
src: true
},
img: {
src: true,
srcset: true
},
input: {
src: true
},
link: {
href: true
},
script: {
src: true
},
source: {
src: true,
srcset: true
},
table: {
background: true
},
td: {
background: true
},
th: {
background: true
},
track: {
src: true
},
video: {
poster: true
}
};
const plugin = (options = {}) => (tree) => {
options.url = options.url || "";
options.attributes = options.attributes || {};
options.allTags = options.allTags || false;
options.styleTag = options.styleTag || false;
options.inlineCss = options.inlineCss || false;
options.tags = options.allTags ? defu.defu(options.tags, defaultTags) : options.tags || {};
tree.walk = tree.walk || api_js.walk;
const process = (node) => {
if (options.url && (typeof options.url !== "string" || options.url === "")) {
return node;
}
if (!["array", "object"].includes(typeof options.tags)) {
return node;
}
if (node.tag === "style" && node.content && options.styleTag) {
node.content = postcss__default([
postcssBaseurl({
base: options.url
})
]).process(node.content.join("").trim()).css;
}
if (node.attrs?.style && options.inlineCss) {
node.attrs.style = prependUrl(node.attrs.style, options.url);
}
for (const [attribute, value] of Object.entries(options.attributes)) {
if (node.attrs?.[attribute]) {
handleSingleValueAttributes(node, attribute, value);
}
}
const tags = Array.isArray(options.tags) ? Object.entries(defaultTags).filter(([tag]) => options.tags.includes(tag)) : Object.entries(options.tags);
tags.forEach(([tag, attributes]) => {
if (node.tag !== tag) {
return node;
}
for (const [attribute, value] of Object.entries(attributes)) {
if (node.attrs?.[attribute]) {
if (["href", "src", "poster", "background"].includes(attribute)) {
handleSingleValueAttributes(node, attribute, value);
}
if (attribute === "srcset") {
const parsed = srcset.parseSrcset(node.attrs[attribute]);
parsed.map((p) => {
if (!isUrl__default(p.url)) {
p.url = typeof value === "boolean" ? options.url + p.url : value + p.url;
}
return p;
});
node.attrs[attribute] = srcset.stringifySrcset(parsed);
}
}
}
});
return node;
};
const handleSingleValueAttributes = (node, attribute, value) => {
if (isUrl__default(node.attrs[attribute])) {
return node;
}
node.attrs[attribute] = typeof value === "boolean" ? options.url + node.attrs[attribute] : value + node.attrs[attribute];
};
const prependUrl = (value, url) => {
const { css } = postcss__default([postcssBaseurl({ base: url })]).process(`div { ${value} }`);
return css.replace(/div {\s|\s}$/gm, "");
};
return tree.walk(process);
};

module.exports = plugin;
88 changes: 88 additions & 0 deletions dist/index.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
type BaseURLConfig = {
/**
The URL string to prepend.
@default ''
@example
```
baseUrl({
url: 'https://example.com/',
})
```
*/
url: string;

/**
Tags to apply the `url` to. When using this option, the `url` will only be prepended to the specified tags.
@example
Using an array of strings representing tag names:
```
baseUrl({
url: 'https://cdn.example.com/',
tags: ['img'],
})
```
Using an object to specify tags and their attributes to apply the `url` to:
```
baseUrl({
url: 'https://foo.com/',
tags: {
img: {
src: true,
srcset: 'https://bar.com/',
},
},
})
```
*/
tags?: string[] | Record<string, unknown>;

/**
Key-value pairs of attributes and the string to prepend to their existing value.
@default {}
@example
Prepend `https://example.com/` to all `data-url` attribute values:
```
baseUrl({
attributes: {
'data-url': 'https://example.com/',
}
})
```
*/
attributes?: Record<string, unknown>;

/**
Whether the string defined in `url` should be prepended to `url()` values in CSS `<style>` tags.
@default false
*/
styleTag?: boolean;

/**
Whether the string defined in `url` should be prepended to `url()` values in inline CSS.
@default false
*/
inlineCss?: boolean;

/**
Prepend the `url` to all known tags, not just those defined in `tags`.
@default false
*/
allTags?: boolean;
};

export type { BaseURLConfig };
88 changes: 88 additions & 0 deletions dist/index.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
type BaseURLConfig = {
/**
The URL string to prepend.
@default ''
@example
```
baseUrl({
url: 'https://example.com/',
})
```
*/
url: string;

/**
Tags to apply the `url` to. When using this option, the `url` will only be prepended to the specified tags.
@example
Using an array of strings representing tag names:
```
baseUrl({
url: 'https://cdn.example.com/',
tags: ['img'],
})
```
Using an object to specify tags and their attributes to apply the `url` to:
```
baseUrl({
url: 'https://foo.com/',
tags: {
img: {
src: true,
srcset: 'https://bar.com/',
},
},
})
```
*/
tags?: string[] | Record<string, unknown>;

/**
Key-value pairs of attributes and the string to prepend to their existing value.
@default {}
@example
Prepend `https://example.com/` to all `data-url` attribute values:
```
baseUrl({
attributes: {
'data-url': 'https://example.com/',
}
})
```
*/
attributes?: Record<string, unknown>;

/**
Whether the string defined in `url` should be prepended to `url()` values in CSS `<style>` tags.
@default false
*/
styleTag?: boolean;

/**
Whether the string defined in `url` should be prepended to `url()` values in inline CSS.
@default false
*/
inlineCss?: boolean;

/**
Prepend the `url` to all known tags, not just those defined in `tags`.
@default false
*/
allTags?: boolean;
};

export type { BaseURLConfig };
Loading

0 comments on commit 61722f1

Please sign in to comment.