@wix/origami-transforms 0.1.26 → 0.1.28
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/svg.d.ts +4 -1
- package/dist/transform/bi.d.ts +4 -0
- package/dist/transform/context.d.ts +1 -0
- package/dist/transform/css/runPlugins.d.ts +4 -1
- package/dist/transform/layoutError.d.ts +1 -0
- package/dist/transform/pipeline.d.ts +5 -7
- package/dist/transform/types.d.ts +5 -2
- package/package.json +7 -5
- package/src/cssPlugins/customEnums.js +2 -1
- package/src/cssPlugins/presets.js +2 -1
- package/src/cssPlugins/shapeDividers.js +1 -1
- package/src/cssPlugins/stackChildren.js +7 -5
- package/src/cssPlugins/svg.js +5 -2
- package/src/cssPlugins/wixVectorUrls.js +1 -1
- package/src/jsxPlugins/mediaValidation/index.js +5 -1
- package/src/transform/bi.js +34 -0
- package/src/transform/context.js +39 -0
- package/src/transform/css/postcssPlugin.js +18 -3
- package/src/transform/css/runPlugins.js +24 -2
- package/src/transform/jsx/runPlugins.js +20 -2
- package/src/transform/layoutError.js +1 -0
- package/src/transform/pipeline.js +23 -3
- package/src/transform/types.js +2 -2
package/dist/cssPlugins/svg.d.ts
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Aggregate completed dev invocations; paths are only in this local set, never in rows. */
|
|
2
|
+
export function countPlugin(context: any, duration: any): void;
|
|
3
|
+
/** @param {'malformed_custom_enum' | 'unmappable_preset' | 'svg_not_loaded' | 'media_not_wix'} key */
|
|
4
|
+
export function warn(key: "malformed_custom_enum" | "unmappable_preset" | "svg_not_loaded" | "media_not_wix"): void;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export function pluginError(plugin: any, context: any, cause: any): Error
|
|
1
|
+
export function pluginError(plugin: any, context: any, cause: any): Error & {
|
|
2
|
+
error_name: import("@wix/origami-bi").ErrorName;
|
|
3
|
+
error_kind: import("@wix/origami-bi").ErrorKind;
|
|
4
|
+
};
|
|
2
5
|
export function runCssPlugins({ plugins, root, context }: {
|
|
3
6
|
plugins: any;
|
|
4
7
|
root: any;
|
|
@@ -15,15 +15,10 @@ export function createTransformPipeline({ layout, jsxPlugins, cssPlugins }: {
|
|
|
15
15
|
* @param {{ ssr?: boolean | undefined }} [contextOptions] */
|
|
16
16
|
transformJsx: (code: string, id: string, { ssr }?: {
|
|
17
17
|
ssr?: boolean | undefined;
|
|
18
|
-
}) => Promise<
|
|
19
|
-
code: any;
|
|
20
|
-
map: any;
|
|
21
|
-
}>;
|
|
18
|
+
}) => Promise<any>;
|
|
22
19
|
postcssPlugin: {
|
|
20
|
+
Once: (...args: any[]) => any;
|
|
23
21
|
postcssPlugin: string;
|
|
24
|
-
Once(root: any, { result }: {
|
|
25
|
-
result: any;
|
|
26
|
-
}): void | Promise<void>;
|
|
27
22
|
};
|
|
28
23
|
babelOptionsFor: (id: any, { ssr }?: {
|
|
29
24
|
ssr?: boolean | undefined;
|
|
@@ -43,5 +38,8 @@ export function createTransformPipeline({ layout, jsxPlugins, cssPlugins }: {
|
|
|
43
38
|
};
|
|
44
39
|
/** Whether a stylesheet whose last CSS run failed declared `file`, so an edit to it may fix the stylesheet. */
|
|
45
40
|
failedStylesheetDependsOn: (file: any) => boolean;
|
|
41
|
+
close: () => any;
|
|
42
|
+
/** @param {string} file @param {() => void} reload */
|
|
43
|
+
handleHotUpdate: (file: string, reload: () => void) => any;
|
|
46
44
|
setCommand: (value: any) => void;
|
|
47
45
|
};
|
|
@@ -69,7 +69,10 @@ export type JsxTransformPlugin = TransformPluginBase & {
|
|
|
69
69
|
* that needs nothing loaded stays synchronous for PostCSS.
|
|
70
70
|
*/
|
|
71
71
|
export type CssTransformPlugin = TransformPluginBase & {
|
|
72
|
-
prepare?: (root: import("postcss").Root, context: CssTransformContext) => Promise<void
|
|
72
|
+
prepare?: (root: import("postcss").Root, context: CssTransformContext) => Promise<void | {
|
|
73
|
+
fetched: number;
|
|
74
|
+
failed: number;
|
|
75
|
+
}> | void;
|
|
73
76
|
validate?: (root: import("postcss").Root, context: CssTransformContext) => void;
|
|
74
77
|
transform: (root: import("postcss").Root, context: CssTransformContext) => void;
|
|
75
78
|
};
|
|
@@ -80,5 +83,5 @@ export type CssTransformPlugin = TransformPluginBase & {
|
|
|
80
83
|
*/
|
|
81
84
|
export type CssTransformContext = TransformContext & {
|
|
82
85
|
addDependency?: (file: string) => void;
|
|
83
|
-
warn?: (node: import("postcss").Node, text: string) => void;
|
|
86
|
+
warn?: (node: import("postcss").Node, text: string, key?: "malformed_custom_enum" | "unmappable_preset" | "svg_not_loaded") => void;
|
|
84
87
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/origami-transforms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Babel and PostCSS checks and transforms for Wix origami page files.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -12,16 +12,18 @@
|
|
|
12
12
|
"lint": "oxlint .",
|
|
13
13
|
"test": "node --test test/*.spec.js",
|
|
14
14
|
"test:browser": "node test/spx.browser.mjs && node test/semanticContainer.browser.mjs",
|
|
15
|
-
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
15
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
19
|
"@babel/core": "^7.26.0",
|
|
19
20
|
"@babel/parser": "^7.25.0",
|
|
20
21
|
"@babel/types": "^7.25.2",
|
|
21
|
-
"@wix/origami-fonts": "1.0.
|
|
22
|
+
"@wix/origami-fonts": "1.0.4",
|
|
22
23
|
"@wix/origami-system-components": "1.10.6",
|
|
23
24
|
"postcss": "^8.5.3",
|
|
24
|
-
"postcss-custom-media": "^12.0.1"
|
|
25
|
+
"postcss-custom-media": "^12.0.1",
|
|
26
|
+
"@wix/origami-bi": "0.10.0"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
29
|
"@types/node": "^22.0.0",
|
|
@@ -69,5 +71,5 @@
|
|
|
69
71
|
},
|
|
70
72
|
"type": "module",
|
|
71
73
|
"types": "./dist/index.d.ts",
|
|
72
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "1fbeabb662063771eaf3dbbb2b84d3ea8ba71598556ee82a91d545fa"
|
|
73
75
|
}
|
|
@@ -331,7 +331,8 @@ export const customEnumsPlugin = {
|
|
|
331
331
|
`type, such as wixEditorElements.Button, in an element rule under the editor element's. That ` +
|
|
332
332
|
`element's enums are left as authored.`
|
|
333
333
|
: `custom-enums: @custom-enum ${block.params}${where(block)}: can't be read; it needs a --key, a ` +
|
|
334
|
-
`default: that is one of its @option blocks, and an element rule around it. Its key is left as authored
|
|
334
|
+
`default: that is one of its @option blocks, and an element rule around it. Its key is left as authored.`,
|
|
335
|
+
'malformed_custom_enum'
|
|
335
336
|
)
|
|
336
337
|
}
|
|
337
338
|
|
|
@@ -53,7 +53,8 @@ export const presetsPlugin = {
|
|
|
53
53
|
declaration,
|
|
54
54
|
`presets: ${context.relativePath ?? context.filePath}: ` +
|
|
55
55
|
`${declaration.prop} on "${selectors.join(', ')}": set --preset on #id or .id alone, ` +
|
|
56
|
-
`and an inner element's as --preset-{key} on the rule of the element it sits in. Left as authored
|
|
56
|
+
`and an inner element's as --preset-{key} on the rule of the element it sits in. Left as authored.`,
|
|
57
|
+
'unmappable_preset'
|
|
57
58
|
)
|
|
58
59
|
continue
|
|
59
60
|
}
|
|
@@ -125,7 +125,7 @@ export const shapeDividersPlugin = {
|
|
|
125
125
|
const allVars = { ...baseVars.get(id), ...vars }
|
|
126
126
|
const declarations = layerDeclarations(id, allVars)
|
|
127
127
|
if (declarations === undefined) {
|
|
128
|
-
if (allVars['--svgId']) context.warn?.(rule, `Couldn't draw the shape divider's SVG ${allVars['--svgId']}: it didn't load from Wix's CDN, or has no view box
|
|
128
|
+
if (allVars['--svgId']) context.warn?.(rule, `Couldn't draw the shape divider's SVG ${allVars['--svgId']}: it didn't load from Wix's CDN, or has no view box`, 'svg_not_loaded')
|
|
129
129
|
continue
|
|
130
130
|
}
|
|
131
131
|
const layerRule = postcss.rule({ selector: `${rule.selector} ${LAYER}`, source: rule.source })
|
|
@@ -17,14 +17,15 @@ import { literalIdOf, parseStructure, structureFileOf, walk } from './structureF
|
|
|
17
17
|
const CONTENT_WRAPPER = 'internal-container-content-wrapper'
|
|
18
18
|
const BASE = ''
|
|
19
19
|
|
|
20
|
-
// What a stack child may write, per direction. `0` is allowed wherever a length is.
|
|
20
|
+
// What a stack child may write, per direction. `0` is allowed wherever a length is. Exports write `width: max-content`
|
|
21
|
+
// on a stack child, and DM's import accepts it.
|
|
21
22
|
const ALLOWED = {
|
|
22
|
-
vertical: { width: ['spx', 'px', 'vw', 'vh', '%', 'calc'], margin: ['spx', 'px', 'vw', 'vh', '%'] },
|
|
23
|
-
horizontal: { width: ['spx', 'px', 'vw', 'vh', 'calc'], margin: ['spx', 'px', 'vw', 'vh'] },
|
|
23
|
+
vertical: { width: ['spx', 'px', 'vw', 'vh', '%', 'calc', 'max-content'], margin: ['spx', 'px', 'vw', 'vh', '%'] },
|
|
24
|
+
horizontal: { width: ['spx', 'px', 'vw', 'vh', 'calc', 'max-content'], margin: ['spx', 'px', 'vw', 'vh'] },
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const UNIT = /^-?(?:\d+\.?\d*|\.\d+)(px|vw|vh|%)$/i
|
|
27
|
-
const LABELS = { spx: '--spx()', px: 'px', vw: 'vw', vh: 'vh', '%': '%', calc: 'calc()' }
|
|
28
|
+
const LABELS = { spx: '--spx()', px: 'px', vw: 'vw', vh: 'vh', '%': '%', calc: 'calc()', 'max-content': 'max-content' }
|
|
28
29
|
|
|
29
30
|
/** `width` → 'width', `margin`/`margin-*` → 'margin', anything else → undefined. */
|
|
30
31
|
const kindOf = prop =>
|
|
@@ -47,9 +48,10 @@ const tokens = value => {
|
|
|
47
48
|
return result
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
/** The kind of length a token is: 'spx', 'px', 'vw', 'vh', '%', 'calc', 'zero', or undefined. */
|
|
51
|
+
/** The kind of length a token is: 'spx', 'px', 'vw', 'vh', '%', 'calc', 'max-content', 'zero', or undefined. */
|
|
51
52
|
const lengthOf = token => {
|
|
52
53
|
if (/^--spx\(.*\)$/.test(token)) return 'spx'
|
|
54
|
+
if (/^max-content$/i.test(token)) return 'max-content'
|
|
53
55
|
if (/^calc\(.*\)$/i.test(token)) return 'calc'
|
|
54
56
|
if (/^-?0*\.?0+$/.test(token)) return 'zero'
|
|
55
57
|
return UNIT.exec(token)?.[1].toLowerCase()
|
package/src/cssPlugins/svg.js
CHANGED
|
@@ -16,13 +16,16 @@ const loading = new Map()
|
|
|
16
16
|
const svgFileName = id => (id.endsWith('.svg') ? id : `${id}.svg`)
|
|
17
17
|
|
|
18
18
|
const load = async id => {
|
|
19
|
+
let ok = false
|
|
19
20
|
try {
|
|
20
21
|
const response = await fetch(SHAPES_URL + svgFileName(id), { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) })
|
|
21
22
|
const svg = response.ok ? await response.text() : ''
|
|
22
|
-
|
|
23
|
+
ok = SVG_TAG.test(svg)
|
|
24
|
+
loaded.set(id, ok ? svg : null)
|
|
23
25
|
} catch {
|
|
24
26
|
// Not loaded: the plugin warns and keeps the value as written.
|
|
25
27
|
}
|
|
28
|
+
return ok
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/** Loads the SVGs of `ids` that aren't loaded yet. */
|
|
@@ -33,7 +36,7 @@ export const loadSvgs = ids => {
|
|
|
33
36
|
if (!loading.has(id)) loading.set(id, load(id).finally(() => loading.delete(id)))
|
|
34
37
|
return loading.get(id)
|
|
35
38
|
})
|
|
36
|
-
return pending.length ? Promise.all(pending).then((
|
|
39
|
+
return pending.length ? Promise.all(pending).then(results => ({ fetched: results.length, failed: results.filter(ok => !ok).length })) : undefined
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/** The SVG's markup, once `loadSvgs` loaded it. */
|
|
@@ -29,7 +29,7 @@ export const wixVectorUrlsPlugin = {
|
|
|
29
29
|
declaration.value = declaration.value.replace(WIX_VECTOR_URL, (url, _quote, id) => {
|
|
30
30
|
const svg = loadedSvg(id)
|
|
31
31
|
if (svg === undefined) {
|
|
32
|
-
context.warn?.(declaration, `Couldn't load the SVG ${id} from Wix's CDN, so ${url} stays as written
|
|
32
|
+
context.warn?.(declaration, `Couldn't load the SVG ${id} from Wix's CDN, so ${url} stays as written`, 'svg_not_loaded')
|
|
33
33
|
return url
|
|
34
34
|
}
|
|
35
35
|
return svgDataUrl(scaleSvg(svg, svgInfo(svg), 'none'))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { warn } from '../../transform/bi.js'
|
|
1
2
|
// Fails the build on an Image whose `uri` is not a Wix media ID, such as a web URL or a local file.
|
|
2
3
|
// The Wix import accepts such a `uri` but the image is broken on the site; `wix origami upload-media`
|
|
3
4
|
// uploads it and points the Image at the uploaded file. `yarn dev` only warns, so the page still
|
|
@@ -41,7 +42,10 @@ export const mediaValidationPlugin = {
|
|
|
41
42
|
})
|
|
42
43
|
if (violations.length === 0) return
|
|
43
44
|
if (context.command === 'serve') {
|
|
44
|
-
for (const violation of violations)
|
|
45
|
+
for (const violation of violations) {
|
|
46
|
+
warn('media_not_wix')
|
|
47
|
+
console.warn(`[origami] ${violation}`)
|
|
48
|
+
}
|
|
45
49
|
return
|
|
46
50
|
}
|
|
47
51
|
throw new LayoutError(violations, first)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { currentRow } from '@wix/origami-bi'
|
|
2
|
+
const summaries = new WeakMap()
|
|
3
|
+
|
|
4
|
+
/** Aggregate completed dev invocations; paths are only in this local set, never in rows. */
|
|
5
|
+
export function countPlugin(context, duration) {
|
|
6
|
+
const parent = currentRow()
|
|
7
|
+
if (!parent || context.command !== 'serve') return
|
|
8
|
+
const { run } = parent
|
|
9
|
+
let summary = summaries.get(run)
|
|
10
|
+
if (!summary) {
|
|
11
|
+
summary = { files: new Set(), plugins: 0, duration_ms: 0 }
|
|
12
|
+
summaries.set(run, summary)
|
|
13
|
+
const timer = setInterval(() => {
|
|
14
|
+
if (!run.commandRow.isOpen) { clearInterval(timer); summaries.delete(run); return }
|
|
15
|
+
if (!summary.plugins) return
|
|
16
|
+
run.commandRow.runInContext(() => run.openRow('step', 'transform_summary', {
|
|
17
|
+
step_details: { files: summary.files.size, plugins: summary.plugins, duration_ms: Math.round(summary.duration_ms) },
|
|
18
|
+
}).end())
|
|
19
|
+
summary.files.clear(); summary.plugins = 0; summary.duration_ms = 0
|
|
20
|
+
}, 60_000)
|
|
21
|
+
timer.unref()
|
|
22
|
+
}
|
|
23
|
+
summary.files.add(context.filePath)
|
|
24
|
+
summary.plugins++
|
|
25
|
+
summary.duration_ms += duration
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @param {'malformed_custom_enum' | 'unmappable_preset' | 'svg_not_loaded' | 'media_not_wix'} key */
|
|
29
|
+
export function warn(key) {
|
|
30
|
+
const current = currentRow()
|
|
31
|
+
// A passing dev plugin row is discarded; warnings must retain a live parent.
|
|
32
|
+
const parent = current?.step_type === 'build_plugin' ? current.parent : current
|
|
33
|
+
parent?.runInContext(() => parent.run.openRow('warning', key, { error_name: key }).end())
|
|
34
|
+
}
|
package/src/transform/context.js
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
|
+
import { currentRow } from '@wix/origami-bi'
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
3
|
+
import { dirname, join, relative } from 'node:path'
|
|
4
|
+
import { parse } from '@babel/parser'
|
|
1
5
|
import { relativeToSite } from './relativeToSite.js'
|
|
2
6
|
|
|
7
|
+
const identities = new Map()
|
|
8
|
+
export function pageIdFor(context) {
|
|
9
|
+
if (!currentRow()?.run.enabled || !context.layout?.root) return undefined
|
|
10
|
+
const { root } = context.layout
|
|
11
|
+
const file = context.filePath
|
|
12
|
+
let folder = dirname(file)
|
|
13
|
+
const pages = join(root, 'src/pages')
|
|
14
|
+
while (relative(pages, folder) && !relative(pages, folder).startsWith('..')) {
|
|
15
|
+
const structure = ['structure.jsx', 'structure.tsx'].map(name => join(folder, name)).find(existsSync)
|
|
16
|
+
if (structure) {
|
|
17
|
+
try {
|
|
18
|
+
const modified = statSync(structure).mtimeMs
|
|
19
|
+
if (identities.get(structure)?.modified === modified) return identities.get(structure).id
|
|
20
|
+
const ast = parse(readFileSync(structure, 'utf8'), { sourceType: 'module', plugins: ['jsx', 'typescript'] })
|
|
21
|
+
let id
|
|
22
|
+
const visit = node => {
|
|
23
|
+
if (!node || typeof node !== 'object') return
|
|
24
|
+
if (node.type === 'JSXOpeningElement' && node.name?.name === 'Page') {
|
|
25
|
+
const attribute = node.attributes.find(value => value.type === 'JSXAttribute' && value.name.name === 'id')
|
|
26
|
+
if (attribute?.value?.type === 'StringLiteral') id ??= attribute.value.value
|
|
27
|
+
}
|
|
28
|
+
for (const value of Object.values(node)) {
|
|
29
|
+
if (Array.isArray(value)) value.forEach(visit)
|
|
30
|
+
else if (value && typeof value === 'object') visit(value)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
visit(ast)
|
|
34
|
+
identities.set(structure, { modified, id })
|
|
35
|
+
return id
|
|
36
|
+
} catch { return undefined }
|
|
37
|
+
}
|
|
38
|
+
folder = dirname(folder)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
3
42
|
export const createContextFactory =
|
|
4
43
|
({ layout, command }) =>
|
|
5
44
|
(id, { ssr = false } = {}) =>
|
|
@@ -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 } 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,7 +16,7 @@ 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('plugin_error', `CSS transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`, {
|
|
17
20
|
cause,
|
|
18
21
|
})
|
|
19
22
|
|
|
@@ -22,17 +25,36 @@ export const runCssPlugins = ({ plugins, root, context }) => {
|
|
|
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
|
+
const layout = cause instanceof LayoutError
|
|
42
|
+
row?.end(false, { error_name: layout ? 'layout_error' : 'plugin_error' }, {
|
|
43
|
+
outcome: layout ? 'layout_error' : 'plugin_error', violations: layout ? cause.violations.length : 0,
|
|
44
|
+
})
|
|
28
45
|
if (cause instanceof LayoutError) {
|
|
29
46
|
layoutErrors.push(cause)
|
|
30
47
|
continue
|
|
31
48
|
}
|
|
32
49
|
throw pluginError(plugin, context, cause)
|
|
50
|
+
} finally {
|
|
51
|
+
countPlugin(context, performance.now() - started)
|
|
33
52
|
}
|
|
34
53
|
}
|
|
35
54
|
}
|
|
55
|
+
if (layoutErrors.length) currentRow()?.run.openRow('op', 'css_layout_errors', {
|
|
56
|
+
step_details: { violations: layoutErrors.reduce((count, error) => count + error.violations.length, 0) },
|
|
57
|
+
}).end()
|
|
36
58
|
if (layoutErrors.length === 1) throw layoutErrors[0]
|
|
37
59
|
if (layoutErrors.length > 1) {
|
|
38
60
|
throw new LayoutError(
|
|
@@ -2,22 +2,40 @@
|
|
|
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 } 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
|
+
const layout = cause instanceof LayoutError
|
|
27
|
+
row?.end(false, { error_name: layout ? 'layout_error' : 'plugin_error' }, {
|
|
28
|
+
outcome: layout ? 'layout_error' : 'plugin_error', violations: layout ? cause.violations.length : 0,
|
|
29
|
+
})
|
|
14
30
|
if (cause instanceof LayoutError) throw cause
|
|
15
31
|
// Tooling such as Vite's error overlay prints only the message, so the reason goes in it as well. A
|
|
16
32
|
// plugin may throw any value, so it is read without assuming an Error.
|
|
17
|
-
throw
|
|
33
|
+
throw namedError('plugin_error',
|
|
18
34
|
`JSX transform "${plugin.name}" failed for ${context.relativePath ?? context.filePath}: ${reason(cause)}`,
|
|
19
35
|
{ cause }
|
|
20
36
|
)
|
|
37
|
+
} finally {
|
|
38
|
+
countPlugin(context, performance.now() - started)
|
|
21
39
|
}
|
|
22
40
|
}
|
|
23
41
|
}
|
|
@@ -11,6 +11,7 @@ export class LayoutError extends Error {
|
|
|
11
11
|
constructor(violations, location = {}) {
|
|
12
12
|
super(violations.length === 1 ? violations[0] : `\n ${violations.join('\n ')}`)
|
|
13
13
|
this.name = 'LayoutError'
|
|
14
|
+
this.error_name = 'layout_error'
|
|
14
15
|
this.stack = ''
|
|
15
16
|
this.violations = violations
|
|
16
17
|
this.location = 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'
|
|
@@ -33,6 +35,10 @@ const createState = ({ layout, registries, command }) => {
|
|
|
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
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* and applied before any of them run.
|
|
37
37
|
*
|
|
38
38
|
* @typedef {TransformPluginBase & {
|
|
39
|
-
* prepare?: (root: import('postcss').Root, context: CssTransformContext) => Promise<void> | void,
|
|
39
|
+
* prepare?: (root: import('postcss').Root, context: CssTransformContext) => Promise<void | { fetched: number, failed: number }> | void,
|
|
40
40
|
* validate?: (root: import('postcss').Root, context: CssTransformContext) => void,
|
|
41
41
|
* transform: (root: import('postcss').Root, context: CssTransformContext) => void
|
|
42
42
|
* }} CssTransformPlugin
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
*
|
|
47
47
|
* @typedef {TransformContext & {
|
|
48
48
|
* addDependency?: (file: string) => void,
|
|
49
|
-
* warn?: (node: import('postcss').Node, text: string) => void
|
|
49
|
+
* warn?: (node: import('postcss').Node, text: string, key?: 'malformed_custom_enum' | 'unmappable_preset' | 'svg_not_loaded') => void
|
|
50
50
|
* }} CssTransformContext
|
|
51
51
|
* `addDependency` declares a file the plugin read besides the stylesheet, so the bundler re-runs the
|
|
52
52
|
* stylesheet when it changes. `warn` reports a problem at `node` without failing the run, as a PostCSS
|