@wix/origami-transforms 0.1.27 → 0.1.29

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.
Files changed (58) hide show
  1. package/dist/cssPlugins/componentPosition.d.ts +6 -2
  2. package/dist/cssPlugins/customEnums.d.ts +5 -2
  3. package/dist/cssPlugins/customMedia.d.ts +7 -2
  4. package/dist/cssPlugins/fonts.d.ts +6 -2
  5. package/dist/cssPlugins/index.d.ts +2 -2
  6. package/dist/cssPlugins/layout/index.d.ts +7 -2
  7. package/dist/cssPlugins/mixins.d.ts +7 -2
  8. package/dist/cssPlugins/presets.d.ts +6 -2
  9. package/dist/cssPlugins/shapeDividers.d.ts +6 -2
  10. package/dist/cssPlugins/spx/index.d.ts +7 -2
  11. package/dist/cssPlugins/stackChildren.d.ts +5 -2
  12. package/dist/cssPlugins/svg.d.ts +4 -1
  13. package/dist/cssPlugins/wixVectorUrls.d.ts +6 -2
  14. package/dist/jsxPlugins/index.d.ts +2 -2
  15. package/dist/jsxPlugins/mediaValidation/index.d.ts +5 -2
  16. package/dist/jsxPlugins/origamiCompatibility/index.d.ts +6 -2
  17. package/dist/jsxPlugins/pageSlots.d.ts +7 -6
  18. package/dist/jsxPlugins/validation/checks.d.ts +7 -7
  19. package/dist/jsxPlugins/validation/index.d.ts +5 -2
  20. package/dist/jsxPlugins/validation/rules.d.ts +25 -0
  21. package/dist/transform/bi.d.ts +13 -0
  22. package/dist/transform/context.d.ts +1 -0
  23. package/dist/transform/css/runPlugins.d.ts +4 -1
  24. package/dist/transform/layoutError.d.ts +8 -4
  25. package/dist/transform/pipeline.d.ts +7 -9
  26. package/dist/transform/types.d.ts +16 -13
  27. package/package.json +6 -4
  28. package/src/cssPlugins/componentPosition.js +5 -1
  29. package/src/cssPlugins/customEnums.js +19 -12
  30. package/src/cssPlugins/customMedia.js +10 -3
  31. package/src/cssPlugins/fonts.js +5 -1
  32. package/src/cssPlugins/index.js +1 -1
  33. package/src/cssPlugins/layout/index.js +8 -1
  34. package/src/cssPlugins/layout/parser.js +15 -14
  35. package/src/cssPlugins/mixins.js +13 -6
  36. package/src/cssPlugins/presets.js +7 -2
  37. package/src/cssPlugins/shapeDividers.js +6 -2
  38. package/src/cssPlugins/spx/calls.js +2 -1
  39. package/src/cssPlugins/spx/cascade.js +3 -2
  40. package/src/cssPlugins/spx/index.js +8 -1
  41. package/src/cssPlugins/stackChildren.js +6 -2
  42. package/src/cssPlugins/svg.js +5 -2
  43. package/src/cssPlugins/wixVectorUrls.js +6 -2
  44. package/src/jsxPlugins/index.js +1 -1
  45. package/src/jsxPlugins/mediaValidation/index.js +14 -5
  46. package/src/jsxPlugins/origamiCompatibility/index.js +5 -1
  47. package/src/jsxPlugins/pageSlots.js +6 -1
  48. package/src/jsxPlugins/validation/checks.js +15 -8
  49. package/src/jsxPlugins/validation/index.js +9 -3
  50. package/src/jsxPlugins/validation/rules.js +14 -0
  51. package/src/transform/bi.js +64 -0
  52. package/src/transform/context.js +39 -0
  53. package/src/transform/css/postcssPlugin.js +18 -3
  54. package/src/transform/css/runPlugins.js +23 -3
  55. package/src/transform/jsx/runPlugins.js +17 -2
  56. package/src/transform/layoutError.js +8 -3
  57. package/src/transform/pipeline.js +25 -5
  58. package/src/transform/types.js +30 -8
@@ -8,6 +8,8 @@
8
8
  // stylesheet the plugins see is the whole page: a rule declared in one carrier and a rule that
