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