@kaliber/build 0.0.138 → 0.0.140-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
@@ -54,6 +54,7 @@ const {
54
54
  publicPath = '/',
55
55
  symlinks = true,
56
56
  webpackLoaders: userDefinedWebpackLoaders = [],
57
+ cssNativeCustomProperties = false,
57
58
  } = {}
58
59
  } = require('@kaliber/config')
59
60
 
@@ -68,7 +69,7 @@ const babelLoader = {
68
69
  cacheDirectory: './.babelcache/',
69
70
  cacheCompression: false,
70
71
  babelrc: false, // this needs to be false, any other value will cause .babelrc to interfere with these settings
71
- presets: ['@babel/preset-react'],
72
+ presets: [['@babel/preset-react', { 'runtime': 'automatic' }]],
72
73
  plugins: [
73
74
  ['@babel/plugin-proposal-decorators', { legacy: true }],
74
75
  ['@babel/plugin-proposal-class-properties', { loose: true }],
@@ -85,11 +86,12 @@ const babelLoader = {
85
86
 
86
87
  const cssLoaderGlobalScope = {
87
88
  loader: 'css-loader',
88
- options: { globalScopeBehaviour: true }
89
+ options: { globalScopeBehaviour: true, nativeCustomProperties: cssNativeCustomProperties }
89
90
  }
90
91
 
91
92
  const cssLoader = {
92
- loader: 'css-loader'
93
+ loader: 'css-loader',
94
+ options: { nativeCustomProperties: cssNativeCustomProperties }
93
95
  }
94
96
 
95
97
  const cssLoaderMinifyOnly = {
@@ -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.138",
2
+ "version": "0.0.140-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",
@@ -8,15 +8,15 @@ Color schemes are defined by creating files in a `colorScheme` directory or `col
8
8
  color: var(--color-white);
9
9
 
10
10
  & .color-scheme-icon {
11
- color: color-mod(var(--color-red-dark) alpha(0.4));
11
+ color: var(--color-red-700);
12
12
  }
13
13
 
14
14
  & .color-scheme-exclusion {
15
- color: var(--color-red-light);
15
+ color: var(--color-red-300);
16
16
  }
17
17
 
18
18
  & ::selection {
19
- background-color: var(--color-red-light);
19
+ background-color: var(--color-red-300);
20
20
  }
21
21
  }
22
22
  ```
@@ -68,7 +68,7 @@ Examples of *incorrect* code for this rule:
68
68
  Before:
69
69
  ```css
70
70
  .abc {
71
- color: #AABBCC;
71
+ color: #aabbcc;
72
72
  opacity: 0.5;
73
73
  }
74
74
  ```
@@ -76,8 +76,8 @@ Before:
76
76
  After:
77
77
  ```css
78
78
  .abc {
79
- color: #AABBCC80;
79
+ color: #aabbcc80;
80
80
  /* or */
81
- color: color-mod(#AABBCC alpha(50%));
81
+ color: rgba(170, 187, 204, 0.5);
82
82
  }
83
83
  ```
@@ -51,7 +51,7 @@ html {
51
51
  }
52
52
 
53
53
  body {
54
- color: var(--color-gray-90);
54
+ color: var(--color-gray-900);
55
55
  font-family: var(--font-family-base);
56
56
  font-size: var(--font-size-md);
57
57
  font-weight: var(--font-weight-base-400);
@@ -3,6 +3,9 @@ module.exports = {
3
3
  Warning: do not use this to set values that differ in each environment,
4
4
  only use this for configuration that is the same across all config environments
5
5
  */
6
+ kaliber: {
7
+ cssNativeCustomProperties: true
8
+ },
6
9
  rollbar: {
7
10
  post_client_item: 'get an access token at rollbar.com',
8
11
  }
@@ -1,8 +1,8 @@
1
1
  .component {
2
2
  padding: 20px;
3
3
  border-radius: 4px;
4
- border: 1px solid var(--color-gray-80);
5
- background-color: var(--color-gray-10);
4
+ border: 1px solid var(--color-gray-800);
5
+ background-color: var(--color-gray-100);
6
6
  @media (--viewport-lg) { padding: 40px; }
7
7
 
8
8
  & > *:not(:last-child) { margin-bottom: 40px; }
@@ -1,4 +1,4 @@
1
1
  .component {
2
- background-color: var(--color-gray-70);
2
+ background-color: var(--color-gray-700);
3
3
  padding: 40px 0;
4
4
  }
@@ -0,0 +1,6 @@
1
+ :root {
2
+ --color-gray-100: #f8f8f8;
3
+ --color-gray-700: #4d4d4d;
4
+ --color-gray-800: #1e1e1e;
5
+ --color-gray-900: #1c1c1c;
6
+ }
@@ -1,9 +1,3 @@
1
- :export {
2
- breakpointSm: 480px;
3
- breakpointMd: 768px;
4
- breakpointLg: 1024px;
5
- }
6
-
7
1
  @custom-media --viewport-sm screen and (min-width: 480px);
8
2
  @custom-media --viewport-md screen and (min-width: 768px);
9
3
  @custom-media --viewport-lg screen and (min-width: 1024px);
@@ -0,0 +1 @@
1
+ @custom-selector :--enter :hover, :focus;
@@ -2,5 +2,5 @@
2
2
  --font-family-base: 'Barlow', sans-serif;
3
3
  --font-weight-base-400: 400;
4
4
 
5
- --font-size-md: 18px;
5
+ --font-size-18: 18px;
6
6
  }
@@ -7,7 +7,7 @@ html {
7
7
  }
8
8
 
9
9
  body {
10
- color: var(--color-gray-90);
10
+ color: var(--color-gray-900);
11
11
  font-family: var(--font-family-base);
12
12
  font-size: var(--font-size-md);
13
13
  font-weight: var(--font-weight-base-400);
@@ -1,5 +1,9 @@
1
1
  import '/reset.css'
2
2
  import '/index.css'
3
+ import '/cssGlobal/colors.css'
4
+ import '/cssGlobal/sizes.css'
5
+ import '/cssGlobal/type.css'
6
+
3
7
  import stylesheet from '@kaliber/build/lib/stylesheet'
4
8
  import javascript from '@kaliber/build/lib/javascript'
5
9
  import polyfill from '@kaliber/build/lib/polyfill'
@@ -12,7 +12,6 @@ h4,
12
12
  h5,
13
13
  h6 {
14
14
  margin: 0;
15
- &:not(:last-child) { padding-bottom: 0.8333em; }
16
15
  }
17
16
 
18
17
  address { font-style: normal; }
@@ -8,7 +8,7 @@ const vm = require('vm')
8
8
  const isProduction = process.env.NODE_ENV === 'production'
9
9
 
10
10
  function createPlugins(
11
- { minifyOnly, globalScopeBehaviour, cssGlobalFiles },
11
+ { minifyOnly, globalScopeBehaviour, nativeCustomProperties, cssGlobalFiles },
12
12
  { resolveForImport, resolveForUrlReplace, resolveForImportExportParser }
13
13
  ) {
14
14
 
@@ -22,14 +22,13 @@ function createPlugins(
22
22
  require('postcss-modules-values'),
23
23
  require('postcss-preset-env')({
24
24
  features: {
25
- 'custom-properties': { preserve: false, importFrom: cssGlobalFiles },
25
+ 'custom-properties': !nativeCustomProperties && { preserve: false, importFrom: cssGlobalFiles },
26
26
  'custom-media-queries': { preserve: false, importFrom: cssGlobalFiles },
27
27
  'custom-selectors': { preserve: false, importFrom: cssGlobalFiles },
28
28
  'media-query-ranges': true,
29
29
  'nesting-rules': true,
30
30
  'hexadecimal-alpha-notation': true,
31
31
  'color-functional-notation': true,
32
- 'color-mod-function': true,
33
32
  'font-variant-property': true,
34
33
  'all-property': true,
35
34
  'any-link-pseudo-class': true,
@@ -60,12 +59,12 @@ module.exports = function CssLoader(source, map) {
60
59
  const callback = this.async()
61
60
 
62
61
  const loaderOptions = loaderUtils.getOptions(this) || {}
63
- const { minifyOnly = false, globalScopeBehaviour = false } = loaderOptions
62
+ const { minifyOnly = false, globalScopeBehaviour = false, nativeCustomProperties = false } = loaderOptions
64
63
 
65
64
  const cssGlobalFiles = cachedFindCssGlobalFiles(this)
66
65
  cssGlobalFiles.forEach(x => this.addDependency(x))
67
66
 
68
- const plugins = getPlugins(this, { minifyOnly, globalScopeBehaviour, cssGlobalFiles })
67
+ const plugins = getPlugins(this, { minifyOnly, globalScopeBehaviour, nativeCustomProperties, cssGlobalFiles })
69
68
  const filename = relative(this.rootContext, this.resourcePath)
70
69
  const options = {
71
70
  from: this.resourcePath,
@@ -109,12 +108,12 @@ module.exports = function CssLoader(source, map) {
109
108
  }
110
109
  }
111
110
 
112
- function getPlugins(loaderContext, { minifyOnly, globalScopeBehaviour, cssGlobalFiles }) {
111
+ function getPlugins(loaderContext, { minifyOnly, globalScopeBehaviour, nativeCustomProperties, cssGlobalFiles }) {
113
112
  const key = `plugins${minifyOnly ? '-minifyOnly' : ''}${globalScopeBehaviour ? '-globalScope' : ''}`
114
113
 
115
- return cachedInConmpilation(loaderContext, key, () => {
114
+ return cachedInCompilation(loaderContext, key, () => {
116
115
  const handlers = createHandlers(loaderContext)
117
- const plugins = createPlugins({ minifyOnly, globalScopeBehaviour, cssGlobalFiles }, handlers)
116
+ const plugins = createPlugins({ minifyOnly, globalScopeBehaviour, nativeCustomProperties, cssGlobalFiles }, handlers)
118
117
  return plugins
119
118
  })
120
119
  }
@@ -149,7 +148,7 @@ function createHandlers(loaderContext) {
149
148
 
150
149
  function executeModuleAt(url, source) {
151
150
  const key = `module-${url}`
152
- return cachedInConmpilation(loaderContext, key, () => {
151
+ return cachedInCompilation(loaderContext, key, () => {
153
152
 
154
153
  const sandbox = {
155
154
  module: {},
@@ -162,12 +161,12 @@ function createHandlers(loaderContext) {
162
161
  }
163
162
 
164
163
  function cachedFindCssGlobalFiles(loaderContext) {
165
- return cachedInConmpilation(loaderContext, 'global-css-files', () =>
164
+ return cachedInCompilation(loaderContext, 'global-css-files', () =>
166
165
  findCssGlobalFiles(loaderContext.rootContext)
167
166
  )
168
167
  }
169
168
 
170
- function cachedInConmpilation(loaderContext, key, f) {
169
+ function cachedInCompilation(loaderContext, key, f) {
171
170
  const c = loaderContext._compilation
172
171
  const cache = c.kaliberCache || (c.kaliberCache = {})
173
172
  if (cache[key]) return cache[key]
@@ -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('')
@@ -1,6 +0,0 @@
1
- :root {
2
- --color-gray-10: #f8f8f8;
3
- --color-gray-70: #4d4d4d;
4
- --color-gray-80: #1e1e1e;
5
- --color-gray-90: #1c1c1c;
6
- }