@magic/css 0.7.39 → 0.7.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,8 +29,44 @@ parse/stringify/write css in js
29
29
  npm install --save-exact @magic/css
30
30
  ```
31
31
 
32
+
32
33
  #### usage:
33
34
 
35
+ ##### cli:
36
+
37
+ @magic/css includes a cli script that can handle most usecases the internal javascript api allows.
38
+
39
+ to use this cli from any directory,
40
+ `npm install -g @magic.css` is a useful shortcut.
41
+ after the global install, you can just call `mcss` from anywhere in your terminal.
42
+
43
+ after installation, add:
44
+ `"mcss": "mcss"`
45
+ to your package.json "scripts" section and then
46
+ `npm run mcss`
47
+ to see the help output below.
48
+
49
+ ```
50
+ @magic/css
51
+ commands:
52
+ stringify - convert css in js to css
53
+ parse - convert css in js to an array of key value pairs
54
+ full - get a full result object.
55
+
56
+ flags:
57
+ --minified - output minified css - alias: ["--m", "-m"]
58
+ --help - alias: ["-h"]
59
+ --out - directory to write output files to. omit to print to stdout - alias: ["--o", "-o"]
60
+ --in - directory with source files, needs index.js to exist - alias: ["--i", "-i"]
61
+
62
+ examples:
63
+ mcss parse --in ./styles --out ./css
64
+ mcss stringify --in ./styles --out ./css
65
+ mcss full --in ./styles --out ./css
66
+ ```
67
+
68
+ #### library:
69
+
34
70
  ##### init
35
71
  ```javascript
36
72
  import css from '@magic/css'
@@ -487,6 +523,22 @@ overflowX is output as overflowx in earlier versions, the regex expected lowerca
487
523
  #### 0.7.39
488
524
  update dependencies
489
525
 
490
- #### 0.7.40 - unreleased
526
+ #### 0.7.40
527
+ update dependencies
528
+
529
+ #### 0.7.41
530
+ update dependencies
531
+
532
+ #### 0.7.42
533
+ update dependencies
534
+
535
+ #### 0.7.43
536
+ * update dependencies
537
+ * added font v2 to allow handling of local() fonts (see https://magic.github.io/css/#styles-webfonts)
538
+ * woff2 files are placed before woff files
539
+ * use single quotes in output css
540
+ * add cli
541
+
542
+ #### 0.7.44 - unreleased
491
543
  ...
492
544
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic/css",
3
- "version": "0.7.39",
3
+ "version": "0.7.43",
4
4
  "author": "Wizards & Witches",
5
5
  "description": "css in js",
6
6
  "license": "AGPL-3.0",
@@ -17,6 +17,10 @@
17
17
  "test": "t --exclude docs example config.js",
18
18
  "calls": "calls"
19
19
  },
20
+ "bin": {
21
+ "mcss": "src/bin/index.js",
22
+ "magic-css": "src/bin/index.js"
23
+ },
20
24
  "engines": {
21
25
  "node": ">=14.15.4"
22
26
  },
@@ -35,11 +39,11 @@
35
39
  "dependencies": {
36
40
  "@magic/cases": "0.0.5",
37
41
  "@magic/deep": "0.1.7",
38
- "@magic/fs": "0.0.16",
42
+ "@magic/fs": "0.0.18",
39
43
  "@magic/log": "0.1.10",
40
44
  "@magic/types": "0.1.16",
41
- "autoprefixer": "10.3.5",
42
- "postcss": "8.3.7"
45
+ "autoprefixer": "10.4.0",
46
+ "postcss": "8.4.5"
43
47
  },
44
48
  "devDependencies": {
45
49
  "@magic-modules/git-badges": "0.0.11",
@@ -47,9 +51,9 @@
47
51
  "@magic-modules/no-spy": "0.0.6",
48
52
  "@magic-modules/pre": "0.0.11",
49
53
  "@magic-themes/docs": "0.0.14",
50
- "@magic/core": "0.0.129",
51
- "@magic/format": "0.0.29",
52
- "@magic/test": "0.1.74"
54
+ "@magic/core": "0.0.133",
55
+ "@magic/format": "0.0.33",
56
+ "@magic/test": "0.1.77"
53
57
  },
