commit b3b5d4ab09d0073a31fc517d599141f9b5776a89
parent fd161a24aefc27e6c8997f2f4601d465a1d521d3
Author: Florian Zia <fzia@mozilla.com>
Date: Fri, 12 Dec 2025 13:00:56 +0000
Bug 2000992 - Add moz-src:/// URI support to storybook loaders r=tgiles
Differential Revision: https://phabricator.services.mozilla.com/D275607
Diffstat:
3 files changed, 117 insertions(+), 36 deletions(-)
diff --git a/browser/components/storybook/.storybook/main.js b/browser/components/storybook/.storybook/main.js
@@ -5,7 +5,7 @@
const path = require("path");
const webpack = require("webpack");
-const rewriteChromeUri = require("./chrome-uri-utils.js");
+const { rewriteChromeUri, rewriteMozSrcUri } = require("./moz-uri-utils.js");
const mdIndexer = require("./markdown-story-indexer.js");
const projectRoot = path.resolve(__dirname, "../../../../");
@@ -105,6 +105,13 @@ module.exports = {
})
);
+ config.plugins.push(
+ // Rewrite moz-src:/// URI imports to file system paths.
+ new webpack.NormalModuleReplacementPlugin(/^moz-src:\/\/\//, resource => {
+ resource.request = rewriteMozSrcUri(resource.request);
+ })
+ );
+
config.module.rules.push({
test: /\.ftl$/,
type: "asset/source",
@@ -113,12 +120,12 @@ module.exports = {
config.module.rules.push({
test: /\.m?js$/,
exclude: /\.storybook/,
- use: [{ loader: path.resolve(__dirname, "./chrome-styles-loader.js") }],
+ use: [{ loader: path.resolve(__dirname, "./moz-styles-loader.js") }],
});
// Replace the default CSS rule with a rule to emit a separate CSS file and
// export the URL. This allows us to rewrite the source to use CSS imports
- // via the chrome-styles-loader.
+ // via the moz-styles-loader.
let cssFileTest = /\.css$/.toString();
let cssRuleIndex = config.module.rules.findIndex(
rule => rule.test.toString() === cssFileTest
diff --git a/browser/components/storybook/.storybook/moz-styles-loader.js b/browser/components/storybook/.storybook/moz-styles-loader.js
@@ -4,10 +4,11 @@
/* eslint-env node */
/**
- * This file contains a webpack loader which rewrites JS source files to use CSS
- * imports when running in Storybook. This allows JS files loaded in Storybook to use
- * chrome:// URIs when loading external stylesheets without having to worry
- * about Storybook being able to find and detect changes to the files.
+ * This file contains a webpack loader which rewrites JS source files to use
+ * CSS imports when running in Storybook. This allows JS files loaded in
+ * Storybook to use chrome:// and moz-src:/// URIs when loading external
+ * stylesheets without having to worry about Storybook being able to find and
+ * detect changes to the files.
*
* This loader allows Lit-based custom element code like this to work with
* Storybook:
@@ -54,53 +55,109 @@
* </template>
* `;
* }
+ *
+ * For moz-src:/// URIs the path is resolved relative to the importing file:
+ *
+ * render() {
+ * return html`
+ * <link rel="stylesheet" href="moz-src:///third_party/js/prosemirror/prosemirror-view/style/prosemirror.css" />
+ * ...
+ * `;
+ * }
+ *
+ * Gets rewritten to:
+ *
+ * import prosemirrorStyles from "../../../../third_party/js/prosemirror/prosemirror-view/style/prosemirror.css";
+ * ...
+ * render() {
+ * return html`
+ * <link rel="stylesheet" href=${prosemirrorStyles} />
+ * ...
+ * `;
+ * }
*/
const path = require("path");
-const projectRoot = path.join(process.cwd(), "../../..");
-const rewriteChromeUri = require("./chrome-uri-utils.js");
+const projectRoot = path.resolve(__dirname, "../../../../");
+const { rewriteChromeUri, rewriteMozSrcUri } = require("./moz-uri-utils.js");
/**
- * Return an array of the unique chrome:// CSS URIs referenced in this file.
+ * Return an array of the unique chrome:// and moz-src:/// CSS URIs referenced in this file.
*
* @param {string} source - The source file to scan.
- * @returns {string[]} Unique list of chrome:// CSS URIs
+ * @returns {string[]} Unique list of chrome:// and moz-src:/// CSS URIs
*/
-function getReferencedChromeUris(source) {
- const chromeRegex = /chrome:\/\/.*?\.css/g;
+function getReferencedCssUris(source) {
+ const cssRegexes = [/chrome:\/\/.*?\.css/g, /moz-src:\/\/\/.*?\.css/g];
const matches = new Set();
- for (let match of source.matchAll(chromeRegex)) {
- // Add the full URI to the set of matches.
- matches.add(match[0]);
+ for (let regex of cssRegexes) {
+ for (let match of source.matchAll(regex)) {
+ // Add the full URI to the set of matches.
+ matches.add(match[0]);
+ }
}
return [...matches];
}
/**
- * Replace references to chrome:// URIs with the relative path on disk from the
- * project root.
+ * Resolve a CSS URI to a local path and its absolute dependency path.
+ *
+ * @param {string} cssUri - The CSS URI to resolve.
+ * @param {string} resourcePath - The path of the file.
+ * @returns {{localPath: string, dependencyPath: string}} The local relative path and absolute dependency path.
+ */
+function resolveCssUri(cssUri, resourcePath) {
+ let localPath = "";
+ let dependencyPath = "";
+
+ if (cssUri.startsWith("chrome://")) {
+ localPath = rewriteChromeUri(cssUri);
+ if (localPath) {
+ dependencyPath = path.join(projectRoot, localPath);
+ }
+ }
+ if (cssUri.startsWith("moz-src:///")) {
+ const absolutePath = rewriteMozSrcUri(cssUri);
+ if (absolutePath) {
+ localPath = path.relative(path.dirname(resourcePath), absolutePath);
+ // Ensure the path is treated as a relative file and not a package when imported.
+ if (!localPath.startsWith(".")) {
+ localPath = `./${localPath}`;
+ }
+ dependencyPath = absolutePath;
+ }
+ }
+
+ return { localPath, dependencyPath };
+}
+
+/**
+ * Replace references to chrome:// and moz-src:/// URIs with the relative path
+ * on disk from the project root.
*
* @this {WebpackLoader} https://webpack.js.org/api/loaders/
* @param {string} source - The source file to update.
* @returns {string} The updated source.
*/
-async function rewriteChromeUris(source) {
- const chromeUriToLocalPath = new Map();
- // We're going to rewrite the chrome:// URIs, find all referenced URIs.
- let chromeDependencies = getReferencedChromeUris(source);
- for (let chromeUri of chromeDependencies) {
- let localRelativePath = rewriteChromeUri(chromeUri);
- if (localRelativePath) {
- localRelativePath = localRelativePath.replaceAll("\\", "/");
- // Store the mapping to a local path for this chrome URI.
- chromeUriToLocalPath.set(chromeUri, localRelativePath);
+async function rewriteCssUris(source) {
+ const cssUriToLocalPath = new Map();
+ // We're going to rewrite the chrome:// and moz-src:/// URIs, find all referenced URIs.
+ let cssDependencies = getReferencedCssUris(source);
+ for (let cssUri of cssDependencies) {
+ const { localPath, dependencyPath } = resolveCssUri(
+ cssUri,
+ this.resourcePath
+ );
+ if (localPath) {
+ // Store the mapping to a local path for this URI.
+ cssUriToLocalPath.set(cssUri, localPath);
// Tell webpack the file being handled depends on the referenced file.
- this.addMissingDependency(path.join(projectRoot, localRelativePath));
+ this.addMissingDependency(dependencyPath);
}
}
- // Rewrite the source file with mapped chrome:// URIs.
+ // Rewrite the source file with mapped chrome:// and moz-src:/// URIs.
let rewrittenSource = source;
- for (let [chromeUri, localPath] of chromeUriToLocalPath.entries()) {
+ for (let [cssUri, localPath] of cssUriToLocalPath.entries()) {
// Generate an import friendly variable name for the default export from
// the CSS file e.g. __chrome_styles_loader__moztoggleStyles.
let cssImport = `__chrome_styles_loader__${path
@@ -112,10 +169,10 @@ async function rewriteChromeUris(source) {
path.basename(this.resourcePath) == "moz-label.mjs" ||
this.resourcePath.endsWith(".js")
) {
- rewrittenSource = rewrittenSource.replaceAll(`"${chromeUri}"`, cssImport);
+ rewrittenSource = rewrittenSource.replaceAll(`"${cssUri}"`, cssImport);
} else {
rewrittenSource = rewrittenSource.replaceAll(
- chromeUri,
+ cssUri,
`\$\{${cssImport}\}`
);
}
@@ -134,12 +191,12 @@ async function rewriteChromeUris(source) {
* @param {Map} sourceMap - Source map data, unused.
* @param {object} meta - Metadata, unused.
*/
-module.exports = async function chromeUriLoader(source) {
+module.exports = async function mozUriLoader(source) {
// Get a callback to tell webpack when we're done.
const callback = this.async();
// Rewrite the source async since that appears to be preferred (and will be
// necessary once we support rewriting CSS/SVG/etc).
- const newSource = await rewriteChromeUris.call(this, source);
+ const newSource = await rewriteCssUris.call(this, source);
// Give webpack the rewritten content.
callback(null, newSource);
};
diff --git a/browser/components/storybook/.storybook/moz-uri-utils.js b/browser/components/storybook/.storybook/moz-uri-utils.js
@@ -3,7 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env node */
+const path = require("path");
const [prefixMap, aliasMap, sourceMap] = require("./chrome-map.js");
+const projectRoot = path.resolve(__dirname, "../../../../");
function rewriteChromeUri(uri) {
if (uri in aliasMap) {
@@ -27,4 +29,19 @@ function rewriteChromeUri(uri) {
return "";
}
-module.exports = rewriteChromeUri;
+function rewriteMozSrcUri(uri) {
+ if (!uri.startsWith("moz-src:///")) {
+ return "";
+ }
+ const relativePath = uri.replace(/^moz-src:\/\/\//, "");
+ const resolvedPath = path.resolve(projectRoot, relativePath);
+ if (!resolvedPath.startsWith(projectRoot)) {
+ return "";
+ }
+ return resolvedPath;
+}
+
+module.exports = {
+ rewriteChromeUri,
+ rewriteMozSrcUri,
+};