Mirror: CSS prefixing helpers in less than 1KB 馃寛
1#!/usr/bin/env node
2
3const prefixMap = require('inline-style-prefixer/lib/data').default.prefixMap;
4const mdnProperties = require('mdn-data/css/properties.json');
5
6const PREFIX_MS = 'ms';
7const PREFIX_MOZ = 'Moz';
8const PREFIX_WEBKIT = 'Webkit';
9const prefixPropRe = /^-(ms|moz|webkit)-/;
10
11/** A list of all properties that have to be prefixed */
12const properties = Object.keys(prefixMap)
13 .map(prop => ({
14 // Convert inline-style-based name to CSS property name
15 name: prop.replace(/[A-Z]/g, '-$&').toLowerCase(),
16 // This describes what kind of prefixes are necessary:
17 ms: !!prefixMap[prop].includes(PREFIX_MS),
18 moz: !!prefixMap[prop].includes(PREFIX_MOZ),
19 webkit: !!prefixMap[prop].includes(PREFIX_WEBKIT),
20 }))
21 // Omit CSS properties that are not listed by MDN or are obsolete
22 .filter(({ name }) => (
23 mdnProperties[name] &&
24 mdnProperties[name].status !== 'obsolete' &&
25 // Skip some properties that aren't widely supported or don't need prefixing:
26 name !== 'backdrop-filter' &&
27 name !== 'filter' &&
28 // Skip some properties that are obsolete:
29 name !== 'scroll-snap-points-x' &&
30 name !== 'scroll-snap-points-y' &&
31 name !== 'scroll-snap-points-destination' &&
32 name !== 'scroll-snap-points-coordinate' &&
33 name !== 'flow-into' &&
34 name !== 'flow-from' &&
35 name !== 'wrap-flow' &&
36 name !== 'wrap-through' &&
37 name !== 'wrap-margin'
38 ));
39
40// See SUPPORT.md on background-clip
41properties.push({
42 name: 'background-clip',
43 ms: false,
44 moz: false,
45 webkit: true
46});
47
48// These are supported in Firefox, Chrome, and Safari
49// NOTE: Their variants with before/after are not supported
50// by Firefox and should be avoided
51properties.push(...[
52 'border-start',
53 'border-start-color',
54 'border-start-style',
55 'border-start-width',
56 'border-end',
57 'border-end-color',
58 'border-end-style',
59 'border-end-width',
60 'border-start-start-radius',
61 'border-start-end-radius',
62 'border-end-start-radius',
63 'border-end-end-radius',
64].map(name => ({ name, ms: false, moz: true, webkit: true })));
65
66/** A list of stable, non-prefixable property names */
67const stablePropertyNames = Object.keys(mdnProperties)
68 .filter(x => (
69 // Only include non-obsolete CSS properties
70 mdnProperties[x].status !== 'obsolete' &&
71 x !== 'all' &&
72 x !== '--*' &&
73 // Skip some properties that aren't widely supported:
74 x !== 'text-decoration-skip-ink' &&
75 x !== 'text-decoration-thickness' &&
76 // Exclude prefixed properties
77 !prefixPropRe.test(x) &&
78 // Exclude properties that are to be prefixed (i.e. non-standard)
79 !properties.some(({ name }) => name === x)
80 ));
81
82/** Finds the minimum starting substring that uniquely identifier a property out of all known CSS properties */
83const findMinimumSubstr = name => {
84 for (let i = 2, l = name.length; i < l; i++) {
85 const substr = name.slice(0, i);
86 // Check for any name that conflicts with the substring in all known CSS properties
87 if (stablePropertyNames.every(x => x === name || !x.startsWith(substr))) {
88 return substr;
89 }
90 }
91
92 return name;
93};
94
95/** Lists each prefixed property with the minimum substring that is needed to uniquely identity it */
96const prefixPatterns = properties.map(prop => ({
97 ...prop,
98 name: findMinimumSubstr(prop.name)
99}));
100
101/** For Webkit prefixes, these properties will need some values to be prefixed */
102const prefixValuePatterns = ['position', 'background-clip'].map(findMinimumSubstr);
103
104/** Accepts a filter and builds a list of names in `prefixPatterns` */
105const reducePrefixes = (filter = x => !!x) => {
106 const set = prefixPatterns.reduce((acc, prop) => {
107 if (filter(prop)) acc.add(prop.name);
108 return acc;
109 }, new Set());
110
111 return [...set].sort((a, b) => {
112 if (a.length === b.length) return a.localeCompare(b);
113 return a.length - b.length;
114 });
115};
116
117/** Creates a regex matching property names */
118const buildRegex = groups => `^(${groups.join('|')})`;
119
120// Create all prefix sets for each prefix
121const msPrefixes = buildRegex(reducePrefixes(x => x.ms));
122const mozPrefixes = buildRegex(reducePrefixes(x => x.moz));
123const webkitPrefixes = buildRegex(reducePrefixes(x => x.webkit));
124
125// Create a regex for webkit value transforms
126const webkitValuePrefix = buildRegex(prefixValuePatterns);
127
128module.exports = `
129var msPrefixRe = /${msPrefixes}/;
130var mozPrefixRe = /${mozPrefixes}/;
131var webkitPrefixRe = /${webkitPrefixes}/;
132var webkitValuePrefixRe = /${webkitValuePrefix}/;
133`.trim();