Skip to content

Commit

Permalink
Implement the loader
Browse files Browse the repository at this point in the history
  • Loading branch information
ezzatron committed Nov 2, 2024
1 parent 103ae55 commit 2ab9fee
Show file tree
Hide file tree
Showing 45 changed files with 884 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.makefiles/
/artifacts/
/node_modules/
/package-lock.json
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.github/
/.makefiles/
/artifacts/
/test/fixture/
/CHANGELOG.md
12 changes: 12 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": ["prettier-plugin-organize-imports"],
"overrides": [
{
"files": "*.md",
"options": {
"printWidth": 80,
"proseWrap": "always"
}
}
]
}
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog], and this project adheres to [Semantic
Versioning].

[keep a changelog]: https://keepachangelog.com/en/1.0.0/
[semantic versioning]: https://semver.org/spec/v2.0.0.html

## Unreleased

### Added

- Initial release.
64 changes: 64 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
CHANGELOG_TAG_URL_PREFIX := https://github.com/iconduit/browserconfig-loader/releases/tag/
JS_ARETHETYPESWRONG_REQ += artifacts/dist
JS_PUBLINT_REQ += artifacts/dist
JS_SKYPACK_PACKAGE_CHECK_REQ += artifacts/dist
JS_VITEST_REQ := artifacts/dist

-include .makefiles/Makefile
-include .makefiles/pkg/js/v1/Makefile
-include .makefiles/pkg/js/v1/with-npm.mk
-include .makefiles/pkg/js/v1/with-arethetypeswrong.mk
-include .makefiles/pkg/js/v1/with-publint.mk
-include .makefiles/pkg/js/v1/with-skypack-package-check.mk
-include .makefiles/pkg/js/v1/with-tsc.mk
-include .makefiles/pkg/changelog/v1/Makefile

.makefiles/%:
@curl -sfL https://makefiles.dev/v1 | bash /dev/stdin "$@"

################################################################################

.PHONY: precommit
precommit:: run-fixtures

.PHONY: ci
ci:: run-fixtures

################################################################################

