@kaliber/build 0.0.138-beta.1 → 0.0.138-beta.2

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": "off",
245
+ "react/jsx-uses-react": "warn",
246
246
  "react/jsx-uses-vars": "warn",
247
247
  "react/jsx-wrap-multilines": ["warn", {
248
248
  "declaration": "parens-new-line",
@@ -259,7 +259,6 @@
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",
263
262
  "react/require-render-return": "warn",
264
263
  "react/self-closing-comp": "warn",
265
264
  "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', { 'runtime': 'automatic' }]],
71
+ presets: ['@babel/preset-react'],
72
72
  plugins: [
73
73
  ['@babel/plugin-proposal-decorators', { legacy: true }],
74
74
  ['@babel/plugin-proposal-class-properties', { loose: true }],
@@ -254,7 +254,7 @@ module.exports = function build({ watch }) {
254
254
 
255
255
  function resolveOptions() {
256
256
  return {
257
- extensions: ['.js', '.mjs'],
257
+ extensions: ['.js', '.mjs', '.cjs'],
258
258
  modules: ['node_modules'],
259
259
  plugins: [absolutePathResolverPlugin(srcDir), fragmentResolverPlugin()],
260
260
  symlinks,
@@ -313,14 +313,14 @@ module.exports = function build({ watch }) {
313
313
 
314
314
  {
315
315
  resource: {
316
- test: /(\.html\.js|\.js|\.mjs)$/,
316
+ test: /(\.html\.js|\.js|\.mjs|\.cjs)$/,
317
317
  or: [{ exclude: /node_modules/ }, ...compileWithBabel],
318
318
  },
319
319
  loaders: [babelLoader]
320
320
  },
321
321
 
322
322
  {
323
- test: /(\.js|\.mjs)$/,
323
+ test: /(\.js|\.mjs|\.cjs)$/,
324
324
  type: 'javascript/auto',
325
325
  },
326
326
 
@@ -1,25 +1,21 @@
1
- import { hydrateRoot } from 'react-dom/client'
1
+ import ReactDom from 'react-dom'
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')
6
5
 
7
- // eslint-disable-next-line @kaliber/naming-policy
8
6
  export function ComponentServerWrapper({ componentName, props, renderedComponent }) {
9
- const scriptContent = restructureDomNodes()
10
- const safeComponentInfo = safeJsonStringify({ componentName, props })
11
-
7
+ const componentInfo = JSON.stringify({ componentName, props })
12
8
  return (
13
9
  <>
14
10
  {/* It is not possible to render the html of a React-rendered component without a container
15
11
  because dangerouslySetInnerHTML is the only route to get raw html into the resulting html */}
16
- <E dangerouslySetInnerHTML={{ __html:
17
- `</${E}><!--start--><!--${safeComponentInfo}-->${renderedComponent}<!--end--><${E}>`
18
- }} />
12
+ <kaliber-component-container dangerouslySetInnerHTML={{ __html: renderedComponent }} />
19
13
 
20
- {/* Use render blocking script to set a container marker and remove the custom components.
14
+ {/* Use render blocking script to remove the container and supply the correct comment nodes.
21
15
  This ensures the page is never rendered with the intermediate structure */}
22
- <script dangerouslySetInnerHTML={{ __html: scriptContent }} />
16
+ <script dangerouslySetInnerHTML={{
17
+ __html: restructureDomNodes(componentInfo).replace(/<\/?script>/gi, '')
18
+ }} />
23
19
  </>
24
20
  )
25
21
  }
@@ -41,44 +37,34 @@ export function findComponents({ componentName }) {
41
37
  }
42
38
  }
43
39
 
44
- export function hydrate(component, { nodes: initialNodes, startNode, endNode }) {
45
- const container = createVirtualReactContainer({ initialNodes, startNode, endNode })
46
- const root = hydrateRoot(container, component)
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) })
47
50
 
48
- return { update: x => root.render(x) }
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 }
49
58
  }
50
59
 
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)
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
82
68
  }
83
69
 
84
70
  function findAllComponents() {
@@ -88,32 +74,31 @@ function findAllComponents() {
88
74
 
89
75
  function groupComponentsByName(allComponents) {
90
76
  return allComponents.reduce(
91
- (result, { info: { componentName, props }, nodes, startNode, endNode }) => {
77
+ (result, { info: { componentName, props }, nodes, endNode }) => {
92
78
  const components = result[componentName] || (result[componentName] = [])
93
- components.push({ componentName, nodes, startNode, endNode, props })
79
+ components.push({ componentName, nodes, endNode, props })
94
80
  return result
95
81
  },
96
82
  {}
97
83
  )
98
84
  }
99
85
 
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, '')
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, '')
110
95
  }
111
96
 
112
97
  function extractServerRenderedComponents(container) {
113
98
  // These steps work with the DOM structure created by the render blocking script
114
99
  const steps = [
115
100
  [not(isStart), ignore, repeat],
116
- [isStart, addNode('startNode'), nextStep],
101
+ [isStart, ignore, nextStep],
117
102
  [isComment, dataAsJson('info'), nextStep],
118
103
  [not(isEnd), addNodeToCollection('nodes'), repeat],
119
104
  [isEnd, addNode('endNode'), commitAndRestart]
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.138-beta.1",
2
+ "version": "0.0.138-beta.2",
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": "^18.2.0",
82
- "react-dom": "^18.2.0",
81
+ "react": "^17.0.2",
82
+ "react-dom": "^17.0.2",
83
83
  "rollbar": "^2.19.3",
84
84
  "stylelint": "^13.7.1",
85
85
  "stylelint-use-nesting": "^3.0.0",
@@ -25,15 +25,24 @@ function createClientCode({ importPath, id, wrapper: wrapperPath }) {
25
25
  |${wrapper}
26
26
  |
27
27
  |const components = findComponents({ componentName: '${id}' })
28
- |const renderResults = components.map(componentInfo => {
28
+ |let renderInfo = components.map(componentInfo => {
29
29
  | const { props } = componentInfo
30
- | return { props, result: hydrate(${wrapped}, componentInfo) }
30
+ | return {
31
+ | componentInfo,
32
+ | renderInfo: hydrate(${wrapped}, componentInfo),
33
+ | }
31
34
  |})
32
35
  |
33
36
  |if (module.hot) {
34
37
  | require('@kaliber/build/lib/hot-module-replacement-client')
35
38
  | module.hot.accept('./${importPath}', () => {
36
- | renderResults.forEach(({ props, result }) => result.update(${wrapped}))
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
+ | })
37
46
  | })
38
47
  |}
39
48
  |`.split(/^[ \t]*\|/m).join('')
@@ -14,18 +14,21 @@ function ReactUniversalClientLoader(source, map, meta) {
14
14
 
15
15
  function createClientCode({ importPath, id }) {
16
16
  return `|import Component from './${importPath}'
17
- |import { hydrateRoot } from 'react-dom/client'
17
+ |import { hydrate } from 'react-dom'
18
18
  |
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
- |})
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
+ |}
24
24
  |
25
25
  |if (module.hot) {
26
26
  | require('@kaliber/build/lib/hot-module-replacement-client')
27
27
  | module.hot.accept('./${importPath}', () => {
28
- | renderResults.forEach(({ props, root }) => root.render(<Component {...props} />))
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
+ | }
29
32
  | })
30
33
  |}
31
34
  |`.split(/^[ \t]*\|/m).join('')