@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.
- package/dist/cssPlugins/componentPosition.d.ts +6 -2
- package/dist/cssPlugins/customEnums.d.ts +5 -2
- package/dist/cssPlugins/customMedia.d.ts +7 -2
- package/dist/cssPlugins/fonts.d.ts +6 -2
- package/dist/cssPlugins/index.d.ts +2 -2
- package/dist/cssPlugins/layout/index.d.ts +7 -2
- package/dist/cssPlugins/mixins.d.ts +7 -2
- package/dist/cssPlugins/presets.d.ts +6 -2
- package/dist/cssPlugins/shapeDividers.d.ts +6 -2
- package/dist/cssPlugins/spx/index.d.ts +7 -2
- package/dist/cssPlugins/stackChildren.d.ts +5 -2
- package/dist/cssPlugins/svg.d.ts +4 -1
- package/dist/cssPlugins/wixVectorUrls.d.ts +6 -2
- package/dist/jsxPlugins/index.d.ts +2 -2
- package/dist/jsxPlugins/mediaValidation/index.d.ts +5 -2
- package/dist/jsxPlugins/origamiCompatibility/index.d.ts +6 -2
- package/dist/jsxPlugins/pageSlots.d.ts +7 -6
- package/dist/jsxPlugins/validation/checks.d.ts +7 -7
- package/dist/jsxPlugins/validation/index.d.ts +5 -2
- package/dist/jsxPlugins/validation/rules.d.ts +25 -0
- package/dist/transform/bi.d.ts +13 -0
- package/dist/transform/context.d.ts +1 -0
- package/dist/transform/css/runPlugins.d.ts +4 -1
- package/dist/transform/layoutError.d.ts +8 -4
- package/dist/transform/pipeline.d.ts +7 -9
- package/dist/transform/types.d.ts +16 -13
- package/package.json +6 -4
- package/src/cssPlugins/componentPosition.js +5 -1
- package/src/cssPlugins/customEnums.js +19 -12
- package/src/cssPlugins/customMedia.js +10 -3
- package/src/cssPlugins/fonts.js +5 -1
- package/src/cssPlugins/index.js +1 -1
- package/src/cssPlugins/layout/index.js +8 -1
- package/src/cssPlugins/layout/parser.js +15 -14
- package/src/cssPlugins/mixins.js +13 -6
- package/src/cssPlugins/presets.js +7 -2
- package/src/cssPlugins/shapeDividers.js +6 -2
- package/src/cssPlugins/spx/calls.js +2 -1
- package/src/cssPlugins/spx/cascade.js +3 -2
- package/src/cssPlugins/spx/index.js +8 -1
- package/src/cssPlugins/stackChildren.js +6 -2
- package/src/cssPlugins/svg.js +5 -2
- package/src/cssPlugins/wixVectorUrls.js +6 -2
- package/src/jsxPlugins/index.js +1 -1
- package/src/jsxPlugins/mediaValidation/index.js +14 -5
- package/src/jsxPlugins/origamiCompatibility/index.js +5 -1
- package/src/jsxPlugins/pageSlots.js +6 -1
- package/src/jsxPlugins/validation/checks.js +15 -8
- package/src/jsxPlugins/validation/index.js +9 -3
- package/src/jsxPlugins/validation/rules.js +14 -0
- package/src/transform/bi.js +64 -0
- package/src/transform/context.js +39 -0
- package/src/transform/css/postcssPlugin.js +18 -3
- package/src/transform/css/runPlugins.js +23 -3
- package/src/transform/jsx/runPlugins.js +17 -2
- package/src/transform/layoutError.js +8 -3
- package/src/transform/pipeline.js +25 -5
- 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) =>
|
|
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
|
|
75
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
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
|
},
|
package/src/transform/types.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|