-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3d871ea
Showing
405 changed files
with
156,409 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,397 @@ | ||
const { EleventyHtmlBasePlugin } = require("@11ty/eleventy"); | ||
|
||
const BROWSERS = [ | ||
"chrome", | ||
"chrome_android", | ||
"edge", | ||
"firefox", | ||
"firefox_android", | ||
"safari", | ||
"safari_ios", | ||
]; | ||
|
||
const MDN_URL_ROOT = "https://developer.mozilla.org/docs/web/"; | ||
|
||
function processMdnPath(path, area) { | ||
if (area === "api") { | ||
return path.replace(/\//g, "."); | ||
} | ||
|
||
if (area === "html") { | ||
if (path.startsWith("Global_attributes/")) { | ||
return path.substring("Global_attributes/".length) + " global attribute"; | ||
} | ||
|
||
if (path.startsWith("Element/")) { | ||
const attribute = path.includes("#") ? path.substring(path.indexOf("#") + 1) : null; | ||
if (attribute) { | ||
return `<${path.substring("Element/".length, path.indexOf("#"))} ${attribute}> attribute`; | ||
} else { | ||
return `<${path.substring("Element/".length)}> element`; | ||
} | ||
} | ||
|
||
if (path.toLowerCase().startsWith("attributes/") && path.split("/").length === 3) { | ||
const [_, attr, value] = path.split("/"); | ||
return `${attr}="${value}" attribute`; | ||
} | ||
} | ||
|
||
if (area === "css") { | ||
if (path.startsWith("color_value/")) { | ||
return `${path.substring("color_value/".length)} color value`; | ||
} else if (path.startsWith("gradient/")) { | ||
return `${path.substring("gradient/".length)}() gradient function`; | ||
} else if (path.startsWith("::")) { | ||
return `${path} pseudo-element`; | ||
} else if (path.startsWith(":")) { | ||
return `${path} pseudo-class`; | ||
} else if (path.startsWith("transform-function/")) { | ||
return `${path.substring("transform-function/".length)}() function`; | ||
} else if (path.includes("#")) { | ||
const [property, value] = path.split("#"); | ||
return `${property}: ${value}; declaration`; | ||
} else { | ||
return `${path} property`; | ||
} | ||
} | ||
|
||
if (area === "javascript") { | ||
if (path.toLowerCase().startsWith("reference/global_objects/")) { | ||
const object = path.substring("reference/global_objects/".length); | ||
if (object.includes("/")) { | ||
return object.replace(/\//g, "."); | ||
} else { | ||
return `${object} global object`; | ||
} | ||
} | ||
|
||
if (path.toLowerCase().startsWith("reference/operators/")) { | ||
return `${path.substring("reference/operators/".length)} operator`; | ||
} | ||
|
||
if (path.toLowerCase().startsWith("reference/statements/")) { | ||
return `${path.substring("reference/statements/".length)} statement`; | ||
} | ||
} | ||
|
||
return path; | ||
} | ||
|
||
function processMdnUrl(url) { | ||
let path = url.substring(MDN_URL_ROOT.length); | ||
const area = path.split("/")[0].toLowerCase(); | ||
path = path.substring(area.length + 1); | ||
const title = processMdnPath(path, area); | ||
return { title, url, area } | ||
} | ||
|
||
// Add more data to a feature's object, based on what our templates need. | ||
function augmentFeatureData(id, feature, bcd) { | ||
// Add the id. | ||
feature.id = id; | ||
|
||
// Make the spec always an array. | ||
if (!feature.spec) { | ||
feature.spec = []; | ||
} else if (!Array.isArray(feature.spec)) { | ||
feature.spec = [feature.spec]; | ||
} | ||
|
||
const bcdKeysData = (feature.compat_features || []).map(key => { | ||
// Find the BCD entry for this key. | ||
const keyParts = key.split("."); | ||
let data = bcd; | ||
for (const part of keyParts) { | ||
data = data[part]; | ||
} | ||
|
||
return data && data.__compat ? { key, compat: data.__compat } : null; | ||
}).filter(data => !!data); | ||
|
||
// Add MDN doc links, if any. | ||
const mdnUrls = {}; | ||
let hasMdnUrls = false; | ||
for (const { compat } of bcdKeysData) { | ||
if (compat.mdn_url) { | ||
const urlData = processMdnUrl(compat.mdn_url); | ||
if (!mdnUrls[urlData.area]) { | ||
mdnUrls[urlData.area] = []; | ||
} | ||
hasMdnUrls = true; | ||
mdnUrls[urlData.area].push(urlData); | ||
} | ||
} | ||
|
||
feature.mdnUrls = mdnUrls; | ||
feature.hasMdnUrls = hasMdnUrls; | ||
|
||
// Add the BCD data to the feature. | ||
feature.bcdData = bcdKeysData; | ||
|
||
// Add impl_url links, if any, per browser. | ||
const browserImplUrls = Object.values(BROWSERS).reduce((acc, browser) => { | ||
acc[browser] = []; | ||
return acc; | ||
}, {}); | ||
|
||
for (const { compat } of bcdKeysData) { | ||
for (const browser of BROWSERS) { | ||
const browserSupport = compat.support[browser]; | ||
if (!browserSupport.version_added && browserSupport.impl_url) { | ||
browserImplUrls[browser] = [...new Set([...browserImplUrls[browser], browserSupport.impl_url])]; | ||
} | ||
} | ||
} | ||
|
||
feature.implUrls = browserImplUrls; | ||
} | ||
|
||
let features = null; | ||
let bcd = null; | ||
|
||
async function getDeps() { | ||
if (!features) { | ||
const module = await import("web-features"); | ||
features = module.default; | ||
} | ||
|
||
if (!bcd) { | ||
const json = await import("@mdn/browser-compat-data", { | ||
assert: { type: "json" }, | ||
}); | ||
bcd = json.default; | ||
} | ||
|
||
return { features, bcd }; | ||
} | ||
|
||
module.exports = function (eleventyConfig) { | ||
eleventyConfig.addPlugin(EleventyHtmlBasePlugin); | ||
eleventyConfig.addPassthroughCopy("site/assets"); | ||
|
||
eleventyConfig.addGlobalData("versions", async () => { | ||
const { default: webFeaturesPackageJson } = await import("./node_modules/web-features/package.json", { | ||
assert: { type: "json" }, | ||
}); | ||
|
||
const { bcd } = await getDeps(); | ||
|
||
return { | ||
"date": (new Date()).toLocaleDateString(), | ||
"webFeatures": webFeaturesPackageJson.version, | ||
"bcd": bcd.__meta.version | ||
}; | ||
}); | ||
|
||
// FIXME: Ideally, web-features would have this data. | ||
eleventyConfig.addGlobalData("browsers", async () => { | ||
const { bcd } = await getDeps(); | ||
|
||
return BROWSERS.map(browser => { | ||
return { | ||
id: browser, | ||
name: bcd.browsers[browser].name, | ||
releases: bcd.browsers[browser].releases | ||
}; | ||
}); | ||
}); | ||
|
||
eleventyConfig.addGlobalData("perMonth", async () => { | ||
const { features, bcd } = await getDeps(); | ||
|
||
const monthly = new Map(); | ||
|
||
for (const id in features) { | ||
const feature = features[id]; | ||
augmentFeatureData(id, feature, bcd); | ||
|
||
if (feature.status.baseline === "high") { | ||
// If this is a baseline high feature. | ||
const yearMonth = feature.status.baseline_high_date.substring(0, 7); | ||
if (!monthly.has(yearMonth)) { | ||
monthly.set(yearMonth, {}); | ||
} | ||
if (!monthly.get(yearMonth).high) { | ||
monthly.get(yearMonth).high = []; | ||
} | ||
monthly.get(yearMonth).high.push(feature); | ||
} else if (feature.status.baseline === "low") { | ||
// If this is a baseline low feature. | ||
const yearMonth = feature.status.baseline_low_date.substring(0, 7); | ||
if (!monthly.has(yearMonth)) { | ||
monthly.set(yearMonth, {}); | ||
} | ||
if (!monthly.get(yearMonth).low) { | ||
monthly.get(yearMonth).low = []; | ||
} | ||
monthly.get(yearMonth).low.push(feature); | ||
} else { | ||
// This is not a baseline feature, check each supported browser's release dates. | ||
for (const browser of BROWSERS) { | ||
const version = feature.status.support[browser]; | ||
if (version) { | ||
const browserReleaseYearMonth = bcd.browsers[browser].releases[version].release_date.substring(0, 7); | ||
if (!monthly.has(browserReleaseYearMonth)) { | ||
monthly.set(browserReleaseYearMonth, {}); | ||
} | ||
if (!monthly.get(browserReleaseYearMonth)[browser]) { | ||
monthly.get(browserReleaseYearMonth)[browser] = []; | ||
} | ||
monthly.get(browserReleaseYearMonth)[browser].push(feature); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return [...monthly].sort((a, b) => { | ||
return (new Date(b[0])) - (new Date(a[0])); | ||
}); | ||
}); | ||
|
||
eleventyConfig.addGlobalData("allFeatures", async () => { | ||
const { features, bcd } = await getDeps(); | ||
|
||
const all = []; | ||
|
||
for (const id in features) { | ||
const feature = features[id]; | ||
augmentFeatureData(id, feature, bcd); | ||
all.push(feature); | ||
} | ||
|
||
return all; | ||
}); | ||
|
||
eleventyConfig.addGlobalData("baselineFeatures", async () => { | ||
const { features, bcd } = await getDeps(); | ||
|
||
const baseline = []; | ||
|
||
for (const id in features) { | ||
const feature = features[id]; | ||
|
||
// Baseline features only. | ||
if (feature.status.baseline === "high") { | ||
augmentFeatureData(id, feature, bcd); | ||
baseline.push(feature); | ||
} | ||
} | ||
|
||
return baseline.sort((a, b) => { | ||
// Sort by baseline_high_date, descending, so the most recent is first. | ||
return new Date(b.status.baseline_high_date) - new Date(a.status.baseline_high_date); | ||
}); | ||
}); | ||
|
||
eleventyConfig.addGlobalData("nonBaselineFeatures", async () => { | ||
const { features, bcd } = await getDeps(); | ||
|
||
const nonBaseline = []; | ||
|
||
for (const id in features) { | ||
const feature = features[id]; | ||
|
||
// Non-baseline features only. | ||
if (!feature.status.baseline) { | ||
augmentFeatureData(id, feature, bcd); | ||
nonBaseline.push(feature); | ||
} | ||
} | ||
|
||
return nonBaseline; | ||
}); | ||
|
||
eleventyConfig.addGlobalData("recentBaselineFeatures", async () => { | ||
const { features, bcd } = await getDeps(); | ||
|
||
const recentBaseline = []; | ||
|
||
for (const id in features) { | ||
const feature = features[id]; | ||
augmentFeatureData(id, feature, bcd); | ||
|
||
// Only baseline low. | ||
if (feature.status.baseline === "low") { | ||
recentBaseline.push(feature); | ||
} | ||
} | ||
|
||
return recentBaseline.sort((a, b) => { | ||
// Sort by baseline_low_date, descending, so the most recent is first. | ||
return new Date(b.status.baseline_low_date) - new Date(a.status.baseline_low_date); | ||
}); | ||
}); | ||
|
||
eleventyConfig.addGlobalData("missingOneBrowserFeatures", async () => { | ||
const { features, bcd } = await getDeps(); | ||
|
||
const missingOne = []; | ||
|
||
for (const id in features) { | ||
const feature = features[id]; | ||
augmentFeatureData(id, feature, bcd); | ||
|
||
// Only non-baseline features. | ||
if (!feature.status.baseline) { | ||
// And, out of those, only those that are missing support in just one browser (engine). | ||
const noSupport = []; | ||
for (const { id: browserId } of BROWSERS) { | ||
if (!feature.status.support[browserId]) { | ||
noSupport.push(browserId); | ||
} | ||
} | ||
|
||
if (noSupport.length === 1) { | ||
missingOne.push(feature); | ||
} | ||
|
||
if (noSupport.length === 2) { | ||
// If one of the two values is a substring of the other, then these are the same engine. | ||
const [first, second] = noSupport; | ||
if (first.includes(second) || second.includes(first)) { | ||
missingOne.push(feature); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return missingOne; | ||
}); | ||
|
||
eleventyConfig.addGlobalData("missingInEdgeFeatures", async () => { | ||
const { features, bcd } = await getDeps(); | ||
|
||
const missingInEdge = []; | ||
|
||
for (const id in features) { | ||
const feature = features[id]; | ||
augmentFeatureData(id, feature, bcd); | ||
|
||
// Only non-baseline features. | ||
if (!feature.status.baseline) { | ||
// And, out of those, only those that are missing support in just Edge. | ||
const noSupport = []; | ||
for (const { id: browserId } of BROWSERS) { | ||
if (!feature.status.support[browserId]) { | ||
noSupport.push(browserId); | ||
} | ||
} | ||
|
||
if (noSupport.length === 1 && noSupport[0] === "edge") { | ||
missingInEdge.push(feature); | ||
} | ||
} | ||
} | ||
|
||
return missingInEdge; | ||
}); | ||
|
||
return { | ||
dir: { | ||
input: "site", | ||
output: "docs", | ||
}, | ||
pathPrefix: "/web-features-explorer/", | ||
}; | ||
}; |
Oops, something went wrong.