9
9
  // depends on it in another arrive in the same document.
10
10
 
11
+ import { currentRow } from '@wix/origami-bi'
12
+ import { warn as biWarn } from '../bi.js'
11
13
  import { removeCustomEnums } from '../../cssPlugins/customEnums.js'
12
14
  import { pluginError, runCssPlugins } from './runPlugins.js'
13
15
 
@@ -54,7 +56,10 @@ export const createCssPostcssPlugin = ({ selectPlugins, contextFor, failures })
54
56
  }
55
57
  // A plugin reports a problem that need not stop the build as a PostCSS warning, which Vite prints with the
56
58
  // node's code frame.
57
- const warn = (node, text) => node.warn(result, text)
59
+ const warn = (node, text, key) => {
60
+ if (key) biWarn(key)
61
+ return node.warn(result, text)
62
+ }
58
63
  const context = { ...fileContext, addDependency, warn }
59
64
  const fail = error => {
60
65
  failures?.set(from, declared)
@@ -70,10 +75,20 @@ export const createCssPostcssPlugin = ({ selectPlugins, contextFor, failures })
70
75
  }
71
76
  // PostCSS awaits the hook only when it returns a promise; a stylesheet nothing loads for stays synchronous.
72
77
  const loading = plugins.flatMap(plugin => {
78
+ if (!plugin.prepare) return []
79
+ const row = currentRow()?.run.openRow('op', 'css_prepare', { step_details: { plugin: plugin.name, fetched: 0, failed: 0 } })
73
80
  try {
74
- const prepared = plugin.prepare?.(root, context)
75
- return prepared ? [prepared.catch(cause => fail(pluginError(plugin, context, cause)))] : []
81
+ const prepared = row ? row.runInContext(() => plugin.prepare(root, context)) : plugin.prepare(root, context)
82
+ if (!prepared) { row?.end(); return [] }
83
+ return [prepared.then(counts => {
84
+ if (counts) row?.update({ step_details: counts })
85
+ row?.end()
86
+ }, cause => {
87
+ row?.end(false, { error_name: 'plugin_error' })
88
+ return fail(pluginError(plugin, context, cause))
89
+ })]
76
90
  } catch (cause) {
91
+ row?.end(false, { error_name: 'plugin_error' })
77
92
  return fail(pluginError(plugin, context, cause))
78
93
  }
79
94
  })
@@ -5,6 +5,9 @@
5
5
  // A LayoutError comes from a plugin that only checks, so the Root is as it was and the later plugins still run:
6
6
  // the author gets every plugin's violations in one error, at the first one's position, not one plugin's per build.
7
7
 
8
+ import { currentRow, namedError } from '@wix/origami-bi'
9
+ import { countPlugin, declaredCheck, recordPluginFailure } from '../bi.js'
10
+ import { pageIdFor } from '../context.js'
8
11
  import { LayoutError } from '../layoutError.js'
9
12
 
10
13
  const reason = cause => (cause instanceof Error ? cause.message : String(cause))
@@ -13,29 +16,46 @@ const reason = cause => (cause instanceof Error ? cause.message : String(cause))
13
16
  export const pluginError = (plugin, context, cause) =>
14
17
  // Tooling such as Vite's error overlay prints only the message, so the reason goes in it as well. A
15
18
  // plugin may throw any value, so it is read without assuming an Error.
