summaryrefslogtreecommitdiffstats
path: root/browser/components/storybook/.storybook
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/storybook/.storybook')
-rw-r--r--browser/components/storybook/.storybook/chrome-uri-loader.js98
-rw-r--r--browser/components/storybook/.storybook/main.js67
-rw-r--r--browser/components/storybook/.storybook/preview-head.html5
-rw-r--r--browser/components/storybook/.storybook/preview.mjs75
4 files changed, 245 insertions, 0 deletions
diff --git a/browser/components/storybook/.storybook/chrome-uri-loader.js b/browser/components/storybook/.storybook/chrome-uri-loader.js
new file mode 100644
index 0000000000..b558732152
--- /dev/null
+++ b/browser/components/storybook/.storybook/chrome-uri-loader.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* eslint-env node */
+
+/**
+ * This file contains a webpack loader which has the goal of rewriting chrome://
+ * URIs to local paths. This allows JS files loaded in Storybook to load JS
+ * files using their chrome:// URI. Using the chrome:// URI is avoidable in many
+ * cases, however in some cases such as importing the lit.all.mjs file from
+ * browser/components/ there is no way to avoid it on the Firefox side.
+ *
+ * This loader depends on the `./mach storybook manifest` step to generate the
+ * rewrites.js file. That file exports an object with the files we know how to
+ * rewrite chrome:// URIs for.
+ *
+ * This loader allows code like this to work with storybook:
+ *
+ * import { html } from "chrome://global/content/vendor/lit.all.mjs";
+ * import "chrome://global/content/elements/moz-button-group.mjs";
+ *
+ * In this example the file would be rewritten in the webpack bundle as:
+ *
+ * import { html } from "toolkit/content/widgets/vendor/lit.all.mjs";
+ * import "toolkit/content/widgets/moz-button-group/moz-button-group.mjs";
+ */
+
+const path = require("path");
+
+// Object<ChromeURI, LocalPath> - This is generated by `./mach storybook manifest`.
+const rewrites = require("./rewrites.js");
+
+const projectRoot = path.join(process.cwd(), "../../..");
+
+/**
+ * Return an array of the unique chrome:// URIs referenced in this file.
+ *
+ * @param {string} source - The source file to scan.
+ * @returns {string[]} Unique list of chrome:// URIs
+ */
+function getReferencedChromeUris(source) {
+ // We can only rewrite files that get imported. Which means currently we only
+ // support .js and .mjs. In the future we hope to rewrite .css and .svg.
+ const chromeRegex = /chrome:\/\/.*?\.(js|mjs)/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]);
+ }
+ return [...matches];
+}
+
+/**
+ * Replace references to chrome:// 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 = rewrites[chromeUri];
+ if (localRelativePath) {
+ localRelativePath = localRelativePath.replaceAll("\\", "/");
+ // Store the mapping to a local path for this chrome URI.
+ chromeUriToLocalPath.set(chromeUri, localRelativePath);
+ // Tell webpack the file being handled depends on the referenced file.
+ this.addDependency(path.join(projectRoot, localRelativePath));
+ }
+ }
+ // Rewrite the source file with mapped chrome:// URIs.
+ let rewrittenSource = source;
+ for (let [chromeUri, localPath] of chromeUriToLocalPath.entries()) {
+ rewrittenSource = rewrittenSource.replaceAll(chromeUri, localPath);
+ }
+ return rewrittenSource;
+}
+
+/**
+ * The WebpackLoader export. Runs async since apparently that's preferred.
+ *
+ * @param {string} source - The source to rewrite.
+ * @param {Map} sourceMap - Source map data, unused.
+ * @param {Object} meta - Metadata, unused.
+ */
+module.exports = async function chromeUriLoader(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);
+ // Give webpack the rewritten content.
+ callback(null, newSource);
+};
diff --git a/browser/components/storybook/.storybook/main.js b/browser/components/storybook/.storybook/main.js
new file mode 100644
index 0000000000..625f3ab526
--- /dev/null
+++ b/browser/components/storybook/.storybook/main.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* eslint-env node */
+
+const path = require("path");
+const projectRoot = path.resolve(__dirname, "../../../../");
+
+// ./mach environment --format json
+// topobjdir should be the build location
+
+module.exports = {
+ stories: [
+ "../stories/**/*.stories.mdx",
+ "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
+ `${projectRoot}/toolkit/**/*.stories.@(js|jsx|mjs|ts|tsx)`,
+ ],
+ // Additions to the staticDirs might also need to get added to
+ // MozXULElement.importCss in preview.mjs to enable auto-reloading.
+ staticDirs: [
+ `${projectRoot}/toolkit/content/widgets/`,
+ `${projectRoot}/browser/themes/shared/`,
+ ],
+ addons: [
+ "@storybook/addon-links",
+ "@storybook/addon-essentials",
+ "@storybook/addon-a11y",
+ ],
+ framework: "@storybook/web-components",
+ webpackFinal: async (config, { configType }) => {
+ // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
+ // You can change the configuration based on that.
+ // 'PRODUCTION' is used when building the static version of storybook.
+
+ // Make whatever fine-grained changes you need
+ config.resolve.alias.browser = `${projectRoot}/browser`;
+ config.resolve.alias.toolkit = `${projectRoot}/toolkit`;
+ config.resolve.alias[
+ "toolkit-widgets"
+ ] = `${projectRoot}/toolkit/content/widgets/`;
+
+ config.module.rules.push({
+ test: /\.ftl$/,
+ type: "asset/source",
+ });
+
+ config.module.rules.push({
+ test: /\.mjs/,
+ loader: path.resolve(__dirname, "./chrome-uri-loader.js"),
+ });
+
+ config.optimization = {
+ splitChunks: false,
+ runtimeChunk: false,
+ sideEffects: false,
+ usedExports: false,
+ concatenateModules: false,
+ minimizer: [],
+ };
+
+ // Return the altered config
+ return config;
+ },
+ core: {
+ builder: "webpack5",
+ },
+};
diff --git a/browser/components/storybook/.storybook/preview-head.html b/browser/components/storybook/.storybook/preview-head.html
new file mode 100644
index 0000000000..484bb64f73
--- /dev/null
+++ b/browser/components/storybook/.storybook/preview-head.html
@@ -0,0 +1,5 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
diff --git a/browser/components/storybook/.storybook/preview.mjs b/browser/components/storybook/.storybook/preview.mjs
new file mode 100644
index 0000000000..49f3bc4c47
--- /dev/null
+++ b/browser/components/storybook/.storybook/preview.mjs
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { DOMLocalization } from "@fluent/dom";
+import { FluentBundle, FluentResource } from "@fluent/bundle";
+
+// Base Fluent set up.
+let storybookBundle = new FluentBundle("en-US");
+let loadedResources = new Set();
+function* generateBundles() {
+ yield* [storybookBundle];
+}
+document.l10n = new DOMLocalization([], generateBundles);
+document.l10n.connectRoot(document.documentElement);
+
+// Any fluent imports should go through MozXULElement.insertFTLIfNeeded.
+window.MozXULElement = {
+ async insertFTLIfNeeded(name) {
+ if (loadedResources.has(name)) {
+ return;
+ }
+
+ // This should be browser, locales-preview or toolkit.
+ let [root, ...rest] = name.split("/");
+ let ftlContents;
+
+ // TODO(mstriemer): These seem like they could be combined but I don't want
+ // to fight with webpack anymore.
+ if (root == "toolkit") {
+ // eslint-disable-next-line no-unsanitized/method
+ let imported = await import(
+ /* webpackInclude: /.*[\/\\].*\.ftl$/ */
+ `toolkit/locales/en-US/${name}`
+ );
+ ftlContents = imported.default;
+ } else if (root == "browser") {
+ // eslint-disable-next-line no-unsanitized/method
+ let imported = await import(
+ /* webpackInclude: /.*[\/\\].*\.ftl$/ */
+ `browser/locales/en-US/${name}`
+ );
+ ftlContents = imported.default;
+ } else if (root == "locales-preview") {
+ // eslint-disable-next-line no-unsanitized/method
+ let imported = await import(
+ /* webpackInclude: /\.ftl$/ */
+ `browser/locales-preview/${rest}`
+ );
+ ftlContents = imported.default;
+ }
+
+ if (loadedResources.has(name)) {
+ // Seems possible we've attempted to load this twice before the first call
+ // resolves, so once the first load is complete we can abandon the others.
+ return;
+ }
+
+ let ftlResource = new FluentResource(ftlContents);
+ storybookBundle.addResource(ftlResource);
+ loadedResources.add(name);
+ document.l10n.translateRoots();
+ },
+
+ // For some reason Storybook doesn't watch the static folder. By creating a
+ // method with a dynamic import we can pull the desired files into the bundle.
+ async importCss(name) {
+ // eslint-disable-next-line no-unsanitized/method
+ let file = await import(
+ /* webpackInclude: /.*[\/\\].*\.css$/ */
+ `browser/themes/shared/${name}`
+ );
+ return file;
+ },
+};