@kaliber/build 0.0.137 → 0.0.138-beta.1

Sign up to get free protection for your applications and to get access to all the features.
package/.eslintrc CHANGED
@@ -242,7 +242,7 @@
242
242
  "react/jsx-no-undef": ["warn", { "allowGlobals": true }],
243
243
  "react/jsx-pascal-case": ["warn", { "allowAllCaps": true }],
244
244
  "react/jsx-tag-spacing": "warn",
245
- "react/jsx-uses-react": "warn",
245
+ "react/jsx-uses-react": "off",
246
246
  "react/jsx-uses-vars": "warn",
247
247
  "react/jsx-wrap-multilines": ["warn", {
248
248
  "declaration": "parens-new-line",
@@ -259,6 +259,7 @@
259
259
  // "react/no-typos": "warn", // waiting for https://github.com/yannickcr/eslint-plugin-react/issues/1389
260
260
  "react/no-unused-prop-types": "warn",
261
261
  "react/prop-types": ["error", { "skipUndeclared": true }],
262
+ "react/react-in-jsx-scope": "off",
262
263
  "react/require-render-return": "warn",
263
264
  "react/self-closing-comp": "warn",
264
265
  "react/void-dom-elements-no-children": "warn",
package/lib/build.js CHANGED
@@ -68,7 +68,7 @@ const babelLoader = {
68
68
  cacheDirectory: './.babelcache/',
69
69
  cacheCompression: false,
70
70
  babelrc: false, // this needs to be false, any other value will cause .babelrc to interfere with these settings
71
- presets: ['@babel/preset-react'],
71
+ presets: [['@babel/preset-react', { 'runtime': 'automatic' }]],
72
72
  plugins: [
73
73
  ['@babel/plugin-proposal-decorators', { legacy: true }],
74
74
  ['@babel/plugin-proposal-class-properties', { loose: true }],
@@ -1,21 +1,25 @@
1
- import ReactDom from 'react-dom'
1
+ import { hydrateRoot } from 'react-dom/client'
2
2
  import { safeJsonStringify } from '@kaliber/safe-json-stringify'
3
3
 
4
4
  const containerMarker = 'data-kaliber-component-container'
5
+ const E = /** @type {any} */ ('kaliber-component-container')
5
6
 
7
+ // eslint-disable-next-line @kaliber/naming-policy
6
8
  export function ComponentServerWrapper({ componentName, props, renderedComponent }) {
7
- const componentInfo = JSON.stringify({ componentName, props })
9
+ const scriptContent = restructureDomNodes()
10
+ const safeComponentInfo = safeJsonStringify({ componentName, props })
11
+
8
12
  return (
9
13
  <>
10
14
  {/* It is not possible to render the html of a React-rendered component without a container
11
15
  because dangerouslySetInnerHTML is the only route to get raw html into the resulting html */}
12
- <kaliber-component-container dangerouslySetInnerHTML={{ __html: renderedComponent }} />
16
+ <E dangerouslySetInnerHTML={{ __html:
17
+ `</${E}><!--start--><!--${safeComponentInfo}-->${renderedComponent}<!--end--><${E}>`
18
+ }} />
13
19
 
14
- {/* Use render blocking script to remove the container and supply the correct comment nodes.
20
+ {/* Use render blocking script to set a container marker and remove the custom components.
15
21
  This ensures the page is never rendered with the intermediate structure */}
16
- <script dangerouslySetInnerHTML={{
17
- __html: restructureDomNodes(componentInfo).replace(/<\/?script>/gi, '')
18
- }} />
22
+ <script dangerouslySetInnerHTML={{ __html: scriptContent }} />
19
23
  </>
20
24
  )
21
25
  }
@@ -37,34 +41,44 @@ export function findComponents({ componentName }) {
37
41
  }
38
42
  }
39
43
 
40
- export function hydrate(
41
- component,
42
- {
43
- nodes,
44
- endNode: insertBefore,
45
- container = createContainer({ eventTarget: insertBefore.parentNode }),
46
- },
47
- ) {
48
- // Move the rendered nodes to a container before hydrating
49
- nodes.forEach((x) => { container.appendChild(x) })
44
+ export function hydrate(component, { nodes: initialNodes, startNode, endNode }) {
45
+ const container = createVirtualReactContainer({ initialNodes, startNode, endNode })
46
+ const root = hydrateRoot(container, component)
50
47
 
51
- ReactDom.hydrate(component, container)
52
-
53
- // Capture the rendered nodes before they are moved by inserting the container
54
- const renderedNodes = Array.from(container.childNodes)
55
- insertBefore.parentNode.insertBefore(container, insertBefore)
56
-
57
- return { container, renderedNodes }
48
+ return { update: x => root.render(x) }
58
49
  }
59
50
 
60
- function createContainer({ eventTarget }) {
61
- // React attaches event listeners to the container on hydrate or render. This does not make
62
- // sense for document fragments, so we forward all EventTarget methods.
63
- const container = document.createDocumentFragment()
64
- container.addEventListener = (...args) => eventTarget.addEventListener(...args)
65
- container.removeEventListener = (...args) => eventTarget.removeEventListener(...args)
66
- container.dispatchEvent = (...args) => eventTarget.dispatchEvent(...args)
67
- return container
51
+ function createVirtualReactContainer({ initialNodes, startNode, endNode }) {
52
+ const parent = startNode.parentNode
53
+ let nodes = initialNodes.slice() // we could derive initialNodes from startNode and endNode
54
+
55
+ const container = {
56
+ addEventListener: (...args) => parent.addEventListener(...args),
57
+ removeEventListener: (...args) => parent.removeEventListener(...args),
58
+ dispatchEvent: (...args) => parent.dispatchEvent(...args),
59
+ get firstChild() { return nodes[0] || null },
60
+ get nodeType() { return parent.DOCUMENT_FRAGMENT_NODE },
61
+ get ownerDocument() { return parent.ownerDocument },
62
+ get nodeName() { return 'virtualized container' },
63
+ removeChild(child) {
64
+ const result = parent.removeChild(child)
65
+ nodes = nodes.filter(x => x !== child)
66
+ return result
67
+ },
68
+ appendChild(child) {
69
+ endNode.before(child)
70
+ nodes = nodes.concat([child])
71
+ return child
72
+ },
73
+ insertBefore(node, child) {
74
+ const childIndex = nodes.findIndex(x => x === child)
75
+ const result = parent.insertBefore(node, child)
76
+ nodes.splice(childIndex, 0, result)
77
+ return result
78
+ },
79
+ }
80
+ // The statement below is a lie. We supply an object that has all methods that React calls on it
81
+ return /** @type {Element} */ (container)
68
82
  }
69
83
 
70
84
  function findAllComponents() {
@@ -74,31 +88,32 @@ function findAllComponents() {
74
88
 
75
89
  function groupComponentsByName(allComponents) {
76
90
  return allComponents.reduce(
77
- (result, { info: { componentName, props }, nodes, endNode }) => {
91
+ (result, { info: { componentName, props }, nodes, startNode, endNode }) => {
78
92
  const components = result[componentName] || (result[componentName] = [])
79
- components.push({ componentName, nodes, endNode, props })
93
+ components.push({ componentName, nodes, startNode, endNode, props })
80
94
  return result
81
95
  },
82
96
  {}
83
97
  )
84
98
  }
85
99
 
86
- function restructureDomNodes(componentInfo) {
87
- const safeComponentInfo = safeJsonStringify(componentInfo)
88
- return `|var d=document,s=d.currentScript,p=s.parentNode,c=s.previousSibling;
89
- |p.setAttribute('${containerMarker}',''); // set marker on container so we can retrieve nodes that contain components
90
- |p.replaceChild(d.createComment('start'),c); // replace kaliber-component-container element with a 'start' comment
91
- |p.insertBefore(d.createComment(${safeComponentInfo}),s); // create a comment containing the component info
92
- |Array.from(c.childNodes).forEach(x=>{p.insertBefore(x,s)}); // insert all children from the kaliber-component-container element
93
- |p.replaceChild(d.createComment('end'),s); // create an 'end' comment
94
- |`.replace(/^\s*\|/gm, '').replace(/\s*\/\/[^;]*?$/gm, '').replace(/\n/g, '')
100
+ function restructureDomNodes() {
101
+ return `
102
+ var d=document,s=d.currentScript,p=s.parentNode;
103
+ ${/* set marker on container so we can retrieve nodes that contain components */''}
104
+ p.setAttribute('${containerMarker}','');
105
+ ${/* remove all (empty) container tags */''}
106
+ Array.from(p.querySelectorAll('${E}')).forEach(x=>p.removeChild(x));
107
+ ${/* remove the script tag itself */''}
108
+ p.removeChild(s);
109
+ `.replace(/(^\s*|\n)/gm, '')
95
110
  }
96
111
 
97
112
  function extractServerRenderedComponents(container) {
98
113
  // These steps work with the DOM structure created by the render blocking script
99
114
  const steps = [
100
115
  [not(isStart), ignore, repeat],
101
- [isStart, ignore, nextStep],
116
+ [isStart, addNode('startNode'), nextStep],
102
117
  [isComment, dataAsJson('info'), nextStep],
103
118
  [not(isEnd), addNodeToCollection('nodes'), repeat],
104
119
  [isEnd, addNode('endNode'), commitAndRestart]
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.137",
2
+ "version": "0.0.138-beta.1",
3
3
  "name": "@kaliber/build",
4
4
  "description": "Zero configuration, opinionated webpack / react build setup",
5
5
  "scripts": {
@@ -78,8 +78,8 @@
78
78
  "postcss-values-parser": "^4.0.0",
79
79
  "progress-bar-webpack-plugin": "^2.1.0",
80
80
  "raw-loader": "^4.0.1",
81
- "react": "^17.0.2",
82
- "react-dom": "^17.0.2",
81
+ "react": "^18.2.0",
82
+ "react-dom": "^18.2.0",
83
83
  "rollbar": "^2.19.3",
84
84
  "stylelint": "^13.7.1",
85
85
  "stylelint-use-nesting": "^3.0.0",
@@ -25,24 +25,15 @@ function createClientCode({ importPath, id, wrapper: wrapperPath }) {
25
25
  |${wrapper}
26
26
  |
27
27
  |const components = findComponents({ componentName: '${id}' })
28
- |let renderInfo = components.map(componentInfo => {
28
+ |const renderResults = components.map(componentInfo => {
29
29
  | const { props } = componentInfo
30
- | return {
31
- | componentInfo,
32
- | renderInfo: hydrate(${wrapped}, componentInfo),
33
- | }
30
+ | return { props, result: hydrate(${wrapped}, componentInfo) }
34
31
  |})
35
32
  |
36
33
  |if (module.hot) {
37
34
  | require('@kaliber/build/lib/hot-module-replacement-client')
38
35
  | module.hot.accept('./${importPath}', () => {
39
- | renderInfo = renderInfo.map(({ componentInfo, renderInfo }) => {
40
- | const { props, endNode } = componentInfo, { container, renderedNodes } = renderInfo
41
- | return {
42
- | componentInfo,
43
- | renderInfo: hydrate(${wrapped}, { nodes: renderedNodes, endNode, container }),
44
- | }
45
- | })
36
+ | renderResults.forEach(({ props, result }) => result.update(${wrapped}))
46
37
  | })
47
38
  |}
48
39
  |`.split(/^[ \t]*\|/m).join('')
@@ -14,21 +14,18 @@ function ReactUniversalClientLoader(source, map, meta) {
14
14
 
15
15
  function createClientCode({ importPath, id }) {
16
16
  return `|import Component from './${importPath}'
17
- |import { hydrate } from 'react-dom'
17
+ |import { hydrateRoot } from 'react-dom/client'
18
18
  |
19
- |const elements = document.querySelectorAll('*[data-componentid="${id}"]')
20
- |for (let i = 0; i < elements.length; i++) {
21
- | const props = JSON.parse(elements[i].dataset.props)
22
- | hydrate(<Component {...props} />, elements[i])
23
- |}
19
+ |const elements = Array.from(document.querySelectorAll('*[data-componentid="${id}"]'))
20
+ |const renderResults = elements.map(element => {
21
+ | const props = JSON.parse(element.dataset.props)
22
+ | return { props, root: hydrateRoot(element, <Component {...props} />) }
23
+ |})
24
24
  |
25
25
  |if (module.hot) {
26
26
  | require('@kaliber/build/lib/hot-module-replacement-client')
27
27
  | module.hot.accept('./${importPath}', () => {
28
- | for (let i = 0; i < elements.length; i++) {
29
- | const props = JSON.parse(elements[i].dataset.props)
30
- | hydrate(<Component {...props} />, elements[i])
31
- | }
28
+ | renderResults.forEach(({ props, root }) => root.render(<Component {...props} />))
32
29
  | })
33
30
  |}
34
31
  |`.split(/^[ \t]*\|/m).join('')