Stackline Open Source maintained continuation

Webpack · Sass source maps · MIT

@stackline/resolve-url-loader

Rebase CSS url() values against the Sass file that declared them while retaining the callable loader and join contract of resolve-url-loader@5.0.0.

npm install @stackline/resolve-url-loader

Keep existing webpack rules unchanged with "resolve-url-loader": "npm:@stackline/resolve-url-loader@1.0.0".

01 / Source-map model

Resolve from the source, not the generated CSS.

Sass leaves relative asset URLs unchanged even when a partial is compiled into CSS somewhere else. This loader uses the incoming source map to locate the original Sass declaration, tests candidate paths through webpack's loader filesystem, rewrites the URL, and emits CSS plus an adjusted map.

Webpack evaluates a use array from right to left. Put this loader immediately before sass-loader in the array so it runs immediately after Sass, and enable source maps on Sass, this loader, and the webpack build.

02 / Webpack setup

Place it after Sass executes.

In webpack configuration order, resolve-url-loader appears to the left of sass-loader. The example keeps source maps available throughout the chain.

module.exports = {
  devtool: 'source-map',
  module: {
    rules: [{
      test: /\.scss$/,
      use: [
        'css-loader',
        {
          loader: '@stackline/resolve-url-loader',
          options: { sourceMap: true }
        },
        {
          loader: 'sass-loader',
          options: { sourceMap: true }
        }
      ]
    }]
  }
}

03 / Public API

One loader, five join helpers.

Open the complete API reference →

loader(content, sourceMap)

Callable CommonJS root

Webpack binds its loader context as this. The loader validates options, adjusts the input map, rewrites CSS asynchronously, and completes through this.async().

createJoinFunction(name, implementation)

Build a custom join

Wrap an attempt-producing implementation with validation, debug formatting, successful-result selection, and fallback selection. The result has the required two-argument outer shape.

defaultJoin(options, loader) → join(item)

Use original-source bases

The default join checks the declaration substring, value, property, and selector bases in order, or the configured root for an absolute URL.

asGeneratorcreateJoinImplementationcreateJoinFunctiondefaultJoinGeneratordefaultJoin

Exact root shape: the CommonJS export is a callable loader with those five enumerable properties, in that order. That is six callable public values total—the loader itself plus five attached helpers—not six attached helper properties.

04 / Join contract

Custom search stays lazy and bounded.

A join function takes exactly (options, loader) and returns a function that takes exactly one item. A generator receives the item, options, and loader, then returns an array or iterator of base/URI candidates. asGenerator() normalizes arrays and tuple values while removing duplicates.

createJoinImplementation() evaluates at most 100,000 iterator steps, accepts only valid absolute base directories, and records ordered attempts. createJoinFunction() validates each attempt and returns the first success, otherwise the first fallback, otherwise null.

  1. 01
    generateYield ordered base and URI candidates
  2. 02
    normalizeConvert values to unique two-item tuples
  3. 03
    attemptJoin and stat through webpack's filesystem
  4. 04
    selectFirst success, then first fallback, then null
  5. 05
    diagnoseDeduplicated attempt tables when debug is enabled

05 / Intentional correction

A Windows drive URL becomes a Windows drive path.

Windows host input file:///D:/project/src/card.scss
Corrected Windows path D:/project/src/card.scss

Upstream removed the file:// prefix but could leave /D:/..., which fails Windows absolute-path and directory checks. On Windows only, version 1.0.0 removes that one extra slash when the remaining value starts with a drive-letter segment. POSIX hosts retain their leading slash, including a valid drive-looking path such as file:///D:/literal-posix-name.

06 / Historical deep entries

Packed paths stay addressable.

Read the complete compatibility boundary →

There is intentionally no restrictive exports map. Existing consumers can continue to resolve the eight JavaScript entries shipped beneath lib/; corresponding first-party declarations are additive. Vendored codec files are implementation and provenance material, not a new supported public API.

lib/engine/postcssPostCSS transformation engine
lib/file-protocolSource-map file protocol helpers
lib/join-functionFive join helper exports
lib/join-function/debugPath and attempt diagnostics
lib/join-function/fs-utilsWebpack filesystem predicates
lib/log-to-test-harnessHistorical harness serialization
lib/position-algerbraPreserved misspelled position module
lib/value-processorCSS URL tokenizer and rewriter

07 / Codec provenance

Vendored deliberately, attributed completely.

The source-map processor and codecs from adjust-sourcemap-loader@4.0.0, commit 5f173eef, are vendored beneath lib/vendor/adjust-sourcemap-loader/. Keeping that exact implementation local prevents an unrelated dependency release from silently changing source-path decoding.

The vendored files retain their MIT license. This package also retains the upstream resolve-url-loader MIT notice and inventories the four exact production dependencies: loader-utils@2.0.4, postcss@8.5.26, regex-parser@2.3.1, and source-map@0.6.1.

This is an independent maintained continuation. It is not affiliated with or endorsed by Ben Holloway or the upstream resolve-url-loader project.

Inspect third-party provenance →

08 / Migration

Choose the size of your diff.

Full migration checklist →
Lowest change

Alias the dependency

"resolve-url-loader":
  "npm:@stackline/resolve-url-loader@1.0.0"

Existing webpack loader names and require('resolve-url-loader/lib/...') calls stay unchanged. Commit the manifest and lockfile together.

Explicit adoption

Use the scoped name

import loader, { defaultJoin }
  from '@stackline/resolve-url-loader'

CommonJS remains the runtime compatibility anchor. The ESM facade, named imports, and TypeScript declarations are additive packaging surfaces.

Re-test in your application webpack 4 and 5Sass source mapsquery and hash retentioncustom joinsdeep importsWindows paths

09 / Runtime boundary

Build tooling, not browser code.

This package is a Node.js webpack loader. It expects webpack's loader context, callback, filesystem, and source-map pipeline, and it imports Node filesystem, path, operating-system, and utility modules. It has no browser runtime, browser bundle, DOM API, Vite/PostCSS plugin contract, or standalone CSS transformation command.

Source maps and CSS are build inputs. Custom join functions execute as trusted webpack configuration code with access to the loader and its filesystem. Bound input sizes and build time, keep remote fetching outside the loader, and do not treat successful path rebasing as containment or URL approval.

The implementation does not add support for image-set(), invent automatic root search, or promise identical filesystem behavior across platforms. A missing or unusable input map produces the established warning/error behavior rather than reconstructing source provenance.

Read the security and reporting boundary →

10 / Reference files

The contract is inspectable.