blob: 169ac46bd5c6cde32fe199076cee595bb5a3a2c7 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Plugin for rollup to correctly resolve resources.
*/
const path = require('path');
function normalizeSlashes(filepath) {
return filepath.replace(/\\/gi, '/');
}
function relativePath(from, to) {
return normalizeSlashes(path.relative(from, to));
}
function joinPaths(a, b) {
return normalizeSlashes(path.join(a, b));
}
/**
* Determines the path to |source| from the root directory based on the origin
* of the request for it. For example, if ../a/b.js is requested from
* c/d/e/f.js, the returned path will be c/d/a/b.js.
* @param {string} origin The origin of the request.
* @param {string} source The requested resource
* @return {string} Path to source from the root directory.
*/
function combinePaths(origin, source) {
const originDir = origin ? path.dirname(origin) : '';
return normalizeSlashes(path.normalize(path.join(originDir, source)));
}
/**
* @param {string} source Requested resource
* @param {string} origin The origin of the request
* @param {string} urlPrefix The URL prefix to check |source| for.
* @param {string} urlSrcPath The path that corresponds to the URL prefix.
* @param {!Array<string>} excludes List of paths that should be excluded from
* bundling.
* @return {string} The path to |source|. If |source| does not map to
* |urlSrcPath|, returns an empty string. If |source| maps to a location
* in |urlSrcPath| but is listed in |excludes|, returns the URL
* corresponding to |source|. Otherwise, returns the full path for |source|.
*/
function getPathForUrl(source, origin, urlPrefix, urlSrcPath, excludes) {
let schemeRelativeUrl = urlPrefix;
if (urlPrefix.includes('://')) {
const url = new URL(urlPrefix);
schemeRelativeUrl = '//' + url.host + url.pathname;
}
let pathFromUrl = '';
if (source.startsWith(urlPrefix)) {
pathFromUrl = source.slice(urlPrefix.length);
} else if (source.startsWith(schemeRelativeUrl)) {
pathFromUrl = source.slice(schemeRelativeUrl.length);
} else if (
!source.includes('://') && !source.startsWith('//') && !!origin &&
origin.startsWith(urlSrcPath)) {
// Relative import from a file that lives in urlSrcPath.
pathFromUrl = combinePaths(relativePath(urlSrcPath, origin), source);
}
if (!pathFromUrl) {
return '';
}
if (excludes.includes(urlPrefix + pathFromUrl) ||
excludes.includes(schemeRelativeUrl + pathFromUrl)) {
return urlPrefix + pathFromUrl;
}
return joinPaths(urlSrcPath, pathFromUrl);
}
export default function plugin(
rootPath, hostUrl, excludes, externalPaths, allowEmptyExtension) {
const urlsToPaths = new Map();
for (const externalPath of externalPaths) {
const [url, path] = externalPath.split('|', 2);
urlsToPaths.set(url, path);
}
return {
name: 'webui-path-resolver-plugin',
resolveId(source, origin) {
if (path.extname(source) === '' && !allowEmptyExtension) {
this.error(
`Invalid path (missing file extension) was found: ${source}`);
}
// Normalize origin paths to use forward slashes.
if (origin) {
origin = normalizeSlashes(origin);
}
for (const [url, path] of urlsToPaths) {
const resultPath = getPathForUrl(source, origin, url, path, excludes);
if (resultPath.includes('://') || resultPath.startsWith('//')) {
return {id: resultPath, external: 'absolute'};
} else if (resultPath) {
return resultPath;
}
}
// Not in the URL path map -> should be in the root directory.
// Check if it should be excluded from the bundle. Check for an absolute
// path before combining with the origin path.
const fullSourcePath =
(source.startsWith('/') && !source.startsWith(rootPath)) ?
path.join(rootPath, source) :
combinePaths(origin, source);
if (fullSourcePath.startsWith(rootPath)) {
const pathFromRoot = relativePath(rootPath, fullSourcePath);
if (excludes.includes(pathFromRoot)) {
const url = new URL(pathFromRoot, hostUrl);
return {id: url.href, external: 'absolute'};
}
}
return null;
},
};
}