@platformos/platformos-graph 0.0.7 → 0.0.9

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 (65) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +10 -11
  3. package/dist/getWebComponentMap.js +1 -1
  4. package/dist/getWebComponentMap.js.map +1 -1
  5. package/dist/graph/augment.js +0 -3
  6. package/dist/graph/augment.js.map +1 -1
  7. package/dist/graph/build.d.ts +2 -2
  8. package/dist/graph/build.js +7 -8
  9. package/dist/graph/build.js.map +1 -1
  10. package/dist/graph/module.d.ts +6 -9
  11. package/dist/graph/module.js +54 -123
  12. package/dist/graph/module.js.map +1 -1
  13. package/dist/graph/serialize.d.ts +2 -2
  14. package/dist/graph/serialize.js +2 -2
  15. package/dist/graph/serialize.js.map +1 -1
  16. package/dist/graph/test-helpers.d.ts +0 -13
  17. package/dist/graph/test-helpers.js +0 -10
  18. package/dist/graph/test-helpers.js.map +1 -1
  19. package/dist/graph/traverse.d.ts +3 -3
  20. package/dist/graph/traverse.js +19 -349
  21. package/dist/graph/traverse.js.map +1 -1
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +4 -6
  24. package/dist/index.js.map +1 -1
  25. package/dist/toSourceCode.d.ts +1 -4
  26. package/dist/toSourceCode.js +8 -52
  27. package/dist/toSourceCode.js.map +1 -1
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/dist/types.d.ts +21 -78
  30. package/dist/types.js.map +1 -1
  31. package/dist/utils/index.d.ts +0 -3
  32. package/dist/utils/index.js +0 -18
  33. package/dist/utils/index.js.map +1 -1
  34. package/docs/how-it-works.md +9 -10
  35. package/fixtures/skeleton/app/views/layouts/application.liquid +13 -0
  36. package/fixtures/skeleton/app/views/pages/index.liquid +5 -0
  37. package/fixtures/skeleton/app/views/partials/header.liquid +3 -0
  38. package/fixtures/skeleton/app/views/partials/parent.liquid +1 -1
  39. package/package.json +5 -5
  40. package/src/getWebComponentMap.ts +1 -1
  41. package/src/graph/augment.ts +1 -13
  42. package/src/graph/build.spec.ts +33 -173
  43. package/src/graph/build.ts +11 -14
  44. package/src/graph/module.ts +60 -144
  45. package/src/graph/serialize.spec.ts +22 -29
  46. package/src/graph/serialize.ts +2 -2
  47. package/src/graph/test-helpers.ts +1 -18
  48. package/src/graph/traverse.ts +31 -504
  49. package/src/index.ts +3 -3
  50. package/src/toSourceCode.ts +14 -55
  51. package/src/types.ts +23 -100
  52. package/src/utils/index.ts +0 -24
  53. package/fixtures/skeleton/blocks/_private.liquid +0 -1
  54. package/fixtures/skeleton/blocks/_static.liquid +0 -10
  55. package/fixtures/skeleton/blocks/group.liquid +0 -27
  56. package/fixtures/skeleton/blocks/render-static.liquid +0 -22
  57. package/fixtures/skeleton/blocks/text.liquid +0 -14
  58. package/fixtures/skeleton/jsconfig.json +0 -9
  59. package/fixtures/skeleton/layout/theme.liquid +0 -14
  60. package/fixtures/skeleton/sections/custom-section.liquid +0 -6
  61. package/fixtures/skeleton/sections/header-group.json +0 -36
  62. package/fixtures/skeleton/sections/header.liquid +0 -1
  63. package/fixtures/skeleton/templates/index.json +0 -20
  64. /package/fixtures/skeleton/assets/{theme.css → app.css} +0 -0
  65. /package/fixtures/skeleton/assets/{theme.js → app.js} +0 -0
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## What it looks like
10
10
 
11
- A theme graph looks a bit like this:
11
+ An app graph looks a bit like this:
12
12
 
13
13
  ![Graph](./graph.png)
14
14
 
@@ -18,15 +18,15 @@ It's a web of modules and links between them.
18
18
 
19
19
  A **Graph** is a set of Nodes and Edges.
