@kaliber/build 0.0.113 → 0.0.117

Sign up to get free protection for your applications and to get access to all the features.
package/lib/build.js CHANGED
@@ -52,7 +52,7 @@ const { kaliber: { compileWithBabel: userDefinedcompileWithBabel = [], publicPat
52
52
 
53
53
  const recognizedTemplates = Object.keys(templateRenderers)
54
54
 
55
- const kaliberBuildClientModules = [/(@kaliber\/build\/lib\/(stylesheet|javascript|polyfill|withPolyfill|hot-module-replacement-client|rollbar)|ansi-regex)/]
55
+ const kaliberBuildClientModules = [/(@kaliber\/build\/lib\/(stylesheet|javascript|polyfill|withPolyfill|hot-module-replacement-client|rollbar|universalComponents)|ansi-regex)/]
56
56
  const compileWithBabel = kaliberBuildClientModules.concat(userDefinedcompileWithBabel)
57
57
 
58
58
  const babelLoader = {
package/lib/serve.js CHANGED
@@ -4,6 +4,7 @@ const helmet = require('helmet')
4
4
  const { access } = require('fs')
5
5
  const { parsePath } = require('history')
6
6
  const { resolve } = require('path')
7
+ const morgan = require('morgan')
7
8
 
8
9
  const templateRenderers = require('./getTemplateRenderers')
9
10
 
@@ -30,6 +31,7 @@ const isProduction = process.env.NODE_ENV === 'production'
30
31
 
31
32
  const notCached = ['html', 'txt', 'json', 'xml']
32
33
 
34
+ if (isProduction) app.use(morgan('combined'))
33
35
  // hsts-headers are sent by our loadbalancer
34
36
  app.use(helmet(Object.assign({ hsts: false, contentSecurityPolicy: false }, helmetOptions)))
35
37
  app.use(compression())
@@ -0,0 +1,145 @@
1
+ import ReactDom from 'react-dom'
2
+
3
+ const containerMarker = 'data-kaliber-component-container'
4
+
5
+ export function ComponentServerWrapper({ componentName, props, renderedComponent }) {
6
+ const componentInfo = JSON.stringify({ componentName, props })
7
+ return (
8
+ <>
9
+ {/* It is not possible to render the html of a React-rendered component without a container
10
+ because dangerouslySetInnerHTML is the only route to get raw html into the resulting html */}
11
+ <kaliber-component-container dangerouslySetInnerHTML={{ __html: renderedComponent }} />
12
+
13
+ {/* Use render blocking script to remove the container and supply the correct comment nodes.
14
+ This ensures the page is never rendered with the intermediate structure */}
15
+ <script dangerouslySetInnerHTML={{ __html: restructureDomNodes(componentInfo) }} />
16
+ </>
17
+ )
18
+ }
19
+
20
+ export function findComponents({ componentName }) {
21
+ if (typeof window === 'undefined') throw new Error(`The function 'findComponents' can only be used in the browser`)
22
+
23
+ const findComponentCache = getFindComponentCache()
24
+ const components = findComponentCache[componentName] || []
25
+ if (!components.length) console.warn(`Could not find any component with name '${componentName}', dit you render it to the page?`)
26
+ return components
27
+
28
+ function getFindComponentCache() {
29
+ if (!findComponents.cache) findComponents.cache = findAndGroupAllComponents()
30
+ return findComponents.cache
31
+
32
+ function findAndGroupAllComponents() {
33
+ return groupComponentsByName(findAllComponents())
34
+ }
35
+ }
36
+ }
37
+
38
+ export function hydrate(
39
+ component,
40
+ {
41
+ nodes,
42
+ endNode: insertBefore,
43
+ container = createContainer({ eventTarget: insertBefore.parentNode }),
44
+ },
45
+ ) {
46
+ // Move the rendered nodes to a container before hydrating
47
+ nodes.forEach((x) => { container.appendChild(x) })
48
+
49
+ ReactDom.hydrate(component, container)
50
+
51
+ // Capture the rendered nodes before they are moved by inserting the container
52
+ const renderedNodes = Array.from(container.childNodes)
53
+ insertBefore.parentNode.insertBefore(container, insertBefore)
54
+
55
+ return { container, renderedNodes }
56
+ }
57
+
58
+ function createContainer({ eventTarget }) {
59
+ // React attaches event listeners to the container on hydrate or render. This does not make
60
+ // sense for document fragments, so we forward all EventTarget methods.
61
+ const container = document.createDocumentFragment()
62
+ container.addEventListener = (...args) => eventTarget.addEventListener(...args)
63
+ container.removeEventListener = (...args) => eventTarget.removeEventListener(...args)
64
+ container.dispatchEvent = (...args) => eventTarget.dispatchEvent(...args)
65
+ return container
66
+ }
67
+
68
+ function findAllComponents() {
69
+ const containers = document.querySelectorAll(`*[${containerMarker}]`)
70
+ return Array.from(containers).flatMap(extractServerRenderedComponents) // this requires flatMap polyfill (es2019)
71
+ }
72
+
73
+ function groupComponentsByName(allComponents) {
74
+ return allComponents.reduce(
75
+ (result, { info: { componentName, props }, nodes, endNode }) => {
76
+ const components = result[componentName] || (result[componentName] = [])
77
+ components.push({ componentName, nodes, endNode, props })
78
+ return result
79
+ },
80
+ {}
81
+ )
82
+ }
83
+
84
+ function restructureDomNodes(componentInfo) {
85
+ return `|var d=document,s=d.currentScript,p=s.parentNode,c=s.previousSibling;
86
+ |p.setAttribute('${containerMarker}',''); // set marker on container so we can retrieve nodes that contain components
87
+ |p.replaceChild(d.createComment('start'),c); // replace kaliber-component-container element with a 'start' comment
88
+ |p.insertBefore(d.createComment(JSON.stringify(${componentInfo})),s); // create a comment containing the component info
89
+ |Array.from(c.childNodes).forEach(x=>{p.insertBefore(x,s)}); // insert all children from the kaliber-component-container element
90
+ |p.replaceChild(d.createComment('end'),s); // create an 'end' comment
91
+ |`.replace(/^\s*\|/gm, '').replace(/\s*\/\/[^;]*?$/gm, '').replace(/\n/g, '')
92
+ }
93
+
94
+ function extractServerRenderedComponents(container) {
95
+ // These steps work with the DOM structure created by the render blocking script
96
+ const steps = [
97
+ [not(isStart), ignore, repeat],
98
+ [isStart, ignore, nextStep],
99
+ [isComment, dataAsJson('info'), nextStep],
100
+ [not(isEnd), addNodeToCollection('nodes'), repeat],
101
+ [isEnd, addNode('endNode'), commitAndRestart]
102
+ ]
103
+
104
+ return executeSteps({ steps, node: container.firstChild })
105
+ }
106
+
107
+ function executeSteps({ steps, node, data = {}, set = [], originalSteps = steps }) {
108
+ if (!steps.length || !node) return set
109
+
110
+ const [[predicate, extractData, determineNext]] = steps
111
+
112
+ return executeSteps(
113
+ predicate(node)
114
+ ? determineNext({ node, steps, data: extractData({ data, node }), set, originalSteps })
115
+ : tryNextStep({ node, steps, data, set, originalSteps })
116
+ )
117
+ }
118
+
119
+ // Predicates
120
+ function isStart(x) { return isComment(x) && x.data === 'start' }
121
+ function isEnd(x) { return isComment(x) && x.data === 'end' }
122
+ function isComment(x) { return x.nodeType === 8 }
123
+ function not(f) { return x => !f(x) }
124
+
125
+ // Extraction
126
+ function ignore({ data }) { return data }
127
+ function dataAsJson(key) { return ({ data, node }) => ({ ...data, [key]: JSON.parse(node.data) }) }
128
+ function addNodeToCollection(key) {
129
+ return ({ data, node }) => ({ ...data, [key]: (data[key] ?? []).concat(node) })
130
+ }
131
+ function addNode(key) { return ({ data, node }) => ({ ...data, [key]: node }) }
132
+
133
+ // Control
134
+ function repeat({ node, ...state }) {
135
+ return { node: node.nextSibling, ...state }
136
+ }
137
+ function nextStep({ node, steps, ...state }) {
138
+ return { node: node.nextSibling, steps: steps.slice(1), ...state }
139
+ }
140
+ function tryNextStep({ steps, ...state }) {
141
+ return { steps: steps.slice(1), ...state }
142
+ }
143
+ function commitAndRestart({ node, originalSteps, data, set }) {
144
+ return { node: node.nextSibling, steps: originalSteps, data: {}, set: set.concat(data) }
145
+ }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.113",
2
+ "version": "0.0.117",
3
3
  "name": "@kaliber/build",
4
4
  "description": "Zero configuration, opinionated webpack / react build setup",
5
5
  "scripts": {
@@ -26,7 +26,7 @@
26
26
  "@babel/preset-env": "^7.11.5",
27
27
  "@babel/preset-react": "^7.10.4",
28
28
  "@babel/runtime": "^7.11.2",
29
- "@kaliber/config": "^0.0.7",
29
+ "@kaliber/config": "^0.0.8",
30
30
  "@kaliber/eslint-plugin": "*",
31
31
  "ansi-regex": "^5.0.0",
32
32
  "babel-eslint": "^10.1.0",
@@ -37,21 +37,21 @@
37
37
  "cross-spawn": "^7.0.3",
38
38
  "cssnano": "^4.1.0",
39
39
  "eslint": "^7.9.0",
40
- "eslint-config-standard": "^14.1.0",
41
- "eslint-config-standard-react": "^9.2.0",
40
+ "eslint-config-standard": "^16.0.3",
41
+ "eslint-config-standard-react": "^11.0.1",
42
42
  "eslint-import-resolver-node": "^0.3.4",
43
43
  "eslint-plugin-import": "^2.22.0",
44
44
  "eslint-plugin-jsx-a11y": "^6.3.1",
45
45
  "eslint-plugin-node": "^11.0.0",
46
- "eslint-plugin-promise": "^4.2.1",
46
+ "eslint-plugin-promise": "^5.1.0",
47
47
  "eslint-plugin-react": "^7.21.0",
48
48
  "eslint-plugin-react-hooks": "^4.1.2",
49
- "eslint-plugin-standard": "^4.0.1",
49
+ "eslint-plugin-standard": "^5.0.0",
50
50
  "express": "^4.17.1",
51
51
  "file-loader": "^6.1.0",
52
52
  "find-yarn-workspace-root": "^2.0.0",
53
- "fs-extra": "^9.0.1",
54
- "generic-names": "^2.0.1",
53
+ "fs-extra": "^10.0.0",
54
+ "generic-names": "^3.0.0",
55
55
  "gm": "^1.23.1",
56
56
  "helmet": "^4.1.1",
57
57
  "history": "^5.0.0",
@@ -62,8 +62,9 @@
62
62
  "import-fresh": "^3.2.1",
63
63
  "json-loader": "^0.5.7",
64
64
  "loader-utils": "^2.0.0",
65
+ "morgan": "^1.10.0",
65
66
  "npm-run-all": "^4.1.5",
66
- "pkg-dir": "^4.2.0",
67
+ "pkg-dir": "^5.0.0",
67
68
  "postcss": "^7.0.34",
68
69
  "postcss-apply": "^0.12.0",
69
70
  "postcss-calc": "^7.0.4",
@@ -76,8 +77,8 @@
76
77
  "postcss-values-parser": "^4.0.0",
77
78
  "progress-bar-webpack-plugin": "^2.1.0",
78
79
  "raw-loader": "^4.0.1",
79
- "react": "^16.13.0",
80
- "react-dom": "^16.13.0",
80
+ "react": "^17.0.2",
81
+ "react-dom": "^17.0.2",
81
82
  "rollbar": "^2.19.3",
82
83
  "source-map": "0.6.1",
83
84
  "stylelint": "^13.7.1",
@@ -86,7 +87,7 @@
86
87
  "terser-webpack-plugin": "^4.2.2",
87
88
  "time-fix-plugin": "^2.0.7",
88
89
  "url-loader": "^4.1.0",
89
- "walk-sync": "^2.2.0",
90
+ "walk-sync": "^3.0.0",
90
91
  "webpack": "^4.44.2",
91
92
  "webpack-node-externals": "^2.5.2",
92
93
  "ws": "^7.3.1"
@@ -16,6 +16,7 @@
16
16
  "paths": {
17
17
  "/*?universal": ["src/*"],
18
18
  "/*": ["src/*"]
19
- }
19
+ },
20
+ "downlevelIteration": true
20
21
  }
21
22
  }
@@ -8,8 +8,8 @@ const vm = require('vm')
8
8
  const isProduction = process.env.NODE_ENV === 'production'
9
9
 
10
10
  function createPlugins(
11
- { minifyOnly, globalScopeBehaviour },
12
- { resolveForImport, resolveForUrlReplace, resolveForImportExportParser, cssGlobalFiles }
11
+ { minifyOnly, globalScopeBehaviour, cssGlobalFiles },
12
+ { resolveForImport, resolveForUrlReplace, resolveForImportExportParser }
13
13
  ) {
14
14
 
15
15
  return [
@@ -49,11 +49,11 @@ function createPlugins(
49
49
  require('../postcss-plugins/postcss-kaliber-scoped')(),
50
50
  ].filter(Boolean)
51
51
  ),
52
- isProduction && require('cssnano')({ preset: ['default', { cssDeclarationSorter: false }] })
52
+ isProduction && require('cssnano')({ preset: ['default', { svgo: false, cssDeclarationSorter: false }] })
53
53
  ].filter(Boolean)
54
54
  }