VALID_FIXTURES := $(wildcard test/fixture/valid/*)
INVALID_FIXTURES := $(wildcard test/fixture/invalid/*)

.PHONY: run-fixtures
run-fixtures: run-fixtures-valid run-fixtures-invalid

.PHONY: run-fixtures-valid
run-fixtures-valid: $(addprefix run-fixture-valid-,$(notdir $(VALID_FIXTURES)))

.PHONY: run-fixtures-invalid
run-fixtures-invalid: $(addprefix run-fixture-invalid-,$(notdir $(INVALID_FIXTURES)))

.PHONY: run-fixture-valid-%
run-fixture-valid-%: artifacts/link-dependencies.touch artifacts/dist
FIXTURE="valid/$*" $(JS_EXEC) webpack --config test/webpack.config.js

.PHONY: run-fixture-invalid-%
run-fixture-invalid-%: artifacts/link-dependencies.touch artifacts/dist
if FIXTURE="invalid/$*" $(JS_EXEC) webpack --config test/webpack.config.js; then exit 1; else exit 0; fi

################################################################################

artifacts/dist: artifacts/dist/cjs artifacts/dist/esm
@touch "$@"

artifacts/dist/cjs: tsconfig.build.cjs.json tsconfig.json artifacts/link-dependencies.touch $(JS_SOURCE_FILES)
@rm -rf "$@"
$(JS_EXEC) tsc -p "$<"
echo '{"type":"commonjs"}' > "$@/package.json"
@touch "$@"

artifacts/dist/esm: tsconfig.build.esm.json tsconfig.json artifacts/link-dependencies.touch $(JS_SOURCE_FILES)
@rm -rf "$@"
$(JS_EXEC) tsc -p "$<"
echo '{"type":"module"}' > "$@/package.json"
@touch "$@"
62 changes: 60 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,60 @@
# browserconfig-loader
A Webpack loader for browserconfig.xml files
# Iconduit browser configuration loader

_A Webpack loader for `browserconfig.xml` files_

[![Current NPM version][badge-npm-version-image]][badge-npm-version-link]
[![Build status][badge-build-image]][badge-build-link]
[![Test coverage][badge-coverage-image]][badge-coverage-link]

[badge-build-image]:
https://img.shields.io/github/actions/workflow/status/iconduit/browserconfig-loader/ci-library.yml?branch=main&style=for-the-badge
[badge-build-link]:
https://github.com/iconduit/browserconfig-loader/actions/workflows/ci-library.yml
[badge-coverage-image]:
https://img.shields.io/codecov/c/gh/iconduit/browserconfig-loader?style=for-the-badge
[badge-coverage-link]: https://codecov.io/gh/iconduit/browserconfig-loader
[badge-npm-version-image]:
https://img.shields.io/npm/v/%40iconduit%2Fbrowserconfig-loader?label=%40iconduit%2Fbrowserconfig-loader&logo=npm&style=for-the-badge
[badge-npm-version-link]:
https://npmjs.com/package/@iconduit/browserconfig-loader

This loader processes [browser configuration] files, adding any images or polling
URI assets to the bundle and resolving their URLs in the output browser configuration
file.

[browser configuration]: https://msdn.microsoft.com/library/dn320426(v%3Dvs.85)

It supports:

- Resolving [tile images]
- Resolving [badge polling URI assets]
- Resolving [notification polling URI assets]

[tile images]:
https://msdn.microsoft.com/library/dn320426(v%3Dvs.85)#specifying-tile-images-and-assets
[badge polling uri assets]:
https://msdn.microsoft.com/library/dn320426(v%3Dvs.85)#defining-badge-polling
[notification polling uri assets]:
https://msdn.microsoft.com/library/dn320426(v%3Dvs.85)#defining-notification-polling

## Usage

```js
// webpack.config.js
export default {
// ...
module: {
rules: [
{
test: /\.(png|jpg|gif|xml)$/i,
type: "asset/resource",
},
{
test: /\/browserconfig\.xml$/i,
type: "asset/resource",
use: "@iconduit/browserconfig-loader",
},
],
},
};
```
50 changes: 50 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import js from "@eslint/js";
import import_ from "eslint-plugin-import";
import node from "eslint-plugin-n";
import promise from "eslint-plugin-promise";
import globals from "globals";
import ts from "typescript-eslint";

export default ts.config(
{
ignores: [".makefiles", "artifacts", "test/fixture"],
},
js.configs.recommended,
// eslint-disable-next-line import/no-named-as-default-member
...ts.configs.recommended,
node.configs["flat/recommended-module"],
import_.flatConfigs.recommended,
import_.flatConfigs.typescript,
promise.configs["flat/recommended"],
{
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
globals: {
...globals.es2022,
...globals.node,
},
},
settings: {
"import/resolver": {
typescript: true,
node: true,
},
},
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{
// allow unused args if they start with _
argsIgnorePattern: "^_",
},
],
// handled by import/no-unresolved
"n/no-missing-import": "off",
// don't check for unsupported features - too much config to make this work
"n/no-unsupported-features/es-builtins": "off",
"n/no-unsupported-features/es-syntax": "off",
"n/no-unsupported-features/node-builtins": "off",
},
},
);
83 changes: 83 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"name": "@iconduit/browserconfig-loader",
"version": "0.0.0",
"description": "A Webpack loader for browserconfig.xml files",
"keywords": [
"app",
"browser",
"browserconfig",
"config",
"iconduit",
"import",
"loader",
"web",
"webpack"
],
"repository": "iconduit/browserconfig-loader",
"bugs": "https://github.com/iconduit/browserconfig-loader/issues",
"homepage": "https://github.com/iconduit/browserconfig-loader",
"author": "Erin Millard <[email protected]>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"type": "module",
"types": "artifacts/dist/esm/index.d.ts",
"main": "artifacts/dist/esm/index.js",
"exports": {
".": {
"import": {
"types": "./artifacts/dist/esm/index.d.ts",
"default": "./artifacts/dist/esm/index.js"
},
"require": {
"types": "./artifacts/dist/cjs/index.d.ts",
"default": "./artifacts/dist/cjs/index.js"
},
"default": {
"types": "./artifacts/dist/esm/index.d.ts",
"default": "./artifacts/dist/esm/index.js"
}
}
},
"sideEffects": false,
"files": [
"/artifacts/dist/"
],
"engines": {
"node": ">=18"
},
"scripts": {
"prepublishOnly": "make artifacts/dist"
},
"dependencies": {
"fast-xml-parser": "^4.5.0",
"loader-utils": "^3.3.1"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.16.4",
"@eslint/js": "^9.13.0",
"@skypack/package-check": "^0.2.2",
"@types/eslint__js": "^8.42.3",
"@types/loader-utils": "^2.0.6",
"@types/node": "^22.8.5",
"@vitest/coverage-v8": "^2.1.4",
"@vitest/eslint-plugin": "^1.1.7",
"eslint": "^9.13.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-n": "^17.12.0",
"eslint-plugin-promise": "^7.1.0",
"globals": "^15.11.0",
"html-bundler-webpack-plugin": "^4.1.4",
"prettier": "^3.3.3",
"prettier-plugin-organize-imports": "^4.1.0",
"publint": "^0.2.12",
"typescript": "^5.6.3",
"typescript-eslint": "^8.12.2",
"vitest": "^2.1.4",
"webpack": "^5 <5.96.0",
"webpack-cli": "^5.1.4"
}
}
81 changes: 81 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { XMLBuilder, XMLParser } from "fast-xml-parser";
import { resolve } from "path";
import { callbackify } from "util";
import type { LoaderDefinitionFunction } from "webpack";

const browserConfigLoader: LoaderDefinitionFunction = function (source) {
const XMLOptions = {
attributeNamePrefix: "@_",
ignoreAttributes: false,
suppressEmptyNode: true,
};
const config = new XMLParser(XMLOptions).parse(source) as object;

if (
!hasProperty(config, "browserconfig") ||
!hasProperty(config.browserconfig, "msapplication")
) {
return source;
}

const { msapplication } = config.browserconfig;
const tasks: Promise<void>[] = [];

const loadSrc = async (obj: unknown): Promise<void> => {
if (!hasProperty(obj, "@_src") || typeof obj["@_src"] !== "string") return;

obj["@_src"] = await this.importModule(resolve(this.context, obj["@_src"]));
};

if (hasProperty(msapplication, "tile")) {
const { tile } = msapplication;

for (const tag of [
"TileImage",
"square70x70logo",
"square150x150logo",
"square310x310logo",
"wide310x150logo",
]) {
if (hasProperty(tile, tag)) tasks.push(loadSrc(tile[tag]));
}
}

if (hasProperty(msapplication, "badge")) {
const { badge } = msapplication;

if (hasProperty(badge, "polling-uri")) {
tasks.push(loadSrc(badge["polling-uri"]));
}
}

if (hasProperty(msapplication, "notification")) {
const { notification } = msapplication;

for (const tag of [
"polling-uri",
"polling-uri2",
"polling-uri3",
"polling-uri4",
"polling-uri5",
]) {
if (hasProperty(notification, tag))
tasks.push(loadSrc(notification[tag]));
}
}

callbackify(async () => {
await Promise.all(tasks);

return new XMLBuilder(XMLOptions).build(config);
})(this.async());
};

export default browserConfigLoader;

function hasProperty<T extends string>(
obj: unknown,
tag: T,
): obj is { [K in T]: unknown } {
return typeof obj === "object" && obj != null && tag in obj;
}
Loading

0 comments on commit 2ab9fee

Please sign in to comment.