20
20
 
21
- A **Theme Graph** is a set of *Modules* (nodes) and *References* (edges) defined by an array of *Entry Points*. It has these properties:
22
- - `rootUri` - root of the theme, e.g. `file:/path/to/theme`
23
- - `entryPoints` - array of modules that define the theme (all templates and sections)
24
- - `modules` - all the modules in the theme indexed by URI
21
+ An **App Graph** is a set of *Modules* (nodes) and *References* (edges) defined by an array of *Entry Points*. It has these properties:
22
+ - `rootUri` - root of the app, e.g. `file:/path/to/app`
23
+ - `entryPoints` - array of modules that define the app (pages and layouts)
24
+ - `modules` - all the modules in the app indexed by URI
25
25
 
26
- A **Module** is an object that represents a theme file. It has these properties:
27
- - `uri` - module identifier, e.g. `file:/path/to/theme/snippets/file.liquid`
26
+ A **Module** is an object that represents an app file. It has these properties:
27
+ - `uri` - module identifier, e.g. `file:/path/to/app/views/partials/file.liquid`
28
28
  - `type` - e.g. `liquid`, `json`, `javascript`, `css`.
29
- - `kind` - e.g. `block`, `section`, `snippet`, `template`, etc.
29
+ - `kind` - e.g. `layout`, `partial`, `page`.
30
30
  - `references` - array of *References* that point to this module.
31
31
  - `dependencies` - array of *References* that this module depends on.
32
32
 
@@ -35,8 +35,7 @@ A **Reference** is an object that defines a link between two modules. It has the
35
35
  - `target` - a `uri` and `range` that defines which module is being dependended on and optionally what is being depended on in that file,
36
36
  - `type` - one of the following:
37
37
  - `direct` - the file can't exist without the other, e.g. `{% render 'child' %}`
38
- - `preset` - the file has a preset that depends on an other file, e.g. a section that has a preset that includes `group` and `text` blocks.
39
- - `indirect` - the file loosely depends on the other, but not explicilty. e.g. when a file accepts all public theme blocks (`"type": "@theme"`))
38
+ - `indirect` - the file loosely depends on the other, but not explicitly.
40
39
 
41
40
 
42
41
  ## Algorithm overview
@@ -0,0 +1,13 @@
1
+ <html lang="en">
2
+ <head>
3
+ <meta charset="UTF-8">
4
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
+ <title>Document</title>
6
+ <script type="module" src="{{ 'app.js' | asset_url }}"></script>
7
+ <link rel="stylesheet" href="{{ 'app.css' | asset_url }}">
8
+ </head>
9
+ <body>
10
+ {% render 'header' %}
11
+ {% yield %}
12
+ </body>
13
+ </html>
@@ -0,0 +1,5 @@
1
+ ---
2
+ slug: index
3
+ layout: application
4
+ ---
5
+ {% render "parent", children: "hello" %}
@@ -0,0 +1,3 @@
1
+ <header>
2
+ {% render "child", children: "nav" %}
3
+ </header>
@@ -1,5 +1,5 @@
1
1
  {% doc %}
2
- Parent snippet that renders the children and depends on the parent-element custom element
2
+ Parent partial that renders the children and depends on the parent-element custom element
3
3
 
4
4
  @param {string} children
5
5
  {% enddoc %}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@platformos/platformos-graph",
3
- "version": "0.0.7",
4
- "description": "Shopify Theme Graph as a data structure",
3
+ "version": "0.0.9",
4
+ "description": "platformOS App Graph as a data structure",
5
5
  "author": "platformOS",
6
6
  "homepage": "https://github.com/Platform-OS/platformos-tools/tree/master/packages/platformos-graph#readme",
7
7
  "repository": {
@@ -29,13 +29,13 @@
29
29
  "type-check": "tsc --noEmit"
30
30
  },
31
31
  "dependencies": {
32
- "@platformos/liquid-html-parser": "^0.0.7",
33
- "@platformos/platformos-check-common": "0.0.7",
32
+ "@platformos/liquid-html-parser": "^0.0.9",
33
+ "@platformos/platformos-check-common": "0.0.9",
34
34
  "acorn": "^8.16.0",
35
35
  "acorn-walk": "^8.3.5",
36
36
  "vscode-uri": "^3.1.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@platformos/platformos-check-node": "0.0.7"
39
+ "@platformos/platformos-check-node": "0.0.9"
40
40
  }