55
55
 
56
- /** @type {import('webpack').loader.Loader} */
56
+ /** @type {import('webpack').LoaderDefinitionFunction} */
57
57
  module.exports = function CssLoader(source, map) {
58
58
 
59
59
  const self = this
@@ -61,7 +61,11 @@ module.exports = function CssLoader(source, map) {
61
61
 
62
62
  const loaderOptions = loaderUtils.getOptions(this) || {}
63
63
  const { minifyOnly = false, globalScopeBehaviour = false } = loaderOptions
64
- const plugins = getPlugins(this, { minifyOnly, globalScopeBehaviour })
64
+
65
+ const cssGlobalFiles = cachedFindCssGlobalFiles(this)
66
+ cssGlobalFiles.forEach(x => this.addDependency(x))
67
+
68
+ const plugins = getPlugins(this, { minifyOnly, globalScopeBehaviour, cssGlobalFiles })
65
69
  const filename = relative(this.rootContext, this.resourcePath)
66
70
  const options = {
67
71
  from: this.resourcePath,
@@ -105,29 +109,24 @@ module.exports = function CssLoader(source, map) {
105
109
  }
106
110
  }
107
111
 
108
- function getPlugins(loaderContext, { minifyOnly, globalScopeBehaviour }) {
112
+ function getPlugins(loaderContext, { minifyOnly, globalScopeBehaviour, cssGlobalFiles }) {
109
113
  const key = `plugins${minifyOnly ? '-minifyOnly' : ''}${globalScopeBehaviour ? '-globalScope' : ''}`
110
- const c = loaderContext._compilation
111
- const cache = c.kaliberCache || (c.kaliberCache = {})
112
- if (cache[key]) return cache[key]
113
114
 
114
- const handlers = createHandlers(loaderContext, cache)
115
- const plugins = createPlugins({ minifyOnly, globalScopeBehaviour }, handlers)
116
- return (cache[key] = plugins)
115
+ return cachedInConmpilation(loaderContext, key, () => {
116
+ const handlers = createHandlers(loaderContext)
117
+ const plugins = createPlugins({ minifyOnly, globalScopeBehaviour, cssGlobalFiles }, handlers)
118
+ return plugins
119
+ })
117
120
  }
118
121
 
119
- /** @param {import('webpack').loader.LoaderContext} loaderContext */
120
- function createHandlers(loaderContext, cache) {
121
- const cssGlobalFiles = findCssGlobalFiles(loaderContext.rootContext)
122
- cssGlobalFiles.forEach(x => loaderContext._compilation.compilationDependencies.add(x))
123
-
122
+ /** @param {import('webpack').LoaderContext<any>} loaderContext */
123
+ function createHandlers(loaderContext) {
124
124
  return {
125
125
  resolveForImport: (id, basedir, importOptions) => resolve(basedir, id),
126
126
  resolveForUrlReplace: (url, file) => isDependency(url)
127
127
  ? resolveAndExecute(dirname(file), url)
128
128
  : Promise.resolve(url),
129
129
  resolveForImportExportParser: (url, file) => resolveAndExecute(dirname(file), url),
130
- cssGlobalFiles,
131
130
  }
132
131
 
133
132
  async function resolveAndExecute(context, request) {
@@ -150,17 +149,32 @@ function createHandlers(loaderContext, cache) {
150
149
 
151
150
  function executeModuleAt(url, source) {
152
151
  const key = `module-${url}`
153
- if (cache[key]) return cache[key]
154
-
155
- const sandbox = {
156
- module: {},
157
- __webpack_public_path__: loaderContext._compiler.options.output.publicPath || '/',
158
- }
159
- const result = vm.runInNewContext(source, sandbox, { displayErrors: true, contextName: `Execute ${url}` })
160
- return (cache[key] = result)
152
+ return cachedInConmpilation(loaderContext, key, () => {
153
+
154
+ const sandbox = {
155
+ module: {},
156
+ __webpack_public_path__: loaderContext._compiler.options.output.publicPath || '/',
157
+ }
158
+ const result = vm.runInNewContext(source, sandbox, { displayErrors: true, contextName: `Execute ${url}` })
159
+ return result
160
+ })
161
161
  }
162
162
  }
163
163
 
164
+ function cachedFindCssGlobalFiles(loaderContext) {
165
+ return cachedInConmpilation(loaderContext, 'global-css-files', () =>
166
+ findCssGlobalFiles(loaderContext.rootContext)
167
+ )
168
+ }
169
+
170
+ function cachedInConmpilation(loaderContext, key, f) {
171
+ const c = loaderContext._compilation
172
+ const cache = c.kaliberCache || (c.kaliberCache = {})
173
+ if (cache[key]) return cache[key]
174
+
175
+ return (cache[key] = f())
176
+ }
177
+
164
178
  function throwErrorForWarnings(filename, warnings) {
165
179
  if (warnings.length) throw new Error(warnings
166
180
  .sort(({ line: a = 0 }, { line: b = 0 }) => a - b)
@@ -0,0 +1,53 @@
1
+ const { relative } = require('path')
2
+ const { OriginalSource } = require('webpack-sources')
3
+
4
+ module.exports = ReactContainerlessUniversalClientLoader
5
+
6
+ function ReactContainerlessUniversalClientLoader(source, map, meta) {
7
+ const filename = relative(this.rootContext, this.resourcePath)
8
+ const importPath = relative(this.context, this.resourcePath)
9
+ const id = filename.replace(/[/.]/g, '_')
10
+ const wrapper = get(require('@kaliber/config'), 'kaliber.universal.clientWrapper')
11
+ const code = createClientCode({ importPath, id, wrapper })
12
+ const generated = new OriginalSource(code, this.resourcePath + '?generated').sourceAndMap()
13
+ this.callback(null, generated.source, generated.map, meta)
14
+ }
15
+
16
+ function createClientCode({ importPath, id, wrapper: wrapperPath }) {
17
+ const component = '<Component {...props} />'
18
+ const { wrapper, wrapped } = {
19
+ wrapper: wrapperPath ? `import Wrapper from '${wrapperPath}'` : '',
20
+ wrapped: wrapperPath ? `<Wrapper {...props}>${component}</Wrapper>` : component,
21
+ }
22
+
23
+ return `|import Component from './${importPath}'
24
+ |import { findComponents, hydrate, reload } from '@kaliber/build/lib/universalComponents'
25
+ |${wrapper}
26
+ |
27
+ |const components = findComponents({ componentName: '${id}' })
28
+ |let renderInfo = components.map(componentInfo => {
29
+ | const { props } = componentInfo
30
+ | return {
31
+ | componentInfo,
32
+ | renderInfo: hydrate(${wrapped}, componentInfo),
33
+ | }
34
+ |})
35
+ |
36
+ |if (module.hot) {
37
+ | require('@kaliber/build/lib/hot-module-replacement-client')
38
+ | 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
+ | })
46
+ | })
47
+ |}
48
+ |`.split(/^[ \t]*\|/m).join('')
49
+ }
50
+
51
+ function get(o, path) {
52
+ return path.split('.').reduce((result, key) => result && result[key], o )
53
+ }
@@ -0,0 +1,63 @@
1
+ const { relative } = require('path')
2
+
3
+ module.exports = ReactContainerlessUniversalServerLoader
4
+
5
+ function ReactContainerlessUniversalServerLoader(source, map) {
6
+ const filename = relative(this.rootContext, this.resourcePath)
7
+ const importPath = relative(this.context, this.resourcePath)
8
+ const id = filename.replace(/[/.]/g, '_')
9
+ const clientWrapper = get(require('@kaliber/config'), 'kaliber.universal.clientWrapper')
10
+ const serverWrapper = get(require('@kaliber/config'), 'kaliber.universal.serverWrapper')
11
+ return createServerCode({ importPath, id, clientWrapper, serverWrapper })
12
+ }
13
+
14
+ function createServerCode({
15
+ importPath,
16
+ id,
17
+ serverWrapper: serverWrapperPath,
18
+ clientWrapper: clientWrapperPath,
19
+ }) {
20
+ const client = wrap({
21
+ importPath: clientWrapperPath,
22
+ wrapperName: 'ClientWrapper',
23
+ component: '<Component {...props} />',
24
+ })
25
+
26
+ const server = wrap({
27
+ importPath: serverWrapperPath,
28
+ wrapperName: 'ServerWrapper',
29
+ component: '<PropsWrapper serverProps={props} />',
30
+ })
31
+
32
+ return `|import Component from './${importPath}?original'
33
+ |import assignStatics from 'hoist-non-react-statics'
34
+ |import { renderToString } from 'react-dom/server'
35
+ |import { ComponentServerWrapper } from '@kaliber/build/lib/universalComponents'
36
+ |${server.wrapper}
37
+ |${client.wrapper}
38
+ |
39
+ |assignStatics(WrappedForServer, Component)
40
+ |
41
+ |export default function WrappedForServer(props) {
42
+ | return ${server.wrapped}
43
+ |}
44
+ |
45
+ |function PropsWrapper({ serverProps, ...additionalProps }) {
46
+ | const componentName = '${id}'
47
+ | const props = { ...additionalProps, ...serverProps }
48
+ | const renderedComponent = renderToString(${client.wrapped})
49
+ | return <ComponentServerWrapper {...{ componentName, props, renderedComponent }} />
50
+ |}
51
+ |`.replace(/^[\s]*\|/mg, '')
52
+ }
53
+
54
+ function wrap({ wrapperName, component, importPath }) {
55
+ return {
56
+ wrapper: importPath ? `import ${wrapperName} from '${importPath}'` : '',
57
+ wrapped: importPath ? `<${wrapperName} {...props}>${component}</${wrapperName}>` : component,
58
+ }
59
+ }
60
+
61
+ function get(o, path) {
62
+ return path.split('.').reduce((result, key) => result && result[key], o )
63
+ }
@@ -73,12 +73,12 @@ module.exports = function reactUniversalPlugin(webCompilerOptions) {
73
73
  })
74
74
  })
75
75
 
76
- // we claim entries ending with `entry.js` and record them as client entries for the web compiler
76
+ // we claim entries ending with `entry.js` and `.universal.js` and record them as client entries for the web compiler
77
77
  compiler.hooks.claimEntries.tap(p, entries => {
78
78
  const [claimed, unclaimed] = Object.keys(entries).reduce(
79
79
  ([claimed, unclaimed], name) => {
80
80
  const entry = entries[name]
81
- if (entry.endsWith('.entry.js')) claimed[name] = entry
81
+ if (entry.endsWith('.entry.js') || entry.endsWith('.universal.js')) claimed[name] = entry
82
82
  else unclaimed[name] = entry
83
83
 
84
84
  return [claimed, unclaimed]
@@ -97,6 +97,9 @@ module.exports = function reactUniversalPlugin(webCompilerOptions) {
97
97
 
98
98
  When a module marked with `?universal` has been resolved, add the `react-universal-server-loader` to it's
99
99
  loaders and add the module marked with `?universal-client` as client entry.
100
+
101
+ When a module marked with `.universal.js` has been resolved, add the `react-containerless-universal-server-loader` to it's
102
+ loaders and add the module marked with `?continerless-universal-client` as client entry.
100
103
  */
101
104
  compiler.hooks.normalModuleFactory.tap(p, normalModuleFactory => {
102
105
 
@@ -118,6 +121,13 @@ module.exports = function reactUniversalPlugin(webCompilerOptions) {
118
121
  if (!clientEntries[name]) clientEntries[name] = './' + name + '?universal-client'
119
122
  }
120
123
 
124
+ if (path.endsWith('.universal.js') && query !== '?original') {
125
+ loaders.push({ loader: require.resolve('../webpack-loaders/react-containerless-universal-server-loader') })
126
+
127
+ const name = relative(compiler.context, path)
128
+ if (!clientEntries[name]) clientEntries[name] = './' + name + '?containerless-universal-client'
129
+ }
130
+
121
131
  if (path.endsWith('.entry.js')) {
122
132
  data.loaders = [{ loader: require.resolve('../webpack-loaders/ignore-content-loader') }]
123
133
  }
@@ -159,9 +169,13 @@ module.exports = function reactUniversalPlugin(webCompilerOptions) {
159
169
  }
160
170
 
161
171
  function getJavascriptChunkNames(chunk, compiler) {
162
- // find univeral modules in the current chunk (client chunk names) and grab their filenames (uniquely)
172
+ // find universal modules in the current chunk (client chunk names) and grab their filenames (uniquely)
163
173
  return chunk.getModules()
164
- .filter(x => x.resource && (x.resource.endsWith('?universal') || x.resource.endsWith('.entry.js')))
174
+ .filter(x => x.resource && (
175
+ x.resource.endsWith('?universal') ||
176
+ x.resource.endsWith('.entry.js') ||
177
+ x.resource.endsWith('.universal.js')
178
+ ))
165
179
  .map(x => relative(compiler.context, x.resource.replace('?universal', '')))
166
180
  }
167
181
 
@@ -177,6 +191,9 @@ function createWebCompiler(compiler, options) {
177
191
  if (query === '?universal-client')
178
192
  loaders.push({ loader: require.resolve('../webpack-loaders/react-universal-client-loader') })
179
193
 
194
+ if (query === '?containerless-universal-client')
195
+ loaders.push({ loader: require.resolve('../webpack-loaders/react-containerless-universal-client-loader') })
196
+
180
197
  return data
181
198
  })
182
199
  })
@@ -9,7 +9,7 @@ function absolutePathResolverPlugin(path) {
9
9
  const innerRequest = request.request
10
10
  if (innerRequest && innerRequest.startsWith('/')) {
11
11
  const newRequest = Object.assign({}, request, {
12
- path: path,
12
+ path,
13
13
  request: './' + innerRequest.slice(1)
14
14
  })
15
15
  resolver.doResolve(resolver.hooks.resolve, newRequest, 'looking for file in ' + path, resolveContext, callback)