npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@such-code/html-resource-loader

v2.4.0

Published

Webpack loader to loads resources from HTML using set of user defined rules.

Downloads

4

Readme

@such-code/html-resource-loader

Installation

npm i -D @such-code/html-resource-loader

Webpack loader to parse html markup and load all configured resources. Should be configured with rules. Loaded resource result will be executed to get content (commonjs module required for this operation).

Rules

Loader options consists of array of rules that specify which resources should be loaded and how to treat the result.

Rule structure
import { Element, Node } from 'domhandler/lib';

/**
 * Object layout to represent loader rule.
 */
export declare type Rule = {
    /** All selectors must match to add element for processing. */
    selector: Array<TagRuleSelector | AttrRuleSelector>;
    /** Determines what should be taken as a resource paths source. */
    source: AttrRuleSource;
    /** How Element will mutate after successful rule application. */
    target: TagRuleTarget | AttrRuleTarget | ContentRuleTarget;
};

// --- SELECTORS --- //
/**
 * Configuration object for loose tag selection by type (ex. { type: 'tag' } will select all element tags).
 */
export declare type TypeRuleSelector = {
    type: 'tag' | 'script' | 'style';
    exclude?: boolean;
};
/**
 * Represents configuration object for selecting tag.
 */
export declare type TagRuleSelector = {
    /** Tag name or RegExp to match multiple similar tags. */
    tag: string | RegExp;
    /** Optional param to invert result of selection. */
    exclude?: boolean;
};
/**
 * Configuration object to represent attribute selector.
 */
export declare type AttrRuleSelector = {
    /** Attribute name or RegExp to math multiple attribute names. */
    attr: string | RegExp;
    /** Optional param to invert result of selection. */
    exclude?: boolean;
    /** Filter result by attribute content. */
    filter?: ($: string) => boolean;
};

// --- SOURCE --- //

/**
 * Represent object to extract resource path.
 */
export declare type AttrRuleSource = {
    /** Attribute name or RegExp (only first match will be used, so make sure you know what are you doing). */
    attr: string | RegExp;
    /** Optional flag to remove specified attribute in processed Node. Default `false`. */
    remove?: boolean;
    /** Optional function if specific source extraction is required. */
    deserialize?: ($: string) => string;
    /**
     * By default resolution is made by webpack and it is correct approach, but if it is really required to do
     * something special custom resolver could be used.
     */
    resolve?: ($context: string, $path: string) => string | Promise<string>;
};

// --- TARGET --- //

/**
 * Represent target as an attribute to contain processed resource result.
 */
export declare type AttrRuleTarget = {
    /** Attribute name where result will be placed. */
    attr: string;
    /** Optional serialization function if specific handling is required. */
    serialize?: ($: string, $prev?: string) => string;
};
/**
 * Represents source element tag as a target for processed content. Only option is ot replace original tag.
 */
export declare type TagRuleTarget = {
    /** Only option for the moment. Tag can only be replaced. */
    tag: 'replace';
    /** Optional serialization function for specific treatment. */
    serialize?: ($: Node | Array<Node>, $prev: Element) => Node | Array<Node>;
    /** Removes newlines and spaces from an end and beginning of received data. Default value is `true`. */
    trimContent?: boolean;
};
/**
 * Target for initial element child content manipulation.
 */
export declare type ContentRuleTarget = {
    /**
     * Specifies content handling strategy. To replace possible Element content use 'replace'. To insert result in the
     * beginning of Element child nodes use 'prepend'. 'append' will insert result in the end of child nodes.
     */
    content: 'replace' | 'append' | 'prepend';
};

How to use

Configure all rules to process html resources and pass them to loader using options.

Configuration example

Check webpack.config.js for more.

Imagine you need to load images from style attribute.

<div class="border border-info p-2" style="background-image: url(src/test/assets/mobile.gif); --md-background-image: url(src/test/assets/desktop.gif)">
    Example.
</div>

Load could be configured like that to process those resources.

/**
 * Parses html style content into object `{ [styleName: string]: string }`.
 */
function extractStyleAsObject($style) {
    return $style
        .split(';')
        .filter($ => $.length > 1)
        .reduce(($acc, $expr) => {
            let [style, value] = $expr.split(':', 2);
            return {
                ...$acc,
                [style.trim()]: value.trim(),
            };
        }, {});
}
/**
 * Converts result of extractStyleAsObject back to string.
 */
function renderStyleObject($style) {
    return Object
        .keys($style)
        .map($ => {
            return `${$}:${$style[$]}`;
        })
        .join(';')
}
/**
 * RegExp for `url(link/to/resource.ext)` extraction.
 */
const URL_REGEX = /^url\(("|'|)((?:.(?!\1\)))*.)\1\)$/;
/**
 * Extracts resource path from `url(path/to/resource.ext)` | `url('res.ext')` | `url("name.ext")` 
 */
function extractFromUrl($) {
    if (URL_REGEX.test($)) {
        return $.replace(URL_REGEX, '$2');
    }
    return $;
}
/**
 * Rule factory for html element style processing.
 */
function buildStyleUrlRule($styleName) {
    return {
        selector: [
            {
                // Select element with style attribute.
                attr: 'style',
                // Filter out element where style has `$styleName`.
                filter: $ => {
                    const style = extractStyleAsObject($);
                    return $styleName in style;
                }
            }
        ],
        source: {
            // Use style attribute content as a source.
            attr: 'style',
            // Return only resource path for only specified style parameter.
            deserialize: $ => {
                const style = extractStyleAsObject($);
                return extractFromUrl(style[$styleName]);
            }
        },
        target: {
            // Loaded result must be inserted back to style attribute.
            attr: 'style',
            // Specific serialization is required to update only one parameter.
            serialize: ($url, $attrValue) => {
                const style = extractStyleAsObject($attrValue);
                style[$styleName] = `url(${$url})`;
                return renderStyleObject(style);
            }
        }
    };
}

const resourceLoaderRules = [
    buildStyleUrlRule('background-image'),
    buildStyleUrlRule('--md-background-image'),
];

module.exports = {
    ...{},
    module: {
        rules: [
            {
                test: /\.html?$/i,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[path][name].[ext]',
                        }
                    },
                    {
                        loader: '@such-code/html-resource-loader',
                        options: {
                            rules: resourceLoaderRules
                        }
                    }
                ],
            },
            {
                test: /\.gif$/i,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: 'images/[path][name].[ext]',
                            limit: 1000
                        }
                    }
                ]
            },
        ]
    },
};