54
58
  "keywords": [
55
59
  "css-in-js",
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path'
4
+
5
+ import cli from '@magic/cli'
6
+ import fs from '@magic/fs'
7
+ import log from '@magic/log'
8
+
9
+ import css from '../index.mjs'
10
+
11
+ const cwd = process.cwd()
12
+
13
+ const { args, commands } = cli({
14
+ commands: ['full', 'parse', 'stringify'],
15
+ options: [
16
+ ['--in', '--i', '-i'],
17
+ ['--out', '--o', '-o'],
18
+ ['--minified', '--m', '-m'],
19
+ ],
20
+ single: ['--in', '--out', '--minified'],
21
+ help: {
22
+ name: '@magic/css',
23
+ header: 'css in js pipeline.',
24
+ commands: {
25
+ full: 'get a full result object.',
26
+ parse: 'convert css in js to an array of key value pairs',
27
+ stringify: 'convert css in js to css',
28
+ },
29
+ options: {
30
+ '--in': 'directory with index.js that has a css in js object as default export',
31
+ '--out': 'directory to write output files to. omit to print to stdout',
32
+ '--minified': 'output minified css',
33
+ },
34
+ example: `
35
+ mcss parse --in ./css --out ./css
36
+ mcss stringify --in ./styles --out ./css
37
+ `.trim(),
38
+ },
39
+ })
40
+
41
+ const logResult = style => log.success('mcss output', `\n\n${style}\n`)
42
+
43
+ const maybeWrite = async ({ args, commands, style }) => {
44
+ const outFile = commands.stringify ? 'main.css' : 'theme.js'
45
+
46
+ if (args.out) {
47
+ if (!path.isAbsolute(args.out)) {
48
+ args.out = path.join(cwd, args.out)
49
+ }
50
+
51
+ if (!args.out.endsWith('.css') && !args.out.endsWith('.js')) {
52
+ args.out = path.join(args.out, outFile)
53
+ }
54
+
55
+ const dirname = path.dirname(args.out)
56
+ await fs.mkdirp(dirname)
57
+
58
+ await fs.writeFile(args.out, style)
59
+ } else {
60
+ logResult(style)
61
+ }
62
+
63
+ log.success('mcss is done.')
64
+ }
65
+
66
+ const run = async () => {
67
+ if (!args.in) {
68
+ args.in = cwd
69
+ log.info('--in not specified, using process.cwd()', args.in)
70
+ } else if (!path.isAbsolute(args.in)) {
71
+ args.in = path.join(cwd, args.in)
72
+ }
73
+
74
+ if (!args.in.endsWith('index.js')) {
75
+ args.in = path.join(args.in, 'index.js')
76
+ }
77
+
78
+ const { default: theme } = await import(args.in)
79
+
80
+ const result = await css(theme)
81
+
82
+ if (commands.full) {
83
+ const style = JSON.stringify(result, null, 2)
84
+ maybeWrite({ args, commands, style })
85
+ } else if (commands.stringify) {
86
+ const style = args.hasOwnProperty('minified') ? result.minified : result.css
87
+ maybeWrite({ args, commands, style })
88
+ } else if (commands.parse) {
89
+ const style = JSON.stringify(result.parsed, null, 2)
90
+ maybeWrite({ args, commands, style })
91
+ } else {
92
+ log.error('MISSING_COMMAND', 'Either parse, stringify or full are required.')
93
+ }
94
+ }
95
+
96
+ run()
@@ -67,6 +67,11 @@ const recurseParse = (mod, opts) => {
67
67
 
68
68
  const isStyle = style => style && style.length === 2 && is.string(style[0]) && is.object(style[1])
69
69
 
70
+ /*
71
+ * we can not use deep.flatten here, the flattened array has a specific structure:
72
+ * [[key, styles], [key, styles]],
73
+ * deep.flatten would return [key, styles, key, styles]
74
+ */
70
75
  const flat = a => {
71
76
  if (isStyle(a)) {
72
77
  const [k, v] = a
@@ -83,10 +88,14 @@ const flat = a => {
83
88
  }
84
89
  }
85
90
  })
91
+
86
92
  return flattened
87
93
  }