16
- new Error(`CSS transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`, {
19
+ namedError(declaredCheck(plugin, cause) !== undefined ? 'layout_error' : 'plugin_error', `CSS transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`, {
17
20
  cause,
18
21
  })
19
22
 
20
23
  export const runCssPlugins = ({ plugins, root, context }) => {
21
- /** @type {LayoutError[]} */
24
+ /** @type {LayoutError<string>[]} */
22
25
  const layoutErrors = []
23
26
  for (const phase of ['validate', 'transform']) {
24
27
  for (const plugin of plugins) {
28
+ if (!plugin[phase]) continue
29
+ const pageId = pageIdFor(context)
30
+ const row = currentRow()?.run.openRow('build_plugin', plugin.name, {
31
+ ...(pageId ? { item_type: 'page', item_id: pageId } : {}),
32
+ step_details: { phase, outcome: 'ok', violations: 0 },
33
+ })
34
+ const started = performance.now()
25
35
  try {
26
- plugin[phase]?.(root, context)
36
+ if (row) row.runInContext(() => plugin[phase](root, context))
37
+ else plugin[phase](root, context)
38
+ if (context.command === 'serve') row?.discard()
39
+ else row?.end()
27
40
  } catch (cause) {
41
+ if (row) row.runInContext(() => recordPluginFailure(plugin, cause, pageId))
28
42
  if (cause instanceof LayoutError) {
29
43
  layoutErrors.push(cause)
30
44
  continue
31
45
  }
32
46
  throw pluginError(plugin, context, cause)
47
+ } finally {
48
+ countPlugin(context, performance.now() - started)
33
49
  }
34
50
  }
35
51
  }
52
+ if (layoutErrors.length) currentRow()?.run.openRow('op', 'css_layout_errors', {
53
+ step_details: { violations: layoutErrors.reduce((count, error) => count + error.violations.length, 0) },
54
+ }).end()
36
55
  if (layoutErrors.length === 1) throw layoutErrors[0]
37
56
  if (layoutErrors.length > 1) {
38
57
  throw new LayoutError(
58
+ { checks: layoutErrors.flatMap(error => error.plugin.checks) },
39
59
  layoutErrors.flatMap(error => error.violations),
40
60
  layoutErrors[0].location
41
61
  )
@@ -2,22 +2,37 @@
2
2
  // to Babel, which drives this stage — the only thing added here is attributing a failure to the
3
3
  // plugin and the file that caused it. A LayoutError is the author's, so it passes through.
4
4
 
5
+ import { currentRow, namedError } from '@wix/origami-bi'
6
+ import { countPlugin, declaredCheck, recordPluginFailure } from '../bi.js'
7
+ import { pageIdFor } from '../context.js'
5
8
  import { LayoutError } from '../layoutError.js'
6
9
 
7
10
  const reason = cause => (cause instanceof Error ? cause.message : String(cause))
8
11
 
9
12
  export const runJsxPlugins = ({ plugins, program, context }) => {
10
13
  for (const plugin of plugins) {
14
+ const pageId = pageIdFor(context)
15
+ const row = currentRow()?.run.openRow('build_plugin', plugin.name, {
16
+ ...(pageId ? { item_type: 'page', item_id: pageId } : {}),
17
+ step_details: { outcome: 'ok', violations: 0 },
18
+ })
19
+ const started = performance.now()
11
20
  try {
12
- plugin.transform(program, context)
21
+ if (row) row.runInContext(() => plugin.transform(program, context))
22
+ else plugin.transform(program, context)
23
+ if (context.command === 'serve') row?.discard()
24
+ else row?.end()
13
25
  } catch (cause) {
26
+ if (row) row.runInContext(() => recordPluginFailure(plugin, cause, pageId))
14
27
  if (cause instanceof LayoutError) throw cause
15
28
  // Tooling such as Vite's error overlay prints only the message, so the reason goes in it as well. A
16
29
  // plugin may throw any value, so it is read without assuming an Error.
17
- throw new Error(
30
+ throw namedError(declaredCheck(plugin, cause) !== undefined ? 'layout_error' : 'plugin_error',
18
31
  `JSX transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`,
19
32
  { cause }
20
33
  )
34
+ } finally {
35
+ countPlugin(context, performance.now() - started)
21
36
  }
22
37
  }
23
38
  }
@@ -2,16 +2,21 @@
2
2
  // without naming the plugin, and it carries no stack: the bundler already names the file, and the
3
3
  // messages say where in it.
4
4
 
5
+ /** @template {string} Check */
5
6
  export class LayoutError extends Error {
6
7
  /**
7
- * @param {readonly string[]} violations
8
+ * @param {Pick<import('./types.js').TransformPluginBase<Check>, 'checks'>} plugin
9
+ * @param {readonly import('./types.js').Violation<NoInfer<Check>>[]} violations
8
10
  * @param {{ file?: string, line?: number, column?: number }} [location] The first violation's, which the
9
11
  * bundler prints as the error's position (Vite's CSS stage reads `file`, `line` and `column`).
10
12
  */
11
- constructor(violations, location = {}) {
12
- super(violations.length === 1 ? violations[0] : `\n ${violations.join('\n ')}`)
13
+ constructor(plugin, violations, location = {}) {
14
+ const messages = violations.map(violation => violation.message)
15
+ super(messages.length === 1 ? messages[0] : `\n ${messages.join('\n ')}`)
13
16
  this.name = 'LayoutError'
17
+ this.error_name = 'layout_error'
14
18
  this.stack = ''
19
+ this.plugin = plugin
15
20
  this.violations = violations
16
21
  this.location = location
17
22
  Object.assign(this, location)
@@ -1,4 +1,6 @@
1
1
  // Select and cache transform plugins by stage, file, and SSR context.
2
+ import { AsyncLocalStorage } from 'node:async_hooks'
3
+ import { currentRow, trackStepSync } from '@wix/origami-bi'
2
4
  import { transformAsync } from '@babel/core'
3
5
  import { cssPlugins as defaultCssPlugins } from '../cssPlugins/index.js'
4
6
  import { jsxPlugins as defaultJsxPlugins } from '../jsxPlugins/index.js'
@@ -28,11 +30,15 @@ const createState = ({ layout, registries, command }) => {
28
30
  /**
29
31
  * @param {{
30
32
  * layout: import('./types.js').SiteLayout,
31
- * jsxPlugins?: readonly import('./types.js').JsxTransformPlugin[],
32
- * cssPlugins?: readonly import('./types.js').CssTransformPlugin[],
33
+ * jsxPlugins?: readonly import('./types.js').JsxTransformPlugin<string>[],
34
+ * cssPlugins?: readonly import('./types.js').CssTransformPlugin<string>[],
33
35
  * }} options
34
36
  */
35
37
  export const createTransformPipeline = ({ layout, jsxPlugins = defaultJsxPlugins, cssPlugins = defaultCssPlugins }) => {
38
+ // Vite/Rolldown can dispatch hooks outside creation's async context.
39
+ const snapshot = AsyncLocalStorage.snapshot()
40
+ let devStep
41
+ const inContext = fn => snapshot(() => devStep ? devStep.runInContext(fn) : fn())
36
42
  const registries = {
37
43
  jsx: createTransformRegistry(jsxPlugins),
38
44
  css: createTransformRegistry(cssPlugins),
@@ -47,6 +53,7 @@ export const createTransformPipeline = ({ layout, jsxPlugins = defaultJsxPlugins
47
53
  const cssFailures = new Map()
48
54
 
49
55
  const babelOptionsFor = createBabelOptionsSelector({ contextFor, selectPlugins: selectFor('jsx') })
56
+ const postcssPlugin = createCssPostcssPlugin({ contextFor, selectPlugins: selectFor('css'), failures: cssFailures })
50
57
 
51
58
  return {
52
59
  /** Whether any JSX transform selects this module (structure.jsx/.tsx by default).
@@ -58,7 +65,7 @@ export const createTransformPipeline = ({ layout, jsxPlugins = defaultJsxPlugins
58
65
  * @param {string} code
59
66
  * @param {string} id
60
67
  * @param {{ ssr?: boolean | undefined }} [contextOptions] */
61
- transformJsx: async (code, id, { ssr = false } = {}) => {
68
+ transformJsx: async (code, id, { ssr = false } = {}) => inContext(async () => {
62
69
  const filename = id.split('?', 1)[0]
63
70
  const options = babelOptionsFor(id, { ssr })
64
71
  const result = await transformAsync(code, {
@@ -77,15 +84,28 @@ export const createTransformPipeline = ({ layout, jsxPlugins = defaultJsxPlugins
77
84
  },
78
85
  })
79
86
  return { code: result.code, map: result.map }
80
- },
87
+ }),
81
88
 
82
- postcssPlugin: createCssPostcssPlugin({ contextFor, selectPlugins: selectFor('css'), failures: cssFailures }),
89
+ postcssPlugin: { ...postcssPlugin, Once: (...args) => inContext(() => postcssPlugin.Once(...args)) },
83
90
  babelOptionsFor,
84
91
 
85
92
  /** Whether a stylesheet whose last CSS run failed declared `file`, so an edit to it may fix the stylesheet. */
86
93
  failedStylesheetDependsOn: file => [...cssFailures.values()].some(files => files.has(file)),
87
94
 
95
+ close: () => devStep?.end(),
96
+
97
+ /** @param {string} file @param {() => void} reload */
98
+ handleHotUpdate: (file, reload) => inContext(() => {
99
+ if (command === 'serve' && [...cssFailures.values()].some(files => files.has(file)))
100
+ trackStepSync('op', 'hmr_full_reload', {}, reload)
101
+ }),
102
+
88
103
  setCommand: value => {
104
+ if (value === 'serve' && !devStep) snapshot(() => {
105
+ const run = currentRow()?.run
106
+ devStep = run?.commandRow.runInContext(() => run.openRow('step', 'build_plugins', {}))
107
+ })
108
+ inContext(() => currentRow()?.run.openRow('op', 'pipeline_create', { step_details: { plugins: jsxPlugins.length + cssPlugins.length, command: value } }).end())
89
109
  command = value
90
110
  state = undefined
91
111
  },
@@ -18,15 +18,32 @@
18
18
  * @property {SiteLayout} layout
19
19
  * @property {'serve' | 'build'} command
20
20
  * @property {boolean} ssr Always false for the CSS stage, which PostCSS drives without an SSR signal.
21
- *
21
+ */
22
+
23
+ /**
24
+ * @template {string} Check
25
+ * @typedef {{ check: Check, message: string }} Violation
26
+ */
27
+
28
+ /**
29
+ * @template {string} Check
30
+ * @typedef {Error & { check: Check }} CheckError
31
+ */
32
+
33
+ /**
34
+ * @template {string} Check
22
35
  * @typedef {object} TransformPluginBase
36
+ * @property {readonly Check[]} checks
23
37
  * @property {string} name
24
38
  * @property {number} order Ascending. Ties break by registration order.
25
39
  * @property {(context: TransformContext) => boolean} [appliesTo] Whether the plugin runs for this
26
40
  * file. Must depend only on the file's identity, not on anything that can change between transforms
27
41
  * of the same file: the pipeline memoizes the selection per file path.
28
- *
29
- * @typedef {TransformPluginBase & {
42
+ */
43
+
44
+ /**
45
+ * @template {string} Check
46
+ * @typedef {TransformPluginBase<Check> & {
30
47
  * transform: (program: any, context: TransformContext) => void,
31
48
  * parserPlugins?: readonly string[]
32
49
  * }} JsxTransformPlugin
@@ -34,19 +51,24 @@
34
51
  * nothing walks it for you, so a plugin that can rule itself out cheaply should just return.
35
52
  * `parserPlugins` are Babel parser plugins the file needs; they are unioned across selected plugins
36
53
  * and applied before any of them run.
37
- *
38
- * @typedef {TransformPluginBase & {
39
- * prepare?: (root: import('postcss').Root, context: CssTransformContext) => Promise<void> | void,
54
+ */
55
+
56
+ /**
57
+ * @template {string} Check
58
+ * @typedef {TransformPluginBase<Check> & {
59
+ * prepare?: (root: import('postcss').Root, context: CssTransformContext) => Promise<void | { fetched: number, failed: number }> | void,
40
60
  * validate?: (root: import('postcss').Root, context: CssTransformContext) => void,
41
61
  * transform: (root: import('postcss').Root, context: CssTransformContext) => void
42
62
  * }} CssTransformPlugin
43
63
  * `prepare` loads what the plugin needs that can't be read synchronously, such as a file from a CDN, before any
44
64
  * plugin's `validate` or `transform` runs. It returns a promise only when it has something to load, so a stylesheet
45
65
  * that needs nothing loaded stays synchronous for PostCSS.
46
- *
66
+ */
67
+
68
+ /**
47
69
  * @typedef {TransformContext & {
48
70
  * addDependency?: (file: string) => void,
49
- * warn?: (node: import('postcss').Node, text: string) => void
71
+ * warn?: (node: import('postcss').Node, text: string, key?: 'malformed_custom_enum' | 'unmappable_preset' | 'svg_not_loaded') => void
50
72
  * }} CssTransformContext
51
73
  * `addDependency` declares a file the plugin read besides the stylesheet, so the bundler re-runs the
52
74
  * stylesheet when it changes. `warn` reports a problem at `node` without failing the run, as a PostCSS