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 'margin-start',
53 'margin-end',
54 'padding-start',
55 'padding-end',
56 'border-start',
57 'border-start-color',
58 'border-start-style',
59 'border-start-width',
60 'border-end',
61 'border-end-color',
62 'border-end-style',
63 'border-end-width',
64 'border-start-start-radius',
65 'border-start-end-radius',
66 'border-end-start-radius',
67 'border-end-end-radius',
68].map(name => ({ name, ms: false, moz: true, webkit: true })));
69
70/** A list of stable, non-prefixable property names */
71const stablePropertyNames = Object.keys(mdnProperties)
72 .filter(x => (
73 // Only include non-obsolete CSS properties
74 mdnProperties[x].status !== 'obsolete' &&
75 x !== 'all' &&
76 x !== '--*' &&
77 // Skip some properties that aren't widely supported:
78 x !== 'text-decoration-skip-ink' &&
79 x !== 'text-decoration-thickness' &&
80 // Exclude prefixed properties
81 !prefixPropRe.test(x) &&
82 // Exclude properties that are to be prefixed (i.e. non-standard)
83 !properties.some(({ name }) => name === x)
84 ));
85
86/** Lists each prefixed property with the minimum substring that is needed to uniquely identity it */
87const prefixPatterns = properties
88 .map(prop => {
89 let name = prop.name;
90 for (let i = 2, l = name.length; i < l; i++) {
91 const substr = name.slice(0, i);
92 // Check for any name that conflicts with the substring in all known CSS properties
93 if (stablePropertyNames.every(x => x === name || !x.startsWith(substr))) {
94 name = substr;
95 break;
96 }
97 }
98
99 return { ...prop, name };
100 });
101
102/** Accepts a filter and builds a list of names in `prefixPatterns` */
103const reducePrefixes = (filter = x => !!x) => {
104 const set = prefixPatterns.reduce((acc, prop) => {
105 if (filter(prop)) acc.add(prop.name);
106 return acc;
107 }, new Set());
108
109 return [...set].sort((a, b) => {
110 if (a.length === b.length) return a.localeCompare(b);
111 return a.length - b.length;
112 });
113};
114
115const buildRegex = groups => `^(${groups.join('|')})`;
116
117// Create all prefix sets for each prefix
118const msPrefixes = buildRegex(reducePrefixes(x => x.ms));
119const mozPrefixes = buildRegex(reducePrefixes(x => x.moz));
120const webkitPrefixes = buildRegex(reducePrefixes(x => x.webkit));
121
122module.exports = `
123var msPrefixRe = /${msPrefixes}/;
124var mozPrefixRe = /${mozPrefixes}/;
125var webkitPrefixRe = /${webkitPrefixes}/;
126`.trim();