@wix/origami-transforms 0.1.6 → 0.1.7

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.
@@ -0,0 +1,4 @@
1
+ export function structureFileOf(stylesheetPath: any): string | undefined;
2
+ export function parseStructure(structurePath: any): any;
3
+ export function walk(node: any, visit: any): void;
4
+ export function literalIdOf(openingElement: any): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/origami-transforms",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Babel and PostCSS checks and transforms for Wix origami page files.",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -54,5 +54,5 @@
54
54
  },
55
55
  "type": "module",
56
56
  "types": "./dist/index.d.ts",
57
- "falconPackageHash": "104bb1dbbdebc96c665a8cdf46acdfeb8ceef46bd8a7fcbd1ff2d10b"
57
+ "falconPackageHash": "9674cee94f59f7db338de844100c7527c7eeddff5e2d58f750c8f488"
58
58
  }
@@ -4,18 +4,16 @@
4
4
  // `customEnums.ts`), which the page imports and the bundler inlines. The blocks are removed from the output.
5
5
  // Native `if()` can't do it: Firefox doesn't have it, and a var named like its key refers to itself.
6
6
  //
7
- // The instance's component comes from the item's structure file, as in stackChildren.js: the element with
7
+ // The instance's component comes from the item's structure file (structureFile.js): the element with
8
8
  // `id="comp-1"`, and the `@origami-wrappers/…` import it is rendered from. See customEnums.md.
9
9
 
10
- import { existsSync, readFileSync, realpathSync } from 'node:fs'
10
+ import { realpathSync } from 'node:fs'
11
11
  import { createRequire } from 'node:module'
12
- import { basename, dirname, join } from 'node:path'
13
- import { parseSync } from '@babel/core'
12
+ import { basename, dirname } from 'node:path'
14
13
  import postcss from 'postcss'
15
14
  import { LayoutError } from '../transform/layoutError.js'
15
+ import { literalIdOf, parseStructure, structureFileOf, walk } from './structureFile.js'
16
16
 
17
- const STRUCTURE_FILES = ['structure.tsx', 'structure.jsx']
18
- const GENERATED = '_generated'
19
17
  // `@origami-wrappers/<module>/<symbol>`, and nothing that could resolve outside its package.
20
18
  const WRAPPER_IMPORT = /^@origami-wrappers\/[\w-]+\/[\w-]+$/
21
19
  // `#id` or `.id` (exports give an instance both), then only what narrows that same element: classes,
@@ -23,44 +21,21 @@ const WRAPPER_IMPORT = /^@origami-wrappers\/[\w-]+\/[\w-]+$/
23
21
  // and no pseudo-elements, which the component doesn't read vars from.