41
41
  }
@@ -46,7 +46,7 @@ export async function findWebComponentReferences(
46
46
  result: WebComponentMap,
47
47
  ): Promise<Void> {
48
48
  const sourceCode = await getSourceCode(uri);
49
- if (sourceCode.type !== 'javascript') {
49
+ if (!uri.endsWith('.js')) {
50
50
  return;
51
51
  }
52
52
 
@@ -1,9 +1,4 @@
1
- import {
2
- memoize,
3
- memo,
4
- recursiveReadDirectory as findAllFiles,
5
- path,
6
- } from '@platformos/platformos-check-common';
1
+ import { memoize, path } from '@platformos/platformos-check-common';
7
2
  import { toSourceCode } from '../toSourceCode';
8
3
  import { AugmentedDependencies, IDependencies } from '../types';
9
4
  import { identity } from '../utils';
@@ -11,8 +6,6 @@ import { identity } from '../utils';
11
6
  export function augmentDependencies(rootUri: string, ideps: IDependencies): AugmentedDependencies {
12
7
  return {
13
8
  fs: ideps.fs,
14
- getBlockSchema: memoize(ideps.getBlockSchema, identity),
15
- getSectionSchema: memoize(ideps.getSectionSchema, identity),
16
9
 
17
10
  // parse at most once
18
11
  getSourceCode: memoize(
@@ -25,10 +18,5 @@ export function augmentDependencies(rootUri: string, ideps: IDependencies): Augm
25
18
  ),
26
19
 
27
20
  getWebComponentDefinitionReference: ideps.getWebComponentDefinitionReference,
28
- getThemeBlockNames: memo(() =>
29
- findAllFiles(ideps.fs, path.join(rootUri, 'blocks'), ([uri]) => uri.endsWith('.liquid')).then(
30
- (uris) => uris.map((uri) => path.basename(uri, '.liquid')),
31
- ),
32
- ),
33
21
  };
34
22
  }
@@ -1,7 +1,7 @@
1
1
  import { path as pathUtils, SourceCodeType } from '@platformos/platformos-check-common';
2
2
  import { assert, beforeAll, beforeEach, describe, expect, it } from 'vitest';
3
- import { buildThemeGraph } from '../index';
4
- import { Dependencies, JsonModuleKind, LiquidModuleKind, ModuleType, ThemeGraph } from '../types';
3
+ import { buildAppGraph } from '../index';
4
+ import { Dependencies, LiquidModuleKind, ModuleType, AppGraph } from '../types';
5
5
  import { getDependencies, skeleton } from './test-helpers';
6
6
 
7
7
  describe('Module: index', () => {
@@ -14,162 +14,51 @@ describe('Module: index', () => {
14
14
  dependencies = await getDependencies(rootUri);
15
15
  }, 15000);
16
16
 
17
- describe('Unit: buildThemeGraph', { timeout: 10000 }, () => {
18
- it('build a graph of the theme', { timeout: 10000 }, async () => {
19
- const graph = await buildThemeGraph(rootUri, dependencies);
17
+ describe('Unit: buildAppGraph', { timeout: 10000 }, () => {
18
+ it('builds a graph of the app', { timeout: 10000 }, async () => {
19
+ const graph = await buildAppGraph(rootUri, dependencies);
20
20
  expect(graph).toBeDefined();
21
21
  });
22
22
 
23
- describe('with a valid theme graph', () => {
24
- let graph: ThemeGraph;
23
+ describe('with a valid app graph', () => {
24
+ let graph: AppGraph;
25
25
 
26
26
  beforeEach(async () => {
27
- graph = await buildThemeGraph(rootUri, dependencies);
27
+ graph = await buildAppGraph(rootUri, dependencies);
28
28
  });
29
29
 
30
- it('have a root URI', () => {
30
+ it('has a root URI', () => {
31
31
  expect(graph.rootUri).toBeDefined();
32
32
  expect(graph.rootUri).toBe(rootUri);
33
33
  });
34
34
 
35
- // We're using sections as entry points because the section rendering API can render
36
- // any section without it needing a preset or default value in its schema.
37
- it('infers entry points from the templates folder and section files', () => {
38
- expect(graph.entryPoints).toHaveLength(3);
35
+ it('infers entry points from layouts and pages', () => {
36
+ expect(graph.entryPoints).toHaveLength(2);
39
37
  expect(graph.entryPoints.map((x) => x.uri)).toEqual(
40
38
  expect.arrayContaining([
41
- p('templates/index.json'),
42
- p('sections/custom-section.liquid'),
43
- p('sections/header.liquid'),
39
+ p('app/views/layouts/application.liquid'),
40
+ p('app/views/pages/index.liquid'),
44
41
  ]),
45
42
  );
46
43
  });
47
44
 
48
- it("finds layout/theme.liquid's dependencies and references", () => {
49
- const themeLayout = graph.modules[p('layout/theme.liquid')];
50
- assert(themeLayout);
45
+ it("finds app/views/layouts/application.liquid's dependencies", () => {
46
+ const layout = graph.modules[p('app/views/layouts/application.liquid')];
47
+ assert(layout);
48
+ assert(layout.type === ModuleType.Liquid);
49
+ assert(layout.kind === LiquidModuleKind.Layout);
51
50
 
52
- // outgoing links
53
- const deps = themeLayout.dependencies;
54
- assert(deps.map((x) => x.source.uri).every((x) => x === p('layout/theme.liquid')));
55
- expect(deps.map((x) => x.target.uri)).toEqual(
56
- expect.arrayContaining([
57
- p('sections/header-group.json'),
58
- p('assets/theme.js'),
59
- p('assets/theme.css'),
60
- ]),
61
- );
62
-
63
- // ingoing links
64
- const refs = themeLayout.references;
65
- expect(refs).toHaveLength(1);
66
- assert(refs.map((x) => x.target.uri).every((x) => x === p('layout/theme.liquid')));
67
- expect(refs.map((x) => x.source.uri)).toEqual(
68
- expect.arrayContaining([p('templates/index.json')]),
69
- );
70
- });
71
-
72
- it("finds templates/index.json's dependencies and references", () => {
73
- const indexTemplate = graph.modules[p('templates/index.json')];
74
- assert(indexTemplate);
75
- assert(indexTemplate.type === ModuleType.Json);
76
- assert(indexTemplate.kind === JsonModuleKind.Template);
77
-
78
- // outgoing links
79
- const deps = indexTemplate.dependencies;
80
- assert(deps.map((x) => x.source.uri).every((x) => x === p('templates/index.json')));
81
- expect(deps.map((x) => x.target.uri)).toEqual(
82
- expect.arrayContaining([
83
- p('layout/theme.liquid'),
84
- p('sections/custom-section.liquid'),
85
- p('blocks/group.liquid'),
86
- p('blocks/text.liquid'),
87
- ]),
88
- );
89
-
90
- // ingoing links
91
- const refs = indexTemplate.references;
92
- expect(refs).toHaveLength(0);
93
- });
94
-
95
- it("finds sections/custom-section's dependencies and references", () => {
96
- const customSection = graph.modules[p('sections/custom-section.liquid')];
97
- assert(customSection);
98
- assert(customSection.type === ModuleType.Liquid);
99
- assert(customSection.kind === LiquidModuleKind.Section);
100
-
101
- // outgoing links
102
- const deps = customSection.dependencies;
103
- assert(deps.map((x) => x.source.uri).every((x) => x === customSection.uri));
51
+ const deps = layout.dependencies;
104
52
  expect(deps.map((x) => x.target.uri)).toEqual(
105
53
  expect.arrayContaining([
106
- p('blocks/group.liquid'),
107
- p('blocks/text.liquid'),
108
- p('blocks/_private.liquid'),
109
- ]),
110
- );
111
-
112
- // ingoing links
113
- const refs = customSection.references;
114
- assert(refs.map((x) => x.target.uri).every((x) => x === customSection.uri));
115
- expect(refs.map((x) => x.source.uri)).toEqual(
116
- expect.arrayContaining([p('templates/index.json'), p('sections/header-group.json')]),
117
- );
118
- expect(refs).toHaveLength(2);
119
- });
120
-
121
- it("finds blocks/group's dependencies and references", () => {
122
- const groupBlock = graph.modules[p('blocks/group.liquid')];
123
- assert(groupBlock);
124
- assert(groupBlock.type === ModuleType.Liquid);
125
- assert(groupBlock.kind === LiquidModuleKind.Block);
126
-
127
- const deps = groupBlock.dependencies;
128
- assert(deps.map((x) => x.source.uri).every((x) => x === groupBlock.uri));
129
- expect(deps).toEqual(
130
- expect.arrayContaining([
131
- {
132
- source: loc('blocks/group.liquid'),
133
- target: loc('app/views/partials/parent.liquid'),
134
- type: 'direct',
135
- }, // direct dep in partial
136
- {
137
- source: loc('blocks/group.liquid'),
138
- target: loc('blocks/text.liquid'),
139
- type: 'indirect',
140
- }, // indirect because of @theme
141
- {
142
- source: loc('blocks/group.liquid'),
143
- target: loc('blocks/text.liquid'),
144
- type: 'preset',
145
- }, // direct dep in preset
146
- ]),
147
- );
148
-
149
- const refs = groupBlock.references;
150
- assert(refs.map((x) => x.target.uri).every((x) => x === groupBlock.uri));
151
- expect(refs.map((x) => x.source.uri)).toEqual(
152
- expect.arrayContaining([
153
- p('templates/index.json'),
154
- p('sections/custom-section.liquid'), // @theme ref
155
- p('sections/header-group.json'), // custom-section > group
156
- p('blocks/group.liquid'), // @theme ref
54
+ p('assets/app.js'),
55
+ p('assets/app.css'),
56
+ p('app/views/partials/header.liquid'),
157
57
  ]),
158
58
  );
159
-
160
- expect(refs).toContainEqual(
161
- // Expecting the `@theme` reference in the custom-section schema to be indirect
162
- expect.objectContaining({
163
- type: 'indirect',
164
- source: {
165
- uri: p('sections/custom-section.liquid'),
166
- range: [expect.any(Number), expect.any(Number)],
167
- },
168
- }),
169
- );
170
59
  });
171
60
 
172
- it("finds the app/views/partials/parent's dependencies and references", async () => {
61
+ it("finds app/views/partials/parent's dependencies and references", async () => {
173
62
  const parentPartial = graph.modules[p('app/views/partials/parent.liquid')];
174
63
  assert(parentPartial);
175
64
  assert(parentPartial.type === ModuleType.Liquid);
@@ -179,22 +68,10 @@ describe('Module: index', () => {
179
68
  const deps = parentPartial.dependencies;
180
69
  assert(deps.map((x) => x.source.uri).every((x) => x === parentPartial.uri));
181
70
  expect(deps.map((x) => x.target.uri)).toEqual(
182
- expect.arrayContaining([
183
- p('app/views/partials/child.liquid'), // {% render 'child' %}
184
- p('assets/theme.js'), // <parent-element>
185
- ]),
186
- );
187
-
188
- // ingoing links
189
- const refs = parentPartial.references;
190
- assert(refs.map((x) => x.target.uri).every((x) => x === parentPartial.uri));
191
- expect(refs.map((x) => x.source.uri)).toEqual(
192
- expect.arrayContaining([
193
- p('blocks/group.liquid'), // {% render 'parent' %}
194
- ]),
71
+ expect.arrayContaining([p('app/views/partials/child.liquid'), p('assets/app.js')]),
195
72
  );
196
73
 
197
- // {% render 'child', children: children %} dependency
74
+ // {% render 'child' %} dependency
198
75
  const parentSource = await dependencies.getSourceCode(
199
76
  p('app/views/partials/parent.liquid'),
200
77
  );
@@ -210,36 +87,19 @@ describe('Module: index', () => {
210
87
  ],
211
88
  }),
212
89
  );
213
-
214
- // <parent-element> dependency
215
- expect(parentPartial.dependencies.map((x) => x.source)).toContainEqual(
216
- expect.objectContaining({
217
- uri: p('app/views/partials/parent.liquid'),
218
- range: [
219
- parentSource.source.indexOf('<parent-element'),
220
- parentSource.source.indexOf('<parent-element') + '<parent-element'.length,
221
- ],
222
- }),
223
- );
224
90
  });
225
91
 
226
- it("finds the blocks/_static's dependencies and references", () => {
227
- const staticBlock = graph.modules[p('blocks/_static.liquid')];
228
- assert(staticBlock);
229
- assert(staticBlock.type === ModuleType.Liquid);
230
- assert(staticBlock.kind === LiquidModuleKind.Block);
231
-
232
- // outgoing links
233
- const deps = staticBlock.dependencies;
234
- expect(deps).toEqual([]);
92
+ it("finds app/views/partials/child's references", () => {
93
+ const childPartial = graph.modules[p('app/views/partials/child.liquid')];
94
+ assert(childPartial);
95
+ assert(childPartial.type === ModuleType.Liquid);
96
+ assert(childPartial.kind === LiquidModuleKind.Partial);
235
97
 
236
- // ingoing links
237
- const refs = staticBlock.references;
238
- assert(refs.map((x) => x.target.uri).every((x) => x === staticBlock.uri));
98
+ const refs = childPartial.references;
239
99
  expect(refs.map((x) => x.source.uri)).toEqual(
240
100
  expect.arrayContaining([
241
- p('sections/header-group.json'),
242
- p('blocks/render-static.liquid'),
101
+ p('app/views/partials/parent.liquid'),
102
+ p('app/views/partials/header.liquid'),
243
103
  ]),
244
104
  );
245
105
  });
@@ -1,35 +1,32 @@
1
1
  import {
2
2
  recursiveReadDirectory as findAllFiles,
3
+ isLayout,
4
+ isPage,
3
5
  path,
4
6
  UriString,
5
7
  } from '@platformos/platformos-check-common';
6
- import { IDependencies, ThemeGraph, ThemeModule } from '../types';
8
+ import { IDependencies, AppGraph, AppModule } from '../types';
7
9
  import { augmentDependencies } from './augment';
8
10
  import { getModule } from './module';
9
11
  import { traverseModule } from './traverse';
10
12
 
11
- export async function buildThemeGraph(
13
+ export async function buildAppGraph(
12
14
  rootUri: UriString,
13
15
  ideps: IDependencies,
14
16
  entryPoints?: UriString[],
15
- ): Promise<ThemeGraph> {
17
+ ): Promise<AppGraph> {
16
18
  const deps = augmentDependencies(rootUri, ideps);
17
19
 
18
20
  entryPoints =
19
21
  entryPoints ??
20
22
  (await findAllFiles(deps.fs, rootUri, ([uri]) => {
21
- // Templates are entry points in the theme graph.
22
- const isTemplateFile = uri.startsWith(path.join(rootUri, 'templates'));
23
-
24
- // Since any section file can be rendered directly by the Section Rendering API,
25
- // we consider all section files as entry points.
26
- const isSectionFile =
27
- uri.startsWith(path.join(rootUri, 'sections')) && uri.endsWith('.liquid');
28
-
29
- return isTemplateFile || isSectionFile;
23
+ if (!uri.endsWith('.liquid')) return false;
24
+ // Layouts are entry points — they wrap all page content.
25
+ // Pages are also entry points — they are directly requested.
26
+ return isLayout(uri) || isPage(uri);
30
27
  }));
31
28
 
32
- const graph: ThemeGraph = {
29
+ const graph: AppGraph = {
33
30
  entryPoints: [],
34
31
  modules: {},
35
32
  rootUri,
@@ -37,7 +34,7 @@ export async function buildThemeGraph(
37
34
 
38
35
  graph.entryPoints = entryPoints
39
36
  .map((uri) => getModule(graph, uri))
40
- .filter((x): x is ThemeModule => x !== undefined);
37
+ .filter((x): x is AppModule => x !== undefined);
41
38
 
42
39
  await Promise.all(graph.entryPoints.map((entry) => traverseModule(entry, graph, deps)));
43
40