88
94
  }
89
95
 
96
+ /*
97
+ * parse the styles css object into an array of key-value pairs.
98
+ */
90
99
  const parse = (styles, opts = {}) => {
91
100
  // first check if the user sent us a function that resolves to a css object
92
101
  if (is.function(styles)) {
@@ -96,7 +105,7 @@ const parse = (styles, opts = {}) => {
96
105
  // this might trigger additionally to the is.function if statement above
97
106
  if (is.array(styles)) {
98
107
  styles = styles.map(s => recurseParse(s, opts))
99
- } else if (!is.array(styles) && is.object(styles)) {
108
+ } else if (!is.array(styles) && is.objectNative(styles)) {
100
109
  styles = Object.entries(styles).map(s => recurseParse(s, opts))
101
110
  } else {
102
111
  log.error('invalid styles received', styles)
@@ -0,0 +1,150 @@
1
+ import is from '@magic/types'
2
+ import deep from '@magic/deep'
3
+ import log from '@magic/log'
4
+
5
+ import { recurseStringify } from './recurseStringify.mjs'
6
+
7
+ export const fontFileTypes = {
8
+ eot: url => `src: url('${url}.eot'); src: url('${url}.eot#iefix') format('embedded-opentype')`,
9
+ woff2: url => `url('${url}.woff2') format('woff2')`,
10
+ woff: url => `url('${url}.woff') format('woff')`,
11
+ ttf: url => `url('${url}.ttf') format('truetype')`,
12
+ svg: (url, family) => `url('${url}.svg#${family}') format('svg')`,
13
+ }
14
+
15
+ const fontV1 = (name, font) => {
16
+ log.warn('W_DEPRECATED', 'font name', font.family, 'is declared in a deprecated way.')
17
+ log('See: https://magic.github.io/css/#styles-webfonts for more information.')
18
+
19
+ let { family, url, types = 'woff2', weights = 400, styles = 'normal', ...rest } = font
20
+
21
+ if (!url.endsWith('/')) {
22
+ url += '/'
23
+ }
24
+
25
+ if (!is.array(weights)) {
26
+ weights = [weights]
27
+ }
28
+
29
+ if (!is.array(types)) {
30
+ types = [types]
31
+ }
32
+
33
+ if (!is.array(styles)) {
34
+ styles = [styles]
35
+ }
36
+
37
+ return weights.map(fontWeight => {
38
+ return styles.map(fontStyle => {
39
+ const weightStyleUrl = `${url}${family}-${fontWeight}-${fontStyle}`
40
+
41
+ let fontString = `${name} ${recurseStringify({
42
+ fontFamily: `'${family}'`,
43
+ fontStyle,
44
+ fontWeight,
45
+ ...rest,
46
+ })}`
47
+
48
+ let fontFileString = 'src:'
49
+ if (types.includes('eot')) {
50
+ fontFileString = ''
51
+ }
52
+
53
+ const fontFileStrings = []
54
+ Object.entries(fontFileTypes).map(([name, fn]) => {
55
+ if (types.includes(name)) {
56
+ const str = fn(weightStyleUrl, family)
57
+ fontFileStrings.push(str)
58
+ }
59
+ })
60
+
61
+ fontFileString += fontFileStrings.join(',') + ';'
62
+
63
+ fontString = fontString.replace('}\n', `${fontFileString} }\n`)
64
+ return fontString
65
+ })
66
+ })
67
+ }
68
+
69
+ const fontV2 = (name, font) => {
70
+ let { family, url, types = 'woff2', styles, ...rest } = font
71
+
72
+ if (!url.endsWith('/')) {
73
+ url += '/'
74
+ }
75
+
76
+ if (!is.array(types)) {
77
+ types = [types]
78
+ }
79
+
80
+ const fontStrings = Object.entries(styles).map(entry => {
81
+ const [fontStyle, weights] = entry
82
+
83
+ return Object.entries(weights).map(([fontWeight, local]) => {
84
+ const weightStyleUrl = `${url}${family}-${fontWeight}-${fontStyle}`
85
+
86
+ const fontFileStrings = []
87
+
88
+ let fontFileString = ''
89
+ if (types.includes('eot')) {
90
+ fontFileString = ''
91
+ }
92
+
93
+ local.forEach(l => {
94
+ fontFileStrings.push(`local('${l}')`)
95
+ })
96
+
97
+ Object.entries(fontFileTypes).map(([name, fn]) => {
98
+ if (types.includes(name)) {
99
+ const str = fn(weightStyleUrl, family)
100
+ fontFileStrings.push(str)
101
+ }
102
+ })
103
+
104
+ fontFileString += fontFileStrings.join(',')
105
+
106
+ const props = recurseStringify({
107
+ fontFamily: `'${family}'`,
108
+ fontStyle,
109
+ fontWeight,
110
+ src: fontFileString,
111
+ ...rest,
112
+ })
113
+
114
+ const fontString = `${name} ${props}`
115
+
116
+ return fontString
117
+ })
118
+ })
119
+
120
+ return deep.flatten(fontStrings).join('\n')
121
+ }
122
+
123
+ export const fontFaces = ({ res, name, items }) => {
124
+ if (is.array(items)) {
125
+ const fontStrings = items.map(font => {
126
+ if (is.objectNative(font.styles)) {
127
+ return fontV2(name, font)
128
+ } else {
129
+ return fontV1(name, font)
130
+ }
131
+ })
132
+
133
+ return deep.flatten(fontStrings).join('\n')
134
+ } else {
135
+ const { fontFamily, fontDir = '', fontRoot, ...rest } = items
136
+ res = `${name} ${recurseStringify({ fontFamily: `'${fontFamily}'`, ...rest })}`
137
+
138
+ const eotString = `src: url('${fontDir}${fontFamily}.eot');`
139
+
140
+ const srcString = `${eotString} src: ${[
141
+ `url('${fontDir}${fontFamily}.eot#iefix') format('embedded-opentype')`,
142
+ `url('${fontDir}${fontFamily}.ttf') format('truetype')`,
143
+ `url('${fontDir}${fontFamily}.woff2') format('woff2')`,
144
+ `url('${fontDir}${fontFamily}.woff') format('woff')`,
145
+ `url('${fontDir}${fontFamily}.svg#${fontFamily}') format('svg');`,
146
+ ].join(', ')}`
147
+
148
+ return res.replace('}\n', `${srcString} }\n`)
149
+ }
150
+ }
@@ -4,11 +4,34 @@ import autoprefixer from 'autoprefixer'
4
4
  import parse from '../parse/index.mjs'
5
5
  import recurseStringify from './recurseStringify.mjs'
6
6
 
7
+ import { fontFaces } from './fontFaces.mjs'
8
+
9
+ const keyframes = ({ name, items, plugins }) => `${name} { ${recurseStringify(items, plugins)} }\n`
10
+ const mediaqueries = ({ name, items, plugins }) =>
11
+ `${name} { ${recurseStringify(items, plugins)} }\n`
12
+
13
+ /*
14
+ * stringify the styles css object into a valid css string.
15
+ */
7
16
  const stringify = async (styles, opts = {}) => {
17
+ const { plugins = {} } = opts
18
+
19
+ plugins.stringify = {
20
+ '@font-face': fontFaces,
21
+ '@keyframes': keyframes,
22
+ '@media': mediaqueries,
23
+ ...plugins.stringify,
24
+ }
25
+
8
26
  const parsed = parse(styles, opts)
9
27
 
10
- const stringified = recurseStringify(parsed)
28
+ const stringified = recurseStringify(parsed, plugins.stringify)
29
+
30
+ /*
31
+ * test the resulting css using postcss and autoprefix vendors where needed.
32
+ */
11
33
  const result = await postcss([autoprefixer]).process(stringified, { from: undefined })
34
+
12
35
  result.warnings().forEach(warn => {
13
36
  console.warn(warn.toString())
14
37
  })
@@ -2,102 +2,24 @@ import is from '@magic/types'
2
2
 
3
3
  import stringifyProps from './props.mjs'
4
4
 
5
- export const fontFaces = ({ res, name, items }) => {
6
- if (is.array(items)) {
7
- const fontStrings = []
8
-
9
- items.forEach(font => {
10
- let { family, url, types = 'woff2', weights = 400, styles = 'normal', ...rest } = font
11
-
12
- if (!url.endsWith('/')) {
13
- url += '/'
14
- }
15
- if (!is.array(weights)) {
16
- weights = [weights]
17
- }
18
- if (!is.array(styles)) {
19
- styles = [styles]
20
- }
21
- if (!is.array(types)) {
22
- types = [types]
23
- }
24
-
25
- weights.forEach(fontWeight => {
26
- styles.forEach(fontStyle => {
27
- const weightStyleUrl = `${url}${family}-${fontWeight}-${fontStyle}`
28
-
29
- let fontString = `${name} ${recurseStringify({
30
- fontFamily: `"${family}"`,
31
- fontStyle,
32
- fontWeight,
33
- ...rest,
34
- })}`
35
-
36
- const fontFileTypes = {
37
- eot: url =>
38
- `src: url('${url}.eot'); src: url('${url}.eot#iefix') format('embedded-opentype')`,
39
- ttf: url => `url('${url}.ttf') format('truetype')`,
40
- woff: url => `url('${url}.woff') format('woff')`,
41
- woff2: url => `url('${url}.woff2') format('woff2')`,
42
- svg: url => `url('${url}.svg#${family}') format('svg')`,
43
- }
44
-
45
- let fontFileString = 'src:'
46
- if (types.includes('eot')) {
47
- fontFileString = ''
48
- }
49
-
50
- const fontFileStrings = []
51
- Object.entries(fontFileTypes).map(([name, fn]) => {
52
- if (types.includes(name)) {
53
- const str = fn(weightStyleUrl)
54
- fontFileStrings.push(str)
55
- }
56
- })
57
-
58
- fontFileString += fontFileStrings.join(',') + ';'
59
-
60
- fontString = fontString.replace('}\n', `${fontFileString} }\n`)
61
- fontStrings.push(fontString)
62
- })
63
- })
64
- })
65
-
66
- return fontStrings.join('\n')
67
- } else {
68
- const { fontFamily, fontDir = '', fontRoot, ...rest } = items
69
- res = `${name} ${recurseStringify({ fontFamily: `"${fontFamily}"`, ...rest })}`
70
-
71
- const eotString = `src: url('${fontDir}${fontFamily}.eot');`
72
-
73
- const srcString = `${eotString} src: ${[
74
- `url('${fontDir}${fontFamily}.eot#iefix') format('embedded-opentype')`,
75
- `url('${fontDir}${fontFamily}.ttf') format('truetype')`,
76
- `url('${fontDir}${fontFamily}.woff') format('woff')`,
77
- `url('${fontDir}${fontFamily}.woff2') format('woff2')`,
78
- `url('${fontDir}${fontFamily}.svg#${fontFamily}') format('svg');`,
79
- ].join(', ')}`
80
-
81
- return res.replace('}\n', `${srcString} }\n`)
82
- }
83
- }
84
-
85
- export const recurseStringify = mod => {
86
- let res = mod
87
-
88
- if (is.array(mod)) {
89
- const [name, items] = mod
5
+ export const recurseStringify = (res, plugins = []) => {
6
+ if (is.array(res)) {
7
+ const [name, items] = res
90
8
  if (is.string(name)) {
91
- if (name.startsWith('@keyframes') || name.startsWith('@media')) {
92
- return `${name} { ${recurseStringify(items)} }\n`
93
- } else if (name.startsWith('@font-face')) {
94
- return fontFaces({ res, name, items })
9
+ let result = ''
10
+ Object.entries(plugins).forEach(([lookup, fn]) => {
11
+ if (name.startsWith(lookup)) {
12
+ result = fn({ name, items, plugins })
13
+ }
14
+ })
15
+ if (result) {
16
+ return result
95
17
  }
96
18
  }
97
19
 
98
- return mod.map(recurseStringify).join(' ')
99
- } else if (is.object(mod)) {
100
- return `{ ${stringifyProps(mod)} }\n`
20
+ return res.map(r => recurseStringify(r, plugins)).join(' ')
21
+ } else if (is.object(res)) {
22
+ return `{ ${stringifyProps(res)} }\n`
101
23
  }
102
24
 
103
25
  return res