24
22
  const INSTANCE = /^[#.]([\w-]+)(?!.*::)(?:[.:[][^\s>+~]*)?((?:\s+[^\s>+~]+)*)$/
25
23
 
26
- /** The folder holding the structure file a stylesheet is checked against: its own, or its item's for `_generated/`. */
27
- const structureFolderOf = stylesheetPath => {
28
- const folder = dirname(stylesheetPath)
29
- return basename(folder) === GENERATED ? dirname(folder) : folder
30
- }
31
-
32
24
  /** Id → the wrapper import it is rendered from, for elements with a literal `id` and an imported wrapper tag. */
33
25
  const importsById = structurePath => {
34
- const ast = parseSync(readFileSync(structurePath, 'utf8'), {
35
- babelrc: false,
36
- configFile: false,
37
- filename: structurePath,
38
- parserOpts: { plugins: structurePath.endsWith('.tsx') ? ['jsx', 'typescript'] : ['jsx'] },
39
- })
26
+ const program = parseStructure(structurePath)
40
27
  const sources = new Map()
41
- for (const node of ast?.program.body ?? []) {
28
+ for (const node of program?.body ?? []) {
42
29
  if (node.type !== 'ImportDeclaration' || !WRAPPER_IMPORT.test(node.source.value)) continue
43
30
  for (const specifier of node.specifiers) sources.set(specifier.local.name, node.source.value)
44
31
  }
45
32
  const byId = new Map()
46
- const visit = node => {
47
- if (Array.isArray(node)) return node.forEach(visit)
48
- if (!node || typeof node.type !== 'string') return
49
- if (node.type === 'JSXOpeningElement' && node.name.type === 'JSXIdentifier') {
50
- const id = node.attributes.find(
51
- attribute =>
52
- attribute.type === 'JSXAttribute' &&
53
- attribute.name.name === 'id' &&
54
- attribute.value?.type === 'StringLiteral'
55
- )?.value.value
56
- const source = sources.get(node.name.name)
57
- if (id !== undefined && source !== undefined) byId.set(id, source)
58
- }
59
- for (const [key, value] of Object.entries(node)) {
60
- if (key !== 'loc' && value && typeof value === 'object') visit(value)
61
- }
62
- }
63
- visit(ast?.program)
33
+ walk(program, node => {
34
+ if (node.type !== 'JSXOpeningElement' || node.name.type !== 'JSXIdentifier') return
35
+ const id = literalIdOf(node)
36
+ const source = sources.get(node.name.name)
37
+ if (id !== undefined && source !== undefined) byId.set(id, source)
38
+ })
64
39
  return byId
65
40
  }
66
41
 
@@ -221,9 +196,7 @@ export const customEnumsPlugin = {
221
196
  transform(root, context) {
222
197
  const { byFolder, blocks, malformed } = definitionsIn(root)
223
198
  if (blocks.length === 0) return
224
- const structurePath = STRUCTURE_FILES.map(file => join(structureFolderOf(context.filePath), file)).find(
225
- existsSync
226
- )
199
+ const structurePath = structureFileOf(context.filePath)
227
200
  if (structurePath !== undefined) context.addDependency?.(structurePath)
228
201
  const instances = structurePath === undefined ? new Map() : enumsById(structurePath, byFolder)
229
202
  const keys = new Set([...instances.values()].flatMap(enums => enums.map(({ key }) => key)))
@@ -3,18 +3,14 @@
3
3
  //
4
4
  // A stack is a parent whose rule declares `--container-layout-type: StackContainerLayout`; its
5
5
  // `flex-direction` says which way it stacks (column → vertical, anything else → horizontal, as in
6
- // CSS). Parent and child are the JSX tree's, so the tree comes from the item's `structure.tsx` (or `.jsx`): next
7
- // to the stylesheet, or one folder up when the stylesheet is the generated one in `_generated/`. Elements and
8
- // rules meet on `id` and `#id`. The stylesheet is the page's, with its
6
+ // CSS). Parent and child are the JSX tree's, so the tree comes from the item's structure file (see
7
+ // structureFile.js). The stylesheet is the page's, with its
9
8
  // carriers already inlined (see ../transform/css/postcssPlugin.js), so every rule is in view.
10
9
 
11
- import { existsSync, readFileSync } from 'node:fs'
12
- import { basename, dirname, join } from 'node:path'
13
- import { parseSync } from '@babel/core'
10
+ import { basename } from 'node:path'
14
11
  import { LayoutError } from '../transform/layoutError.js'
12
+ import { literalIdOf, parseStructure, structureFileOf, walk } from './structureFile.js'
15
13
 
16
- const STRUCTURE_FILES = ['structure.tsx', 'structure.jsx']
17
- const GENERATED = '_generated'
18
14
  const STACK_CONTAINER = 'StackContainerLayout'
19
15
  const BASE = ''
20
16
 
@@ -56,46 +52,21 @@ const lengthOf = token => {
56
52
  return UNIT.exec(token)?.[1].toLowerCase()
57
53
  }
58
54
 
59
- /** The folder holding the structure file a stylesheet is checked against: its own, or its item's for `_generated/`. */
60
- const structureFolderOf = stylesheetPath => {
61
- const folder = dirname(stylesheetPath)
62
- return basename(folder) === GENERATED ? dirname(folder) : folder
63
- }
64
-
65
55
  /** Child id → its direct parent's id, for elements with a literal `id`. Slot props are not children. */
66
56
  const parentsIn = structurePath => {
67
- const ast = parseSync(readFileSync(structurePath, 'utf8'), {
68
- babelrc: false,
69
- configFile: false,
70
- filename: structurePath,
71
- parserOpts: { plugins: structurePath.endsWith('.tsx') ? ['jsx', 'typescript'] : ['jsx'] },
72
- })
73
57
  const parents = new Map()
74
- const idOf = element => {
75
- const attribute = element.openingElement.attributes.find(
76
- node => node.type === 'JSXAttribute' && node.name.name === 'id' && node.value?.type === 'StringLiteral'
77
- )
78
- return attribute?.value.value
79
- }
80
58
  const directChildren = node =>
81
59
  node.children.flatMap(child =>
82
60
  child.type === 'JSXElement' ? [child] : child.type === 'JSXFragment' ? directChildren(child) : []
83
61
  )
84
- const visit = node => {
85
- if (Array.isArray(node)) return node.forEach(visit)
86
- if (!node || typeof node.type !== 'string') return
87
- if (node.type === 'JSXElement') {
88
- const parent = idOf(node)
89
- for (const child of directChildren(node)) {
90
- const id = idOf(child)
91
- if (parent !== undefined && id !== undefined) parents.set(id, parent)
92
- }
93
- }
94
- for (const [key, value] of Object.entries(node)) {
95
- if (key !== 'loc' && value && typeof value === 'object') visit(value)
62
+ walk(parseStructure(structurePath), node => {
63
+ if (node.type !== 'JSXElement') return
64
+ const parent = literalIdOf(node.openingElement)
65
+ for (const child of directChildren(node)) {
66
+ const id = literalIdOf(child.openingElement)
67
+ if (parent !== undefined && id !== undefined) parents.set(id, parent)
96
68
  }
97
- }
98
- visit(ast?.program)
69
+ })
99
70
  return parents
100
71
  }
101
72
 
@@ -158,9 +129,7 @@ export const stackChildrenPlugin = {
158
129
  // After mixins copy their bodies in, before spx rewrites --spx() calls away.
159
130
  order: 12,
160
131
  transform(root, context) {
161
- const structurePath = STRUCTURE_FILES.map(file => join(structureFolderOf(context.filePath), file)).find(
162
- existsSync
163
- )
132
+ const structurePath = structureFileOf(context.filePath)
164
133
  if (structurePath === undefined) return
165
134
  context.addDependency?.(structurePath)
166
135
  const parents = parentsIn(structurePath)
@@ -0,0 +1,43 @@
1
+ // The item's structure file, for CSS plugins that check a stylesheet against its JSX tree: `structure.tsx` (or
2
+ // `.jsx`) next to the stylesheet, or one folder up when the stylesheet is the generated one in `_generated/`.
3
+ // Elements and rules meet on a literal `id` and `#id`.
4
+
5
+ import { existsSync, readFileSync } from 'node:fs'
6
+ import { basename, dirname, join } from 'node:path'
7
+ import { parseSync } from '@babel/core'
8
+
9
+ const STRUCTURE_FILES = ['structure.tsx', 'structure.jsx']
10
+ const GENERATED = '_generated'
11
+
12
+ /** The structure file `stylesheetPath` is checked against, or undefined when its item has none. */
13
+ export const structureFileOf = stylesheetPath => {
14
+ const folder = dirname(stylesheetPath)
15
+ const item = basename(folder) === GENERATED ? dirname(folder) : folder
16
+ return STRUCTURE_FILES.map(file => join(item, file)).find(existsSync)
17
+ }
18
+
19
+ /** The structure file's program. */
20
+ export const parseStructure = structurePath =>
21
+ parseSync(readFileSync(structurePath, 'utf8'), {
22
+ babelrc: false,
23
+ configFile: false,
24
+ filename: structurePath,
25
+ parserOpts: { plugins: structurePath.endsWith('.tsx') ? ['jsx', 'typescript'] : ['jsx'] },
26
+ })?.program
27
+
28
+ /** Calls `visit` on every node under `node`, parents first. */
29
+ export const walk = (node, visit) => {
30
+ if (Array.isArray(node)) return node.forEach(child => walk(child, visit))
31
+ if (!node || typeof node.type !== 'string') return
32
+ visit(node)
33
+ for (const [key, value] of Object.entries(node)) {
34
+ if (key !== 'loc' && value && typeof value === 'object') walk(value, visit)
35
+ }
36
+ }
37
+
38
+ /** A JSX opening element's `id`, when it is a string literal. */
39
+ export const literalIdOf = openingElement =>
40
+ openingElement.attributes.find(
41
+ attribute =>
42
+ attribute.type === 'JSXAttribute' && attribute.name.name === 'id' && attribute.value?.type === 'StringLiteral'
43
+ )?.value.value